From 0d6925ee6ff40898ed52103af5db23dea3af1185 Mon Sep 17 00:00:00 2001 From: Philippe Elsass Date: Wed, 18 Nov 2020 11:57:15 +0000 Subject: [PATCH] Make `ElseIf` into an `ElseIfStatement` --- src/astUtils/reflection.spec.ts | 9 ++- src/astUtils/reflection.ts | 5 +- src/astUtils/visitors.spec.ts | 20 ++++-- src/parser/Parser.ts | 34 +++++----- src/parser/Statement.ts | 112 ++++++++++++++++++++------------ 5 files changed, 113 insertions(+), 67 deletions(-) diff --git a/src/astUtils/reflection.spec.ts b/src/astUtils/reflection.spec.ts index 70d313162..513360130 100644 --- a/src/astUtils/reflection.spec.ts +++ b/src/astUtils/reflection.spec.ts @@ -1,12 +1,12 @@ /* eslint-disable no-multi-spaces */ import { Position, Range } from 'vscode-languageserver'; import { expect } from 'chai'; -import { PrintStatement, Block, Body, AssignmentStatement, CommentStatement, ExitForStatement, ExitWhileStatement, ExpressionStatement, FunctionStatement, IfStatement, IncrementStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, EmptyStatement } from '../parser/Statement'; +import { PrintStatement, Block, Body, AssignmentStatement, CommentStatement, ExitForStatement, ExitWhileStatement, ExpressionStatement, FunctionStatement, IfStatement, IncrementStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, EmptyStatement, ElseIfStatement } from '../parser/Statement'; import { FunctionExpression, NamespacedVariableNameExpression, BinaryExpression, CallExpression, DottedGetExpression, IndexedGetExpression, GroupingExpression, LiteralExpression, EscapedCharCodeLiteralExpression, ArrayLiteralExpression, AALiteralExpression, UnaryExpression, VariableExpression, SourceLiteralExpression, NewExpression, CallfuncExpression, TemplateStringQuasiExpression, XmlAttributeGetExpression, TemplateStringExpression, TaggedTemplateStringExpression, AnnotationExpression } from '../parser/Expression'; import type { Token } from '../lexer'; import { TokenKind } from '../lexer'; import { BrsString } from '../brsTypes'; -import { isPrintStatement, isIfStatement, isBody, isAssignmentStatement, isBlock, isExpressionStatement, isCommentStatement, isExitForStatement, isExitWhileStatement, isFunctionStatement, isIncrementStatement, isGotoStatement, isLabelStatement, isReturnStatement, isEndStatement, isStopStatement, isForStatement, isForEachStatement, isWhileStatement, isDottedSetStatement, isIndexedSetStatement, isLibraryStatement, isNamespaceStatement, isImportStatement, isExpression, isBinaryExpression, isCallExpression, isFunctionExpression, isNamespacedVariableNameExpression, isDottedGetExpression, isXmlAttributeGetExpression, isIndexedGetExpression, isGroupingExpression, isLiteralExpression, isEscapedCharCodeLiteralExpression, isArrayLiteralExpression, isAALiteralExpression, isUnaryExpression, isVariableExpression, isSourceLiteralExpression, isNewExpression, isCallfuncExpression, isTemplateStringQuasiExpression, isTemplateStringExpression, isTaggedTemplateStringExpression, isBrsFile, isXmlFile, isClassStatement, isStatement, isAnnotationExpression } from './reflection'; +import { isPrintStatement, isIfStatement, isBody, isAssignmentStatement, isBlock, isExpressionStatement, isCommentStatement, isExitForStatement, isExitWhileStatement, isFunctionStatement, isIncrementStatement, isGotoStatement, isLabelStatement, isReturnStatement, isEndStatement, isStopStatement, isForStatement, isForEachStatement, isWhileStatement, isDottedSetStatement, isIndexedSetStatement, isLibraryStatement, isNamespaceStatement, isImportStatement, isExpression, isBinaryExpression, isCallExpression, isFunctionExpression, isNamespacedVariableNameExpression, isDottedGetExpression, isXmlAttributeGetExpression, isIndexedGetExpression, isGroupingExpression, isLiteralExpression, isEscapedCharCodeLiteralExpression, isArrayLiteralExpression, isAALiteralExpression, isUnaryExpression, isVariableExpression, isSourceLiteralExpression, isNewExpression, isCallfuncExpression, isTemplateStringQuasiExpression, isTemplateStringExpression, isTaggedTemplateStringExpression, isBrsFile, isXmlFile, isClassStatement, isStatement, isAnnotationExpression, isElseIfStatement } from './reflection'; import { createRange, createToken, createStringLiteral, createIdentifier } from './creators'; import { Program } from '../Program'; import { BrsFile } from '../files/BrsFile'; @@ -40,6 +40,7 @@ describe('reflection', () => { const exitWhile = new ExitWhileStatement({ exitWhile: token }); const funs = new FunctionStatement(ident, new FunctionExpression([], undefined, block, token, token, token, token), undefined); const ifs = new IfStatement({ if: token }, expr, block, []); + const elseifs = new ElseIfStatement({ elseIfToken: token }, expr, block); const increment = new IncrementStatement(expr, token); const print = new PrintStatement({ print: token }, []); const gotos = new GotoStatement({ goto: token, label: token }); @@ -105,6 +106,10 @@ describe('reflection', () => { expect(isIfStatement(ifs)).to.be.true; expect(isIfStatement(body)).to.be.false; }); + it('isElseIfStatement', () => { + expect(isElseIfStatement(elseifs)).to.be.true; + expect(isElseIfStatement(body)).to.be.false; + }); it('isIncrementStatement', () => { expect(isIncrementStatement(increment)).to.be.true; expect(isIncrementStatement(body)).to.be.false; diff --git a/src/astUtils/reflection.ts b/src/astUtils/reflection.ts index 24ed3728a..5cd112bd0 100644 --- a/src/astUtils/reflection.ts +++ b/src/astUtils/reflection.ts @@ -1,4 +1,4 @@ -import type { Body, AssignmentStatement, Block, ExpressionStatement, CommentStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassFieldStatement, ClassMethodStatement, ClassStatement, Statement } from '../parser/Statement'; +import type { Body, AssignmentStatement, Block, ExpressionStatement, CommentStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, ElseIfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassFieldStatement, ClassMethodStatement, ClassStatement, Statement } from '../parser/Statement'; import type { LiteralExpression, Expression, BinaryExpression, CallExpression, FunctionExpression, NamespacedVariableNameExpression, DottedGetExpression, XmlAttributeGetExpression, IndexedGetExpression, GroupingExpression, EscapedCharCodeLiteralExpression, ArrayLiteralExpression, AALiteralExpression, UnaryExpression, VariableExpression, SourceLiteralExpression, NewExpression, CallfuncExpression, TemplateStringQuasiExpression, TemplateStringExpression, TaggedTemplateStringExpression, AnnotationExpression } from '../parser/Expression'; import type { BrsString, BrsInvalid, BrsBoolean, RoString, RoArray, RoAssociativeArray, RoSGNode, FunctionParameterExpression } from '../brsTypes'; import { ValueKind } from '../brsTypes'; @@ -60,6 +60,9 @@ export function isFunctionStatement(element: Statement | Expression | undefined) export function isIfStatement(element: Statement | Expression | undefined): element is IfStatement { return element?.constructor?.name === 'IfStatement'; } +export function isElseIfStatement(element: Statement | Expression | undefined): element is ElseIfStatement { + return element?.constructor?.name === 'ElseIfStatement'; +} export function isIncrementStatement(element: Statement | Expression | undefined): element is IncrementStatement { return element?.constructor?.name === 'IncrementStatement'; } diff --git a/src/astUtils/visitors.spec.ts b/src/astUtils/visitors.spec.ts index df5d0da6e..b8f0f0fff 100644 --- a/src/astUtils/visitors.spec.ts +++ b/src/astUtils/visitors.spec.ts @@ -114,8 +114,9 @@ describe('astUtils visitors', () => { 'IfStatement:1', // if a = 1 'Block:2', // then block 'PrintStatement:3', // print 3 - 'Block:2', // elseif block - 'PrintStatement:3', // print 4 + 'ElseIfStatement:2', // elseif statement + 'Block:3', // elseif block + 'PrintStatement:4', // print 4 'Block:2', // else block 'PrintStatement:3', // print 5 'WhileStatement:1', // while a <> invalid @@ -150,6 +151,7 @@ describe('astUtils visitors', () => { 'IfStatement', // if a = 1 'Block', // then block 'PrintStatement', // print 3 + 'ElseIfStatement', // elseif statement 'Block', // elseif block 'PrintStatement', // print 4 'Block', // else block @@ -194,6 +196,7 @@ describe('astUtils visitors', () => { 'IfStatement', // if a = 1 'Block', // then block 'PrintStatement', // print 3 + 'ElseIfStatement', // elseif statement 'Block', // elseif block 'PrintStatement' // print 4 ]); @@ -302,10 +305,10 @@ describe('astUtils visitors', () => { 'IfStatement:1:BinaryExpression', // if 0> 'IfStatement:1:VariableExpression', // if > 0 'IfStatement:1:LiteralExpression', // if j > <0> - 'IfStatement:1:BinaryExpression', // else if - 'IfStatement:1:VariableExpression', // else if < -10 - 'IfStatement:1:UnaryExpression', // else if j < <-10> - 'IfStatement:1:LiteralExpression', // else if j < -<10> + 'ElseIfStatement:2:BinaryExpression', // else if + 'ElseIfStatement:2:VariableExpression', // else if < -10 + 'ElseIfStatement:2:UnaryExpression', // else if j < <-10> + 'ElseIfStatement:2:LiteralExpression', // else if j < -<10> 'ReturnStatement:1:LiteralExpression' // return ]); }); @@ -375,6 +378,7 @@ describe('astUtils visitors', () => { 'PrintStatement', 'LiteralExpression', //else if + 'ElseIfStatement', 'LiteralExpression', 'Block', 'PrintStatement', @@ -741,18 +745,22 @@ describe('astUtils visitors', () => { 'Block', 'AssignmentStatement', 'LiteralExpression', + //if 'IfStatement', 'BinaryExpression', 'VariableExpression', 'LiteralExpression', 'Block', 'ReturnStatement', + //else if + 'ElseIfStatement', 'BinaryExpression', 'VariableExpression', 'LiteralExpression', 'Block', 'ReturnStatement', 'LiteralExpression', + //else 'Block', 'ReturnStatement', 'CommentStatement' diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts index 628e0bd9c..11cb9d603 100644 --- a/src/parser/Parser.ts +++ b/src/parser/Parser.ts @@ -28,10 +28,10 @@ import { import type { Statement, PrintSeparatorTab, - PrintSeparatorSpace, - ElseIf + PrintSeparatorSpace } from './Statement'; import { + ElseIfStatement, FunctionStatement, CommentStatement, AssignmentStatement, @@ -1381,7 +1381,7 @@ export class Parser { const condition = this.expression(); let thenBranch: Block; - let elseIfBranches: ElseIf[] = []; + let elseIfBranches: ElseIfStatement[] = []; let elseBranch: Block | undefined; let thenToken: Token | undefined; @@ -1472,12 +1472,14 @@ export class Parser { endIfToken = blockEnd; } - elseIfBranches.push({ - condition: elseIfCondition, - thenBranch: elseIfThen, - thenToken: thenToken, - elseIfToken: elseIfToken - }); + elseIfBranches.push(new ElseIfStatement( + { + thenToken: thenToken, + elseIfToken: elseIfToken + }, + elseIfCondition, + elseIfThen + )); } if (this.match(TokenKind.Else)) { @@ -1558,12 +1560,14 @@ export class Parser { throw this.lastDiagnosticAsError(); } - elseIfBranches.push({ - condition: elseIfCondition, - thenBranch: new Block([elseIfThen], this.peek().range), - thenToken: thenToken, - elseIfToken: elseIf - }); + elseIfBranches.push(new ElseIfStatement( + { + thenToken: thenToken, + elseIfToken: elseIf + }, + elseIfCondition, + new Block([elseIfThen], this.peek().range) + )); } if (this.previous().kind !== TokenKind.Newline && this.match(TokenKind.Else)) { elseToken = this.previous(); diff --git a/src/parser/Statement.ts b/src/parser/Statement.ts index de270ac21..749922f40 100644 --- a/src/parser/Statement.ts +++ b/src/parser/Statement.ts @@ -386,11 +386,67 @@ export class FunctionStatement extends Statement implements TypedefProvider { } } -export interface ElseIf { - elseIfToken: Token; - thenToken?: Token; - condition: Expression; - thenBranch: Block; +export class ElseIfStatement extends Statement { + constructor( + readonly tokens: { + elseIfToken: Token; + thenToken?: Token; + }, + readonly condition: Expression, + readonly thenBranch: Block + ) { + super(); + this.range = util.createRangeFromPositions( + this.tokens.elseIfToken.range.start, + (this.thenBranch ?? this.tokens.thenToken ?? this.condition).range.end + ); + } + public readonly range: Range; + + transpile(state: TranspileState) { + const { elseIfToken, thenToken } = this.tokens; + const { condition, thenBranch } = this; + const results = []; + + results.push( + state.indent(), + new SourceNode(elseIfToken.range.start.line + 1, elseIfToken.range.start.character, state.pathAbsolute, 'else if'), + ' ' + ); + + //condition + results.push(...condition.transpile(state)); + //then + results.push(' '); + if (thenToken) { + results.push( + new SourceNode(thenToken.range.start.line + 1, thenToken.range.start.character, state.pathAbsolute, 'then') + ); + } else { + results.push('then'); + } + + //then body + state.lineage.unshift(thenBranch); + let body = thenBranch.transpile(state); + state.lineage.shift(); + + if (body.length > 0) { + results.push(...body); + } + results.push('\n'); + + return results; + } + + walk(visitor: WalkVisitor, options: WalkOptions) { + if (options.walkMode & InternalWalkMode.walkExpressions) { + walk(this, 'condition', visitor, options); + } + if (options.walkMode & InternalWalkMode.walkStatements) { + walk(this, 'thenBranch', visitor, options); + } + } } export class IfStatement extends Statement { @@ -403,7 +459,7 @@ export class IfStatement extends Statement { }, readonly condition: Expression, readonly thenBranch: Block, - readonly elseIfs: ElseIf[], + readonly elseIfs: ElseIfStatement[], readonly elseBranch?: Block ) { super(); @@ -441,34 +497,8 @@ export class IfStatement extends Statement { //else if blocks for (let elseif of this.elseIfs) { - //elseif - results.push( - state.indent(), - new SourceNode(elseif.elseIfToken.range.start.line + 1, elseif.elseIfToken.range.start.character, state.pathAbsolute, 'else if'), - ' ' - ); - - //condition - results.push(...elseif.condition.transpile(state)); - //then - results.push(' '); - if (elseif.thenToken) { - results.push( - new SourceNode(elseif.thenToken.range.start.line + 1, elseif.thenToken.range.start.character, state.pathAbsolute, 'then') - ); - } else { - results.push('then'); - } - - //then body - state.lineage.unshift(elseif.thenBranch); - let body = elseif.thenBranch.transpile(state); - state.lineage.shift(); - - if (body.length > 0) { - results.push(...body); - } - results.push('\n'); + let elseifNodes = elseif.transpile(state); + results.push(elseifNodes); } //else branch @@ -510,18 +540,14 @@ export class IfStatement extends Statement { walk(this, 'thenBranch', visitor, options); } - for (let i = 0; i < this.elseIfs.length; i++) { - if (options.walkMode & InternalWalkMode.walkExpressions) { - walk(this.elseIfs[i], 'condition', visitor, options, this); + if (options.walkMode & InternalWalkMode.walkStatements) { + for (let i = 0; i < this.elseIfs.length; i++) { + walk(this.elseIfs, i, visitor, options, this); } - if (options.walkMode & InternalWalkMode.walkStatements) { - walk(this.elseIfs[i], 'thenBranch', visitor, options, this); + if (this.elseBranch) { + walk(this, 'elseBranch', visitor, options); } } - - if (this.elseBranch && options.walkMode & InternalWalkMode.walkStatements) { - walk(this, 'elseBranch', visitor, options); - } } }