diff --git a/src/parser.ts b/src/parser.ts index 0ffabf50..a36ecfeb 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,5 +1,5 @@ import { log } from 'console' -import Parsimmon, { alt as alt_parser, any, Index, index, lazy, makeSuccess, newline, notFollowedBy, of, Parser, regex, seq, seqObj, string, whitespace } from 'parsimmon' +import Parsimmon, { alt as alt_parser, any, Index, index, lazy, makeSuccess, newline, notFollowedBy, of, Parser, regex, seq, seqObj, string, succeed, whitespace } from 'parsimmon' import unraw from 'unraw' import { ASSIGNATION_OPERATORS, INFIX_OPERATORS, KEYWORDS, LIST_MODULE, PREFIX_OPERATORS, SET_MODULE } from './constants' import { discriminate, hasWhitespace, is, List, mapObject } from './extensions' @@ -19,6 +19,7 @@ const ALL_OPERATORS = [ export const MALFORMED_ENTITY = 'malformedEntity' export const MALFORMED_MEMBER = 'malformedMember' export const MALFORMED_SENTENCE = 'malformedSentence' +export const MALFORMED_MESSAGE_SEND = 'malformedMessageSend' export class ParseError implements BaseProblem { constructor(public code: Name, public sourceMap: SourceMap) { } @@ -103,6 +104,11 @@ const spaces = optional(string(' ').many()) const comment = (position: 'start' | 'end' | 'inner') => lazy('comment', () => regex(/\/\*(.|[\r\n])*?\*\/|\/\/.*/)).map(text => new Annotation('comment', { text, position })) const sameLineComment: Parser = spaces.then(comment('end')) +const withSameLineComment = (result: T): Parser => + optional(sameLineComment).map(comment => comment + ? result.copy({ metadata: result.metadata.concat(comment) }) + : result) + export const sanitizeWhitespaces = (originalFrom: SourceIndex, originalTo: SourceIndex, input: string): [SourceIndex, SourceIndex] => { const EOL = input.includes('\r\n') ? '\r\n' : '\n' const hasLineBreaks = (aString: string) => aString.includes(EOL) @@ -227,7 +233,8 @@ export const Body: Parser = node(BodyNode)(() => sentences: alt( Sentence.skip(__), comment('inner').wrap(_, _), - sentenceError).many(), + sentenceError + ).many(), }).wrap(key('{'), lastKey('}')).map(recover) ) @@ -452,12 +459,35 @@ const prefixMessageChain: Parser = lazy(() => const postfixMessageChain: Parser = lazy(() => messageChain( - primaryExpression, + postfixMessageChainInitialReceiver, key('.').then(name), alt(unamedArguments, Closure.times(1)) ) ) +const postfixMessageChainInitialReceiver: Parser }> = lazy(() => + alt( + primaryExpression, + obj({ + markedMessage: name.mark(), + args: alt(unamedArguments, Closure.times(1)), + }).mark().chain(({ + start, + end, + value: { + markedMessage: { value: message, start: errorStart, end: errorEnd }, + args + }, + }) => Parsimmon((input: string, i: number) => makeSuccess(i, new SendNode({ + receiver: new LiteralNode({ value: null }), + message, + args, + problems: [new ParseError(MALFORMED_MESSAGE_SEND, buildSourceMap(errorStart, errorEnd))], + sourceMap: buildSourceMap(...sanitizeWhitespaces(start, end, input)) + })))) + ).chain(withSameLineComment) +) + const messageChain = (receiver: Parser, message: Parser, args: Parser>): Parser => lazy(() => seq( index, @@ -498,7 +528,6 @@ const primaryExpression: Parser = lazy(() => { ) }) - export const Self: Parser = node(SelfNode)(() => key(KEYWORDS.SELF).result({}) ) diff --git a/test/parser.test.ts b/test/parser.test.ts index 67244e4e..6d47388f 100644 --- a/test/parser.test.ts +++ b/test/parser.test.ts @@ -45,6 +45,17 @@ describe('Wollok parser', () => { })) }) + it('comments after malformed sends should be parsed', () => { + 'vola() //some comment' + .should.be.parsedBy(parse.Send) + .recoveringFrom(parse.MALFORMED_MESSAGE_SEND, 0, 4) + .into(new Send({ + receiver: new Literal({ value: null }), + message: 'vola', + metadata: [new Annotation('comment', { text: '//some comment', position: 'end' })], + })) + }) + it('comments after variable should be parsed', () => { 'const a = 1 //some comment' .should.be.parsedBy(parse.Variable).into(new Variable({ @@ -2607,7 +2618,7 @@ class c {}` .and.have.nested.property('receiver').tracedTo(0, 3) }) - it('should parse compound sending messages', () => { + it('should parse chained sending messages', () => { 'a.m().n().o()'.should.be.parsedBy(parser).into( new Send({ receiver: new Send({ @@ -2723,16 +2734,79 @@ class c {}` 'a.'.should.not.be.parsedBy(parser) }) - it('should not parse a call to a method without the reference that is calling', () => { - 'm(p,q)'.should.not.be.parsedBy(parser) - }) - it('should not parse an expression with a "." at the start', () => { '.m'.should.not.be.parsedBy(parser) }) - }) + it('should recover from malformed message send without arguments', () => { + `m()`.should.be.parsedBy(parser) + .recoveringFrom(parse.MALFORMED_MESSAGE_SEND, 0, 1) + .into(new Send({ + receiver: new Literal({ value: null }), + message: 'm', + args: [], + })) + }) + + it('should recover from malformed message send with one argument', () => { + `m(p)`.should.be.parsedBy(parser) + .recoveringFrom(parse.MALFORMED_MESSAGE_SEND, 0, 1) + .into(new Send({ + receiver: new Literal({ value: null }), + message: 'm', + args: [ new Reference({ name: 'p' }) ], + })) + }) + + it('should recover from malformed message send with multiple arguments', () => { + 'm(p,q)'.should.be.parsedBy(parser) + .recoveringFrom(parse.MALFORMED_MESSAGE_SEND, 0, 1) + .into(new Send({ + receiver: new Literal({ value: null }), + message: 'm', + args: [ + new Reference({ name: 'p' }), + new Reference({ name: 'q' }) + ], + })) + }) + it('should parse malformed message sends with a closure as an argument', () => { + 'm1 {p => p}'.should.be.parsedBy(parser) + .recoveringFrom(parse.MALFORMED_MESSAGE_SEND, 0, 2) + .into( + new Send({ + receiver: new Literal({ value: null }), + message: 'm1', + args: [ + Closure({ + parameters: [new Parameter({ name: 'p' })], + sentences: [new Return({ value: new Reference({ name: 'p' }) })], + code: '{p => p}', + }), + ], + }) + ) + .and.exist.tracedTo(0,11) + .and.have.nested.property('args.0').tracedTo(3, 11) + .and.also.have.nested.property('args.0.members.0.parameters.0').tracedTo(4, 5) + .and.also.have.nested.property('args.0.members.0.body.sentences.0.value').tracedTo(9, 10) + }) + + it('should parse chained send with malformed receiver', () => { + `m1().m2()`.should.be.parsedBy(parser) + .into(new Send({ + receiver: new Send({ + receiver: new Literal({ value: null }), + message: 'm1', + args: [], + }), + message: 'm2', + args: [], + })) + }) + + }) describe('New', () => {