/* This is a custom parser specifically written for the AssemblyScript subset. It accepts some of the most common TypeScript-only patterns that it knows an appropriate error message for but, though it uses TypeScript's codes for diagnostics, doesn't ultimately aim at full compatibility. */ import { Program, LIBRARY_PREFIX } from "./program"; import { Tokenizer, Token, Range } from "./tokenizer"; import { DiagnosticCode, DiagnosticEmitter } from "./diagnostics"; import { I64 } from "./util/i64"; import { normalize as normalizePath } from "./util/path"; import { Node, NodeKind, Source, SourceKind, TypeNode, Expression, AssertionKind, CallExpression, ElementAccessExpression, IdentifierExpression, StringLiteralExpression, Statement, BlockStatement, BreakStatement, ClassDeclaration, ContinueStatement, DeclarationStatement, Decorator, DoStatement, EnumDeclaration, EnumValueDeclaration, ExportImportStatement, ExportMember, ExportStatement, ExpressionStatement, FieldDeclaration, ForStatement, FunctionDeclaration, IfStatement, ImportDeclaration, ImportStatement, MethodDeclaration, Modifier, ModifierKind, NamespaceDeclaration, Parameter, ParameterKind, ReturnStatement, SwitchCase, SwitchStatement, ThrowStatement, TryStatement, TypeDeclaration, TypeParameter, VariableStatement, VariableDeclaration, WhileStatement, addModifier, getModifier, hasModifier, setReusableModifiers } from "./ast"; /** Parser interface. */ export class Parser extends DiagnosticEmitter { /** Program being created. */ program: Program; /** Log of source file names to be requested. */ backlog: string[] = new Array(); /** Log of source file names already processed. */ seenlog: Set = new Set(); /** Constructs a new parser. */ constructor() { super(); this.program = new Program(this.diagnostics); } /** Parses a file and adds its definitions to the program. */ parseFile(text: string, path: string, isEntry: bool): void { var normalizedPath = normalizePath(path); for (var i = 0, k = this.program.sources.length; i < k; ++i) if (this.program.sources[i].normalizedPath == normalizedPath) return; // already parsed this.seenlog.add(normalizedPath); var source = new Source(normalizedPath, text, isEntry ? SourceKind.ENTRY : path.startsWith(LIBRARY_PREFIX) ? SourceKind.LIBRARY : SourceKind.DEFAULT); this.program.sources.push(source); var tn = new Tokenizer(source, this.program.diagnostics); // tn.silentDiagnostics = this.silentDiagnostics; source.tokenizer = tn; while (!tn.skip(Token.ENDOFFILE)) { var statement = this.parseTopLevelStatement(tn); if (statement) { statement.parent = source; source.statements.push(statement); } } } parseTopLevelStatement(tn: Tokenizer, isNamespaceMember: bool = false): Statement | null { var decorators: Decorator[] | null = null; while (tn.skip(Token.AT)) { var decorator = this.parseDecorator(tn); if (!decorator) break; if (!decorators) decorators = new Array(); (decorators).push(decorator); } var modifiers: Modifier[] | null = null; if (tn.skip(Token.EXPORT)) modifiers = addModifier(Node.createModifier(ModifierKind.EXPORT, tn.range()), modifiers); if (tn.skip(Token.DECLARE)) { modifiers = addModifier(Node.createModifier(ModifierKind.DECLARE, tn.range()), modifiers); tn.peek(true); if (tn.nextTokenOnNewLine) this.error(DiagnosticCode.Line_break_not_permitted_here, tn.range(tn.pos)); // recoverable, compatibility } tn.mark(); var statement: Statement | null = null; var modifier: Modifier | null; switch (tn.next()) { case Token.CONST: modifiers = addModifier(Node.createModifier(ModifierKind.CONST, tn.range()), modifiers); if (tn.skip(Token.ENUM)) { statement = this.parseEnum(tn, modifiers, decorators); break; } statement = this.parseVariable(tn, modifiers, decorators); decorators = null; break; case Token.LET: modifiers = addModifier(Node.createModifier(ModifierKind.LET, tn.range()), modifiers); case Token.VAR: statement = this.parseVariable(tn, modifiers, decorators); decorators = null; break; case Token.ENUM: statement = this.parseEnum(tn, modifiers, decorators); decorators = null; break; case Token.FUNCTION: statement = this.parseFunction(tn, modifiers, decorators); decorators = null; break; case Token.ABSTRACT: if (!tn.skip(Token.CLASS)) { this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "class"); break; } modifiers = addModifier(Node.createModifier(ModifierKind.ABSTRACT, tn.range()), modifiers); // fall through case Token.CLASS: statement = this.parseClass(tn, modifiers, decorators); decorators = null; break; case Token.NAMESPACE: statement = this.parseNamespace(tn, modifiers, decorators); decorators = null; break; case Token.IMPORT: if (modifier = getModifier(ModifierKind.EXPORT, modifiers)) { statement = this.parseExportImport(tn, modifier.range); } else statement = this.parseImport(tn); if (modifiers) setReusableModifiers(modifiers); break; case Token.TYPE: statement = this.parseTypeDeclaration(tn, modifiers, decorators); decorators = null; break; default: if (hasModifier(ModifierKind.EXPORT, modifiers)) { tn.reset(); statement = this.parseExport(tn, modifiers); // TODO: why exactly does this have modifiers again? 'declare'? } else { if (modifiers) { if (modifier = getModifier(ModifierKind.DECLARE, modifiers)) this.error(DiagnosticCode._0_modifier_cannot_be_used_here, modifier.range, "declare"); // recoverable setReusableModifiers(modifiers); } tn.reset(); if (!isNamespaceMember) statement = this.parseStatement(tn, true); } break; } if (decorators /* not consumed */) for (var i = 0, k = (decorators).length; i < k; ++i) this.error(DiagnosticCode.Decorators_are_not_valid_here, (decorators)[i].range); return statement; } /** Obtains the next file to parse. */ nextFile(): string | null { if (this.backlog.length) { var filename = this.backlog[0]; for (var i = 0, k = this.backlog.length - 1; i < k; ++i) this.backlog[i] = this.backlog[i + 1]; this.backlog.length--; return filename; } return null; } /** Finishes parsing and returns the program. */ finish(): Program { if (this.backlog.length) throw new Error("backlog is not empty"); this.backlog = []; this.seenlog.clear(); return this.program; } parseType(tn: Tokenizer, acceptParenthesized: bool = true, suppressErrors: bool = false): TypeNode | null { // not TypeScript-compatible var token = tn.next(); var startPos = tn.tokenPos; // void if (token == Token.VOID) return Node.createType(Node.createIdentifierExpression("void", tn.range()), [], false, tn.range(startPos, tn.pos)); var type: TypeNode; // ( ... ) if (acceptParenthesized && token == Token.OPENPAREN) { var innerType = this.parseType(tn, false, suppressErrors); if (!innerType) return null; if (!tn.skip(Token.CLOSEPAREN)) { if (!suppressErrors) this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "}"); return null; } type = innerType; type.range.start = startPos; type.range.end = tn.pos; // this } else if (token == Token.THIS) { type = Node.createType(Node.createThisExpression(tn.range()), [], false, tn.range(startPos, tn.pos)); // true } else if (token == Token.TRUE || token == Token.FALSE) { type = Node.createType(Node.createIdentifierExpression("bool", tn.range()), [], false, tn.range(startPos, tn.pos)); // string literal } else if (token == Token.STRINGLITERAL) { tn.readString(); type = Node.createType(Node.createIdentifierExpression("string", tn.range()), [], false, tn.range(startPos, tn.pos)); // Name } else if (token == Token.IDENTIFIER) { var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var parameters = new Array(); var nullable = false; // Name if (tn.skip(Token.LESSTHAN)) { do { var parameter = this.parseType(tn, true, suppressErrors); if (!parameter) return null; parameters.push(parameter); } while (tn.skip(Token.COMMA)); if (!tn.skip(Token.GREATERTHAN)) { if (!suppressErrors) this.error(DiagnosticCode._0_expected, tn.range(tn.pos), ">"); return null; } } // ... | null if (tn.skip(Token.BAR)) { if (tn.skip(Token.NULL)) { nullable = true; } else { if (!suppressErrors) this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "null"); return null; } } type = Node.createType(identifier, parameters, nullable, tn.range(startPos, tn.pos)); } else { if (!suppressErrors) this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } // ... [][] while (tn.skip(Token.OPENBRACKET)) { var bracketStart = tn.tokenPos; if (!tn.skip(Token.CLOSEBRACKET)) { if (!suppressErrors) this.error(DiagnosticCode._0_expected, tn.range(), "]"); return null; } var bracketRange = tn.range(bracketStart, tn.pos); // ...[] | null nullable = false; if (tn.skip(Token.BAR)) { if (tn.skip(Token.NULL)) { nullable = true; } else { if (!suppressErrors) this.error(DiagnosticCode._0_expected, tn.range(), "null"); return null; } } type = Node.createType(Node.createIdentifierExpression("Array", bracketRange), [ type ], nullable, tn.range(startPos, tn.pos)); if (nullable) break; } return type; } // statements parseDecorator(tn: Tokenizer): Decorator | null { // at '@': Identifier ('.' Identifier)* '(' Arguments var startPos = tn.tokenPos; if (tn.skip(Token.IDENTIFIER)) { var name = tn.readIdentifier(); var expression: Expression = Node.createIdentifierExpression(name, tn.range(startPos, tn.pos)); while (tn.skip(Token.DOT)) { if (tn.skip(Token.IDENTIFIER)) { name = tn.readIdentifier(); expression = Node.createPropertyAccessExpression(expression, Node.createIdentifierExpression(name, tn.range()), tn.range(startPos, tn.pos)); } else { this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } } var args: Expression[] | null; if (tn.skip(Token.OPENPAREN)) { args = this.parseArguments(tn); if (args) return Node.createDecorator(expression, args, tn.range(startPos, tn.pos)); } else return Node.createDecorator(expression, null, tn.range(startPos, tn.pos)); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseVariable(tn: Tokenizer, modifiers: Modifier[] | null, decorators: Decorator[] | null): VariableStatement | null { // at ('const' | 'let' | 'var'): VariableDeclaration (',' VariableDeclaration)* ';'? var startPos = modifiers && modifiers.length ? modifiers[0].range.start : tn.tokenPos; var members = new Array(); var isDeclare = hasModifier(ModifierKind.DECLARE, modifiers); do { var member = this.parseVariableDeclaration(tn, isDeclare, modifiers, decorators); if (!member) return null; members.push(member); } while (tn.skip(Token.COMMA)); var ret = Node.createVariableStatement(members, modifiers, decorators, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } parseVariableDeclaration(tn: Tokenizer, isDeclare: bool = false, parentModifiers: Modifier[] | null, parentDecorators: Decorator[] | null): VariableDeclaration | null { // Identifier (':' Type)? ('=' Expression)? if (!tn.skip(Token.IDENTIFIER)) { this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var type: TypeNode | null = null; if (tn.skip(Token.COLON)) type = this.parseType(tn); var initializer: Expression | null = null; if (tn.skip(Token.EQUALS)) { if (isDeclare) this.error(DiagnosticCode.Initializers_are_not_allowed_in_ambient_contexts, tn.range()); // recoverable initializer = this.parseExpression(tn, Precedence.COMMA + 1); if (!initializer) return null; } else { if (hasModifier(ModifierKind.CONST, parentModifiers)) { if (!hasModifier(ModifierKind.DECLARE, parentModifiers)) this.error(DiagnosticCode._const_declarations_must_be_initialized, identifier.range); } else if (!type) // neither type nor initializer this.error(DiagnosticCode.Type_expected, tn.range(tn.pos)); // recoverable } return Node.createVariableDeclaration(identifier, type, initializer, parentModifiers, parentDecorators, Range.join(identifier.range, tn.range())); } parseEnum(tn: Tokenizer, modifiers: Modifier[] | null, decorators: Decorator[] | null): EnumDeclaration | null { // at 'enum': Identifier '{' (EnumValueDeclaration (',' EnumValueDeclaration )*)? '}' ';'? var startPos = modifiers && modifiers.length ? modifiers[0].range.start : tn.tokenPos; if (tn.next() != Token.IDENTIFIER) { this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); if (tn.next() != Token.OPENBRACE) { this.error(DiagnosticCode._0_expected, tn.range(), "{"); return null; } var members = new Array(); if (!tn.skip(Token.CLOSEBRACE)) { do { var member = this.parseEnumValue(tn); if (!member) return null; members.push(member); } while (tn.skip(Token.COMMA)); if (!tn.skip(Token.CLOSEBRACE)) { this.error(DiagnosticCode._0_expected, tn.range(), "}"); return null; } } var ret = Node.createEnumDeclaration(identifier, members, modifiers, decorators, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } parseEnumValue(tn: Tokenizer): EnumValueDeclaration | null { // Identifier ('=' Expression)? if (!tn.skip(Token.IDENTIFIER)) { this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var value: Expression | null = null; if (tn.skip(Token.EQUALS)) { value = this.parseExpression(tn, Precedence.COMMA + 1); if (!value) return null; } return Node.createEnumValueDeclaration(identifier, value, Range.join(identifier.range, tn.range())); } parseReturn(tn: Tokenizer): ReturnStatement | null { // at 'return': Expression | (';' | '}' | ...'\n') var expr: Expression | null = null; if (tn.peek(true) != Token.SEMICOLON && tn.nextToken != Token.CLOSEBRACE && !tn.nextTokenOnNewLine) { expr = this.parseExpression(tn); if (!expr) return null; } var ret = Node.createReturnStatement(expr, tn.range()); tn.skip(Token.SEMICOLON); return ret; } parseTypeParameters(tn: Tokenizer): TypeParameter[] | null { // at '<': TypeParameter (',' TypeParameter)* '>' var typeParameters = new Array(); if (!tn.skip(Token.GREATERTHAN)) { do { var typeParameter = this.parseTypeParameter(tn); if (!typeParameter) return null; typeParameters.push(typeParameter); } while (tn.skip(Token.COMMA)); if (!tn.skip(Token.GREATERTHAN)) { this.error(DiagnosticCode._0_expected, tn.range(), ">"); return null; } } else this.error(DiagnosticCode.Type_parameter_list_cannot_be_empty, tn.range()); // recoverable return typeParameters; } parseTypeParameter(tn: Tokenizer): TypeParameter | null { // Identifier ('extends' Type)? if (tn.next() == Token.IDENTIFIER) { var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var extendsType: TypeNode | null = null; if (tn.skip(Token.EXTENDS)) { extendsType = this.parseType(tn); if (!extendsType) return null; } return Node.createTypeParameter(identifier, extendsType, Range.join(identifier.range, tn.range())); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseParameters(tn: Tokenizer): Parameter[] | null { // at '(': (Parameter (',' Parameter)*)? ')' var parameters = new Array(); var seenRest: Parameter | null = null; var seenOptional = false; var reportedRest = false; if (tn.peek() != Token.CLOSEPAREN) { do { var param = this.parseParameter(tn); if (!param) return null; if (seenRest && !reportedRest) { this.error(DiagnosticCode.A_rest_parameter_must_be_last_in_a_parameter_list, seenRest.name.range); reportedRest = true; } switch (param.parameterKind) { default: if (seenOptional) this.error(DiagnosticCode.A_required_parameter_cannot_follow_an_optional_parameter, param.name.range); break; case ParameterKind.OPTIONAL: seenOptional = true; break; case ParameterKind.REST: seenRest = param; break; } parameters.push(param); } while (tn.skip(Token.COMMA)); } if (tn.skip(Token.CLOSEPAREN)) return parameters; else this.error(DiagnosticCode._0_expected, tn.range(), ")"); return null; } parseParameter(tn: Tokenizer): Parameter | null { // '...'? Identifier '?'? (':' Type)? ('=' Expression)? var isRest = false; var seenRest = false; var isOptional = false; var seenOptional = false; var startRange: Range | null = null; if (tn.skip(Token.DOT_DOT_DOT)) { isRest = true; startRange = tn.range(); } if (tn.skip(Token.IDENTIFIER)) { if (!isRest) startRange = tn.range(); var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var type: TypeNode | null = null; if (isOptional = tn.skip(Token.QUESTION)) { if (isRest) this.error(DiagnosticCode.A_rest_parameter_cannot_be_optional, identifier.range); } if (tn.skip(Token.COLON)) { type = this.parseType(tn); if (!type) return null; } var initializer: Expression | null = null; if (tn.skip(Token.EQUALS)) { if (isRest) this.error(DiagnosticCode.A_rest_parameter_cannot_have_an_initializer, identifier.range); if (isOptional) this.error(DiagnosticCode.Parameter_cannot_have_question_mark_and_initializer, identifier.range); else isOptional = true; initializer = this.parseExpression(tn, Precedence.COMMA + 1); if (!initializer) return null; } return Node.createParameter(identifier, type, initializer, isRest ? ParameterKind.REST : isOptional ? ParameterKind.OPTIONAL : ParameterKind.DEFAULT, Range.join(startRange, tn.range())); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseFunction(tn: Tokenizer, modifiers: Modifier[] | null, decorators: Decorator[] | null): FunctionDeclaration | null { // at 'function': Identifier ('<' TypeParameters)? '(' Parameters (':' Type)? '{' Statement* '}' ';'? var startPos = modifiers && modifiers.length ? modifiers[0].range.start : tn.tokenPos; if (!tn.skip(Token.IDENTIFIER)) { this.error(DiagnosticCode.Identifier_expected, tn.range(tn.pos)); return null; } var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var typeParameters: TypeParameter[] | null = null; if (tn.skip(Token.LESSTHAN)) { typeParameters = this.parseTypeParameters(tn); if (!typeParameters) return null; } else typeParameters = []; if (!tn.skip(Token.OPENPAREN)) { this.error(DiagnosticCode._0_expected, tn.range(tn.pos), "("); return null; } var parameters = this.parseParameters(tn); if (!parameters) return null; var isSetter = hasModifier(ModifierKind.SET, modifiers); if (isSetter) { if (parameters.length != 1) this.error(DiagnosticCode.A_set_accessor_must_have_exactly_one_parameter, identifier.range); // recoverable if (parameters.length && parameters[0].initializer) this.error(DiagnosticCode.A_set_accessor_parameter_cannot_have_an_initializer, identifier.range); // recoverable } var isGetter = hasModifier(ModifierKind.GET, modifiers); if (isGetter && parameters.length) this.error(DiagnosticCode.A_get_accessor_cannot_have_parameters, identifier.range); // recoverable var returnType: TypeNode | null = null; if (tn.skip(Token.COLON)) { returnType = this.parseType(tn, isSetter); if (!returnType) return null; } else if (!isSetter) { this.error(DiagnosticCode.Type_expected, tn.range(tn.pos)); // recoverable } var isDeclare = hasModifier(ModifierKind.DECLARE, modifiers); var statements: Statement[] | null = null; if (tn.skip(Token.OPENBRACE)) { statements = new Array(); if (isDeclare) this.error(DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts, tn.range()); // recoverable while (!tn.skip(Token.CLOSEBRACE)) { var statement = this.parseStatement(tn); if (!statement) return null; statements.push(statement); } } else if (!isDeclare) this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, tn.range(tn.pos)); var ret = Node.createFunctionDeclaration(identifier, typeParameters, parameters, returnType, statements, modifiers, decorators, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } parseClass(tn: Tokenizer, modifiers: Modifier[] | null, decorators: Decorator[] | null): ClassDeclaration | null { // at 'class': Identifier ('<' TypeParameters)? ('extends' Type)? ('implements' Type (',' Type)*)? '{' ClassMember* '}' var startPos = decorators && decorators.length ? decorators[0].range.start : modifiers && modifiers.length ? modifiers[0].range.start : tn.tokenPos; if (tn.skip(Token.IDENTIFIER)) { var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var typeParameters: TypeParameter[] | null; if (tn.skip(Token.LESSTHAN)) { typeParameters = this.parseTypeParameters(tn); if (!typeParameters) return null; } else typeParameters = []; var extendsType: TypeNode | null = null; if (tn.skip(Token.EXTENDS)) { extendsType = this.parseType(tn); if (!extendsType) return null; } var implementsTypes = new Array(); if (tn.skip(Token.IMPLEMENTS)) { do { var type = this.parseType(tn); if (!type) return null; implementsTypes.push(type); } while (tn.skip(Token.COMMA)); } if (tn.skip(Token.OPENBRACE)) { var members = new Array(); if (!tn.skip(Token.CLOSEBRACE)) { var isDeclare = hasModifier(ModifierKind.DECLARE, modifiers); do { var member = this.parseClassMember(tn, isDeclare); if (!member) return null; members.push(member); } while (!tn.skip(Token.CLOSEBRACE)); } return Node.createClassDeclaration(identifier, typeParameters, extendsType, implementsTypes, members, modifiers, decorators, tn.range(startPos, tn.pos)); } else this.error(DiagnosticCode._0_expected, tn.range(), "{"); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseClassMember(tn: Tokenizer, parentIsDeclare: bool): DeclarationStatement | null { // ('public' | 'private' | 'protected')? ('static' | 'abstract')? ('get' | 'set')? Identifier ... var startPos = tn.pos; var decorators = new Array(); while (tn.skip(Token.AT)) { var decorator = this.parseDecorator(tn); if (!decorator) break; decorators.push(decorator); } var modifiers: Modifier[] | null = null; if (tn.skip(Token.PUBLIC)) modifiers = addModifier(Node.createModifier(ModifierKind.PUBLIC, tn.range()), modifiers); else if (tn.skip(Token.PRIVATE)) modifiers = addModifier(Node.createModifier(ModifierKind.PRIVATE, tn.range()), modifiers); else if (tn.skip(Token.PROTECTED)) modifiers = addModifier(Node.createModifier(ModifierKind.PROTECTED, tn.range()), modifiers); if (tn.skip(Token.STATIC)) modifiers = addModifier(Node.createModifier(ModifierKind.STATIC, tn.range()), modifiers); else if (tn.skip(Token.ABSTRACT)) modifiers = addModifier(Node.createModifier(ModifierKind.ABSTRACT, tn.range()), modifiers); if (tn.skip(Token.READONLY)) modifiers = addModifier(Node.createModifier(ModifierKind.READONLY, tn.range()), modifiers); // check if accessor: ('get' | 'set') ^\n Identifier tn.mark(); var isGetter = false; var isSetter = false; if (isGetter = tn.skip(Token.GET)) { if (tn.peek(true, true) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) modifiers = addModifier(Node.createModifier(ModifierKind.GET, tn.range()), modifiers); else { tn.reset(); isGetter = false; } } else if (isSetter = tn.skip(Token.SET)) { // can't be both if (tn.peek(true, true) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) modifiers = addModifier(Node.createModifier(ModifierKind.SET, tn.range()), modifiers); else { tn.reset(); isSetter = false; } } var isConstructor = tn.skip(Token.CONSTRUCTOR); if (isConstructor || tn.skip(Token.IDENTIFIER)) { var identifier = isConstructor ? Node.createConstructorExpression(tn.range()) : Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var typeParameters: TypeParameter[] | null; if (tn.skip(Token.LESSTHAN)) { if (isConstructor) this.error(DiagnosticCode.Type_parameters_cannot_appear_on_a_constructor_declaration, tn.range()); // recoverable typeParameters = this.parseTypeParameters(tn); if (!typeParameters) return null; } else typeParameters = []; // method: '(' Parameters (':' Type)? '{' Statement* '}' ';'? if (tn.skip(Token.OPENPAREN)) { var parameters = this.parseParameters(tn); if (!parameters) return null; if (isGetter && parameters.length) this.error(DiagnosticCode.A_get_accessor_cannot_have_parameters, identifier.range); if (isSetter) { if (parameters.length != 1) this.error(DiagnosticCode.A_set_accessor_must_have_exactly_one_parameter, identifier.range); if (parameters.length && parameters[0].initializer) this.error(DiagnosticCode.A_set_accessor_parameter_cannot_have_an_initializer, identifier.range); } var returnType: TypeNode | null = null; if (tn.skip(Token.COLON)) { if (identifier.kind == NodeKind.CONSTRUCTOR) this.error(DiagnosticCode.Type_annotation_cannot_appear_on_a_constructor_declaration, tn.range()); else if (isSetter) this.error(DiagnosticCode.A_set_accessor_cannot_have_a_return_type_annotation, tn.range()); returnType = this.parseType(tn, identifier.kind == NodeKind.CONSTRUCTOR || isSetter); if (!returnType) return null; } else if (!isSetter && identifier.kind != NodeKind.CONSTRUCTOR) this.error(DiagnosticCode.Type_expected, tn.range()); // recoverable var statements: Statement[] | null = null; if (tn.skip(Token.OPENBRACE)) { if (parentIsDeclare) this.error(DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts, tn.range()); // recoverable statements = new Array(); while (!tn.skip(Token.CLOSEBRACE)) { var statement = this.parseStatement(tn); if (!statement) return null; statements.push(statement); } } else { if (!parentIsDeclare) this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, tn.range()); // recoverable } var retMethod = Node.createMethodDeclaration(identifier, typeParameters, parameters, returnType, statements, modifiers, decorators, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return retMethod; } else if (isConstructor) { this.error(DiagnosticCode.Constructor_implementation_is_missing, identifier.range()); } else if (isGetter || isSetter) { this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, identifier.range()); // field: (':' Type)? ('=' Expression)? ';'? } else { var modifier: Modifier | null; if (modifier = getModifier(ModifierKind.ABSTRACT, modifiers)) this.error(DiagnosticCode._0_modifier_cannot_be_used_here, modifier.range, "abstract"); // recoverable if (modifier = getModifier(ModifierKind.GET, modifiers)) this.error(DiagnosticCode._0_modifier_cannot_be_used_here, modifier.range, "get"); // recoverable if (modifier = getModifier(ModifierKind.SET, modifiers)) this.error(DiagnosticCode._0_modifier_cannot_be_used_here, modifier.range, "set"); // recoverable var type: TypeNode | null = null; if (tn.skip(Token.COLON)) { type = this.parseType(tn); if (!type) return null; } else this.error(DiagnosticCode.Type_expected, tn.range()); // recoverable var initializer: Expression | null = null; if (tn.skip(Token.EQUALS)) { initializer = this.parseExpression(tn); if (!initializer) return null; } var retField = Node.createFieldDeclaration(identifier, type, initializer, modifiers, decorators, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return retField; } } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseNamespace(tn: Tokenizer, modifiers: Modifier[] | null, decorators: Decorator[] | null): NamespaceDeclaration | null { // at 'namespace': Identifier '{' (Variable | Function)* '}' var startPos = modifiers && modifiers.length ? modifiers[0].range.start : tn.tokenPos; if (tn.skip(Token.IDENTIFIER)) { var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); if (tn.skip(Token.OPENBRACE)) { var members = new Array(); while (!tn.skip(Token.CLOSEBRACE)) { var member = this.parseTopLevelStatement(tn, true); if (!member) return null; members.push(member); } var ret = Node.createNamespaceDeclaration(identifier, members, modifiers, decorators, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } else this.error(DiagnosticCode._0_expected, tn.range(), "{"); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseExport(tn: Tokenizer, modifiers: Modifier[] | null): ExportStatement | null { // at 'export': '{' ExportMember (',' ExportMember)* }' ('from' StringLiteral)? ';'? var startPos = modifiers && modifiers.length ? modifiers[0].range.start : tn.tokenPos; if (tn.skip(Token.OPENBRACE)) { var members = new Array(); if (!tn.skip(Token.CLOSEBRACE)) { do { var member = this.parseExportMember(tn); if (!member) return null; members.push(member); } while (tn.skip(Token.COMMA)); if (!tn.skip(Token.CLOSEBRACE)) { this.error(DiagnosticCode._0_expected, tn.range(), "}"); return null; } } var path: StringLiteralExpression | null = null; if (tn.skip(Token.FROM)) { if (tn.skip(Token.STRINGLITERAL)) path = Node.createStringLiteralExpression(tn.readString(), tn.range()); else { this.error(DiagnosticCode.String_literal_expected, tn.range()); return null; } } var ret = Node.createExportStatement(members, path, modifiers, tn.range(startPos, tn.pos)); if (ret.normalizedPath && !this.seenlog.has(ret.normalizedPath)) { this.backlog.push(ret.normalizedPath); this.seenlog.add(ret.normalizedPath); } tn.skip(Token.SEMICOLON); return ret; } else this.error(DiagnosticCode._0_expected, tn.range(), "{"); return null; } parseExportMember(tn: Tokenizer): ExportMember | null { // Identifier ('as' Identifier)? if (tn.skip(Token.IDENTIFIER)) { var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var asIdentifier: IdentifierExpression | null = null; if (tn.skip(Token.AS)) { if (tn.skip(Token.IDENTIFIER)) asIdentifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); else { this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } } return Node.createExportMember(identifier, asIdentifier, asIdentifier ? Range.join(identifier.range, asIdentifier.range) : identifier.range); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseImport(tn: Tokenizer): ImportStatement | null { // at 'import': ('{' (ImportMember (',' ImportMember)*)? '}' | '*' 'as' Identifier)? 'from' StringLiteral ';'? var startPos = tn.tokenPos; var members: ImportDeclaration[] | null = null; var namespaceName: IdentifierExpression | null = null; var skipFrom = false; if (tn.skip(Token.OPENBRACE)) { members = new Array(); if (!tn.skip(Token.CLOSEBRACE)) { do { var member = this.parseImportDeclaration(tn); if (!member) return null; members.push(member); } while (tn.skip(Token.COMMA)); if (!tn.skip(Token.CLOSEBRACE)) { this.error(DiagnosticCode._0_expected, tn.range(), "}"); return null; } } } else if (tn.skip(Token.ASTERISK)) { if (tn.skip(Token.AS)) { if (tn.skip(Token.IDENTIFIER)) { namespaceName = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); } else { this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } } else { this.error(DiagnosticCode._0_expected, tn.range(), "as"); return null; } } else skipFrom = true; if (skipFrom || tn.skip(Token.FROM)) { if (tn.skip(Token.STRINGLITERAL)) { var path = Node.createStringLiteralExpression(tn.readString(), tn.range()); var ret: ImportStatement; if (namespaceName) { assert(!members); ret = Node.createImportStatementWithWildcard(namespaceName, path, tn.range(startPos, tn.pos)); } else { ret = Node.createImportStatement(members, path, tn.range(startPos, tn.pos)); } if (!this.seenlog.has(ret.normalizedPath)) { this.backlog.push(ret.normalizedPath); this.seenlog.add(ret.normalizedPath); } tn.skip(Token.SEMICOLON); return ret; } else this.error(DiagnosticCode.String_literal_expected, tn.range()); } else this.error(DiagnosticCode._0_expected, tn.range(), "from"); return null; } parseImportDeclaration(tn: Tokenizer): ImportDeclaration | null { // Identifier ('as' Identifier)? if (tn.skip(Token.IDENTIFIER)) { var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var asIdentifier: IdentifierExpression | null = null; if (tn.skip(Token.AS)) { if (tn.skip(Token.IDENTIFIER)) asIdentifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); else { this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } } return Node.createImportDeclaration(identifier, asIdentifier, asIdentifier ? Range.join(identifier.range, asIdentifier.range) : identifier.range); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseExportImport(tn: Tokenizer, startRange: Range): ExportImportStatement | null { // at 'export' 'import': Identifier ('=' Identifier)? ';'? if (tn.skip(Token.IDENTIFIER)) { var asIdentifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); if (tn.skip(Token.EQUALS)) { if (tn.skip(Token.IDENTIFIER)) { var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var ret = Node.createExportImportStatement(identifier, asIdentifier, Range.join(startRange, tn.range())); tn.skip(Token.SEMICOLON); return ret; } else this.error(DiagnosticCode.Identifier_expected, tn.range()); } else this.error(DiagnosticCode._0_expected, tn.range(), "="); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseStatement(tn: Tokenizer, topLevel: bool = false): Statement | null { // at previous token tn.mark(); var token = tn.next(); switch (token) { case Token.BREAK: return this.parseBreak(tn); case Token.CONST: return this.parseVariable(tn, [ Node.createModifier(ModifierKind.CONST, tn.range()) ], null); case Token.CONTINUE: return this.parseContinue(tn); case Token.DO: return this.parseDoStatement(tn); case Token.FOR: return this.parseForStatement(tn); case Token.IF: return this.parseIfStatement(tn); case Token.LET: return this.parseVariable(tn, [ Node.createModifier(ModifierKind.LET, tn.range()) ], null); case Token.VAR: return this.parseVariable(tn, null, null); case Token.OPENBRACE: return this.parseBlockStatement(tn, topLevel); case Token.RETURN: if (topLevel) this.error(DiagnosticCode.A_return_statement_can_only_be_used_within_a_function_body, tn.range()); // recoverable return this.parseReturn(tn); case Token.SEMICOLON: return Node.createEmptyStatement(tn.range(tn.tokenPos)); case Token.SWITCH: return this.parseSwitchStatement(tn); case Token.THROW: return this.parseThrowStatement(tn); case Token.TRY: return this.parseTryStatement(tn); case Token.TYPE: return this.parseTypeDeclaration(tn, null); case Token.WHILE: return this.parseWhileStatement(tn); default: tn.reset(); return this.parseExpressionStatement(tn); } } parseBlockStatement(tn: Tokenizer, topLevel: bool): BlockStatement | null { // at '{': Statement* '}' ';'? var startPos = tn.tokenPos; var statements = new Array(); while (!tn.skip(Token.CLOSEBRACE)) { var statement = this.parseStatement(tn, topLevel); if (!statement) return null; statements.push(statement); } var ret = Node.createBlockStatement(statements, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } parseBreak(tn: Tokenizer): BreakStatement | null { // at 'break': Identifier? ';'? var identifier: IdentifierExpression | null = null; if (tn.peek(true) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) { tn.next(true); identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); } var ret = Node.createBreakStatement(identifier, tn.range()); tn.skip(Token.SEMICOLON); return ret; } parseContinue(tn: Tokenizer): ContinueStatement | null { // at 'continue': Identifier? ';'? var identifier: IdentifierExpression | null = null; if (tn.peek(true) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) { tn.next(true); identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); } var ret = Node.createContinueStatement(identifier, tn.range()); tn.skip(Token.SEMICOLON); return ret; } parseDoStatement(tn: Tokenizer): DoStatement | null { // at 'do': Statement 'while' '(' Expression ')' ';'? var startPos = tn.tokenPos; var statement = this.parseStatement(tn); if (!statement) return null; if (tn.skip(Token.WHILE)) { if (tn.skip(Token.OPENPAREN)) { var condition = this.parseExpression(tn); if (!condition) return null; if (tn.skip(Token.CLOSEPAREN)) { var ret = Node.createDoStatement(statement, condition, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } this.error(DiagnosticCode._0_expected, tn.range(), ")"); } else this.error(DiagnosticCode._0_expected, tn.range(), "("); } else this.error(DiagnosticCode._0_expected, tn.range(), "while"); return null; } parseExpressionStatement(tn: Tokenizer): ExpressionStatement | null { // at previous token var expr = this.parseExpression(tn); if (!expr) return null; var ret = Node.createExpressionStatement(expr); tn.skip(Token.SEMICOLON); return ret; } parseForStatement(tn: Tokenizer): ForStatement | null { // at 'for': '(' Statement? Expression? ';' Expression? ')' Statement var startPos = tn.tokenPos; if (tn.skip(Token.OPENPAREN)) { var initializer: Statement | null = null; if (tn.skip(Token.LET) || tn.skip(Token.CONST) || tn.skip(Token.VAR)) { initializer = this.parseVariable(tn, null, null); } else if (!tn.skip(Token.SEMICOLON)) { initializer = this.parseExpressionStatement(tn); if (!initializer) return null; } if (tn.token == Token.SEMICOLON) { var condition: ExpressionStatement | null = null; if (!tn.skip(Token.SEMICOLON)) { condition = this.parseExpressionStatement(tn); if (!condition) return null; } if (tn.token == Token.SEMICOLON) { var incrementor: Expression | null = null; if (!tn.skip(Token.CLOSEPAREN)) { incrementor = this.parseExpression(tn); if (!incrementor) return null; if (!tn.skip(Token.CLOSEPAREN)) { this.error(DiagnosticCode._0_expected, tn.range(), ")"); return null; } } var statement = this.parseStatement(tn); if (!statement) return null; return Node.createForStatement(initializer, condition ? condition.expression : null, incrementor, statement, tn.range(startPos, tn.pos)); } else this.error(DiagnosticCode._0_expected, tn.range(), ";"); } else this.error(DiagnosticCode._0_expected, tn.range(), ";"); } else this.error(DiagnosticCode._0_expected, tn.range(), "("); return null; } parseIfStatement(tn: Tokenizer): IfStatement | null { // at 'if': '(' Expression ')' Statement ('else' Statement)? var startPos = tn.tokenPos; if (tn.skip(Token.OPENPAREN)) { var condition = this.parseExpression(tn); if (!condition) return null; if (tn.skip(Token.CLOSEPAREN)) { var statement = this.parseStatement(tn); if (!statement) return null; var elseStatement: Statement | null = null; if (tn.skip(Token.ELSE)) { elseStatement = this.parseStatement(tn); if (!elseStatement) return null; } return Node.createIfStatement(condition, statement, elseStatement, tn.range(startPos, tn.pos)); } this.error(DiagnosticCode._0_expected, tn.range(), ")"); } else this.error(DiagnosticCode._0_expected, tn.range(), "("); return null; } parseSwitchStatement(tn: Tokenizer): SwitchStatement | null { // at 'switch': '(' Expression ')' '{' SwitchCase* '}' ';'? var startPos = tn.tokenPos; if (tn.skip(Token.OPENPAREN)) { var condition = this.parseExpression(tn); if (!condition) return null; if (tn.skip(Token.CLOSEPAREN)) { if (tn.skip(Token.OPENBRACE)) { var cases = new Array(); while (!tn.skip(Token.CLOSEBRACE)) { var case_ = this.parseSwitchCase(tn); if (!case_) return null; cases.push(case_); } var ret = Node.createSwitchStatement(condition, cases, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } else this.error(DiagnosticCode._0_expected, tn.range(), "{"); } else this.error(DiagnosticCode._0_expected, tn.range(), ")"); } else this.error(DiagnosticCode._0_expected, tn.range(), "("); return null; } parseSwitchCase(tn: Tokenizer): SwitchCase | null { var startPos = tn.tokenPos; var statements: Statement[], statement: Statement | null; // 'case' Expression ':' Statement* if (tn.skip(Token.CASE)) { var label = this.parseExpression(tn); if (!label) return null; if (tn.skip(Token.COLON)) { statements = new Array(); while (tn.peek() != Token.CASE && tn.nextToken != Token.DEFAULT && tn.nextToken != Token.CLOSEBRACE) { statement = this.parseStatement(tn); if (!statement) return null; statements.push(statement); } return Node.createSwitchCase(label, statements, tn.range(startPos, tn.pos)); } else this.error(DiagnosticCode._0_expected, tn.range(), ":"); // 'default' ':' Statement* } else if (tn.skip(Token.DEFAULT)) { if (tn.skip(Token.COLON)) { statements = new Array(); while (tn.peek() != Token.CASE && tn.nextToken != Token.DEFAULT && tn.nextToken != Token.CLOSEBRACE) { statement = this.parseStatement(tn); if (!statement) return null; statements.push(statement); } return Node.createSwitchCase(null, statements, tn.range(startPos, tn.pos)); } else this.error(DiagnosticCode._0_expected, tn.range(), ":"); } else this.error(DiagnosticCode._case_or_default_expected, tn.range()); return null; } parseThrowStatement(tn: Tokenizer): ThrowStatement | null { // at 'throw': Expression ';'? var startPos = tn.tokenPos; var expression = this.parseExpression(tn); if (!expression) return null; var ret = Node.createThrowStatement(expression, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } parseTryStatement(tn: Tokenizer): TryStatement | null { // at 'try': '{' Statement* '}' ('catch' '(' VariableMember ')' '{' Statement* '}')? ('finally' '{' Statement* '}'? ';'? var startPos = tn.tokenPos; var stmt: Statement | null; if (tn.skip(Token.OPENBRACE)) { var statements = new Array(); while (!tn.skip(Token.CLOSEBRACE)) { stmt = this.parseStatement(tn); if (!stmt) return null; statements.push(stmt); } var catchVariable: IdentifierExpression | null = null; var catchStatements: Statement[] | null = null; var finallyStatements: Statement[] | null = null; if (tn.skip(Token.CATCH)) { if (!tn.skip(Token.OPENPAREN)) { this.error(DiagnosticCode._0_expected, tn.range(), "("); return null; } if (!tn.skip(Token.IDENTIFIER)) { this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } catchVariable = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); if (!tn.skip(Token.CLOSEPAREN)) { this.error(DiagnosticCode._0_expected, tn.range(), ")"); return null; } if (!tn.skip(Token.OPENBRACE)) { this.error(DiagnosticCode._0_expected, tn.range(), "{"); return null; } catchStatements = new Array(); while (!tn.skip(Token.CLOSEBRACE)) { var stmt = this.parseStatement(tn); if (!stmt) return null; catchStatements.push(stmt); } } if (tn.skip(Token.FINALLY)) { if (!tn.skip(Token.OPENBRACE)) { this.error(DiagnosticCode._0_expected, tn.range(), "{"); return null; } finallyStatements = new Array(); while (!tn.skip(Token.CLOSEBRACE)) { stmt = this.parseStatement(tn); if (!stmt) return null; finallyStatements.push(stmt); } } if (!(catchStatements || finallyStatements)) { this.error(DiagnosticCode._0_expected, tn.range(), "catch"); return null; } var ret = Node.createTryStatement(statements, catchVariable, catchStatements, finallyStatements, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } else this.error(DiagnosticCode._0_expected, tn.range(), "{"); return null; } parseTypeDeclaration(tn: Tokenizer, modifiers: Modifier[] | null = null, decorators: Decorator[] | null = null): TypeDeclaration | null { // at 'type': Identifier '=' Type ';'? var startPos = decorators && decorators.length ? decorators[0].range.start : modifiers && modifiers.length ? modifiers[0].range.start : tn.tokenPos; if (tn.skip(Token.IDENTIFIER)) { var name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); if (tn.skip(Token.EQUALS)) { var type = this.parseType(tn); if (!type) return null; var ret = Node.createTypeDeclaration(name, type, modifiers, decorators, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } else this.error(DiagnosticCode._0_expected, tn.range(), "="); } else this.error(DiagnosticCode.Identifier_expected, tn.range()); return null; } parseWhileStatement(tn: Tokenizer): WhileStatement | null { // at 'while': '(' Expression ')' Statement ';'? var startPos = tn.tokenPos; if (tn.skip(Token.OPENPAREN)) { var expression = this.parseExpression(tn); if (!expression) return null; if (tn.skip(Token.CLOSEPAREN)) { var statement = this.parseStatement(tn); if (!statement) return null; var ret = Node.createWhileStatement(expression, statement, tn.range(startPos, tn.pos)); tn.skip(Token.SEMICOLON); return ret; } else this.error(DiagnosticCode._0_expected, tn.range(), ")"); } else this.error(DiagnosticCode._0_expected, tn.range(), "("); return null; } // expressions // see: http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing parseExpressionStart(tn: Tokenizer): Expression | null { var token = tn.next(true); var startPos = tn.tokenPos; var expr: Expression | null = null; if (token == Token.NULL) return Node.createNullExpression(tn.range()); if (token == Token.TRUE) return Node.createTrueExpression(tn.range()); if (token == Token.FALSE) return Node.createFalseExpression(tn.range()); var p = determinePrecedenceStart(token); if (p != Precedence.INVALID) { var operand: Expression | null; // TODO: SpreadExpression, YieldExpression (currently become unsupported UnaryPrefixExpressions) // NewExpression if (token == Token.NEW) { operand = this.parseExpression(tn, Precedence.CALL); if (!operand) return null; if (operand.kind == NodeKind.CALL) return Node.createNewExpression((operand).expression, (operand).typeArguments, (operand).arguments, tn.range(startPos, tn.pos)); this.error(DiagnosticCode.Operation_not_supported, tn.range()); return null; } else { operand = this.parseExpression(tn, p); if (!operand) return null; } // UnaryPrefixExpression if (token == Token.PLUS_PLUS || token == Token.MINUS_MINUS) if ((operand).kind != NodeKind.IDENTIFIER && (operand).kind != NodeKind.ELEMENTACCESS && (operand).kind != NodeKind.PROPERTYACCESS) this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, (operand).range); return Node.createUnaryPrefixExpression(token, operand, tn.range(startPos, tn.pos)); } switch (token) { // ParenthesizedExpression case Token.OPENPAREN: { expr = this.parseExpression(tn); if (!expr) return null; if (!tn.skip(Token.CLOSEPAREN)) { this.error(DiagnosticCode._0_expected, tn.range(), ")"); return null; } return Node.createParenthesizedExpression(expr, tn.range(startPos, tn.pos)); } // ArrayLiteralExpression case Token.OPENBRACKET: { var elementExpressions = new Array(); if (!tn.skip(Token.CLOSEBRACKET)) { do { if (tn.peek() == Token.COMMA) expr = null; // omitted else { expr = this.parseExpression(tn, Precedence.COMMA + 1); if (!expr) return null; } elementExpressions.push(expr); if (tn.peek() == Token.CLOSEBRACKET) break; } while (tn.skip(Token.COMMA)); if (!tn.skip(Token.CLOSEBRACKET)) { this.error(DiagnosticCode._0_expected, tn.range(), "]"); return null; } } return Node.createArrayLiteralExpression(elementExpressions, tn.range(startPos, tn.pos)); } // AssertionExpression (unary prefix) case Token.LESSTHAN: { var toType = this.parseType(tn); if (!toType) return null; if (!tn.skip(Token.GREATERTHAN)) { this.error(DiagnosticCode._0_expected, tn.range(), ">"); return null; } expr = this.parseExpression(tn, Precedence.CALL); if (!expr) return null; return Node.createAssertionExpression(AssertionKind.PREFIX, expr, toType, tn.range(startPos, tn.pos)); } // IdentifierExpression case Token.IDENTIFIER: return Node.createIdentifierExpression(tn.readIdentifier(), tn.range(startPos, tn.pos)); case Token.THIS: return Node.createThisExpression(tn.range(startPos, tn.pos)); case Token.CONSTRUCTOR: return Node.createConstructorExpression(tn.range(startPos, tn.pos)); case Token.SUPER: return Node.createSuperExpression(tn.range(startPos, tn.pos)); // StringLiteralExpression case Token.STRINGLITERAL: return Node.createStringLiteralExpression(tn.readString(), tn.range(startPos, tn.pos)); // IntegerLiteralExpression case Token.INTEGERLITERAL: return Node.createIntegerLiteralExpression(tn.readInteger(), tn.range(startPos, tn.pos)); // FloatLiteralExpression case Token.FLOATLITERAL: return Node.createFloatLiteralExpression(tn.readFloat(), tn.range(startPos, tn.pos)); // RegexpLiteralExpression // note that this also continues on invalid ones so the surrounding AST remains intact case Token.SLASH: var regexpPattern = tn.readRegexpPattern(); // also reports if (!tn.skip(Token.SLASH)) { this.error(DiagnosticCode._0_expected, tn.range(), "/"); return null; } return Node.createRegexpLiteralExpression(regexpPattern, tn.readRegexpFlags() /* also reports */, tn.range(startPos, tn.pos)); default: this.error(DiagnosticCode.Expression_expected, tn.range()); return null; } } tryParseTypeArgumentsBeforeArguments(tn: Tokenizer): TypeNode[] | null { // at '<': Type (',' Type)* '>' '(' tn.mark(); if (!tn.skip(Token.LESSTHAN)) return null; var typeArguments = new Array(); do { var type = this.parseType(tn, true, true); if (!type) { tn.reset(); return null; } typeArguments.push(type); } while (tn.skip(Token.COMMA)); if (tn.skip(Token.GREATERTHAN) && tn.skip(Token.OPENPAREN)) return typeArguments; tn.reset(); return null; } parseArguments(tn: Tokenizer): Expression[] | null { // at '(': (Expression (',' Expression)*)? ')' var args = new Array(); if (!tn.skip(Token.CLOSEPAREN)) { do { var expr = this.parseExpression(tn, Precedence.COMMA + 1); if (!expr) return null; args.push(expr); } while (tn.skip(Token.COMMA)); if (!tn.skip(Token.CLOSEPAREN)) { this.error(DiagnosticCode._0_expected, tn.range(), ")"); return null; } } return args; } parseExpression(tn: Tokenizer, precedence: Precedence = 0): Expression | null { var expr = this.parseExpressionStart(tn); if (!expr) return null; var startPos = expr.range.start; // CallExpression var typeArguments = this.tryParseTypeArgumentsBeforeArguments(tn); // skips '(' on success // there might be better ways to distinguish a LESSTHAN from a CALL with type arguments if (typeArguments || tn.skip(Token.OPENPAREN)) { var args = this.parseArguments(tn); if (!args) return null; expr = Node.createCallExpression(expr, typeArguments, args, tn.range(startPos, tn.pos)); } var token: Token; var next: Expression | null = null; var nextPrecedence: Precedence; while ((nextPrecedence = determinePrecedence(token = tn.peek())) >= precedence) { // precedence climbing tn.next(); switch (token) { // AssertionExpression case Token.AS: var toType = this.parseType(tn); if (!toType) return null; expr = Node.createAssertionExpression(AssertionKind.AS, expr, toType, tn.range(startPos, tn.pos)); break; // ElementAccessExpression case Token.OPENBRACKET: next = this.parseExpression(tn); if (!next) return null; if (!tn.skip(Token.CLOSEBRACKET)) { this.error(DiagnosticCode._0_expected, tn.range(), "]"); return null; } expr = Node.createElementAccessExpression(expr, next, tn.range(startPos, tn.pos)); break; // UnaryPostfixExpression case Token.PLUS_PLUS: case Token.MINUS_MINUS: if (expr.kind != NodeKind.IDENTIFIER && expr.kind != NodeKind.ELEMENTACCESS && expr.kind != NodeKind.PROPERTYACCESS) this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, expr.range); expr = Node.createUnaryPostfixExpression(token, expr, tn.range(startPos, tn.pos)); break; // TernaryExpression case Token.QUESTION: var ifThen = this.parseExpression(tn); if (!ifThen) return null; if (tn.skip(Token.COLON)) { var ifElse = this.parseExpression(tn); if (!ifElse) return null; expr = Node.createTernaryExpression(expr, ifThen, ifElse, tn.range(startPos, tn.pos)); } else { this.error(DiagnosticCode._0_expected, tn.range(), ":"); return null; } break; // CommaExpression case Token.COMMA: var commaExprs = new Array(1); commaExprs[0] = expr; do { expr = this.parseExpression(tn, Precedence.COMMA + 1); if (!expr) return null; commaExprs.push(expr); } while (tn.skip(Token.COMMA)); expr = Node.createCommaExpression(commaExprs, tn.range(startPos, tn.pos)); break; default: next = this.parseExpression(tn, isRightAssociative(token) ? nextPrecedence : 1 + nextPrecedence); if (!next) return null; // PropertyAccessExpression if (token == Token.DOT) { if (next.kind == NodeKind.IDENTIFIER) expr = Node.createPropertyAccessExpression(expr, next, tn.range(startPos, tn.pos)); else if (next.kind == NodeKind.CALL) { // join var propertyCall = next; if (propertyCall.expression.kind == NodeKind.IDENTIFIER) { propertyCall.expression = Node.createPropertyAccessExpression(expr, propertyCall.expression, tn.range(startPos, tn.pos)); } else { this.error(DiagnosticCode.Identifier_expected, propertyCall.expression.range); return null; } expr = propertyCall; } else { this.error(DiagnosticCode.Identifier_expected, next.range); return null; } // BinaryExpression } else expr = Node.createBinaryExpression(token, expr, next, tn.range(startPos, tn.pos)); break; } } return expr; } } /** Operator precedence from least to largest. */ export const enum Precedence { COMMA, SPREAD, YIELD, ASSIGNMENT, CONDITIONAL, LOGICAL_OR, LOGICAL_AND, BITWISE_OR, BITWISE_XOR, BITWISE_AND, EQUALITY, RELATIONAL, SHIFT, ADDITIVE, MULTIPLICATIVE, EXPONENTIATED, UNARY_PREFIX, UNARY_POSTFIX, CALL, MEMBERACCESS, GROUPING, INVALID = -1 } /** Determines the precedence of a starting token. */ function determinePrecedenceStart(kind: Token): i32 { switch (kind) { case Token.DOT_DOT_DOT: return Precedence.SPREAD; case Token.YIELD: return Precedence.YIELD; case Token.EXCLAMATION: case Token.TILDE: case Token.PLUS: case Token.MINUS: case Token.PLUS_PLUS: case Token.MINUS_MINUS: case Token.TYPEOF: case Token.VOID: case Token.DELETE: return Precedence.UNARY_PREFIX; case Token.NEW: return Precedence.MEMBERACCESS; default: return Precedence.INVALID; } } /** Determines the precende of a non-starting token. */ function determinePrecedence(kind: Token): i32 { switch (kind) { case Token.COMMA: return Precedence.COMMA; case Token.EQUALS: case Token.PLUS_EQUALS: case Token.MINUS_EQUALS: case Token.ASTERISK_ASTERISK_EQUALS: case Token.ASTERISK_EQUALS: case Token.SLASH_EQUALS: case Token.PERCENT_EQUALS: case Token.LESSTHAN_LESSTHAN_EQUALS: case Token.GREATERTHAN_GREATERTHAN_EQUALS: case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: case Token.AMPERSAND_EQUALS: case Token.CARET_EQUALS: case Token.BAR_EQUALS: return Precedence.ASSIGNMENT; case Token.QUESTION: return Precedence.CONDITIONAL; case Token.BAR_BAR: return Precedence.LOGICAL_OR; case Token.AMPERSAND_AMPERSAND: return Precedence.LOGICAL_AND; case Token.BAR: return Precedence.BITWISE_OR; case Token.CARET: return Precedence.BITWISE_XOR; case Token.AMPERSAND: return Precedence.BITWISE_AND; case Token.EQUALS_EQUALS: case Token.EXCLAMATION_EQUALS: case Token.EQUALS_EQUALS_EQUALS: case Token.EXCLAMATION_EQUALS_EQUALS: return Precedence.EQUALITY; case Token.AS: case Token.IN: case Token.INSTANCEOF: case Token.LESSTHAN: case Token.GREATERTHAN: case Token.LESSTHAN_EQUALS: case Token.GREATERTHAN_EQUALS: return Precedence.RELATIONAL; case Token.LESSTHAN_LESSTHAN: case Token.GREATERTHAN_GREATERTHAN: case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: return Precedence.SHIFT; case Token.PLUS: case Token.MINUS: return Precedence.ADDITIVE; case Token.ASTERISK: case Token.SLASH: case Token.PERCENT: return Precedence.MULTIPLICATIVE; case Token.ASTERISK_ASTERISK: return Precedence.EXPONENTIATED; case Token.PLUS_PLUS: case Token.MINUS_MINUS: return Precedence.UNARY_POSTFIX; case Token.DOT: case Token.NEW: case Token.OPENBRACKET: return Precedence.MEMBERACCESS; default: return Precedence.INVALID; } } /** Determines whether a non-starting token is right associative. */ function isRightAssociative(kind: Token): bool { switch (kind) { case Token.EQUALS: case Token.PLUS_EQUALS: case Token.MINUS_EQUALS: case Token.ASTERISK_ASTERISK_EQUALS: case Token.ASTERISK_EQUALS: case Token.SLASH_EQUALS: case Token.PERCENT_EQUALS: case Token.LESSTHAN_LESSTHAN_EQUALS: case Token.GREATERTHAN_GREATERTHAN_EQUALS: case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: case Token.AMPERSAND_EQUALS: case Token.CARET_EQUALS: case Token.BAR_EQUALS: case Token.QUESTION: case Token.ASTERISK_ASTERISK: return true; default: return false; } }