assemblyscript/src/parser.ts
2018-01-07 18:15:21 +01:00

1847 lines
64 KiB
TypeScript

/*
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
} from "./program";
import {
Tokenizer,
Token,
Range
} from "./tokenizer";
import {
DiagnosticCode,
DiagnosticEmitter
} from "./diagnostics";
import {
normalize as normalizePath
} from "./util/path";
import {
Node,
NodeKind,
Source,
TypeNode,
Expression,
AssertionKind,
CallExpression,
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,
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<string> = 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(path, text, isEntry);
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();
(<Decorator[]>decorators).push(<Decorator>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;
}
// fall through
case Token.VAR:
case Token.LET:
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 = (<Decorator[]>decorators).length; i < k; ++i)
this.error(DiagnosticCode.Decorators_are_not_valid_here, (<Decorator[]>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<TypeNode>();
var nullable = false;
// Name<T>
if (tn.skip(Token.LESSTHAN)) {
do {
var parameter = this.parseType(tn, true, suppressErrors);
if (!parameter)
return null;
parameters.push(<TypeNode>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<VariableDeclaration>();
var isDeclare = hasModifier(ModifierKind.DECLARE, modifiers);
do {
var member = this.parseVariableDeclaration(tn, isDeclare, modifiers, decorators);
if (!member)
return null;
members.push(<VariableDeclaration>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<EnumValueDeclaration>();
if (!tn.skip(Token.CLOSEBRACE)) {
do {
var member = this.parseEnumValue(tn);
if (!member)
return null;
members.push(<EnumValueDeclaration>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<TypeParameter>();
if (!tn.skip(Token.GREATERTHAN)) {
do {
var typeParameter = this.parseTypeParameter(tn);
if (!typeParameter)
return null;
typeParameters.push(<TypeParameter>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<Parameter>();
if (tn.peek() != Token.CLOSEPAREN) {
do {
var param = this.parseParameter(tn);
if (!param)
return null;
parameters.push(<Parameter>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 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 (tn.skip(Token.COLON)) {
type = this.parseType(tn);
if (!type)
return null;
}
var initializer: Expression | null = null;
if (tn.skip(Token.EQUALS)) {
initializer = this.parseExpression(tn);
if (!initializer)
return null;
}
return Node.createParameter(identifier, type, initializer, isRest, Range.join(<Range>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>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, <Parameter[]>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<TypeNode>();
if (tn.skip(Token.IMPLEMENTS)) {
do {
var type = this.parseType(tn);
if (!type)
return null;
implementsTypes.push(<TypeNode>type);
} while (tn.skip(Token.COMMA));
}
if (tn.skip(Token.OPENBRACE)) {
var members = new Array<DeclarationStatement>();
if (!tn.skip(Token.CLOSEBRACE)) {
var isDeclare = hasModifier(ModifierKind.DECLARE, modifiers);
do {
var member = this.parseClassMember(tn, isDeclare);
if (!member)
return null;
members.push(<DeclarationStatement>member);
} while (!tn.skip(Token.CLOSEBRACE));
}
return Node.createClassDeclaration(identifier, <TypeParameter[]>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<Decorator>();
while (tn.skip(Token.AT)) {
var decorator = this.parseDecorator(tn);
if (!decorator)
break;
decorators.push(<Decorator>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;
}
}
if (tn.skip(Token.CONSTRUCTOR) || tn.skip(Token.IDENTIFIER)) { // order is important
var identifier: IdentifierExpression = tn.token == Token.CONSTRUCTOR
? Node.createConstructorExpression(tn.range())
: Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
var typeParameters: TypeParameter[] | null;
if (tn.skip(Token.LESSTHAN)) {
if (identifier.kind == NodeKind.CONSTRUCTOR)
this.error(DiagnosticCode.Type_parameters_cannot_appear_on_a_constructor_declaration, tn.range()); // recoverable
typeParameters = this.parseTypeParameters(tn);
if (!typeParameters)
return null;
} else
typeParameters = [];
if (identifier.kind == NodeKind.CONSTRUCTOR && tn.peek() != Token.OPENPAREN)
this.error(DiagnosticCode.Constructor_implementation_is_missing, tn.range());
// 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>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, <TypeParameter[]>typeParameters, <Parameter[]>parameters, returnType, statements, modifiers, decorators, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
return retMethod;
// 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<Statement>();
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<ExportMember>();
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(<string>ret.normalizedPath)) {
this.backlog.push(<string>ret.normalizedPath);
this.seenlog.add(<string>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:
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<Statement>();
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>statement, <Expression>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(<Expression>condition, <Statement>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<SwitchCase>();
while (!tn.skip(Token.CLOSEBRACE)) {
var case_ = this.parseSwitchCase(tn);
if (!case_)
return null;
cases.push(<SwitchCase>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<Statement>();
while (tn.peek() != Token.CASE && tn.nextToken != Token.DEFAULT && tn.nextToken != Token.CLOSEBRACE) {
statement = this.parseStatement(tn);
if (!statement)
return null;
statements.push(<Statement>statement);
}
return Node.createSwitchCase(<Expression>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<Statement>();
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>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<Statement>();
while (!tn.skip(Token.CLOSEBRACE)) {
stmt = this.parseStatement(tn);
if (!stmt)
return null;
statements.push(<Statement>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(<Statement>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(<Statement>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>expression, <Statement>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((<CallExpression>operand).expression, (<CallExpression>operand).typeArguments, (<CallExpression>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 ((<Expression>operand).kind != NodeKind.IDENTIFIER && (<Expression>operand).kind != NodeKind.ELEMENTACCESS && (<Expression>operand).kind != NodeKind.PROPERTYACCESS)
this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, (<Expression>operand).range);
return Node.createUnaryPrefixExpression(token, <Expression>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<Expression | null>();
if (!tn.skip(Token.CLOSEBRACKET)) {
do {
if (tn.peek() == Token.COMMA || tn.peek() == Token.CLOSEBRACKET)
expr = null; // omitted
else {
expr = this.parseExpression(tn, Precedence.COMMA + 1);
if (!expr)
return null;
}
elementExpressions.push(expr);
} 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, <Expression>expr, <TypeNode>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<TypeNode>();
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<Expression>();
if (!tn.skip(Token.CLOSEPAREN)) {
do {
var expr = this.parseExpression(tn, Precedence.COMMA + 1);
if (!expr)
return null;
args.push(<Expression>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();
// AssertionExpression
if (token == Token.AS) {
var toType = this.parseType(tn);
if (!toType)
return null;
expr = Node.createAssertionExpression(AssertionKind.AS, expr, toType, tn.range(startPos, tn.pos));
// ElementAccessExpression
} else if (token == Token.OPENBRACKET) {
next = this.parseExpression(tn); // resets precedence
if (!next)
return null;
if (tn.skip(Token.CLOSEBRACKET))
expr = Node.createElementAccessExpression(<Expression>expr, <Expression>next, tn.range(startPos, tn.pos));
else {
this.error(DiagnosticCode._0_expected, tn.range(), "]");
return null;
}
// UnaryPostfixExpression
} else if (token == Token.PLUS_PLUS || token == 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));
// TernaryExpression
} else if (token == 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(<Expression>expr, <Expression>ifThen, <Expression>ifElse, tn.range(startPos, tn.pos));
} else {
this.error(DiagnosticCode._0_expected, tn.range(), ":");
return null;
}
// CommaExpression
} else if (token == Token.COMMA) {
var commaExprs = new Array<Expression>(1);
commaExprs[0] = <Expression>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));
} else {
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(<Expression>expr, <IdentifierExpression>next, tn.range(startPos, tn.pos));
} else if (next.kind == NodeKind.CALL) { // amend
var propertyCall = <CallExpression>next;
if (propertyCall.expression.kind == NodeKind.IDENTIFIER) {
propertyCall.expression = Node.createPropertyAccessExpression(<Expression>expr, <IdentifierExpression>propertyCall.expression, tn.range(startPos, tn.pos));
} else
throw new Error("unexpected expression kind");
expr = propertyCall;
} else {
this.error(DiagnosticCode.Identifier_expected, next.range);
return null;
}
// BinaryExpression
} else
expr = Node.createBinaryExpression(token, <Expression>expr, <Expression>next, tn.range(startPos, tn.pos));
}
}
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:
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;
}
}