UNPKG

js-ast-parser

Version:
1,429 lines (1,277 loc) 54.9 kB
import { JavascriptAst } from './JavascriptAst'; import { IS_PARSING_CASE, IS_PARSING_TRY, IS_PARSING_DOWHILE, IS_PARSING_CLASSDECLARATION, IS_PARSING_COMPUTE, IS_PARSING_IMPORT, IS_PARSING_VARDECLARATION, IS_PARSING_FUNDECLARATION, NEED_PURE_IDENTIFIER, IS_PARSING_ACCESSPROP, IS_PARSING_SWITCH, NodeCode, priorityMap, IS_PARSING_TYPEOF, IS_PARSING_VOID, IS_PARSING_OBJECT_KEY } from './constants'; import { Patterns } from "./patterns"; import { isIdentifierChar, isIdentifierStart, isNumberStart, isNumberCharCode, isHexCharCode, assertUnexpect } from "./javasriptAstParserUtil"; import { UnExpectStatement } from './lib/statement/UnExpectStatement'; import { ImportSpecifier, ImportStatement } from './lib/statement/ImportStatement'; import { StringLiteralExpression } from './lib/expression/StringLiteralExpression'; import { IdentifierLiteralExpression } from './lib/expression/IdentifierLiteralExpression'; import { BinaryExpression } from './lib/expression/BinaryExpression'; import { AstNode } from './lib/AstNode'; import { MemberExpression } from './lib/expression/MemberExpression'; import { CallExpression } from './lib/expression/CallExpression'; import { TernaryExpression } from './lib/expression/TernaryExpression'; import { TemplateLiteralExppression } from './lib/expression/TemplateLiteralExppression'; import { VariableDeclarationStatement } from './lib/statement/VariableDeclarationStatement'; import { FunctionDeclarationStatement } from './lib/statement/FunctionDeclarationStatement'; import { Block } from './Block'; import { SwitchCaseStatement } from './lib/statement/SwitchCaseStatement'; import { TryCathchStatement } from './lib/statement/TryCatchStatement'; import { DoWhileStatement } from './lib/statement/DoWhileStatement'; import { WhileStatement } from './lib/statement/WhileStatement'; import { ForStatement } from './lib/statement/ForStatement'; import { IfStatement } from './lib/statement/IfStatement'; import { ObjectLiteralExpression } from './lib/expression/ObjectLiteralExpression'; import { ArrayLiteralExpression } from './lib/expression/ArrayLiteralExpression'; import { UnaryExpression } from './lib/expression/UnaryExpression'; import { SwitchStatement } from './lib/statement/SwitchStatement'; import { WithStatement } from './lib/statement/WithStatement'; import { NewExpressioin } from './lib/expression/NewExpressioin'; import { ExportDeclarationStatement, ExportSpecifier } from './lib/statement/ExportDeclarationStatement'; import { ClassDeclarationStatement } from './lib/statement/ClassDeclarationStatement'; import { AssignmentExpression } from './lib/expression/AssignmentExpression'; import { RegExpressioin } from './lib/expression/RegExpressioin'; import { Expression } from './lib/expression/Expression'; import { ParticularLiteralExpression } from './lib/expression/ParticularLiteralExpression'; import { NumberLiteralExpression } from './lib/expression/NumberLiteratalExpression'; import { ReturnStatement } from './lib/statement/ReturnStatement'; import { ThrowStatement } from './lib/statement/ThrowStatement'; import { DeleteStatement } from './lib/statement/DeleteStatement'; import { TypeOfExpression } from './lib/expression/TypeOfExpression'; import { FunctionExpression } from './lib/expression/FuntionExpression'; import { JavascriptScopeAnalyzer } from './lib/scope-analyzer/scope-analyzer'; import { BreakStatement } from './lib/statement/BreakStatement'; import { ContinueStatement } from './lib/statement/ContinueStatement'; import { VoidExpression } from './lib/expression/VoidExpression'; import { BracketEnwrapedExpressioin } from './lib/expression/BracketEnwrapedExpressioin'; import { DebuggerStatement } from './lib/statement/DebuggerStatement'; export class JavascriptAstParsser { static nonASCIIwhitespace = /[\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]/; private pos = 0; private currentToken: any = {}; private excutionFlag = 0; private startTokens: any[] = []; private stringStartToken: number; private tokens: any = []; private parsingNode: AstNode; private currentLine = 1; private isParsingFunDeclaration = false; private isParsingLoop = false; private depth = 1; private isParsingVarDeclaration = false; ast: JavascriptAst; private get curCharCode() { return this.input.charCodeAt(this.pos); } private get nextCharCode() { return this.input.charCodeAt(this.pos + 1); } private get curChar() { return this.input.charAt(this.pos); } constructor(private input: string) { } parse(analyze = true) { this.ast = new JavascriptAst(); let topLevelBlock = this.ast.topLevelBlock; this.ast.topLevelBlock.depth = this.depth; while(!this.isReadAll()) { let node: AstNode | Block = this.parseNode(); if (!node) { if (this.currentToken.value === '{') { const reset = this.markPos(); node = this.parseObjLiteralExp(); if ( (node as any).code === NodeCode.ObjectLiteralExpression && !(node as any).properties.length && (node as any).unexpectedNodes.length ) { reset(); node = this.parseBlock(); } } else if (this.currentToken.value === '[') { node = this.parseArrExp() } } else if ((<AstNode>node).isUnexpectEnd) { node = new UnExpectStatement(node, 'unexpect end value') } node && topLevelBlock.body.push(node as any) } debugger; if (analyze) { new JavascriptScopeAnalyzer().analyze(this.ast.topLevelBlock); } return this.ast; } private isReadAll() { return this.pos > this.input.length - 1; } private isLineEnd() { let pos = this.pos, line = this.currentLine; this.skipSpace(); let curLine = this.currentLine; let isLineEnd = curLine > line || this.isReadAll(); this.pos = pos; this.currentLine = line; return isLineEnd; } private advance(offset?: number) { this.pos = this.pos + (offset || 1 ); } private markPos() { let pos = this.pos, token = this.currentToken, len = this.tokens.length, startTokens = this.startTokens.slice(); return () => { this.pos = pos; this.currentToken = token; this.tokens.length = len; this.startTokens = startTokens; } } private skipSpace() { loop: while (!this.isReadAll()) { let ch = this.input.charCodeAt(this.pos) switch (ch) { case 32: case 160: //'' ++this.pos break case 13: if (this.input.charCodeAt(this.pos + 1) === 10) { ++this.pos } case 10: case 8232: case 8233: this.pos ++; this.currentLine ++; break case 47: if(this.nextCharCode === 47) { this.pos ++; this.skipLineComment(); break; } else if (this.nextCharCode === 42) { this.pos ++; this.skipBlockComment(); break; } break loop; default: if (ch > 8 && ch < 14) { ++this.pos } else { break loop } } } } private consumeNextWordIfMatch(expectedWord: string) { let reset = this.markPos(); this.next(); if (this.currentToken.value === expectedWord) { return true } reset(); return false; } private isNextWordIs(expectedWord: string) { let reset = this.markPos(); this.next(); if (this.currentToken.value === expectedWord) { reset(); return true } reset(); return false; } private startParse(node: AstNode, excutionFlag?: number) { let prevParsingNode = this.parsingNode, prevIsParsingLoop = this.isParsingLoop, isParsingFunDeclaration = this.isParsingFunDeclaration; this.parsingNode = node; excutionFlag && (this.excutionFlag |= excutionFlag); return (expectExpressionEnd = true) => { if (expectExpressionEnd && !prevParsingNode) { this.expectExpressionEnd(); } node.loc.end = this.pos; if (this.parsingNode.code === NodeCode.FunctionDeclarationStatement) { this.isParsingFunDeclaration = false } else if ((NodeCode.Loop & this.parsingNode.code) === this.parsingNode.code) { this.isParsingLoop = false; } this.parsingNode = prevParsingNode; this.isParsingLoop = prevIsParsingLoop; this.isParsingFunDeclaration = isParsingFunDeclaration; excutionFlag && (this.excutionFlag &= ~excutionFlag); if (node.isUnexpectEnd && this.parsingNode) { this.parsingNode.isUnexpectEnd = true; } return node; } } private expectToken(expectedToken: string | string[]) { if(this.isReadAll()) { this.parsingNode.isUnexpectEnd = true; return false; } expectedToken = typeof expectedToken === 'string' ? [expectedToken] : expectedToken; for(let value of expectedToken) { // let node = this.parseNode(); // if (node) { // assertUnexpect(this.parsingNode, node, ()=> `expect ${expectedToken}`); // return false; // } this.next(); if (this.currentToken.value !== value) { this.parsingNode.unexpectedNodes.push(new UnExpectStatement(this.currentToken.value, `expect ${value}`, this.currentToken.loc)) return false; } } return true } private expectExpressionEnd() { this.isReadAll() || this.isLineEnd() || this.expectToken(';') // while(true) { // if ( // this.isReadAll() || // this.isLineEnd() || // this.expectToken(';') // ) break // } } private parseNode(context: any = {}) { if (this.isReadAll()) return null; this.next(); let node: AstNode; switch(this.currentToken.type) { case 'Keyword': return this.parseIsStartWithKeyword(); case 'ParticularLiteral': return new ParticularLiteralExpression(this.currentToken); case 'TemplateLiteralExpStart': return this.parseTemplateLiteralExp(); case 'IdentifierLiteral': node = new IdentifierLiteralExpression(this.currentToken); if(this.excutionFlag & NEED_PURE_IDENTIFIER) return node; return this.tryParseAssign(node) || !this.isParsingVarDeclaration && this.tryParse(node) || node; case 'StringLiteral': return this.tryParse(new StringLiteralExpression(this.currentToken)); case 'UnclosedStringLiteral': return new UnExpectStatement(new StringLiteralExpression(this.currentToken), 'unclosed string', this.currentToken.loc); case 'NumberLiteral': node = new NumberLiteralExpression(this.currentToken); return this.tryParse(node); case 'Operator': if(!context.expectOperator && Patterns.prefixUnary.test(this.currentToken.value)) { return this.parseUnaryExpression(); } return null; case 'RegExpStart': if(context.expectOperator) { return null; } return this.parseRegStatement(); case 'IllegalToken': return new UnExpectStatement(this.currentToken.value, 'Illegal token', this.currentToken.loc); } if (!this.currentToken.value && this.isReadAll()) { if (this.parsingNode) this.parsingNode.isUnexpectEnd = true; } return null; } private next() { this.skipSpace(); this.currentToken = { loc: {start: null, end: null} }; this.currentToken.loc.start = this.pos; this.readWord(); this.currentToken.loc.end = this.pos; this.tokens.push(Object.assign(this.currentToken)); return this.currentToken; } private readWord() { if (isIdentifierStart(this.curCharCode)) { this.currentToken.value = this.readIdentifier(); if ( !(this.excutionFlag & IS_PARSING_ACCESSPROP) && !(this.excutionFlag & IS_PARSING_OBJECT_KEY) && (this.currentToken.value === 'instanceof' || this.currentToken.value === 'in') ) { this.currentToken.type = 'Operator' } else if ( Patterns.keywords.test(this.currentToken.value) && !(this.excutionFlag & IS_PARSING_ACCESSPROP) && !(this.excutionFlag & IS_PARSING_OBJECT_KEY) ) { this.currentToken.type = 'Keyword' } else if (Patterns.ParticularLiteral.test(this.currentToken.value)) { this.currentToken.type = 'ParticularLiteral' } else { this.currentToken.type = 'IdentifierLiteral' } } else if (isNumberStart(this.curCharCode)) { this.currentToken.type = 'NumberLiteral' this.currentToken.value = this.readNumber(); } else if (this.curCharCode === 39 || this.curCharCode === 34) { let value = this.readString(); if (this.curCharCode !== this.stringStartToken) { // 遇到换行字符串还没闭合 this.currentToken.type = 'UnclosedStringLiteral' } else { this.currentToken.type = 'StringLiteral' this.advance(); } this.currentToken.value = value; } else if (Patterns.operators.test(this.curChar)) { this.currentToken.type = 'Operator'; this.currentToken.value = this.readOperator(); this.currentToken.isAssignOperator = Patterns.assign.test(this.currentToken.value); } else { let start = this.pos; this.advance(); this.currentToken.value = this.input.slice(start, this.pos); if (Patterns.blockStart.test(this.currentToken.value)) { this.currentToken.type = 'BlockStart'; } else if (Patterns.blockClose.test(this.currentToken.value)) { this.currentToken.type = 'BlockClose' } else if (this.currentToken.value === '`') { this.currentToken.type = 'TemplateLiteralExpStart' } else if (this.currentToken.value && ';.,:'.indexOf(this.currentToken.value) < 0) { this.currentToken.value += this.readIllegalToken(); this.currentToken.type = 'IllegalToken'; } } } private readIllegalToken() { let start = this.pos; while(!this.isLineEnd() && this.curChar !== ';') { this.advance(); } return this.input.slice(start, this.pos); } private readNumber() { let start = this.pos; this.consumeNumber(); return this.input.slice(start, this.pos); } private consumeNumber() { if (this.curCharCode === 48) { // 0开头 switch (this.nextCharCode) { case 98: // '0b'开头,二进制数 this.advance(2); this.consumeBinary(); break; case 120: // '0x'开头, 16进制数 this.advance(2); this.consumeHex(); break; default: this.advance(); break; } } else { this.currentToken.type = 'NumberLiteral'; let isReadingDecimal = false; while((isNumberCharCode(this.curCharCode) || this.curCharCode === 46) && !this.isReadAll()) { // 46 为 '.' if (this.curCharCode === 46) { if (isReadingDecimal) { // 一个nunber里不应有两个小数点 this.consumeUnexpect([]); return; } isReadingDecimal = true; } this.advance(); } } } private readString() { this.stringStartToken = this.curCharCode; this.advance(); let start = this.pos; this.consumeString(); return this.input.slice(start, this.pos); } private consumeString() { while(this.curCharCode !== this.stringStartToken && this.curCharCode !== 10/*\n*/ && !this.isReadAll()) { this.advance(); } } private readIdentifier() { let start = this.pos; this.advance(); this.consumeIdentifier(); return this.input.slice(start, this.pos); } private consumeIdentifier() { while(isIdentifierChar(this.curCharCode)) { this.advance(); if (this.isReadAll()) break; } } private readOperator() { let start = this.pos; if ( Patterns.canReapeat.test(this.curChar) && this.nextCharCode === this.curCharCode || Patterns.canPrefixAssign.test(this.curChar) && this.nextCharCode === 61 /* '='*/ || this.curCharCode === 33 /* '!' */ && this.nextCharCode === 61 ) { this.advance(); } if (this.curCharCode === 61 && this.nextCharCode === 61) { this.advance(); } this.advance(); return this.input.slice(start, this.pos); } private safelyParseNode() { let node = this.parseNode(); if (!this.parsingNode) { return node; } if (assertUnexpect(this.parsingNode, node)) return null; return node; } private getExpectedNode(expectedCode: number) { let node = this.safelyParseNode(); if (node) { if((expectedCode & node.code) === node.code) return node; this.parsingNode.unexpectedNodes.push(new UnExpectStatement(node, '应为表达式')); } else if (this.isReadAll()) { this.parsingNode.isUnexpectEnd = true; } else { if (this.parsingNode && this.parsingNode.unexpectedNodes.length) return null; if (this.currentToken.value === '(' && expectedCode === NodeCode.Expression) return this.parseBracketEnwrapedExpression(); if (this.currentToken.value === '{' && (expectedCode & NodeCode.ObjectLiteralExpression) === NodeCode.ObjectLiteralExpression) return this.parseObjLiteralExp(); if (this.currentToken.value === '[' && (expectedCode & NodeCode.ArrayLiteralExpression) === NodeCode.ArrayLiteralExpression) return this.parseArrExp(); if (this.currentToken.value === '/' && (expectedCode & NodeCode.RegExpression) === NodeCode.RegExpression) return this.parseRegStatement(); this.parsingNode.unexpectedNodes.push(new UnExpectStatement(this.currentToken.value, `字符 '${this.currentToken.value}' 不该出现在此处`, this.currentToken.loc)) } return null; } private expectExpression() { return this.getExpectedNode(NodeCode.Expression) } private tryToGetExpectedNode(expectedCode: number) { let reset = this.markPos(); let node = this.parseNode(); if ( node && !node.unexpectedNodes.length && (node.code & expectedCode) === node.code ) return node; reset(); return null; } private parseBlock(terminationTest: Function = (node: AstNode) => !node && this.currentToken.value === '}') { let prevDepth = this.depth; this.depth ++; let block = new Block(), prevContext = this.excutionFlag, prevParsingNode = this.parsingNode; block.loc = {start:this.pos, end: null as any}; block.depth = this.depth; this.excutionFlag = 0; this.parsingNode = null as any; while(true) { if (this.isReadAll()) { prevParsingNode.isUnexpectEnd = true; break; } let node = this.parseNode(); if (!node) { if (this.currentToken.value === '{') { const reset = this.markPos(); node = this.parseObjLiteralExp(); if ( (node as any).code === NodeCode.ObjectLiteralExpression && !(node as any).properties.length && (node as any).unexpectedNodes.length ) { reset(); node = this.parseBlock(); } } else if (this.currentToken.value === '[') { node = this.parseArrExp() } } if (node) { if (node.isUnexpectEnd) { block.body.push(new UnExpectStatement(node, 'unexpect end value')); } else { block.body.push(node); } } if (terminationTest(node)) break; } this.excutionFlag = prevContext; this.parsingNode = prevParsingNode; this.depth = prevDepth; let len = block.body.length; if (len) { block.loc.end = block.body[len - 1].loc.end as number; } else { block.loc.end = block.loc.start } return block; } private parseUnaryExpression() { let node: UnaryExpression = new UnaryExpression(this.currentToken); let finishParse = this.startParse(node); node.prefix = true; node.operator = this.currentToken.value; node.argument = this.expectExpression(); return finishParse(); } // statement parse start private parseImportStatement() { let node = this.tryToParseCallExpression(new IdentifierLiteralExpression(this.currentToken)) if (node) { return node; } else { node = new ImportStatement(this.currentToken); let finishParse = this.startParse(node, IS_PARSING_IMPORT); if(node.from = this.tryToGetExpectedNode(NodeCode.StringLiteralExpression)) return finishParse(); if (this.consumeNextWordIfMatch('{')) { node.specifiers = this.getImportedSpecifiers(); } else if ( node.imported = this.getExpectedNode(NodeCode.IdentifierLiteralExpression) ) { if ( this.consumeNextWordIfMatch('as') && !(node.local = this.getExpectedNode(NodeCode.IdentifierLiteralExpression)) ) { return finishParse(); } } if ( node.unexpectedNodes.length || !this.expectToken('from') ) return finishParse(); node.from = this.getExpectedNode(NodeCode.StringLiteralExpression); return finishParse(); } } private getImportedSpecifiers() { let specifiers: ImportSpecifier[] = [], expectComma = false; while(!this.consumeNextWordIfMatch('}') && !this.isReadAll()) { if (expectComma) { if (!this.expectToken(',')) { break; } } else { let node: IdentifierLiteralExpression = this.getExpectedNode(NodeCode.IdentifierLiteralExpression); if (!node) break; let specifier: ImportSpecifier = { imported: node, local: null as any } if (this.consumeNextWordIfMatch(':') || this.consumeNextWordIfMatch('as')) { if (!(specifier.local = this.getExpectedNode(NodeCode.IdentifierLiteralExpression))) { break; } } specifiers.push(specifier); } expectComma = !expectComma; } return specifiers; } private parseVarDeclarationStatement() { this.isParsingVarDeclaration = true; let statement: VariableDeclarationStatement = new VariableDeclarationStatement(this.currentToken); let finishParse = this.startParse(statement, IS_PARSING_VARDECLARATION); let declaration: Expression; while(declaration = this.getExpectedNode(NodeCode.IdentifierLiteralExpression | NodeCode.AssignmentExpression)) { statement.declarations.push(declaration); if (!this.consumeNextWordIfMatch(',')) { break } } this.isParsingVarDeclaration = false; return finishParse(); } private parseFunDeclarationStatement() { let isClassMethod = this.currentToken.value !== 'function'; let statement: FunctionDeclarationStatement = new FunctionDeclarationStatement(this.currentToken); let finishParse = this.startParse(statement, IS_PARSING_FUNDECLARATION); this.isParsingFunDeclaration = true; if ( (statement.identifier = this.getExpectedNode(NodeCode.IdentifierLiteralExpression)) && this.expectToken('(') ) { let params: IdentifierLiteralExpression; if (!this.consumeNextWordIfMatch(')')) { while( params = this.getExpectedNode(NodeCode.IdentifierLiteralExpression) ) { statement.params.push(params); if (this.consumeNextWordIfMatch(')')) { break; } else if (this.expectToken(',')) { continue; } } } } if (isClassMethod && statement.identifier) statement.loc.start = statement.identifier.loc.start; if(statement.unexpectedNodes.length) return finishParse(); if (this.expectToken('{')) { statement.body = this.parseBlock(); statement.body.functionBlock = true; } return finishParse(); } private tryParseFunciotnExpress() { let currentToken = this.currentToken; if (this.consumeNextWordIfMatch('(')) { this.isParsingFunDeclaration = true; let expression: FunctionExpression = new FunctionExpression(currentToken); let finishParse = this.startParse(expression); let param: IdentifierLiteralExpression; if (!this.consumeNextWordIfMatch(')')) { while( param = this.getExpectedNode(NodeCode.IdentifierLiteralExpression) ) { expression.params.push(param); if ( this.consumeNextWordIfMatch(')') || !this.expectToken(',')) break; } }; if (!expression.unexpectedNodes.length && this.expectToken('{')) { expression.body = this.parseBlock(); expression.body.functionBlock = true; } return finishParse(); } return null; } private parseObjLiteralExp() { let expression: ObjectLiteralExpression = new ObjectLiteralExpression(this.currentToken); let finishParse = this.startParse(expression); let key: IdentifierLiteralExpression | StringLiteralExpression, value: Expression; this.excutionFlag |= IS_PARSING_OBJECT_KEY; while(!this.consumeNextWordIfMatch('}') && (key = this.getExpectedNode(NodeCode.ObjectKey))) { this.excutionFlag &= ~IS_PARSING_OBJECT_KEY; if (this.expectToken(':') && (value = this.getExpectedNode(NodeCode.Expression))) { expression.properties.push({ key: key as any, value: value }); this.excutionFlag |= IS_PARSING_OBJECT_KEY; if (this.consumeNextWordIfMatch('}') || !this.expectToken(',')) { break } } else { break; } } this.excutionFlag &= ~IS_PARSING_OBJECT_KEY; if (expression.unexpectedNodes.length) { return finishParse() } else { return this.tryParse(finishParse(false)); } } private parseArrExp() { let expression: ArrayLiteralExpression = new ArrayLiteralExpression(this.currentToken); let finishParse = this.startParse(expression); let value: Expression; while(!this.consumeNextWordIfMatch(']') && (value = this.expectExpression())) { expression.items.push(value); if (this.consumeNextWordIfMatch(']') || !this.expectToken(',')) { break; } } if (expression.unexpectedNodes.length) { return finishParse() } else { return this.tryParse(finishParse(false)); } } private parseTryCatchStatment() { let statement: TryCathchStatement = new TryCathchStatement(this.currentToken); let finishParse = this.startParse(statement, IS_PARSING_TRY); if (!this.expectToken('{')) { return finishParse() } statement.body = this.parseBlock(); if(this.consumeNextWordIfMatch('catch')) { let catchHandler: FunctionDeclarationStatement = this.parseCatchHandler() as FunctionDeclarationStatement; if (catchHandler.unexpectedNodes.length) { statement.unexpectedNodes.push(catchHandler) return finishParse(); } statement.catchHandler = catchHandler; } if(this.consumeNextWordIfMatch('finally') && this.expectToken('{')) { statement.finalizer = this.parseBlock(); } if (!statement.catchHandler && !statement.finalizer) statement.unexpectedNodes.push( new UnExpectStatement(this.currentToken.value, 'expect finally', this.currentToken.loc) ) return finishParse(); } private parseCatchHandler() { let statement: FunctionDeclarationStatement = new FunctionDeclarationStatement(this.currentToken); let finishParse = this.startParse(statement, IS_PARSING_FUNDECLARATION); this.isParsingFunDeclaration = true; statement.identifier = new IdentifierLiteralExpression(this.currentToken); if (this.expectToken('(')) { let params: IdentifierLiteralExpression; if (!this.consumeNextWordIfMatch(')')) { while( params = this.getExpectedNode(NodeCode.IdentifierLiteralExpression) ) { statement.params.push(params); if (this.consumeNextWordIfMatch(')')) { break; } else if (this.expectToken(',')) { continue; } } } } if(statement.unexpectedNodes.length) return finishParse(); if (this.expectToken('{')) { statement.body = this.parseBlock(); statement.body.functionBlock = true; } return finishParse(); } private parseDoWihleStatement() { let statement = new DoWhileStatement(this.currentToken); let finishParse = this.startParse(statement, IS_PARSING_DOWHILE); this.isParsingLoop = true; if (!this.expectToken('{')) return finishParse(); statement.body = this.parseBlock(); this.expectToken('while') && this.expectToken('(') && (statement.test = this.expectExpression()) && this.expectToken(')') return finishParse(); } private parseWhileStatment() { let statement: WhileStatement = new WhileStatement(this.currentToken); let finishParse = this.startParse(statement, IS_PARSING_SWITCH); this.isParsingLoop = true; if ( this.expectToken('(') && (statement.test = this.expectExpression()) && this.expectToken(')') && this.expectToken('{') ) { statement.consequence = this.parseBlock() } return finishParse(); } private parseExpressions(endFlag) { let exprs: any = []; while(!this.isReadAll()) { let expr = this.expectExpression(); exprs.push(expr); if (this.isNextWordIs(endFlag) || !this.consumeNextWordIfMatch(',')) return exprs; } } private parseForStatement() { let statement: ForStatement = new ForStatement(this.currentToken); let finishParse = this.startParse(statement) this.isParsingLoop = true; if (!this.expectToken('(')) return finishParse(); if (!this.consumeNextWordIfMatch(';')) { let init = this.getExpectedNode(NodeCode.Expression | NodeCode.VariableDeclarationStatement); if (!init) { return finishParse() } else if ( init.code === NodeCode.IdentifierLiteralExpression || init.code === NodeCode.VariableDeclarationStatement && init.declarations.length === 1 && init.declarations[0].code !== NodeCode.AssignmentExpression ) { if ( (statement.forOf = this.consumeNextWordIfMatch('of')) || (statement.forIn = this.consumeNextWordIfMatch('in')) ) { statement.left = init; if ( ( statement.forIn ? statement.right = this.expectExpression() : statement.right = this.expectExpression() ) && this.expectToken(')') && this.expectToken('{') ) { statement.body = this.parseBlock(); } return finishParse() } } statement.init = init; if (!this.expectToken(';')) return finishParse(); } if ( !this.consumeNextWordIfMatch(';') && !(statement.test = this.expectExpression()) && !this.expectToken(';') ) { return finishParse() } if ( this.consumeNextWordIfMatch(')') || this.expectToken(';') && (statement.update = this.parseExpressions(')')) && this.expectToken(')') ) { if (this.expectToken('{')) statement.body = this.parseBlock() } return finishParse() } private parseRegStatement() { let expression: RegExpressioin = new RegExpressioin(this.currentToken); let finishParse = this.startParse(expression); let pos = this.pos - 1, escape = false; while(!this.isReadAll() && !(!escape && this.curChar === '/')) { this.advance(); if (this.curChar === '\n') { expression.unexpectedNodes.push(new UnExpectStatement(this.input.slice(pos, this.pos), '未完成的正则表达式', { start: pos, end: this.pos })) return finishParse(); } if (escape) { escape = false } else if (this.curChar === '\\') { escape = true; } } this.advance(); expression.pattern = this.input.slice(pos, this.pos); if (this.curChar === 'i' || this.curChar === 'g') { let reset = this.markPos(); this.next(); if (!/i|g|ig|gi/.test(this.currentToken.value)) { reset() } else { expression.flag = this.currentToken.value; } } return finishParse(); } private parseIfStatement() { let statement: IfStatement = new IfStatement(this.currentToken); let finishParse = this.startParse(statement); let expectLineEnd = false; let parseConditionBlock = () => { if (this.consumeNextWordIfMatch('{')) { return this.parseBlock() } else { expectLineEnd = true; let currentLine = this.currentLine; let reset = this.markPos(); let block = new Block(); if (this.consumeNextWordIfMatch('else')) { if (currentLine === this.currentLine) { statement.unexpectedNodes.push(new UnExpectStatement('else', 'statement expect', this.currentToken.loc)); return null; } reset(); return block; } else if (this.consumeNextWordIfMatch(';')) { reset(); return block; } let nextStatement = this.safelyParseNode(); if (nextStatement) { block.body.push(nextStatement) } return block; } } if ( !this.expectToken('(') || !(statement.test = this.expectExpression()) || !this.expectToken(')') ) { return finishParse() } statement.consequent = parseConditionBlock() as Block; if(statement.unexpectedNodes.length || expectLineEnd && !this.isLineEnd()) return finishParse(); expectLineEnd = false; if(this.consumeNextWordIfMatch('else')) { if (this.consumeNextWordIfMatch('if')) { statement.alternate = this.parseIfStatement() as IfStatement; } else if (this.expectToken('{')) { statement.alternate = this.parseBlock(); } } return finishParse(); } private parseBreakStatement() { let statement: BreakStatement = new BreakStatement(this.currentToken); let finishParse = this.startParse(statement); if (!this.isLineEnd() && !this.isNextWordIs(';')) { statement.argument = this.expectExpression(); } if (!this.isParsingLoop) return new UnExpectStatement(finishParse(), '"break" 语句只能在循环语句里使用') return finishParse(); } private parseReturnStatement() { let statement: ReturnStatement = new ReturnStatement(this.currentToken); let finishParse = this.startParse(statement); if (!this.isLineEnd() && !this.isNextWordIs(';')) { statement.argument = this.expectExpression(); } if (!this.isParsingFunDeclaration) return new UnExpectStatement(finishParse(), 'A "return" statement can only be used within a function body') return finishParse(); } private parseContinueStatement() { let statement: ContinueStatement = new ContinueStatement(this.currentToken); let finishParse = this.startParse(statement); if (!this.isLineEnd() && !this.isNextWordIs(';')) { statement.argument = this.expectExpression(); } if (!this.isParsingLoop) return new UnExpectStatement(finishParse(), '"continue" 语句只能用在循环语句内') return finishParse(); } private parseSwitchStatement() { let statement: SwitchStatement = new SwitchStatement(this.currentToken); let finishParse = this.startParse(statement); let caseStatement: SwitchCaseStatement; if (!( this.expectToken('(') && (statement.discriminant = this.expectExpression()) && this.expectToken([')', '{']) )) return finishParse() while(!this.consumeNextWordIfMatch('}')) { if (caseStatement = this.parseCaseStatement() as SwitchCaseStatement) { if (caseStatement.unexpectedNodes.length) { break; } statement.cases.push(caseStatement) } else { break } } return finishParse(); } private parseCaseStatement() { if ( !this.consumeNextWordIfMatch('case') && !this.consumeNextWordIfMatch('default') ) { this.next(); this.parsingNode.unexpectedNodes.push(new UnExpectStatement(this.currentToken.value, 'expect "case" or "default"', this.currentToken.loc)) return null } let statement: SwitchCaseStatement = new SwitchCaseStatement(this.currentToken); let finishParse = this.startParse(statement, IS_PARSING_CASE) this.isParsingLoop = true; statement.isDefault = this.currentToken.value === 'default' if (!statement.isDefault && !(statement.test = this.expectExpression())) { return finishParse(); } if (this.expectToken(':')) { if (this.consumeNextWordIfMatch('{')) { statement.consequent = this.parseBlock() } else { let reset = this.markPos() statement.consequent = this.parseBlock((statement) => { if ( !statement && ['case', 'default', '}'].includes(this.currentToken.value) ) { reset(); return true; } else { reset = this.markPos(); } }) } } return finishParse(); } private parseThrowStatement() { let statement = new ThrowStatement(this.currentToken); let finishParse = this.startParse(statement); statement.argument = this.expectExpression(); return finishParse(); } private parseWithStatement() { let statement = new WithStatement(this.currentToken); let finishParse = this.startParse(statement); if ( this.expectToken('(') && (statement.context = this.expectExpression()) && this.expectToken(')') && this.expectToken('{') ) { statement.body = this.parseBlock(); } return finishParse(); } private parseDeleteStatement() { let statement = new DeleteStatement(this.currentToken); let finishParse = this.startParse(statement); statement.argument = this.getExpectedNode(NodeCode.MemberExpression); return finishParse(); } private parseNewStatement() { let expression: NewExpressioin = new NewExpressioin(this.currentToken); let finishParse = this.startParse(expression); expression.callee = this.getExpectedNode(NodeCode.CallExpression | NodeCode.IdentifierLiteralExpression); return finishParse(); } private parseClassDeclarationStatement() { let statement: ClassDeclarationStatement = new ClassDeclarationStatement(this.currentToken); let finishParse = this.startParse(statement, IS_PARSING_CLASSDECLARATION); if ( !(statement.className = this.getExpectedNode(NodeCode.IdentifierLiteralExpression)) || this.consumeNextWordIfMatch('extends') && !(statement.super = this.getExpectedNode(NodeCode.IdentifierLiteralExpression)) || !this.expectToken('{') ) { return finishParse(); } while(!this.consumeNextWordIfMatch('}') && !this.isReadAll()) { let method = this.parseClassMehod(); if(!method) return finishParse(); statement.methods.push(method as FunctionDeclarationStatement); } return finishParse(); } private parseClassMehod() { let reset = this.markPos(); this.next(); if (this.currentToken.type !== 'IdentifierLiteral') { this.parsingNode.unexpectedNodes.push(new UnExpectStatement( this.currentToken.value, 'expect identifier', this.currentToken.loc )) return null; } reset(); let statement = this.parseFunDeclarationStatement(); if (statement.unexpectedNodes.length) { this.parsingNode.unexpectedNodes.push(statement); return null } return statement; } private parseExportStatement() { let statement: ExportDeclarationStatement = new ExportDeclarationStatement(this.currentToken); let finishParse = this.startParse(statement); if (this.consumeNextWordIfMatch('{')) { let identifier: IdentifierLiteralExpression; while(identifier = this.getExpectedNode(NodeCode.IdentifierLiteralExpression)) { let exportSpecifier: ExportSpecifier = { exported: identifier, local: null as any } if (this.consumeNextWordIfMatch('as')) { if (exportSpecifier.local = this.getExpectedNode(NodeCode.IdentifierLiteralExpression)) { statement.specifiers.push(exportSpecifier) } else { return finishParse(); } } statement.specifiers.push(exportSpecifier); if (this.consumeNextWordIfMatch('}')) break; if (!this.expectToken(',')) return finishParse(); } } else { let identifier: AstNode = this.getExpectedNode(NodeCode.Declaration | NodeCode.IdentifierLiteralExpression); if (identifier) { statement.exported = identifier; if ((identifier.code & NodeCode.Declaration) === identifier.code) { return finishParse(); } if (this.consumeNextWordIfMatch('as') && !(statement.local = this.getExpectedNode(NodeCode.IdentifierLiteralExpression))) { return finishParse(); } } else { return finishParse(); } } this.expectToken('from') && (statement.from = this.getExpectedNode(NodeCode.StringLiteralExpression)) return finishParse(); } private parseTypeOfExpression() { let expression = new TypeOfExpression(this.currentToken); let finishParse = this.startParse(expression, IS_PARSING_TYPEOF); expression.argument = this.expectExpression(); if (expression.unexpectedNodes.length) { return finishParse() } else { return this.tryParse(finishParse(false)); } } private parseVoidExpression() { let expression = new VoidExpression(this.currentToken); let finishParse = this.startParse(expression, IS_PARSING_VOID); expression.argument = this.expectExpression(); if (expression.unexpectedNodes.length) { return finishParse() } else { return this.tryParse(finishParse(false)); } } private parseTemplateLiteralExp() { let node: TemplateLiteralExppression = new TemplateLiteralExppression(this.currentToken); let finishParse = this.startParse(node) let consumeString = () => { while(this.curCharCode !== 96 && !this.isReadAll() && this.input.slice(this.pos, this.pos + 2) !== '${') { this.advance(); } } while(this.curCharCode !== 96) { let start = this.pos, str: StringLiteralExpression; consumeString(); str = new StringLiteralExpression({ value: this.input.slice(start, this.pos), loc: { start: start, end: this.pos } }) if (this.isReadAll()) { // 模版字符串到代码结尾都没有闭合 str && node.unexpectedNodes.push(new UnExpectStatement(str, '未闭合的模板字符串', { start: node.loc.start, end: this.pos })); return finishParse() } node.content.push(str); if (this.curCharCode === 96) break; let value: AstNode; this.pos += 2; if( // 遇到了 '${' 符号 (value = this.expectExpression()) && this.expectToken('}') ) { node.content.push(value); continue; } break; } this.advance(); return finishParse(); } private parseBracketEnwrapedExpression() { let expression: BracketEnwrapedExpressioin = new BracketEnwrapedExpressioin(this.currentToken), excutionFlag = this.excutionFlag; this.excutionFlag = 0; let finishParse = this.startParse(expression); if ( (expression.expression = this.expectExpression()) && this.expectToken(')') ) { this.excutionFlag = excutionFlag; return this.tryParse(finishParse(false)); } this.excutionFlag = excutionFlag; return finishParse(); } private tryParse(expression: Expression) { let result = this.tryParseTernary(expression) || this.tryParseSuffixUnary(expression) || this.tryParseMember(expression) || this.tryParseComputedMember(expression) || this.tryToParseCallExpression(expression) || this.parseBinaryExpression(expression) || expression; if (result === expression && !this.parsingNode) { this.parsingNode = expression; this.expectExpressionEnd(); this.parsingNode = null as any; } return result; } private tryParseTernary(expression: Expression) { if (this.excutionFlag & IS_PARSING_COMPUTE || !this.consumeNextWordIfMatch('?')) { return null; } let statement: TernaryExpression = new TernaryExpression(expression); let finishParse = this.startParse(statement); (statement.consequent = this.expectExpression()) && this.expectToken(':') && (statement.alternate = this.expectExpression()) return finishParse(); } private tryParseMember(propOwener: Expression) { if (!this.consumeNextWordIfMatch('.')) return null// '.'; let expression: MemberExpression = new MemberExpression(propOwener); let finishParse = this.startParse(expression, IS_PARSING_ACCESSPROP); if(expression.property = this.getExpectedNode(NodeCode.IdentifierLiteralExpression)) { let result = finishParse(false); return this.tryParseAssign(result) || this.tryParse(result); } return finishParse(); } private tryParseComputedMember(propOwener: Expression) { if (!this.consumeNextWordIfMatch('[')) return null; let expression: MemberExpression = new MemberExpression(propOwener); expression.isComputed = true; let finishParse = this.startParse(expression); if( (expression.property = this.expectExpression()) && this.consumeNextWordIfMatch(']') ) { let result = finishParse(false); return this.tryParseAssign(result as Expression) || this.tryParse(result); } return finishParse(); } private tryToParseCallExpression(fnName: AstNode) { if (!this.consumeNextWordIfMatch('(')) return null; let node = new CallExpression(fnName); let finishParse = this.startParse(node); if (!this.consumeNextWordIfMatch(')')) { while(!this.isReadAll()) { let nextNode: AstNode = this.expectExpression(); if (nextNode) { node.arguments.push(nextNode); } else { return finishParse(); } if (this.consumeNextWordIfMatch(')')) break; if (!this.expectToken(',')) return finishParse(); } } return this.tryParse(finishParse(false)); } private tryParseSuffixUnary(expression: Expression) { if (expression.code !== NodeCode.IdentifierLiteralExpression && expression.code !== NodeCode.MemberExpression) return null; let reset = this.markPos(); this.next() if (!Patterns.canBeSuffixUnary.test(this.currentToken.value)) { reset(); return null; } let newExpression: UnaryExpression = new UnaryExpression(this.currentToken); newExpression.suffix = true; newExpression.argument = expression; newExpression.loc.start = expression.loc.start; return this.startParse(newExpression)(); } private tryParseAssign(left: Expression) { if ((left.code & NodeCode.AssigmentLeft) !== left.code) return null; let reset = this.markPos(); this.next(); if (!this.currentToken.isAssignOperator) { reset(); return null; } let prevIsParsingVarDeclaration = this.isParsingVarDeclaration; this.isParsingVarDeclaration = false; let expression: AssignmentExpression = new AssignmentExpression({loc: {start: left.loc.start}}); let finishParse = this.startParse(expression); expression.left = left; if (this.excutionFlag & IS_PARSING_VARDECLARATION && this.currentToken.value !== '=') { expression.unexpectedNodes.push(new UnExpectStatement(this.currentToken.value, 'expect "="', this.currentToken.loc)); return finishParse(); } expression.operator = this.currentToken.value; expression.right = this.expectExpression(); this.isParsingVarDeclaration = prevIsParsingVarDeclaration; return finishParse(); } private parseBinaryExpression(first: Expression) { if (this.excutionFlag & IS_PARSING_TYPEOF || this.excutionFlag & IS_PARSING_VOID) return first; let reset = this.markPos(); this.next(); if (this.excutionFlag & IS_PARSING_COMPUTE || this.currentToken.type !== 'Operator') { reset(); return first; } let comparePriorityAndInsert = (op1: BinaryExpression, op2: BinaryExpression, parent?: BinaryE