diff --git a/src/printer/print.ts b/src/printer/print.ts index 932ed4b4..08ebd54b 100644 --- a/src/printer/print.ts +++ b/src/printer/print.ts @@ -1,7 +1,7 @@ -import { IDoc, append, braces, choice, enclose, intersperse, lineBreak, lineBreaks, indent as nativeIndent, nest as nativeNest, parens, render, softBreak } from 'prettier-printer' +import { IDoc, append, braces, choice, enclose, hang as nativeHang, intersperse, lineBreak, lineBreaks, indent as nativeIndent, nest as nativeNest, parens, render, softBreak, softLine, align } from 'prettier-printer' import { KEYWORDS, LIST_MODULE, SET_MODULE } from '../constants' -import { List, match, when } from '../extensions' -import { Assignment, Body, Class, Describe, Expression, Field, If, Import, Literal, Method, Mixin, NamedArgument, New, Node, Package, Parameter, ParameterizedType, Program, Reference, Return, Self, Send, Sentence, Singleton, Test, Variable } from '../model' +import { List, match, when, notEmpty, isEmpty } from '../extensions' +import { Assignment, Body, Class, Describe, Expression, Field, If, Import, Literal, Method, Mixin, NamedArgument, New, Node, Package, Parameter, ParameterizedType, Program, Reference, Return, Self, Send, Sentence, Singleton, Super, Test, Variable } from '../model' import { DocTransformer, WS, body, enclosedList, infixOperators, listEnclosers, listed, setEnclosers, stringify } from './utils' type PrintSettings = { @@ -19,6 +19,7 @@ type PrintSettings = { type PrintContext = { indent: DocTransformer, nest: DocTransformer, + hang: DocTransformer, abbreviateAssignments: boolean, } @@ -32,6 +33,7 @@ function print(node: Node, { maxWidth, indentation, abbreviateAssignments }: Pri format({ indent: nativeIndent(indentationCharacters), nest: nativeNest(indentationCharacters), + hang: nativeHang(indentationCharacters), abbreviateAssignments, })(node)) } @@ -67,6 +69,7 @@ const format: FormatterWithContext = context => node => { when(Reference)(formatReference), when(Self)(formatSelf), when(Import)(formatImport), + when(Super)(formatSuper(context)), ) } @@ -161,11 +164,40 @@ const formatAssignment: FormatterWithContext= context => node => formatAssign(context)(node.variable.name, node.value.args[0], assignmentOperationByMessage[node.value.message]) : formatAssign(context)(node.variable.name, node.value) +const formatSuper: FormatterWithContext = context => node => + [KEYWORDS.SUPER, formatArguments(context)(node.args)] const formatIf: FormatterWithContext = context => node => { const condition = [KEYWORDS.IF, WS, enclose(parens, format(context)(node.condition))] + + if(isOneLineBody(node.thenBody) && (node.elseBody.isSynthetic || isOneLineBody(node.elseBody))){ + return choice( + [ + condition, + WS, + format(context)(node.thenBody.sentences[0]), + notEmpty(node.elseBody.sentences) ? [WS, KEYWORDS.ELSE, WS, format(context)(node.elseBody.sentences[0])] : [], + ], + [ + align([ + context.hang([ + condition, + softLine, + format(context)(node.thenBody.sentences[0]), + ]), + notEmpty(node.elseBody.sentences) ? [ + lineBreak, + context.hang([KEYWORDS.ELSE, softLine, format(context)(node.elseBody.sentences[0])]), + ] : [], + ]), + ] + ) + + + } + const thenBody = body(context.nest)(formatSentences(context)(node.thenBody.sentences)) - const elseBody = node.elseBody.sentences.length > 0 ? body(context.nest)(formatSentences(context)(node.elseBody.sentences)) : undefined + const elseBody = !(isEmpty(node.elseBody.sentences) || node.elseBody.isSynthetic) ? body(context.nest)(formatSentences(context)(node.elseBody.sentences)) : undefined return [ condition, WS, @@ -174,9 +206,13 @@ const formatIf: FormatterWithContext = context => node => { ] } +function isOneLineBody(aBody: Body): aBody is Body & { sentences: [Expression] } { + return aBody.sentences.length === 1 && aBody.sentences[0].is(Expression) +} + const formatNew: FormatterWithContext = context => node => { const args = - enclosedList(context.nest)(parens, node.args.map(arg => intersperse(WS, [arg.name, '=', format(context)(arg.value)]))) + enclosedList(context.nest)(parens, node.args.map(format(context))) return [ KEYWORDS.NEW, WS, @@ -239,7 +275,7 @@ const formatParameterizedType: FormatterWithContext = context => node => [ node.reference.name, node.args.length > 0 ? - enclosedList(context.nest)(parens, node.args.map(format(context))) : + [WS, enclosedList(context.nest)(parens, node.args.map(format(context)))] : [], ] @@ -300,26 +336,27 @@ const formatSend: FormatterWithContext = context => node => { } const formatDotSend: FormatterWithContext = context => node => [ - format(context)(node.receiver), + addParenthesisIfNeeded(context, node.receiver), '.', node.message, - enclosedList(context.nest)(parens, node.args.map(format(context))), + formatArguments(context)(node.args), ] const formatInfixSend: FormatterWithContext = context => node => { - function addParenthesisIfNeeded(expression: Expression): IDoc { - // ToDo: add more cases where parenthesis aren't needed - const formatted = format(context)(expression) - return expression.is(Send) && infixOperators.includes(expression.message) ? enclose(parens, formatted) : formatted - } - return intersperse(WS, [ - addParenthesisIfNeeded(node.receiver), + addParenthesisIfNeeded(context, node.receiver), node.message, - addParenthesisIfNeeded(node.args[0]), + addParenthesisIfNeeded(context, node.args[0]), ]) } +function addParenthesisIfNeeded(context: PrintContext, expression: Expression): IDoc { + // ToDo: add more cases where parenthesis aren't needed + const formatted = format(context)(expression) + return expression.is(Send) && infixOperators.includes(expression.message) ? enclose(parens, formatted) : formatted +} + + // AUXILIARY FORMATTERS const formatSentences = (context: PrintContext) => (sentences: List, simplifyLastReturn = false) => sentences.reduce((formatted, sentence, i, sentences) => { @@ -328,6 +365,8 @@ const formatSentences = (context: PrintContext) => (sentences: List, s return [formatted, formatSentenceInBody(context)(!shouldShortenReturn ? sentence : sentence.value, previousSentence)] }, []) +const formatArguments = (context: PrintContext) => (args: List): IDoc => enclosedList(context.nest)(parens, args.map(format(context))) + const formatSentenceInBody = (context: PrintContext) => (sentence: Sentence, previousSentence: Sentence | undefined): IDoc => { const distanceFromLastSentence = previousSentence ? sentence.sourceMap!.start.line - previousSentence.sourceMap!.end.line : -1 return [Array(distanceFromLastSentence + 1).fill(lineBreak), format(context)(sentence)] @@ -336,7 +375,7 @@ const formatSentenceInBody = (context: PrintContext) => (sentence: Sentence, pre const formatAssign = (context: PrintContext, ignoreNull = false) => (name: string, value: Expression, assignmentOperator = '=') => [ name, // ToDo: diffentiate `var x` from `var x = null` - ignoreNull && value.is(Literal) && value.isNull() ? + ignoreNull && value.is(Literal) && value.isNull() && value.isSynthetic? [] : [ WS, diff --git a/test/printer.test.ts b/test/printer.test.ts index 000b8a95..295b834d 100644 --- a/test/printer.test.ts +++ b/test/printer.test.ts @@ -53,6 +53,62 @@ describe('Wollok Printer', () => { `) }) }) + + describe('If', () => { + it('full then and else body', () => { + `program prueba { + if(true){return 1}else{return 2} + }`.should.be.formattedTo(` + program prueba { + if (true) { + return 1 + } else { + return 2 + } + }`) + }) + + + it('with no else body', () => { + `program prueba { + if(true){return 1} + }`.should.be.formattedTo(` + program prueba { + if (true) { + return 1 + } + }`) + }) + + it('if expression short', () => { + `program prueba { + if(true)1 + else 2 + }`.should.be.formattedTo(` + program prueba { + if (true) 1 else 2 + }`) + }) + + it('if expression long', () => { + `program prueba { + const pepita = object { + method volar(param){} + } + if ("a very very very very very very very very long string".length() > 0)pepita.volar("a very very very very very very very very long argument") else 2 + }`.should.be.formattedTo(` + program prueba { + const pepita = object { + method volar(param) { + + } + } + if ("a very very very very very very very very long string".length() > 0) + pepita.volar("a very very very very very very very very long argument") + else 2 + }`) + }) + }) }) describe('Object definitions', () => { it('testBasicObjectDefinition', () => { @@ -111,7 +167,7 @@ describe('Wollok Printer', () => { } program prueba { - const pepita = object inherits A(n = 5) { + const pepita = object inherits A (n = 5) { var energia = 0 method volar() { @@ -157,7 +213,7 @@ describe('Wollok Printer', () => { } program prueba { - const pepita = object inherits A(edad = 22, nombre = "Carlono") { + const pepita = object inherits A (edad = 22, nombre = "Carlono") { var energia = 0 method volar() { @@ -299,8 +355,441 @@ describe('Wollok Printer', () => { override method volar() { energia += 10 } + }`) + }) + + it('testInheritingObjectDefinitionWithDefinitionItselfAfter', () => { + ` + object pepita + + + + inherits + Ave + + + { var energia = 0 + + + override method volar() { energia += + 10 } } + + + class Ave{method volar() {}} + + `.should.be.formattedTo(` + object pepita inherits Ave { + var energia = 0 + + override method volar() { + energia += 10 + } + } + + class Ave { + method volar() { + + } + }`) + }) + + it('testClassDefinitionWithVar', () => { + ` + class Ave { + + + var energia + + }`.should.be.formattedTo(` + class Ave { + var energia + }`) + }) + + it('testBasicMixinDefinition', () => { + ` + + mixin Volador { + + + var energia + + method volar(lugar) {energia = 0 }} + `.should.be.formattedTo(` + mixin Volador { + var energia + + method volar(lugar) { + energia = 0 + } + }`) + }) + + it('testBasicMixinUse', () => { + ` + mixin Volador { + + + var energia + + method volar(lugar) {energia = 0 }} class Ave + + inherits + + Volador { + + method comer() { energia = 100 + } + }`.should.be.formattedTo( + ` + mixin Volador { + var energia + + method volar(lugar) { + energia = 0 + } } - `) + + class Ave inherits Volador { + method comer() { + energia = 100 + } + }`) + + }) + + it('testObjectInheritingNamedParametersForWKO', () => { + `class Musico{var calidad} + object luisAlberto inherits Musico (calidad + + + + = + 8 + + , + + + cancionPreferida = + "estrelicia" + ) { + + var guitarra + }`.should.be.formattedTo(` + class Musico { + var calidad + } + + object luisAlberto inherits Musico ( + calidad = 8, + cancionPreferida = "estrelicia" + ) { + var guitarra + }`) + }) + + it('testClassDefinition', () => { + ` + + + + + + class Cancion { + + + }`.should.be.formattedTo(` + class Cancion { + + }`) + }) + + it('objectDefinition', () => { + ` + class Musico{} object luisAlberto inherits Musico { + + var guitarra = null + + method cambiarGuitarra(_guitarra) { + guitarra = _guitarra + } + + override method habilidad() = 100.min(8 * guitarra.unidadesDeGuitarra()) + override method remuneracion(presentacion) = if (presentacion.participo(self)) self.costoPresentacion(presentacion) else 0 + + method costoPresentacion(presentacion) = if (presentacion.fecha() < new Date(day = 01, month = 09, year = 2017)) { + return 1000 + } else { + return 1200 + } + + override method interpretaBien(cancion) = true + + }`.should.be.formattedTo(` + class Musico { + + } + + object luisAlberto inherits Musico { + var guitarra = null + + method cambiarGuitarra(_guitarra) { + guitarra = _guitarra + } + + override method habilidad() = 100.min(8 * guitarra.unidadesDeGuitarra()) + + override method remuneracion(presentacion) = if (presentacion.participo(self)) + self.costoPresentacion( + presentacion + ) + else 0 + + method costoPresentacion(presentacion) = if (presentacion.fecha() < new Date( + day = 1, + month = 9, + year = 2017 + )) { + return 1000 + } else { + return 1200 + } + + override method interpretaBien(cancion) = true + }` + ) + }) + + it('testPresentacion', () => { + `class Presentacion { + + const fecha + const locacion + var participantes = [] + + method fecha() = fecha + + method locacion() = locacion + + method participantes() = participantes + + method participo(musico) = participantes.contains(musico) + + method capacidad() = self.locacion().capacidadPorDia(fecha) + + method agregarParticipantes(persona) { + if (participantes.contains(persona)) { + self.error("La persona que se desea agregar ya pertence a la presentacion") + } else { + participantes.add(persona) + } + } + + method quitarParticipante(persona) { + if (not (participantes.isEmpty())) { + if (participantes.contains(persona)) { + participantes.remove(persona) + } else { + self.error("La persona que se desea quitar no era integrante de la presentacion") + } + } else { + self.error("El conjunto de participantes esta vacio") + } + } + + method costoPresentacion() { + var costo = 0 + self.participantes().forEach{ participante => costo += participante.remuneracion(self) } + return costo + } + + }`.should.be.formattedTo(` + class Presentacion { + const fecha + const locacion + var participantes = [] + + method fecha() = fecha + + method locacion() = locacion + + method participantes() = participantes + + method participo(musico) = participantes.contains(musico) + + method capacidad() = self.locacion().capacidadPorDia(fecha) + + method agregarParticipantes(persona) { + if (participantes.contains(persona)) self.error( + "La persona que se desea agregar ya pertence a la presentacion" + ) + else participantes.add(persona) + } + + method quitarParticipante(persona) { + if (participantes.isEmpty().negate()) if (participantes.contains(persona)) + participantes.remove(persona) + else self.error( + "La persona que se desea quitar no era integrante de la presentacion" + ) + else self.error("El conjunto de participantes esta vacio") + } + + method costoPresentacion() { + var costo = 0 + self.participantes().forEach( + { participante => costo += participante.remuneracion(self) } + ) + return costo + } + }`) + }) + + it('testObjectVarsInitialized', () => { + `class Musico{} + object luisAlberto inherits Musico { + + const valorFechaTope = 1200 + const valorFechaNoTope = 1000 + var guitarra = fender + const initializedAsNullVar = null + const uninitializedVar + + const fechaTope = new Date(day = 01, month = 09, year = 2017) + + override method habilidad() = (8 * guitarra.valor()).min(100) + + override method interpretaBien(cancion) = true + + method guitarra(_guitarra) { + guitarra = _guitarra + } + + method costo(presentacion) = if (presentacion.fecha() < fechaTope) valorFechaNoTope else valorFechaTope + + }`.should.be.formattedTo(` + class Musico { + + } + + object luisAlberto inherits Musico { + const valorFechaTope = 1200 + const valorFechaNoTope = 1000 + var guitarra = fender + const initializedAsNullVar = null + const uninitializedVar + const fechaTope = new Date(day = 1, month = 9, year = 2017) + + override method habilidad() = (8 * guitarra.valor()).min(100) + + override method interpretaBien(cancion) = true + + method guitarra(_guitarra) { + guitarra = _guitarra + } + + method costo(presentacion) = if (presentacion.fecha() < fechaTope) + valorFechaNoTope + else valorFechaTope + }`) + }) + }) + describe('Methods Formatter', () => { + it('testBasicFormattingInMethod', () => { + ` + object foo { + method bar( param , param2 ) { + console.println("") + console.println("") + } + } + `.should.be.formattedTo(` + object foo { + method bar(param, param2) { + console.println("") + console.println("") + } + }`) + }) + + it('testBasicFormattingSeveralMethods', () => { + ` + object foo { + method bar( param , param2 ) { + console.println("") + console.println("") + }method bar2() { return 3 } + + + method bar3() { assert.that(true) var a = 1 + 1 console.println(a)} + } + `.should.be.formattedTo(` + object foo { + method bar(param, param2) { + console.println("") + console.println("") + } + + method bar2() = 3 + + method bar3() { + assert.that(true) + var a = 1 + 1 + console.println(a) + } + }`) + }) + + it('testReturnMethod', () => { + ` + object foo { + method bar( param , param2 ) + = 2 + + method bar2() = self.bar(1, "hola") + } + `.should.be.formattedTo(` + object foo { + method bar(param, param2) = 2 + + method bar2() = self.bar(1, "hola") + }`) + }) + + it('testOverrideMethod', () => { + ` + class Parent { + method bar( param , param2 ) + = 2 + + method bar2() { + + return self.bar(1, "hola") + } + } class Child + inherits Parent{ var a = 0 + override method bar(param, param2) = super() + + + 10 + override method bar2() { a+=1 } + } + `.should.be.formattedTo(` + class Parent { + method bar(param, param2) = 2 + + method bar2() = self.bar(1, "hola") + } + + class Child inherits Parent { + var a = 0 + + override method bar(param, param2) = super() + 10 + + override method bar2() { + a += 1 + } + }`) + }) }) }) \ No newline at end of file