UNPKG

@composita/compiler

Version:

Composita language compiler.

1,283 lines (1,182 loc) 57.9 kB
import { Lexer } from '../lexer/lexer'; import { ProgramNode, ComponentNode, OfferedInterfaceNode, RequiredInterfaceNode, ComponentBodyNode, ImplementationNode, InterfaceNode, NameNode, StatementSequenceNode, AttributeNode, StatementNode, ProcedureCallNode, DeclarationNode, ExpressionNode, TextNode, ProtocolNode, ProtocolExpressionNode, ProtocolTermNode, ProtocolFactorNode, MessageDeclarationNode, MessageDirection, ParameterNode, GroupProtocolExpressionNode, CardinalityNode, InterfaceDeclarationNode, OptionalProtocolExpressionNode, RepeatingProtocolExpressionNode, TypeNode, BasicTypeNode, ProtocolFactorExpressionNode, ConstantListNode, VariableListNode, ProcedureNode, ConstantNode, ConstantExpressionNode, VariableNode, IndexedNameNode, ProcedureParameterNode, AnyTypeNode, DesignatorNode, BasicExpressionDesignatorNode, BasicDesignatorNode, DesignatorTypeNode, BaseTargetDesignatorNode, NewNode, ConnectNode, DisconnectNode, DeleteNode, AwaitNode, MoveNode, SendNode, ReceiveNode, AssignmentNode, IfNode, ForeachNode, StatementBlockNode, ReturnNode, WhileNode, RepeatNode, ForNode, ElseIfNode, ConstantCharacterNode, ExistsTestNode, InputTestNode, FunctionCallNode, MessagePattern, FixedMessagePattern, ReceiveTestNode, OperandNode, FactorNode, UnaryFactorNode, FactorPrefix, ExpressionFactorNode, SimpleExpressionNode, UnaryExpressionNode, UnaryTermNode, TermNode, InfixFactorOperator, FactorChainNode, RightFactorNode, RightTermNode, InfixTermOperator, PrefixOperator, LogicalOperator, BinaryExpressionNode, TermChainNode, OffersRequiresExpressionNode, OffersRequiresOperator, TypeCheckExpressionNode, RealNumberNode, IntegerNumberNode, } from '../ast/ast'; import { FixToken, IdentifierToken, TextToken, Tag, IntegerNumberToken, Token, FloatNumberToken, HexNumberToken, } from '../tokens/tokens'; import { SourceLocation } from '../source-location/location'; import { Optional, NonEmptyArray, Constructable3, Constructable2 } from '@composita/ts-utility-types'; import { Diagnosis, CompilerDiagnostic } from '../diagnosis/diagnosis'; import { DiagnosticSeverity } from 'vscode-languageserver-types'; import { IdentifierKeywords } from './keywords'; export class Parser { constructor(private readonly diagnosis: Diagnosis, private lexer: Lexer) {} private hasTag(tag: Tag, n = 1): boolean { const token = this.lexer.peek(n); return this.isTaggedFixToken(token, tag); } private isTaggedFixToken(token: Token, tag: Tag) { return token instanceof FixToken && token.getTag() === tag; } private hasIdentifier(identifier: string): boolean { const token = this.lexer.peek(1); return token instanceof IdentifierToken && token.getIdentifier() === identifier; } private isComponent(): boolean { return this.hasTag(Tag.Component); } private isInterface(): boolean { return this.hasTag(Tag.Interface); } private isEOT(): boolean { return this.hasTag(Tag.EOT); } private error<T = void>(msg: string): T { const range = this.lexer.peek(1).getLocation().range; this.diagnosis.log(new CompilerDiagnostic(range, DiagnosticSeverity.Error, msg)); throw new Error( `[${range.start.line},${range.start.character}:${range.end.line},${range.end.character}]: ${msg}`, ); } private loop(predicate: () => boolean, body: () => void): void { while (predicate() && !this.isEOT()) { body(); } } private expectConsumeFixToken(tag: Tag): void { if (!this.tryConsumeFixToken(tag)) { const msg = `Expected FixToken with Tag ${tag}`; this.error(msg); } } private tryConsumeFixToken(tag: Tag): boolean { if (!this.hasTag(tag)) { return false; } this.lexer.next(); return true; } private expectConsumeIdentifierToken(identifier: string): void { if (!this.tryConsumeIdentifierToken(identifier)) { const msg = `Expected IdentifierToken with Identifier ${identifier}`; this.error(msg); } } private tryParse<T>(parser: () => T): Optional<T> { try { this.diagnosis.saveState(); this.lexer.saveState(); const variable = parser(); this.lexer.popSaveState(); return variable; } catch (error) { this.diagnosis.restoreState(); this.lexer.restoreState(); return undefined; } } private tryConsumeIdentifierToken(identifier: string): boolean { if (!this.hasIdentifier(identifier)) { return false; } this.lexer.next(); return true; } private parseName(): NameNode { const next = this.lexer.peek(1); if (next instanceof IdentifierToken) { this.lexer.next(); return new NameNode(next.getLocation(), next.getIdentifier()); } const msg = 'Failed to parse identifier name'; return this.error(msg); } private parseIndexedName(): IndexedNameNode { const next = this.lexer.peek(1); if (!(next instanceof IdentifierToken)) { return this.error('Failed to parse indexed name.'); } this.lexer.next(); if (this.tryConsumeFixToken(Tag.OpenSquareBracket)) { const params = this.parseParameterList(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseSquareBracket); return new IndexedNameNode(SourceLocation.merge(next.getLocation(), end), next.getIdentifier(), params); } return new IndexedNameNode(next.getLocation(), next.getIdentifier(), new Array<ParameterNode>()); } private parseCardinality(): Optional<CardinalityNode> { if (!this.hasTag(Tag.OpenSquareBracket)) { return undefined; } const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.OpenSquareBracket); let next = this.lexer.next(); if (!(next instanceof IntegerNumberToken)) { return this.error('Expected number token.'); } const min = next.getNumber(); let max: number = min; if (this.tryConsumeFixToken(Tag.Ellipsis)) { next = this.lexer.peek(1); if (next instanceof IntegerNumberToken) { this.lexer.next(); max = next.getNumber(); } else if (this.hasTag(Tag.Asterisk)) { this.lexer.next(); max = Infinity; } else { this.error('Expected number or "*".'); } } const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseSquareBracket); return new CardinalityNode(SourceLocation.merge(start, end), min, max); } private parseInterfaceDeclaration<T extends InterfaceDeclarationNode>( InterfaceDeclaration: Constructable3<T, SourceLocation, NameNode, Optional<CardinalityNode>>, ): T { const name = this.parseName(); const cardinality = this.parseCardinality(); const location = cardinality === undefined ? name.getLocation() : SourceLocation.merge(name.getLocation(), cardinality.getLocation()); return new InterfaceDeclaration(location, name, cardinality); } private parseOfferedInterfaceDeclaration(): OfferedInterfaceNode { return this.parseInterfaceDeclaration(OfferedInterfaceNode); } private parseRequiredInterfaceDeclaration(): RequiredInterfaceNode { return this.parseInterfaceDeclaration(RequiredInterfaceNode); } private parseOffers(): Array<OfferedInterfaceNode> { const offered = new Array<OfferedInterfaceNode>(); if (this.tryConsumeFixToken(Tag.Offers)) { offered.push(...this.parseOfferedInterfaceDeclarationList()); } return offered; } private parseRequires(): Array<RequiredInterfaceNode> { const required = new Array<RequiredInterfaceNode>(); if (this.tryConsumeFixToken(Tag.Requires)) { required.push(...this.parseRequiredInterfaceDeclarationList()); } return required; } private parseFunctionCall(): FunctionCallNode { const name = this.parseName(); this.expectConsumeFixToken(Tag.OpenParentheses); const args = new Array<ExpressionNode>(); if (!this.hasTag(Tag.CloseParentheses)) { args.push(...this.parseExpressionList()); } const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new FunctionCallNode(SourceLocation.merge(name.getLocation(), end), name, args); } private parseMessagePattern(): MessagePattern { if (this.tryConsumeFixToken(Tag.Any)) { return FixedMessagePattern.Any; } if (this.tryConsumeFixToken(Tag.Finish)) { return FixedMessagePattern.Finish; } return this.parseName(); } private tryParseExpression(): Optional<ExpressionNode> { return this.tryParse(this.parseExpression.bind(this)); } private parseReceiveTestExpression(designator: Optional<DesignatorNode>): ReceiveTestNode { const start = designator?.getLocation() ?? this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.QuestionMark); const end = this.lexer.peek(1).getLocation(); const pattern = this.parseMessagePattern(); return new ReceiveTestNode(SourceLocation.merge(start, end), designator, pattern); } private parseInputTestExpression(): InputTestNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeIdentifierToken(IdentifierKeywords.Input); this.expectConsumeFixToken(Tag.OpenParentheses); const designator = this.parseDesignator(); if (!this.hasTag(Tag.Comma)) { if (designator instanceof BasicDesignatorNode) { const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new InputTestNode(SourceLocation.merge(start, end), undefined, designator.getName()); } this.error('Failed parsing input test node.'); } this.expectConsumeFixToken(Tag.Comma); const pattern = this.parseMessagePattern(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new InputTestNode(SourceLocation.merge(start, end), designator, pattern); } private parseExistsExpression(): ExistsTestNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeIdentifierToken(IdentifierKeywords.Exists); this.expectConsumeFixToken(Tag.OpenParentheses); const designator = this.parseDesignator(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new ExistsTestNode(SourceLocation.merge(start, end), designator); } private parseOperand(): OperandNode { const token = this.lexer.peek(1); if (token instanceof TextToken) { this.lexer.next(); const text = token.getText(); if (text.length === 1) { return new ConstantCharacterNode(token.getLocation(), text); } return new TextNode(token.getLocation(), text); } if (token instanceof IntegerNumberToken) { this.lexer.next(); return new IntegerNumberNode(token.getLocation(), token.getNumber()); } if (token instanceof HexNumberToken) { this.lexer.next(); return new IntegerNumberNode(token.getLocation(), token.getNumber()); } if (token instanceof FloatNumberToken) { this.lexer.next(); return new RealNumberNode(token.getLocation(), token.getNumber()); } if (this.hasIdentifier(IdentifierKeywords.Input)) { return this.parseInputTestExpression(); } if (this.hasIdentifier(IdentifierKeywords.Exists)) { return this.parseExistsExpression(); } const designator = this.tryParseDesignator(); if (this.hasTag(Tag.QuestionMark)) { return this.parseReceiveTestExpression(designator); } if (designator === undefined) { return this.parseFunctionCall(); } // we could still have a function call here, let this the checker handle. return designator; } private parseFactor(): FactorNode { const start = this.lexer.peek(1).getLocation(); if (this.tryConsumeFixToken(Tag.Tilde)) { const node = this.parseFactor(); return new UnaryFactorNode(SourceLocation.merge(start, node.getLocation()), FactorPrefix.Not, node); } if (this.tryConsumeFixToken(Tag.OpenParentheses)) { const expression = this.parseExpression(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new ExpressionFactorNode(SourceLocation.merge(start, end), expression); } return this.parseOperand(); } private isFactorInfix(): boolean { return ( this.hasTag(Tag.Asterisk) || this.hasTag(Tag.Slash) || this.hasTag(Tag.Div) || this.hasTag(Tag.Mod) || this.hasTag(Tag.And) ); } private parseFactorInfix(): InfixFactorOperator { if (this.tryConsumeFixToken(Tag.Asterisk)) { return InfixFactorOperator.Times; } if (this.tryConsumeFixToken(Tag.Slash)) { return InfixFactorOperator.Div; } if (this.tryConsumeFixToken(Tag.Div)) { return InfixFactorOperator.DivText; } if (this.tryConsumeFixToken(Tag.Mod)) { return InfixFactorOperator.ModText; } if (this.tryConsumeFixToken(Tag.And)) { return InfixFactorOperator.AndText; } return this.error('Failed to parse factor infix.'); } private parseRightFactor(): RightFactorNode { const start = this.lexer.peek(1).getLocation(); const op = this.parseFactorInfix(); const rightFactor = this.parseFactor(); return new RightFactorNode(SourceLocation.merge(start, rightFactor.getLocation()), op, rightFactor); } private parseTerm(): TermNode { const factor = this.parseFactor(); if (!this.isFactorInfix()) { return factor; } const rightFactors = this.parseListWithPredicate( this.parseRightFactor.bind(this), this.isFactorInfix.bind(this), ); return new FactorChainNode( SourceLocation.merge(factor.getLocation(), rightFactors[rightFactors.length - 1].getLocation()), factor, rightFactors, ); } private isTermInfix(): boolean { return this.hasTag(Tag.Plus) || this.hasTag(Tag.Minus) || this.hasTag(Tag.Or); } private parseTermInfix(): InfixTermOperator { if (this.tryConsumeFixToken(Tag.Plus)) { return InfixTermOperator.Plus; } if (this.tryConsumeFixToken(Tag.Minus)) { return InfixTermOperator.Minus; } if (this.tryConsumeFixToken(Tag.Or)) { return InfixTermOperator.Or; } return this.error('Failed to parse term infix.'); } private parseRightTerm(): RightTermNode { const start = this.lexer.peek(1).getLocation(); const op = this.parseTermInfix(); const rightTerm = this.parseTerm(); return new RightTermNode(SourceLocation.merge(start, rightTerm.getLocation()), op, rightTerm); } private isTermPrefix(): boolean { return this.hasTag(Tag.Plus) || this.hasTag(Tag.Minus); } private parseUnaryPrefix(): PrefixOperator { if (this.tryConsumeFixToken(Tag.Plus)) { return PrefixOperator.Plus; } if (this.tryConsumeFixToken(Tag.Minus)) { return PrefixOperator.Minus; } return this.error('Failed to parse prefix operator.'); } private parseUnaryTerm(): UnaryTermNode { const start = this.lexer.peek(1).getLocation(); const op = this.parseUnaryPrefix(); const term = this.parseTerm(); return new UnaryTermNode(SourceLocation.merge(start, term.getLocation()), op, term); } private parseSimpleExpression(): SimpleExpressionNode { const left = this.isTermPrefix() ? this.parseUnaryTerm() : this.parseTerm(); if (!this.isTermInfix()) { return left; } const rightTerms = this.parseListWithPredicate(this.parseRightTerm.bind(this), this.isTermInfix.bind(this)); return new TermChainNode( SourceLocation.merge(left.getLocation(), rightTerms[rightTerms.length - 1].getLocation()), left, rightTerms, ); } private isBinaryExpressionOperator(): boolean { return ( this.hasTag(Tag.Equal) || this.hasTag(Tag.NumberSign) || this.hasTag(Tag.Less) || this.hasTag(Tag.LessEqual) || this.hasTag(Tag.Greater) || this.hasTag(Tag.GreaterEqual) ); } private parseBinaryExpressionOperator(): LogicalOperator { if (this.tryConsumeFixToken(Tag.Equal)) { return LogicalOperator.Equal; } if (this.tryConsumeFixToken(Tag.NumberSign)) { return LogicalOperator.NotEqual; } if (this.tryConsumeFixToken(Tag.Less)) { return LogicalOperator.Less; } if (this.tryConsumeFixToken(Tag.LessEqual)) { return LogicalOperator.LessEqual; } if (this.tryConsumeFixToken(Tag.Greater)) { return LogicalOperator.More; } if (this.tryConsumeFixToken(Tag.GreaterEqual)) { return LogicalOperator.MoreEqual; } return this.error('Failed to parse term infix.'); } private parseBinaryExpression( start: SourceLocation, attributes: Array<AttributeNode>, left: SimpleExpressionNode, ): BinaryExpressionNode { const op = this.parseBinaryExpressionOperator(); const right = this.parseSimpleExpression(); return new BinaryExpressionNode(SourceLocation.merge(start, right.getLocation()), attributes, left, op, right); } private parseExpression(): ExpressionNode { const attributes = this.parseAttributeList(); const simpleExpression = this.parseSimpleExpression(); if (simpleExpression instanceof DesignatorNode) { const start = attributes.length === 0 ? simpleExpression.getLocation() : attributes[0].getLocation(); if (this.tryConsumeFixToken(Tag.Offers)) { const offeredList = this.parseOfferedInterfaceDeclarationList(); return new OffersRequiresExpressionNode( SourceLocation.merge(start, offeredList[offeredList.length - 1].getLocation()), attributes, simpleExpression, OffersRequiresOperator.Offers, offeredList, ); } if (this.tryConsumeFixToken(Tag.Requires)) { const requiredList = this.parseRequiredInterfaceDeclarationList(); return new OffersRequiresExpressionNode( SourceLocation.merge(start, requiredList[requiredList.length - 1].getLocation()), attributes, simpleExpression, OffersRequiresOperator.Requires, requiredList, ); } if (this.tryConsumeFixToken(Tag.Is)) { const type = this.parseType(); return new TypeCheckExpressionNode( SourceLocation.merge(start, type.getLocation()), attributes, simpleExpression, type, ); } if (this.isBinaryExpressionOperator()) { return this.parseBinaryExpression(start, attributes, simpleExpression); } return simpleExpression; } const start = attributes.length === 0 ? simpleExpression.getLocation() : attributes[0].getLocation(); if (!this.isBinaryExpressionOperator()) { return new UnaryExpressionNode( SourceLocation.merge(start, simpleExpression.getLocation()), attributes, simpleExpression, ); } return this.parseBinaryExpression(start, attributes, simpleExpression); } private parseProcedureCall(): ProcedureCallNode { const name = this.parseName(); const expressions = new Array<ExpressionNode>(); let end = name.getLocation(); if (this.tryConsumeFixToken(Tag.OpenParentheses)) { expressions.push(...this.parseExpressionList()); end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); } return new ProcedureCallNode(SourceLocation.merge(name.getLocation(), end), name, expressions); } private parseStatementSequenceBlock(tag: Tag): Optional<StatementSequenceNode> { let statements: Optional<StatementSequenceNode> = undefined; if (this.hasTag(tag)) { const beginStart = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(tag); statements = this.parseStatementSequence(beginStart); } return statements; } private parseBasicDesignator(): DesignatorNode { const name = this.parseName(); if (this.tryConsumeFixToken(Tag.OpenSquareBracket)) { const expressions = this.parseExpressionList(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseSquareBracket); return new BasicExpressionDesignatorNode(SourceLocation.merge(name.getLocation(), end), name, expressions); } return new BasicDesignatorNode(name.getLocation(), name); } private parseDesignator(): DesignatorNode { const designator = this.parseBasicDesignator(); if (this.tryConsumeFixToken(Tag.OpenParentheses)) { // if ANY we assume type otherwise designator. // see original CCParser implementation if (this.hasTag(Tag.Any)) { const type = this.parseType(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new DesignatorTypeNode(SourceLocation.merge(designator.getLocation(), end), designator, type); } const target = this.parseDesignator(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new BaseTargetDesignatorNode( SourceLocation.merge(designator.getLocation(), end), designator, target, ); } return designator; } private tryParseDesignator(): Optional<DesignatorNode> { return this.tryParse(this.parseDesignator.bind(this)); } private parseStatementSequence(start: SourceLocation): StatementSequenceNode { const attributes = this.parseAttributeList(); const statements = this.parseStatementList(); const end = statements[statements.length - 1].getLocation(); return new StatementSequenceNode(SourceLocation.merge(start, end), attributes, statements); } private parseNewStatement(): NewNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeIdentifierToken(IdentifierKeywords.New); this.expectConsumeFixToken(Tag.OpenParentheses); const designator = this.parseDesignator(); const args = new Array<ExpressionNode>(); if (this.tryConsumeFixToken(Tag.Comma)) { args.push(...this.parseExpressionList()); } const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new NewNode(SourceLocation.merge(start, end), designator, args); } private parseDeleteStatement(): DeleteNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeIdentifierToken(IdentifierKeywords.Delete); this.expectConsumeFixToken(Tag.OpenParentheses); const designator = this.parseDesignator(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new DeleteNode(SourceLocation.merge(start, end), designator); } private parseConnectStatement(): ConnectNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeIdentifierToken(IdentifierKeywords.Conncet); this.expectConsumeFixToken(Tag.OpenParentheses); const designator = this.parseDesignator(); this.expectConsumeFixToken(Tag.Comma); const target = this.parseDesignator(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new ConnectNode(SourceLocation.merge(start, end), designator, target); } private parseDisconnectStatement(): DisconnectNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeIdentifierToken(IdentifierKeywords.Disconnect); this.expectConsumeFixToken(Tag.OpenParentheses); const designator = this.parseDesignator(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new DisconnectNode(SourceLocation.merge(start, end), designator); } private parseMoveStatement(): MoveNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeIdentifierToken(IdentifierKeywords.Move); this.expectConsumeFixToken(Tag.OpenParentheses); const designator = this.parseDesignator(); this.expectConsumeFixToken(Tag.Comma); const target = this.parseDesignator(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new MoveNode(SourceLocation.merge(start, end), designator, target); } private parseAwaitStatement(): AwaitNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeIdentifierToken(IdentifierKeywords.Await); this.expectConsumeFixToken(Tag.OpenParentheses); const expression = this.parseExpression(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); return new AwaitNode(SourceLocation.merge(start, end), expression); } private parseSendStatement(designator: Optional<DesignatorNode>): SendNode { const start = designator?.getLocation() ?? this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.ExclamationMark); const name = this.parseName(); const expressions = new Array<ExpressionNode>(); let end = name.getLocation(); if (this.tryConsumeFixToken(Tag.OpenParentheses)) { expressions.push(...this.parseExpressionList()); end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); } return new SendNode(SourceLocation.merge(start, end), designator, name, expressions); } private parseReceiveStatement(designator: Optional<DesignatorNode>): ReceiveNode { const start = designator?.getLocation() ?? this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.QuestionMark); const name = this.parseName(); const designators = new Array<DesignatorNode>(); let end = name.getLocation(); if (this.tryConsumeFixToken(Tag.OpenParentheses)) { designators.push(...this.parseDesignatorList()); end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.CloseParentheses); } return new ReceiveNode(SourceLocation.merge(start, end), designator, name, designators); } private parseAssignmentStatement(designator: DesignatorNode): AssignmentNode { this.expectConsumeFixToken(Tag.ColonEqual); const expression = this.parseExpression(); return new AssignmentNode( SourceLocation.merge(designator.getLocation(), expression.getLocation()), designator, expression, ); } private parseReturnStatement(): ReturnNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Return); const expression = this.tryParseExpression(); return new ReturnNode(SourceLocation.merge(start, expression?.getLocation() ?? start), expression); } private parseIfStatement(): IfNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.If); const expression = this.parseExpression(); this.expectConsumeFixToken(Tag.Then); const sequence = this.parseStatementSequence(this.lexer.peek(1).getLocation()); const elsIfs = new Array<ElseIfNode>(); this.loop( () => this.hasTag(Tag.Elsif), () => { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Elsif); const expression = this.parseExpression(); this.expectConsumeFixToken(Tag.Then); const sequence = this.parseStatementSequence(this.lexer.peek(1).getLocation()); elsIfs.push(new ElseIfNode(SourceLocation.merge(start, sequence.getLocation()), expression, sequence)); }, ); let elseSequence: Optional<StatementSequenceNode> = undefined; if (this.tryConsumeFixToken(Tag.Else)) { elseSequence = this.parseStatementSequence(this.lexer.peek(1).getLocation()); } const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.End); return new IfNode(SourceLocation.merge(start, end), expression, sequence, elsIfs, elseSequence); } private parseWhileStatement(): WhileNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.While); const expression = this.parseExpression(); this.expectConsumeFixToken(Tag.Do); const sequence = this.parseStatementSequence(this.lexer.peek(1).getLocation()); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.End); return new WhileNode(SourceLocation.merge(start, end), expression, sequence); } private parseRepeatStatement(): RepeatNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Repeat); const sequence = this.parseStatementSequence(this.lexer.peek(1).getLocation()); this.expectConsumeFixToken(Tag.Until); const expression = this.parseExpression(); return new RepeatNode(SourceLocation.merge(start, expression.getLocation()), sequence, expression); } private parseForStatement(): ForNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.For); const designator = this.parseDesignator(); this.expectConsumeFixToken(Tag.ColonEqual); const assignExpression = this.parseExpression(); this.expectConsumeFixToken(Tag.To); const expression = this.parseExpression(); let stride: Optional<ConstantExpressionNode> = undefined; if (this.tryConsumeFixToken(Tag.By)) { stride = this.parseConstantExpression(); } this.expectConsumeFixToken(Tag.Do); const sequence = this.parseStatementSequence(this.lexer.peek(1).getLocation()); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.End); return new ForNode( SourceLocation.merge(start, end), designator, assignExpression, expression, stride, sequence, ); } private parseForeachStatement(): ForeachNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Foreach); const designators = this.parseDesignatorList(); this.expectConsumeFixToken(Tag.Of); const designator = this.parseDesignator(); this.expectConsumeFixToken(Tag.Do); const sequence = this.parseStatementSequence(this.lexer.peek(1).getLocation()); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.End); return new ForeachNode(SourceLocation.merge(start, end), designators, designator, sequence); } private parseStatementBlock(): StatementBlockNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Begin); const sequence = this.parseStatementSequence(this.lexer.peek(1).getLocation()); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.End); return new StatementBlockNode(SourceLocation.merge(start, end), sequence); } private parseStatement(): StatementNode { if (this.hasIdentifier(IdentifierKeywords.New)) { return this.parseNewStatement(); } if (this.hasIdentifier(IdentifierKeywords.Delete)) { return this.parseDeleteStatement(); } if (this.hasIdentifier(IdentifierKeywords.Conncet)) { return this.parseConnectStatement(); } if (this.hasIdentifier(IdentifierKeywords.Disconnect)) { return this.parseDisconnectStatement(); } if (this.hasIdentifier(IdentifierKeywords.Move)) { return this.parseMoveStatement(); } if (this.hasIdentifier(IdentifierKeywords.Await)) { return this.parseAwaitStatement(); } if (this.hasTag(Tag.Return)) { return this.parseReturnStatement(); } if (this.hasTag(Tag.If)) { return this.parseIfStatement(); } if (this.hasTag(Tag.While)) { return this.parseWhileStatement(); } if (this.hasTag(Tag.Repeat)) { return this.parseRepeatStatement(); } if (this.hasTag(Tag.For)) { return this.parseForStatement(); } if (this.hasTag(Tag.Foreach)) { return this.parseForeachStatement(); } if (this.hasTag(Tag.Begin)) { return this.parseStatementBlock(); } const designator = this.tryParseDesignator(); if (this.hasTag(Tag.ExclamationMark)) { return this.parseSendStatement(designator); } if (this.hasTag(Tag.QuestionMark)) { return this.parseReceiveStatement(designator); } if (designator === undefined) { return this.parseProcedureCall(); } if (this.hasTag(Tag.ColonEqual)) { return this.parseAssignmentStatement(designator); } if (designator instanceof BasicDesignatorNode) { return new ProcedureCallNode(designator.getLocation(), designator.getName(), new Array<ExpressionNode>()); } if (designator instanceof BaseTargetDesignatorNode) { const base = designator.getBase(); if (base instanceof BasicDesignatorNode) { return new ProcedureCallNode( designator.getLocation(), base.getName(), new Array<ExpressionNode>(designator.getTarget()), ); } } return this.error('Failed to parse statement. Unknown statement.'); } private parseConstantExpression(): ConstantExpressionNode { const expression = this.parseExpression(); return new ConstantExpressionNode(expression.getLocation(), expression); } private parseConstant(): ConstantNode { const name = this.parseName(); this.expectConsumeFixToken(Tag.Equal); const expression = this.parseConstantExpression(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Semicolon); return new ConstantNode(SourceLocation.merge(name.getLocation(), end), name, expression); } private tryParseConstant(): Optional<ConstantNode> { return this.tryParse(this.parseConstant.bind(this)); } private parseConstantList(): ConstantListNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Constant); const constants = new NonEmptyArray<ConstantNode>(this.parseConstant()); let parsedConstant = true; this.loop( () => parsedConstant, () => { const constant = this.tryParseConstant(); if (constant === undefined) { parsedConstant = false; return; } constants.push(constant); }, ); return new ConstantListNode( SourceLocation.merge(start, constants[constants.length - 1].getLocation()), constants, ); } private parseVariable(): VariableNode { const start = this.lexer.peek(1).getLocation(); const names = this.parseIndexedIdentifierList(); this.expectConsumeFixToken(Tag.Colon); const type = this.parseType(); const attributes = this.parseAttributeList(); const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Semicolon); return new VariableNode(SourceLocation.merge(start, end), names, type, attributes); } private tryParseVariable(): Optional<VariableNode> { return this.tryParse(this.parseVariable.bind(this)); } private parseVariableList(): VariableListNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Variable); const variables = new NonEmptyArray<VariableNode>(this.parseVariable()); let parsedVariable = true; this.loop( () => parsedVariable, () => { const variable = this.tryParseVariable(); if (variable === undefined) { parsedVariable = false; return; } variables.push(variable); }, ); return new VariableListNode( SourceLocation.merge(start, variables[variables.length - 1].getLocation()), variables, ); } private parseProcedure(): ProcedureNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Procedure); const name = this.parseName(); const params = new Array<ProcedureParameterNode>(); if (this.tryConsumeFixToken(Tag.OpenParentheses)) { if (!this.hasTag(Tag.CloseParentheses)) { params.push(...this.parseProcedureParameterList()); } this.expectConsumeFixToken(Tag.CloseParentheses); } const type = this.tryConsumeFixToken(Tag.Colon) ? this.parseType() : undefined; this.expectConsumeFixToken(Tag.Semicolon); const declarations = new Array<DeclarationNode>(); let beginBlock: Optional<StatementSequenceNode> = undefined; let breakLoop = false; this.loop( () => !this.hasTag(Tag.End) && !breakLoop, () => { const declaration = this.parseDeclaration(); if (declaration !== undefined) { declarations.push(declaration); return; } if (beginBlock === undefined) { beginBlock = this.parseStatementSequenceBlock(Tag.Begin); return; } breakLoop = true; }, ); const end = this.consumeEnd(name); return new ProcedureNode(SourceLocation.merge(start, end), name, params, type, declarations, beginBlock); } private parseDeclaration(): Optional<DeclarationNode> { if (this.hasTag(Tag.Component)) { return this.parseComponent(); } if (this.hasTag(Tag.Interface)) { return this.parseInterface(); } if (this.hasTag(Tag.Constant)) { return this.parseConstantList(); } if (this.hasTag(Tag.Variable)) { return this.parseVariableList(); } if (this.hasTag(Tag.Procedure)) { return this.parseProcedure(); } return undefined; } private parseComponentBody(): Optional<ComponentBodyNode> { const start = this.lexer.peek(1).getLocation(); let beginBlock: Optional<StatementSequenceNode> = undefined; let activityBlock: Optional<StatementSequenceNode> = undefined; let finallyBlock: Optional<StatementSequenceNode> = undefined; const declarations = new Array<DeclarationNode>(); const implementations = new Array<ImplementationNode>(); let end = start; let breakLoop = false; this.loop( () => !this.hasTag(Tag.End) && !breakLoop, () => { const declaration = this.parseDeclaration(); if (declaration !== undefined) { declarations.push(declaration); return; } if (this.hasTag(Tag.Implementation)) { implementations.push(this.parseImplementation()); return; } if (beginBlock !== undefined && this.hasTag(Tag.Begin)) { this.error('Cannot have more than one begin per component.'); } if (beginBlock === undefined && this.hasTag(Tag.Begin)) { beginBlock = this.parseStatementSequenceBlock(Tag.Begin); if (beginBlock !== undefined) { end = beginBlock.getLocation(); } return; } if (activityBlock !== undefined && this.hasTag(Tag.Activity)) { this.error('Cannot have more than one activity per component.'); } if (activityBlock === undefined && this.hasTag(Tag.Activity)) { activityBlock = this.parseStatementSequenceBlock(Tag.Activity); if (activityBlock !== undefined) { end = activityBlock.getLocation(); } return; } if (finallyBlock !== undefined && this.hasTag(Tag.Finally)) { this.error('Cannot have more than one finally per component.'); } if (finallyBlock === undefined && this.hasTag(Tag.Finally)) { finallyBlock = this.parseStatementSequenceBlock(Tag.Finally); if (finallyBlock !== undefined) { end = finallyBlock.getLocation(); } return; } if (this.hasTag(Tag.Component)) { const component = this.parseComponent(); if (component !== undefined) { declarations.push(component); } return; } if (this.hasTag(Tag.Implementation)) { const implementation = this.parseImplementation(); if (implementation !== undefined) { implementations.push(implementation); } return; } breakLoop = true; }, ); return new ComponentBodyNode( SourceLocation.merge(start, end), declarations, implementations, beginBlock, activityBlock, finallyBlock, ); } private consumeEnd(name: NameNode): SourceLocation { this.expectConsumeFixToken(Tag.End); const endName = this.parseName(); if (endName.getName() !== name.getName()) { this.error('Name missmatch.'); } const end = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Semicolon); return end; } private parseComponent(): ComponentNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Component); const attributes = this.parseAttributeList(); const name = this.parseName(); const offers = this.parseOffers(); const requires = this.parseRequires(); this.expectConsumeFixToken(Tag.Semicolon); const body = this.parseComponentBody(); const end = this.consumeEnd(name); return new ComponentNode(SourceLocation.merge(start, end), name, attributes, offers, requires, body); } private parseImplementation(): ImplementationNode { const start = this.lexer.peek(1).getLocation(); this.expectConsumeFixToken(Tag.Implementation); const name = this.parseName(); this.expectConsumeFixToken(Tag.Semicolon); const declarations = this.parseDeclarationList(); const statements = this.parseStatementSequenceBlock(Tag.Begin); const end = this.consumeEnd(name); return new ImplementationNode(SourceLocation.merge(start, end), name, declarations, statements); } private parseListWithPredicate<T>(parseFunction: () => T, pred: () => boolean): NonEmptyArray<T> { const list = new NonEmptyArray<T>(parseFunction()); this.loop( () => pred(), () => { list.push(parseFunction()); }, ); return list; } private parseList<T>(parseFunction: () => T, separator: Tag): NonEmptyArray<T> { const list = new NonEmptyArray<T>(parseFunction()); this.loop( () => this.hasTag(separator), () => { this.expectConsumeFixToken(separator); list.push(parseFunction()); }, ); return list; } private parseIdentifierList(): NonEmptyArray<NameNode> { return this.parseList<NameNode>(this.parseName.bind(this), Tag.Comma); } private parseParameterList(): NonEmptyArray<ParameterNode> { return this.parseList<ParameterNode>(this.parseParameter.bind(this), Tag.Semicolon); } private parseProcedureParameterList(): NonEmptyArray<ProcedureParameterNode> { return this.parseList<ProcedureParameterNode>(this.parseProcedureParameter.bind(this), Tag.Semicolon); } private parseIndexedIdentifierList(): NonEmptyArray<IndexedNameNode> { return this.parseList<IndexedNameNode>(this.parseIndexedName.bind(this), Tag.Comma); } private parseExpressionList(): NonEmptyArray<ExpressionNode> { return this.parseList<ExpressionNode>(this.parseExpression.bind(this), Tag.Comma); } private parseDesignatorList(): NonEmptyArray<DesignatorNode> { return this.parseList<DesignatorNode>(this.parseDesignator.bind(this), Tag.Comma); } private parseOfferedInterfaceDeclarationList(): NonEmptyArray<OfferedInterfaceNode> { return this.parseList<OfferedInterfaceNode>(this.parseOfferedInterfaceDeclaration.bind(this), Tag.Comma); } private parseRequiredInterfaceDeclarationList(): NonEmptyArray<RequiredInterfaceNode> { return this.parseList<RequiredInterfaceNode>(this.parseRequiredInterfaceDeclaration.bind(this), Tag.Comma); } private parseStatementList(): NonEmptyArray<StatementNode> { return this.parseList<StatementNode>(this.parseStatement.bind(this), Tag.Semicolon); } private parseAttributeList(): Array<AttributeNode> { if (!this.hasTag(Tag.OpenBrace)) { return new Array<AttributeNode>(); } this.expectConsumeFixToken(Tag.OpenBrace); const names = this.parseIdentifierList(); this.expectConsumeFixToken(Tag.CloseBrace); return names.map((name) => new AttributeNode(name.getLocation(), name)); } private parseDeclarationList(): Array<DeclarationNode> { const declarations = new Array<DeclarationNode>(); let foundDeclaration = true; this.loop( () => foundDeclaration, () => { const declaration = this.parseDeclaration(); if (declaration === undefined) { foundDeclaration = false; return; }