diff --git a/src/cst.ts b/src/cst.ts index 76b1db596..de8e14a9e 100644 --- a/src/cst.ts +++ b/src/cst.ts @@ -168,6 +168,7 @@ export const enum SyntaxKind { ReturnStatement, ExpressionStatement, IfStatement, + AssignStatement, // If statement elements IfStatementCase, @@ -2235,6 +2236,36 @@ export class ReturnStatement extends SyntaxBase { } +export class AssignStatement extends SyntaxBase { + + public readonly kind = SyntaxKind.AssignStatement; + + public constructor( + public pattern: Pattern, + public operator: Assignment, + public expression: Expression, + ) { + super(); + } + + public clone(): AssignStatement { + return new AssignStatement( + this.pattern.clone(), + this.operator.clone(), + this.expression.clone() + ); + } + + public getFirstToken(): Token { + return this.pattern.getFirstToken(); + } + + public getLastToken(): Token { + return this.expression.getLastToken(); + } + +} + export class ExpressionStatement extends SyntaxBase { public readonly kind = SyntaxKind.ExpressionStatement; @@ -2263,6 +2294,7 @@ export type Statement = ReturnStatement | ExpressionStatement | IfStatement + | AssignStatement export class Param extends SyntaxBase { diff --git a/src/parser.ts b/src/parser.ts index 159169cf2..46f89f640 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -66,6 +66,7 @@ import { ClassKeyword, InstanceDeclaration, ClassConstraintClause, + AssignStatement, } from "./cst" import { Stream } from "./util"; @@ -782,6 +783,27 @@ export class Parser { return new Param(pattern); } + private lookaheadIsAssignment(): boolean { + for (let i = 1;; i++) { + const t0 = this.peekToken(i); + switch (t0.kind) { + case SyntaxKind.LineFoldEnd: + case SyntaxKind.BlockStart: + return false; + case SyntaxKind.Assignment: + return true; + } + } + } + + public parseAssignStatement(): AssignStatement { + const pattern = this.parsePattern(); + const operator = this.expectToken(SyntaxKind.Assignment); + const expression = this.parseExpression(); + this.expectToken(SyntaxKind.LineFoldEnd); + return new AssignStatement(pattern, operator, expression); + } + public parseLetBodyElement(): LetBodyElement { const t0 = this.peekTokenAfterModifiers(); switch (t0.kind) { @@ -792,6 +814,9 @@ export class Parser { case SyntaxKind.IfKeyword: return this.parseIfStatement(); default: + if (this.lookaheadIsAssignment()) { + return this.parseAssignStatement(); + } // TODO convert parse errors to include LetKeyword and ReturnKeyword return this.parseExpressionStatement(); } @@ -1165,6 +1190,9 @@ export class Parser { case SyntaxKind.IfKeyword: return this.parseIfStatement(); default: + if (this.lookaheadIsAssignment()) { + return this.parseAssignStatement(); + } return this.parseExpressionStatement(); } } diff --git a/src/scanner.ts b/src/scanner.ts index 227905f06..fcc13e0a1 100644 --- a/src/scanner.ts +++ b/src/scanner.ts @@ -229,7 +229,15 @@ export class Scanner extends BufferedStream { case '{': return new LBrace(startPos); case '}': return new RBrace(startPos); case ',': return new Comma(startPos); - case ':': return new Colon(startPos); + case ':': + const text = this.takeWhile(isOperatorPart); + if (text === '') { + return new Colon(startPos); + } else if (text === '=') { + return new Assignment(':', startPos); + } else { + throw new ScanError(this.file, startPos, ':' + text); + } case '.': { const dots = c0 + this.takeWhile(ch => ch === '.'); if (dots === '.') { @@ -266,7 +274,7 @@ export class Scanner extends BufferedStream { } else if (text === '=') { return new Equals(startPos); } else if (text.endsWith('=') && text[text.length-2] !== '=') { - return new Assignment(text, startPos); + return new Assignment(text.substring(0, text.length-1), startPos); } else { return new CustomOperator(text, startPos); }