assemblyscript/src/parser.ts

3866 lines
107 KiB
TypeScript

/**
* A TypeScript parser for the AssemblyScript subset.
* @module parser
*//***/
import {
CommonFlags,
LIBRARY_PREFIX,
PATH_DELIMITER
} from "./common";
import {
Program
} from "./program";
import {
Tokenizer,
Token,
Range,
CommentHandler,
IdentifierHandling
} from "./tokenizer";
import {
DiagnosticCode,
DiagnosticEmitter
} from "./diagnostics";
import {
normalizePath
} from "./util";
import {
Node,
NodeKind,
Source,
SourceKind,
CommonTypeNode,
TypeNode,
SignatureNode,
Expression,
AssertionKind,
CallExpression,
ClassExpression,
FunctionExpression,
IdentifierExpression,
StringLiteralExpression,
Statement,
BlockStatement,
BreakStatement,
ClassDeclaration,
ContinueStatement,
DeclarationStatement,
DecoratorNode,
DoStatement,
EnumDeclaration,
EnumValueDeclaration,
ExportImportStatement,
ExportMember,
ExportStatement,
ExpressionStatement,
ForStatement,
FunctionDeclaration,
IfStatement,
ImportDeclaration,
ImportStatement,
NamespaceDeclaration,
ParameterNode,
ParameterKind,
ReturnStatement,
SwitchCase,
SwitchStatement,
ThrowStatement,
TryStatement,
TypeDeclaration,
TypeParameterNode,
VariableStatement,
VariableDeclaration,
VoidStatement,
WhileStatement,
mangleInternalPath,
nodeIsCallable,
nodeIsGenericCallable,
IndexSignatureDeclaration
} from "./ast";
/** Parser interface. */
export class Parser extends DiagnosticEmitter {
/** Program being created. */
program: Program;
/** Source file names to be requested next. */
backlog: string[] = new Array();
/** Source file names already seen, that is processed or backlogged. */
seenlog: Set<string> = new Set();
/** Source file names already completely processed. */
donelog: Set<string> = new Set();
/** Optional handler to intercept comments while tokenizing. */
onComment: CommentHandler | null = null;
/** 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);
var internalPath = mangleInternalPath(normalizedPath);
// check if already processed
if (this.donelog.has(internalPath)) return;
this.donelog.add(internalPath); // do not parse again
this.seenlog.add(internalPath); // do not request again
// create the source element
var source = new Source(
normalizedPath,
text,
isEntry
? SourceKind.ENTRY
: path.startsWith(LIBRARY_PREFIX) && path.indexOf(PATH_DELIMITER, LIBRARY_PREFIX.length) < 0
? SourceKind.LIBRARY
: SourceKind.DEFAULT
);
var program = this.program;
program.sources.push(source);
// tokenize and parse
var tn = new Tokenizer(source, program.diagnostics);
tn.onComment = this.onComment;
source.tokenizer = tn;
var statements = source.statements;
while (!tn.skip(Token.ENDOFFILE)) {
let statement = this.parseTopLevelStatement(tn, null);
if (statement) statements.push(statement);
}
tn.finish();
}
/** Parses a top-level statement. */
parseTopLevelStatement(
tn: Tokenizer,
namespace: NamespaceDeclaration | null = null
): Statement | null {
var flags = CommonFlags.NONE;
var startPos: i32 = -1;
// check decorators
var decorators: DecoratorNode[] | null = null;
while (tn.skip(Token.AT)) {
if (startPos < 0) startPos = tn.tokenPos;
let decorator = this.parseDecorator(tn);
if (!decorator) {
this.skipStatement(tn);
continue;
}
if (!decorators) decorators = [];
decorators.push(decorator);
}
// check modifiers
var exportStart: i32 = 0;
var exportEnd: i32 = 0;
if (tn.skip(Token.EXPORT)) {
if (tn.skip(Token.DEFAULT)) {
this.error(
DiagnosticCode.Operation_not_supported,
tn.range()
);
}
if (startPos < 0) startPos = tn.tokenPos;
flags |= CommonFlags.EXPORT;
exportStart = tn.tokenPos;
exportEnd = tn.pos;
}
var declareStart: i32 = 0;
var declareEnd: i32 = 0;
var contextIsAmbient = namespace != null && namespace.is(CommonFlags.AMBIENT);
if (tn.skip(Token.DECLARE)) {
if (startPos < 0) startPos = tn.tokenPos;
if (contextIsAmbient) {
this.error(
DiagnosticCode.A_declare_modifier_cannot_be_used_in_an_already_ambient_context,
tn.range()
); // recoverable
}
flags |= CommonFlags.DECLARE | CommonFlags.AMBIENT;
} else if (contextIsAmbient) {
flags |= CommonFlags.AMBIENT;
}
// parse the statement
var statement: Statement | null = null;
// handle declarations
var first = tn.peek();
if (startPos < 0) startPos = tn.nextTokenPos;
switch (first) {
case Token.CONST: {
tn.next();
flags |= CommonFlags.CONST;
if (tn.skip(Token.ENUM)) {
statement = this.parseEnum(tn, flags, decorators, startPos);
} else {
statement = this.parseVariable(tn, flags, decorators, startPos);
}
decorators = null;
break;
}
case Token.LET: flags |= CommonFlags.LET;
case Token.VAR: {
tn.next();
statement = this.parseVariable(tn, flags, decorators, startPos);
decorators = null;
break;
}
case Token.ENUM: {
tn.next();
statement = this.parseEnum(tn, flags, decorators, startPos);
decorators = null;
break;
}
case Token.FUNCTION: {
tn.next();
statement = this.parseFunction(tn, flags, decorators, startPos);
decorators = null;
break;
}
case Token.ABSTRACT: {
let state = tn.mark();
tn.next();
if (!tn.skip(Token.CLASS)) {
tn.reset(state);
statement = this.parseStatement(tn, true);
break;
} else {
tn.discard(state);
}
flags |= CommonFlags.ABSTRACT;
// fall through
}
case Token.CLASS:
case Token.INTERFACE: {
tn.next();
statement = this.parseClassOrInterface(tn, flags, decorators, startPos);
decorators = null;
break;
}
case Token.NAMESPACE: {
let state = tn.mark();
tn.next();
if (tn.peek(false, IdentifierHandling.PREFER) == Token.IDENTIFIER) {
tn.discard(state);
statement = this.parseNamespace(tn, flags, decorators, startPos);
decorators = null;
} else {
tn.reset(state);
statement = this.parseStatement(tn, true);
}
break;
}
case Token.IMPORT: {
tn.next();
flags |= CommonFlags.IMPORT;
if (flags & CommonFlags.EXPORT) {
statement = this.parseExportImport(tn, startPos);
} else {
statement = this.parseImport(tn);
}
break;
}
case Token.TYPE: { // also identifier
let state = tn.mark();
tn.next();
if (tn.peek(false, IdentifierHandling.PREFER) == Token.IDENTIFIER) {
tn.discard(state);
statement = this.parseTypeDeclaration(tn, flags, decorators, startPos);
decorators = null;
} else {
tn.reset(state);
statement = this.parseStatement(tn, true);
}
break;
}
default: {
// handle plain exports
if (flags & CommonFlags.EXPORT) {
statement = this.parseExport(tn, startPos, (flags & CommonFlags.DECLARE) != 0);
// handle non-declaration statements
} else {
if (exportEnd) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(exportStart, exportEnd), "export"
); // recoverable
}
if (declareEnd) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(declareStart, declareEnd), "declare"
); // recoverable
}
if (!namespace) {
statement = this.parseStatement(tn, true);
} // TODO: else?
}
break;
}
}
// check for decorators that weren't consumed
if (decorators) {
for (let 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 {
var backlog = this.backlog;
return backlog.length ? backlog.shift() : 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();
this.donelog.clear();
return this.program;
}
/** Parses a type. */
parseType(
tn: Tokenizer,
acceptParenthesized: bool = true,
suppressErrors: bool = false
): CommonTypeNode | null {
// NOTE: this parses our limited subset
var token = tn.next();
var startPos = tn.tokenPos;
var type: CommonTypeNode;
// '(' ...
if (token == Token.OPENPAREN) {
// '(' FunctionSignature ')' '|' 'null'?
let isNullableSignature = tn.skip(Token.OPENPAREN);
// FunctionSignature?
let signature = this.tryParseSignature(tn);
if (signature) {
if (isNullableSignature) {
if (!tn.skip(Token.CLOSEPAREN)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
return null;
}
if (!tn.skip(Token.BAR)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "|"
);
return null;
}
if (!tn.skip(Token.NULL)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "null"
);
}
signature.isNullable = true;
}
return signature;
} else if (isNullableSignature || this.tryParseSignatureIsSignature) {
this.error(
DiagnosticCode.Unexpected_token,
tn.range()
);
return null;
}
// Type (',' Type)* ')'
if (acceptParenthesized) {
let 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;
} else {
this.error(
DiagnosticCode.Unexpected_token,
tn.range()
);
return null;
}
// 'void'
} else if (token == Token.VOID) {
type = Node.createType(
Node.createSimpleTypeName("void", tn.range()), [], false, tn.range(startPos, tn.pos)
);
// 'this'
} else if (token == Token.THIS) {
type = Node.createType(
Node.createSimpleTypeName("this", tn.range()), [], false, tn.range(startPos, tn.pos)
);
// 'true'
} else if (token == Token.TRUE || token == Token.FALSE) {
type = Node.createType(
Node.createSimpleTypeName("bool", tn.range()), [], false, tn.range(startPos, tn.pos)
);
// StringLiteral
} else if (token == Token.STRINGLITERAL) {
tn.readString();
type = Node.createType(
Node.createSimpleTypeName("string", tn.range()), [], false, tn.range(startPos, tn.pos)
);
// Identifier
} else if (token == Token.IDENTIFIER) {
let first = Node.createSimpleTypeName(tn.readIdentifier(), tn.range());
let current = first;
let parameters = new Array<TypeNode>();
let nullable = false;
// Identifier ('.' Identifier)+
while (tn.skip(Token.DOT)) {
if (tn.skip(Token.IDENTIFIER)) {
let next = Node.createSimpleTypeName(tn.readIdentifier(), tn.range());
current.next = next;
current = next;
} else {
this.error(
DiagnosticCode.Identifier_expected,
tn.range(tn.pos)
);
return null;
}
}
// Name<T>
if (tn.skip(Token.LESSTHAN)) {
do {
let 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
while (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(first, parameters, nullable, tn.range(startPos, tn.pos));
} else {
if (!suppressErrors) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
}
return null;
}
// ... [][]
while (tn.skip(Token.OPENBRACKET)) {
let bracketStart = tn.tokenPos;
if (!tn.skip(Token.CLOSEBRACKET)) {
if (!suppressErrors) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "]"
);
}
return null;
}
let bracketRange = tn.range(bracketStart, tn.pos);
// ...[] | null
let 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.createSimpleTypeName("Array", bracketRange),
[ type ],
nullable,
tn.range(startPos, tn.pos)
);
if (nullable) break;
}
return type;
}
// Indicates whether tryParseSignature determined that it is handling a Signature
private tryParseSignatureIsSignature: bool = false;
/** Parses a function signature, as used in type declarations. */
tryParseSignature(
tn: Tokenizer
): SignatureNode | null {
// at '(': ('...'? Identifier '?'? ':' Type (',' '...'? Identifier '?'? ':' Type)* )? ')' '=>' Type
var state = tn.mark();
var startPos = tn.tokenPos;
var parameters: ParameterNode[] | null = null;
var thisType: TypeNode | null = null;
var isSignature: bool = false;
if (tn.skip(Token.CLOSEPAREN)) {
isSignature = true;
tn.discard(state);
parameters = [];
} else {
isSignature = false; // not yet known
do {
let kind = ParameterKind.DEFAULT;
if (tn.skip(Token.DOT_DOT_DOT)) {
isSignature = true;
tn.discard(state);
kind = ParameterKind.REST;
}
if (tn.skip(Token.THIS)) {
if (tn.skip(Token.COLON)) {
isSignature = true;
tn.discard(state);
let t = this.parseType(tn, false);
if (!t) return null;
if (t.kind != NodeKind.TYPE) {
this.error(
DiagnosticCode.Operation_not_supported,
t.range
);
this.tryParseSignatureIsSignature = true;
return null;
}
thisType = <TypeNode>t;
} else {
tn.reset(state);
this.tryParseSignatureIsSignature = false;
return null;
}
} else if (tn.skipIdentifier()) {
let name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range(tn.tokenPos, tn.pos));
if (tn.skip(Token.QUESTION)) {
isSignature = true;
tn.discard(state);
if (kind == ParameterKind.REST) {
this.error(
DiagnosticCode.A_rest_parameter_cannot_be_optional,
tn.range()
); // recoverable
} else {
kind = ParameterKind.OPTIONAL;
}
}
if (tn.skip(Token.COLON)) {
isSignature = true;
tn.discard(state);
let type = this.parseType(tn); // not suppressing errors because known
if (!type) {
this.tryParseSignatureIsSignature = isSignature;
return null;
}
let param = new ParameterNode();
param.parameterKind = kind;
param.name = name;
param.type = type;
if (!parameters) parameters = [ param ];
else parameters.push(param);
} else {
if (isSignature) {
this.error(
DiagnosticCode.Type_expected,
tn.range()
); // recoverable
}
}
} else {
if (isSignature) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
} else {
tn.reset(state);
}
this.tryParseSignatureIsSignature = isSignature;
return null;
}
} while (tn.skip(Token.COMMA));
if (!tn.skip(Token.CLOSEPAREN)) {
if (isSignature) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
} else {
tn.reset(state);
}
this.tryParseSignatureIsSignature = isSignature;
return null;
}
}
var returnType: CommonTypeNode | null;
if (tn.skip(Token.EQUALS_GREATERTHAN)) {
isSignature = true;
tn.discard(state);
returnType = this.parseType(tn);
if (!returnType) {
this.tryParseSignatureIsSignature = isSignature;
return null;
}
} else {
if (isSignature) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "=>"
);
} else {
tn.reset(state);
}
this.tryParseSignatureIsSignature = isSignature;
return null;
}
this.tryParseSignatureIsSignature = true;
return Node.createSignature(
parameters || [],
returnType,
thisType,
false,
tn.range(startPos, tn.pos)
);
}
// statements
parseDecorator(
tn: Tokenizer
): DecoratorNode | null {
// at '@': Identifier ('.' Identifier)* '(' Arguments
var startPos = tn.tokenPos;
if (tn.skipIdentifier()) {
let name = tn.readIdentifier();
let expression: Expression = Node.createIdentifierExpression(name, tn.range(startPos, tn.pos));
while (tn.skip(Token.DOT)) {
if (tn.skipIdentifier()) {
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;
}
}
let 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,
flags: CommonFlags,
decorators: DecoratorNode[] | null,
startPos: i32
): VariableStatement | null {
// at ('const' | 'let' | 'var'): VariableDeclaration (',' VariableDeclaration)* ';'?
var members = new Array<VariableDeclaration>();
do {
let member = this.parseVariableDeclaration(tn, flags, decorators);
if (!member) return null;
members.push(<VariableDeclaration>member);
} while (tn.skip(Token.COMMA));
var ret = Node.createVariableStatement(members, decorators, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
return ret;
}
parseVariableDeclaration(
tn: Tokenizer,
parentFlags: CommonFlags,
parentDecorators: DecoratorNode[] | null
): VariableDeclaration | null {
// before: Identifier (':' Type)? ('=' Expression)?
if (!tn.skipIdentifier()) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
return null;
}
var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
var flags = parentFlags;
if (tn.skip(Token.EXCLAMATION)) {
flags |= CommonFlags.DEFINITE_ASSIGNMENT;
}
var type: CommonTypeNode | null = null;
if (tn.skip(Token.COLON)) {
type = this.parseType(tn);
}
var initializer: Expression | null = null;
if (tn.skip(Token.EQUALS)) {
if (flags & CommonFlags.AMBIENT) {
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 (flags & CommonFlags.CONST) {
if (!(flags & CommonFlags.AMBIENT)) {
this.error(
DiagnosticCode._const_declarations_must_be_initialized,
identifier.range
); // recoverable
}
} else if (!type) { // neither type nor initializer
this.error(
DiagnosticCode.Type_expected,
tn.range(tn.pos)
); // recoverable
}
}
var range = Range.join(identifier.range, tn.range());
if ((flags & CommonFlags.DEFINITE_ASSIGNMENT) && initializer) {
this.error(
DiagnosticCode.A_definite_assignment_assertion_is_not_permitted_in_this_context,
range);
}
return Node.createVariableDeclaration(
identifier,
type,
initializer,
parentDecorators,
flags,
range
);
}
parseEnum(
tn: Tokenizer,
flags: CommonFlags,
decorators: DecoratorNode[] | null,
startPos: i32
): EnumDeclaration | null {
// at 'enum': Identifier '{' (EnumValueDeclaration (',' EnumValueDeclaration )*)? '}' ';'?
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>();
while (!tn.skip(Token.CLOSEBRACE)) {
let member = this.parseEnumValue(tn, CommonFlags.NONE);
if (!member) return null;
members.push(<EnumValueDeclaration>member);
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEBRACE)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
return null;
}
}
}
var ret = Node.createEnumDeclaration(
identifier,
members,
decorators,
flags,
tn.range(startPos, tn.pos)
);
tn.skip(Token.SEMICOLON);
return ret;
}
parseEnumValue(
tn: Tokenizer,
parentFlags: CommonFlags
): EnumValueDeclaration | null {
// before: Identifier ('=' Expression)?
if (!tn.skipIdentifier()) {
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,
parentFlags,
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
) {
if (!(expr = this.parseExpression(tn))) return null;
}
var ret = Node.createReturnStatement(expr, tn.range());
tn.skip(Token.SEMICOLON);
return ret;
}
parseTypeParameters(
tn: Tokenizer
): TypeParameterNode[] | null {
// at '<': TypeParameter (',' TypeParameter)* '>'
var typeParameters = new Array<TypeParameterNode>();
var seenOptional = false;
while (!tn.skip(Token.GREATERTHAN)) {
let typeParameter = this.parseTypeParameter(tn);
if (!typeParameter) return null;
if (typeParameter.defaultType !== null) {
seenOptional = true;
} else if (seenOptional) {
this.error(
DiagnosticCode.Required_type_parameters_may_not_follow_optional_type_parameters,
typeParameter.range
);
typeParameter.defaultType = null;
}
typeParameters.push(<TypeParameterNode>typeParameter);
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.GREATERTHAN)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ">"
);
return null;
}
}
}
if (typeParameters.length === 0) {
this.error(
DiagnosticCode.Type_parameter_list_cannot_be_empty,
tn.range()
); // recoverable
}
return typeParameters;
}
parseTypeParameter(
tn: Tokenizer
): TypeParameterNode | null {
// before: Identifier ('extends' Type)? ('=' Type)?
if (tn.next() == Token.IDENTIFIER) {
let identifier = Node.createIdentifierExpression(
tn.readIdentifier(),
tn.range()
);
let extendsType: TypeNode | null = null;
if (tn.skip(Token.EXTENDS)) {
let t = this.parseType(tn);
if (!t) return null;
if (t.kind != NodeKind.TYPE) {
this.error(
DiagnosticCode.Operation_not_supported,
t.range
);
return null;
}
extendsType = <TypeNode>t;
}
let defaultType: TypeNode | null = null;
if (tn.skip(Token.EQUALS)) {
let t = this.parseType(tn);
if (!t) return null;
if (t.kind != NodeKind.TYPE) {
this.error(
DiagnosticCode.Operation_not_supported,
t.range
);
return null;
}
defaultType = <TypeNode>t;
}
return Node.createTypeParameter(
identifier,
extendsType,
defaultType,
Range.join(identifier.range, tn.range())
);
} else {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
}
return null;
}
private parseParametersThis: TypeNode | null = null;
parseParameters(
tn: Tokenizer,
isConstructor: bool = false
): ParameterNode[] | null {
// at '(': (Parameter (',' Parameter)*)? ')'
var parameters = new Array<ParameterNode>();
var seenRest: ParameterNode | null = null;
var seenOptional = false;
var reportedRest = false;
var thisType: CommonTypeNode | null = null;
// check if there is a leading `this` parameter
this.parseParametersThis = null;
if (tn.skip(Token.THIS)) {
if (tn.skip(Token.COLON)) {
thisType = this.parseType(tn); // reports
if (!thisType) return null;
if (thisType.kind == NodeKind.TYPE) {
this.parseParametersThis = <TypeNode>thisType;
} else {
this.error(
DiagnosticCode.Operation_not_supported,
thisType.range
);
}
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ":"
);
return null;
}
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEPAREN)) {
return parameters;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
return null;
}
}
}
while (!tn.skip(Token.CLOSEPAREN)) {
let param = this.parseParameter(tn, isConstructor); // reports
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);
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEPAREN)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
return null;
}
}
}
return parameters;
}
parseParameter(
tn: Tokenizer,
isConstructor: bool = false
): ParameterNode | null {
// before: ('public' | 'private' | 'protected' | '...')? Identifier '?'? (':' Type)? ('=' Expression)?
var isRest = false;
var isOptional = false;
var startRange: Range | null = null;
var accessFlags: CommonFlags = CommonFlags.NONE;
if (isConstructor) {
if (tn.skip(Token.PUBLIC)) {
startRange = tn.range();
accessFlags |= CommonFlags.PUBLIC;
} else if (tn.skip(Token.PROTECTED)) {
startRange = tn.range();
accessFlags |= CommonFlags.PROTECTED;
} else if (tn.skip(Token.PRIVATE)) {
startRange = tn.range();
accessFlags |= CommonFlags.PRIVATE;
}
if (tn.peek() == Token.READONLY) {
let state = tn.mark();
tn.next();
if (tn.peek() != Token.COLON) { // modifier
tn.discard(state);
if (!startRange) startRange = tn.range();
accessFlags |= CommonFlags.READONLY;
} else { // identifier
tn.reset(state);
}
}
}
if (tn.skip(Token.DOT_DOT_DOT)) {
if (accessFlags) {
this.error(
DiagnosticCode.A_parameter_property_cannot_be_declared_using_a_rest_parameter,
tn.range()
);
} else {
startRange = tn.range();
}
isRest = true;
}
if (tn.skipIdentifier()) {
if (!isRest) startRange = tn.range();
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
let type: CommonTypeNode | 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;
} else {
type = Node.createOmittedType(tn.range(tn.pos));
}
let 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;
}
let param = Node.createParameter(
identifier,
type,
initializer,
isRest
? ParameterKind.REST
: isOptional
? ParameterKind.OPTIONAL
: ParameterKind.DEFAULT,
Range.join(<Range>startRange, tn.range())
);
param.flags |= accessFlags;
return param;
} else {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
}
return null;
}
parseFunction(
tn: Tokenizer,
flags: CommonFlags,
decorators: DecoratorNode[] | null,
startPos: i32
): FunctionDeclaration | null {
// at 'function':
// Identifier
// ('<' TypeParameters)?
// '(' Parameters (':' Type)?
// '{' Statement* '}'
// ';'?
if (!tn.skipIdentifier()) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range(tn.pos)
);
return null;
}
var name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
var signatureStart: i32 = -1;
var typeParameters: TypeParameterNode[] | null = null;
if (tn.skip(Token.LESSTHAN)) {
signatureStart = tn.tokenPos;
typeParameters = this.parseTypeParameters(tn);
if (!typeParameters) return null;
flags |= CommonFlags.GENERIC;
}
if (!tn.skip(Token.OPENPAREN)) {
this.error(
DiagnosticCode._0_expected,
tn.range(tn.pos), "("
);
return null;
}
if (signatureStart < 0) {
signatureStart = tn.tokenPos;
}
var parameters = this.parseParameters(tn);
if (!parameters) return null;
var thisType = this.parseParametersThis;
var isSetter = (flags & CommonFlags.SET) != 0;
if (isSetter) {
if (parameters.length != 1) {
this.error(
DiagnosticCode.A_set_accessor_must_have_exactly_one_parameter,
name.range
); // recoverable
}
if (parameters.length && parameters[0].initializer) {
this.error(
DiagnosticCode.A_set_accessor_parameter_cannot_have_an_initializer,
name.range
); // recoverable
}
}
if (flags & CommonFlags.GET) {
if (parameters.length) {
this.error(
DiagnosticCode.A_get_accessor_cannot_have_parameters,
name.range
); // recoverable
}
}
var returnType: CommonTypeNode | null = null;
if (tn.skip(Token.COLON)) {
returnType = this.parseType(tn, true, isSetter);
if (!returnType) return null;
}
if (!returnType) {
returnType = Node.createOmittedType(
tn.range(tn.pos)
);
if (!isSetter) {
this.error(
DiagnosticCode.Type_expected,
returnType.range
); // recoverable
}
}
var signature = Node.createSignature(
parameters,
returnType,
thisType,
false,
tn.range(signatureStart, tn.pos)
);
var body: Statement | null = null;
if (tn.skip(Token.OPENBRACE)) {
if (flags & CommonFlags.AMBIENT) {
this.error(
DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts,
tn.range()
); // recoverable
}
body = this.parseBlockStatement(tn, false);
if (!body) return null;
} else if (!(flags & CommonFlags.AMBIENT)) {
this.error(
DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration,
tn.range(tn.pos)
);
}
var ret = Node.createFunctionDeclaration(
name,
typeParameters,
signature,
body,
decorators,
flags,
tn.range(startPos, tn.pos)
);
tn.skip(Token.SEMICOLON);
return ret;
}
parseFunctionExpression(tn: Tokenizer): FunctionExpression | null {
var startPos = tn.tokenPos;
var name: IdentifierExpression;
var isArrow = false;
// either at 'function':
// Identifier?
// '(' Parameters (':' Type)?
// Statement
if (tn.token == Token.FUNCTION) {
if (tn.skipIdentifier()) {
name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
} else { // empty name
name = Node.createEmptyIdentifierExpression(tn.range(tn.pos));
}
if (!tn.skip(Token.OPENPAREN)) {
this.error(
DiagnosticCode._0_expected,
tn.range(tn.pos), "("
);
return null;
}
// or at '(' of arrow function:
// Parameters (':' Type)?
// Statement
} else {
isArrow = true;
assert(tn.token == Token.OPENPAREN);
name = Node.createEmptyIdentifierExpression(tn.range(tn.tokenPos));
}
// TODO: type parameters? doesn't seem worth it.
var signatureStart = tn.pos;
var parameters = this.parseParameters(tn);
if (!parameters) return null;
return this.parseFunctionExpressionCommon(tn, name, parameters, isArrow, startPos, signatureStart);
}
private parseFunctionExpressionCommon(
tn: Tokenizer,
name: IdentifierExpression,
parameters: ParameterNode[],
isArrow: bool,
startPos: i32 = -1,
signatureStart: i32 = -1
): FunctionExpression | null {
if (startPos < 0) startPos = name.range.start;
if (signatureStart < 0) signatureStart = startPos;
var returnType: CommonTypeNode | null = null;
if (tn.skip(Token.COLON)) {
returnType = this.parseType(tn);
if (!returnType) return null;
} else {
returnType = Node.createOmittedType(tn.range(tn.pos));
this.error(
DiagnosticCode.Type_expected,
returnType.range
); // recoverable
}
if (isArrow) {
if (!tn.skip(Token.EQUALS_GREATERTHAN)) {
this.error(
DiagnosticCode._0_expected,
tn.range(tn.pos), "=>"
);
return null;
}
}
var signature = Node.createSignature(
parameters,
returnType,
null, // TODO?
false,
tn.range(signatureStart, tn.pos)
);
var body: Statement | null;
if (isArrow) {
body = this.parseStatement(tn, false);
} else {
if (!tn.skip(Token.OPENBRACE)) {
this.error(
DiagnosticCode._0_expected,
tn.range(tn.pos), "{"
);
return null;
}
body = this.parseBlockStatement(tn, false);
}
if (!body) return null;
var declaration = Node.createFunctionDeclaration(
name,
null,
signature,
body,
null,
isArrow ? CommonFlags.ARROW : CommonFlags.NONE,
tn.range(startPos, tn.pos)
);
return Node.createFunctionExpression(declaration);
}
parseClassOrInterface(
tn: Tokenizer,
flags: CommonFlags,
decorators: DecoratorNode[] | null,
startPos: i32
): ClassDeclaration | null {
// at ('class' | 'interface'):
// Identifier
// ('<' TypeParameters)?
// ('extends' Type)?
// ('implements' Type (',' Type)*)?
// '{' ClassMember* '}'
var isInterface = tn.token == Token.INTERFACE;
if (!tn.skipIdentifier()) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
return null;
}
var identifier = Node.createIdentifierExpression(
tn.readIdentifier(),
tn.range()
);
var typeParameters: TypeParameterNode[] | null = null;
if (tn.skip(Token.LESSTHAN)) {
typeParameters = this.parseTypeParameters(tn);
if (!typeParameters) return null;
flags |= CommonFlags.GENERIC;
}
var extendsType: TypeNode | null = null;
if (tn.skip(Token.EXTENDS)) {
let t = this.parseType(tn);
if (!t) return null;
if (t.kind != NodeKind.TYPE) {
this.error(
DiagnosticCode.Operation_not_supported,
t.range
);
return null;
}
extendsType = <TypeNode>t;
}
var implementsTypes: TypeNode[] | null = null;
if (tn.skip(Token.IMPLEMENTS)) {
if (isInterface) {
this.error(
DiagnosticCode.Interface_declaration_cannot_have_implements_clause,
tn.range()
); // recoverable
}
do {
let type = this.parseType(tn);
if (!type) return null;
if (!isInterface) {
if (!implementsTypes) implementsTypes = [];
implementsTypes.push(<TypeNode>type);
}
} while (tn.skip(Token.COMMA));
}
if (!tn.skip(Token.OPENBRACE)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "{"
);
return null;
}
var members = new Array<DeclarationStatement>();
var declaration: ClassDeclaration;
if (isInterface) {
assert(!implementsTypes);
declaration = Node.createInterfaceDeclaration(
identifier,
typeParameters,
extendsType,
members,
decorators,
flags,
tn.range(startPos, tn.pos)
);
} else {
declaration = Node.createClassDeclaration(
identifier,
typeParameters,
extendsType,
implementsTypes,
members,
decorators,
flags,
tn.range(startPos, tn.pos)
);
}
if (!tn.skip(Token.CLOSEBRACE)) {
do {
let member = this.parseClassMember(tn, declaration);
if (member) members.push(<DeclarationStatement>member);
} while (!tn.skip(Token.CLOSEBRACE));
}
return declaration;
}
parseClassExpression(tn: Tokenizer): ClassExpression | null {
// at 'class': Identifier? '{' ... '}'
var startPos = tn.tokenPos;
var name: IdentifierExpression;
if (tn.skipIdentifier()) {
name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
} else {
name = Node.createEmptyIdentifierExpression(tn.range(tn.pos));
}
if (!tn.skip(Token.OPENBRACE)) {
this.error(
DiagnosticCode._0_expected,
tn.range(tn.pos), "{"
);
return null;
}
var members = new Array<DeclarationStatement>();
var declaration = Node.createClassDeclaration(
name,
[],
null,
null,
members,
null,
CommonFlags.NONE,
tn.range(startPos, tn.pos)
);
if (!tn.skip(Token.CLOSEBRACE)) {
do {
let member = this.parseClassMember(tn, declaration);
if (member) members.push(<DeclarationStatement>member);
} while (!tn.skip(Token.CLOSEBRACE));
}
return Node.createClassExpression(declaration);
}
parseClassMember(
tn: Tokenizer,
parent: ClassDeclaration
): DeclarationStatement | null {
// before:
// ('public' | 'private' | 'protected')?
// ('static' | 'abstract')?
// 'readonly'?
// ('get' | 'set')?
// Identifier ...
var startPos = tn.pos;
var isInterface = parent.kind == NodeKind.INTERFACEDECLARATION;
var decorators = new Array<DecoratorNode>();
if (tn.skip(Token.AT)) {
do {
let decorator = this.parseDecorator(tn);
if (!decorator) break;
decorators.push(<DecoratorNode>decorator);
} while (tn.skip(Token.AT));
if (isInterface) {
this.error(
DiagnosticCode.Decorators_are_not_valid_here,
Range.join(decorators[0].range, decorators[decorators.length - 1].range)
);
}
}
// inherit ambient status
var flags = parent.flags & CommonFlags.AMBIENT;
// implemented methods are virtual
if (isInterface) flags |= CommonFlags.VIRTUAL;
var accessStart = 0;
var accessEnd = 0;
if (tn.skip(Token.PUBLIC)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "public"
);
}
flags |= CommonFlags.PUBLIC;
accessStart = tn.tokenPos;
accessEnd = tn.pos;
} else if (tn.skip(Token.PRIVATE)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "private"
);
}
flags |= CommonFlags.PRIVATE;
accessStart = tn.tokenPos;
accessEnd = tn.pos;
} else if (tn.skip(Token.PROTECTED)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "protected"
);
}
flags |= CommonFlags.PROTECTED;
accessStart = tn.tokenPos;
accessEnd = tn.pos;
}
var staticStart = 0;
var staticEnd = 0;
var abstractStart = 0;
var abstractEnd = 0;
if (tn.skip(Token.STATIC)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "static"
);
}
flags |= CommonFlags.STATIC;
staticStart = tn.tokenPos;
staticEnd = tn.pos;
} else {
flags |= CommonFlags.INSTANCE;
if (tn.skip(Token.ABSTRACT)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "abstract"
);
}
flags |= CommonFlags.ABSTRACT;
abstractStart = tn.tokenPos;
abstractEnd = tn.pos;
}
if (parent.flags & CommonFlags.GENERIC) flags |= CommonFlags.GENERIC_CONTEXT;
}
var readonlyStart: i32 = 0;
var readonlyEnd: i32 = 0;
if (tn.skip(Token.READONLY)) {
flags |= CommonFlags.READONLY;
readonlyStart = tn.tokenPos;
readonlyEnd = tn.pos;
}
// check if accessor: ('get' | 'set') ^\n Identifier
var state = tn.mark();
var isConstructor = false;
var isGetter = false;
var getStart: i32 = 0;
var getEnd: i32 = 0;
var isSetter = false;
var setStart: i32 = 0;
var setEnd: i32 = 0;
if (!isInterface) {
if (tn.skip(Token.GET)) {
if (tn.peek(true, IdentifierHandling.PREFER) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) {
flags |= CommonFlags.GET;
isGetter = true;
setStart = tn.tokenPos;
setEnd = tn.pos;
if (flags & CommonFlags.READONLY) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(readonlyStart, readonlyEnd), "readonly"
); // recoverable
}
} else {
tn.reset(state);
}
} else if (tn.skip(Token.SET)) {
if (tn.peek(true, IdentifierHandling.PREFER) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) {
flags |= CommonFlags.SET;
isSetter = true;
setStart = tn.tokenPos;
setEnd = tn.pos;
if (flags & CommonFlags.READONLY) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(readonlyStart, readonlyEnd), "readonly"
); // recoverable
}
} else {
tn.reset(state);
}
} else if (tn.skip(Token.CONSTRUCTOR)) {
flags |= CommonFlags.CONSTRUCTOR;
isConstructor = true;
if (flags & CommonFlags.STATIC) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(staticStart, staticEnd), "static"
); // recoverable
}
if (flags & CommonFlags.ABSTRACT) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(abstractStart, abstractEnd), "abstract"
); // recoverable
}
if (flags & CommonFlags.READONLY) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(readonlyStart, readonlyEnd), "readonly"
); // recoverable
}
}
}
var name: IdentifierExpression;
if (isConstructor) {
name = Node.createConstructorExpression(tn.range());
} else {
if (!(isGetter || isSetter) && tn.skip(Token.OPENBRACKET)) {
// TODO: also handle symbols, which might have some of these modifiers
if (flags & CommonFlags.PUBLIC) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(accessStart, accessEnd), "public"
); // recoverable
} else if (flags & CommonFlags.PROTECTED) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(accessStart, accessEnd), "protected"
); // recoverable
} else if (flags & CommonFlags.PRIVATE) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(accessStart, accessEnd), "protected"
); // recoverable
}
if (flags & CommonFlags.STATIC) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(staticStart, staticEnd), "static"
); // recoverable
}
if (flags & CommonFlags.ABSTRACT) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(abstractStart, abstractEnd), "abstract"
); // recoverable
}
if (flags & CommonFlags.READONLY) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(readonlyStart, readonlyEnd), "readonly"
); // recoverable
}
let retIndex = this.parseIndexSignatureDeclaration(tn, decorators);
if (!retIndex) return null;
tn.skip(Token.SEMICOLON);
return retIndex;
}
if (!tn.skipIdentifier()) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
return null;
}
name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
}
var typeParameters: TypeParameterNode[] | null = null;
if (tn.skip(Token.LESSTHAN)) {
let typeParametersStart = tn.tokenPos;
typeParameters = this.parseTypeParameters(tn);
if (!typeParameters) return null;
if (isConstructor) {
this.error(
DiagnosticCode.Type_parameters_cannot_appear_on_a_constructor_declaration,
tn.range(typeParametersStart, tn.pos)
); // recoverable
} else if (isGetter || isSetter) {
this.error(
DiagnosticCode.An_accessor_cannot_have_type_parameters,
tn.range(typeParametersStart, tn.pos)
); // recoverable
} else {
flags |= CommonFlags.GENERIC;
}
}
// method: '(' Parameters (':' Type)? '{' Statement* '}' ';'?
if (tn.skip(Token.OPENPAREN)) {
let signatureStart = tn.tokenPos;
let parameters = this.parseParameters(tn, isConstructor);
if (!parameters) return null;
let thisType = this.parseParametersThis;
if (isConstructor) {
for (let i = 0, k = parameters.length; i < k; ++i) {
let parameter = parameters[i];
if (parameter.isAny(
CommonFlags.PUBLIC |
CommonFlags.PROTECTED |
CommonFlags.PRIVATE |
CommonFlags.READONLY
)) {
let implicitFieldDeclaration = Node.createFieldDeclaration(
parameter.name,
parameter.type,
null, // initialized via parameter
null,
parameter.flags | CommonFlags.INSTANCE,
parameter.range
);
implicitFieldDeclaration.parameterIndex = i;
parameter.implicitFieldDeclaration = implicitFieldDeclaration;
parent.members.push(implicitFieldDeclaration);
}
}
} else if (isGetter) {
if (parameters.length) {
this.error(
DiagnosticCode.A_get_accessor_cannot_have_parameters,
name.range
);
}
} else if (isSetter) {
if (parameters.length != 1) {
this.error(
DiagnosticCode.A_set_accessor_must_have_exactly_one_parameter,
name.range
);
}
if (parameters.length && parameters[0].initializer) {
this.error(
DiagnosticCode.A_set_accessor_parameter_cannot_have_an_initializer,
name.range
);
}
}
let returnType: CommonTypeNode | null = null;
if (tn.skip(Token.COLON)) {
if (name.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, name.kind == NodeKind.CONSTRUCTOR || isSetter);
if (!returnType) return null;
} else {
returnType = Node.createOmittedType(tn.range(tn.pos));
if (!isSetter && name.kind != NodeKind.CONSTRUCTOR) {
this.error(
DiagnosticCode.Type_expected,
returnType.range
); // recoverable
}
}
let signature = Node.createSignature(
parameters,
returnType,
thisType,
false,
tn.range(signatureStart, tn.pos)
);
let body: Statement | null = null;
if (tn.skip(Token.OPENBRACE)) {
if (flags & CommonFlags.AMBIENT) {
this.error(
DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts,
tn.range()
); // recoverable
} else if (flags & CommonFlags.ABSTRACT) {
this.error(
DiagnosticCode.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract,
tn.range(), name.text
); // recoverable
}
body = this.parseBlockStatement(tn, false);
if (!body) return null;
} else if (!(flags & CommonFlags.AMBIENT) && !isInterface) {
this.error(
DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration,
tn.range()
); // recoverable
}
let retMethod = Node.createMethodDeclaration(
name,
typeParameters,
signature,
body,
decorators,
flags,
tn.range(startPos, tn.pos)
);
tn.skip(Token.SEMICOLON);
return retMethod;
} else if (isConstructor) {
this.error(
DiagnosticCode.Constructor_implementation_is_missing,
name.range
);
} else if (isGetter || isSetter) {
this.error(
DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration,
name.range
);
// field: (':' Type)? ('=' Expression)? ';'?
} else {
if (flags & CommonFlags.ABSTRACT) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(abstractStart, abstractEnd), "abstract"
); // recoverable
}
if (flags & CommonFlags.GET) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(getStart, getEnd), "get"
); // recoverable
}
if (flags & CommonFlags.SET) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(setStart, setEnd), "set"
); // recoverable
}
let type: CommonTypeNode | null = null;
if (tn.skip(Token.QUESTION)) {
this.error(
DiagnosticCode.Optional_properties_are_not_supported,
tn.range(startPos, tn.pos)
);
}
if (tn.skip(Token.EXCLAMATION)) {
flags |= CommonFlags.DEFINITE_ASSIGNMENT;
}
if (tn.skip(Token.COLON)) {
type = this.parseType(tn);
if (!type) return null;
} else {
this.error(
DiagnosticCode.Type_expected,
tn.range()
); // recoverable
}
let initializer: Expression | null = null;
if (tn.skip(Token.EQUALS)) {
initializer = this.parseExpression(tn);
if (!initializer) return null;
}
let range = tn.range(startPos, tn.pos);
if ((flags & CommonFlags.DEFINITE_ASSIGNMENT) && ((flags & CommonFlags.STATIC) || isInterface || initializer)) {
this.error(
DiagnosticCode.A_definite_assignment_assertion_is_not_permitted_in_this_context,
range
);
}
let retField = Node.createFieldDeclaration(
name,
type,
initializer,
decorators,
flags,
range
);
tn.skip(Token.SEMICOLON);
return retField;
}
return null;
}
parseIndexSignatureDeclaration(tn: Tokenizer, decorators: DecoratorNode[]): IndexSignatureDeclaration | null {
// at: '[': 'key' ':' Type ']' ':' Type
if (decorators.length) {
this.error(
DiagnosticCode.Decorators_are_not_valid_here,
Range.join(decorators[0].range, decorators[decorators.length - 1].range)
); // recoverable
}
var start = tn.tokenPos;
if (tn.skipIdentifier()) {
let id = tn.readIdentifier();
if (id == "key") {
if (tn.skip(Token.COLON)) {
let keyType = this.parseType(tn);
if (!keyType) return null;
if (keyType.kind != NodeKind.TYPE) {
this.error(
DiagnosticCode.Type_expected,
tn.range()
);
return null;
}
if (tn.skip(Token.CLOSEBRACKET)) {
if (tn.skip(Token.COLON)) {
let valueType = this.parseType(tn);
if (!valueType) return null;
return Node.createIndexSignatureDeclaration(<TypeNode>keyType, valueType, tn.range(start, 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(), ":"
);
}
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "key"
);
}
} else {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
}
return null;
}
parseNamespace(
tn: Tokenizer,
flags: CommonFlags,
decorators: DecoratorNode[] | null,
startPos: i32
): NamespaceDeclaration | null {
// at 'namespace': Identifier '{' (Variable | Function)* '}'
if (tn.skipIdentifier()) {
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
if (tn.skip(Token.OPENBRACE)) {
let members = new Array<Statement>();
let ns = Node.createNamespaceDeclaration(
identifier,
members,
decorators,
flags,
tn.range(startPos, tn.pos)
);
while (!tn.skip(Token.CLOSEBRACE)) {
let member = this.parseTopLevelStatement(tn, ns);
if (member) members.push(member);
}
tn.skip(Token.SEMICOLON);
return ns;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "{"
);
}
} else {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
}
return null;
}
parseExport(
tn: Tokenizer,
startPos: i32,
isDeclare: bool
): ExportStatement | null {
// at 'export': '{' ExportMember (',' ExportMember)* }' ('from' StringLiteral)? ';'?
var path: StringLiteralExpression | null = null;
if (tn.skip(Token.OPENBRACE)) {
let members = new Array<ExportMember>();
while (!tn.skip(Token.CLOSEBRACE)) {
let member = this.parseExportMember(tn);
if (!member) return null;
members.push(member);
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEBRACE)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
return 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;
}
}
let ret = Node.createExportStatement(members, path, isDeclare, tn.range(startPos, tn.pos));
let internalPath = ret.internalPath;
if (internalPath !== null && !this.seenlog.has(internalPath)) {
this.backlog.push(internalPath);
this.seenlog.add(internalPath);
}
tn.skip(Token.SEMICOLON);
return ret;
} else if (tn.skip(Token.ASTERISK)) {
if (tn.skip(Token.FROM)) {
if (tn.skip(Token.STRINGLITERAL)) {
path = Node.createStringLiteralExpression(tn.readString(), tn.range());
let ret = Node.createExportStatement(null, path, isDeclare, tn.range(startPos, tn.pos));
let internalPath = assert(ret.internalPath);
let source = tn.source;
if (!source.exportPaths) source.exportPaths = new Set();
source.exportPaths.add(internalPath);
if (!this.seenlog.has(internalPath)) {
this.backlog.push(internalPath);
this.seenlog.add(internalPath);
}
tn.skip(Token.SEMICOLON);
return ret;
} else {
this.error(
DiagnosticCode.String_literal_expected,
tn.range()
);
}
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "from"
);
}
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "{"
);
}
return null;
}
parseExportMember(
tn: Tokenizer
): ExportMember | null {
// before: Identifier ('as' Identifier)?
if (tn.skipIdentifier()) {
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
let asIdentifier: IdentifierExpression | null = null;
if (tn.skip(Token.AS)) {
if (tn.skipIdentifier(IdentifierHandling.ALWAYS)) {
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();
while (!tn.skip(Token.CLOSEBRACE)) {
let member = this.parseImportDeclaration(tn);
if (!member) return null;
members.push(member);
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEBRACE)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
return null;
}
}
}
} else if (tn.skip(Token.ASTERISK)) {
if (tn.skip(Token.AS)) {
if (tn.skipIdentifier()) {
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)) {
let path = Node.createStringLiteralExpression(tn.readString(), tn.range());
let 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));
}
let internalPath = ret.internalPath;
if (!this.seenlog.has(internalPath)) {
this.backlog.push(internalPath);
this.seenlog.add(internalPath);
}
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 {
// before: Identifier ('as' Identifier)?
if (tn.skipIdentifier(IdentifierHandling.ALWAYS)) {
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
let asIdentifier: IdentifierExpression | null = null;
if (tn.skip(Token.AS)) {
if (tn.skipIdentifier()) {
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,
startPos: i32
): ExportImportStatement | null {
// at 'export' 'import': Identifier ('=' Identifier)? ';'?
if (tn.skipIdentifier()) {
let asIdentifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
if (tn.skip(Token.EQUALS)) {
if (tn.skipIdentifier()) {
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
let ret = Node.createExportImportStatement(identifier, asIdentifier, tn.range(startPos, tn.pos));
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
var state = tn.mark();
var token = tn.next();
var statement: Statement | null = null;
switch (token) {
case Token.BREAK: {
statement = this.parseBreak(tn);
break;
}
case Token.CONST: {
statement = this.parseVariable(tn, CommonFlags.CONST, null, tn.tokenPos);
break;
}
case Token.CONTINUE: {
statement = this.parseContinue(tn);
break;
}
case Token.DO: {
statement = this.parseDoStatement(tn);
break;
}
case Token.FOR: {
statement = this.parseForStatement(tn);
break;
}
case Token.IF: {
statement = this.parseIfStatement(tn);
break;
}
case Token.LET: {
statement = this.parseVariable(tn, CommonFlags.LET, null, tn.tokenPos);
break;
}
case Token.VAR: {
statement = this.parseVariable(tn, CommonFlags.NONE, null, tn.tokenPos);
break;
}
case Token.OPENBRACE: {
statement = this.parseBlockStatement(tn, topLevel);
break;
}
case Token.RETURN: {
if (topLevel) {
this.error(
DiagnosticCode.A_return_statement_can_only_be_used_within_a_function_body,
tn.range()
); // recoverable
}
statement = this.parseReturn(tn);
break;
}
case Token.SEMICOLON: {
return Node.createEmptyStatement(tn.range(tn.tokenPos));
}
case Token.SWITCH: {
statement = this.parseSwitchStatement(tn);
break;
}
case Token.THROW: {
statement = this.parseThrowStatement(tn);
break;
}
case Token.TRY: {
statement = this.parseTryStatement(tn);
break;
}
case Token.VOID: {
statement = this.parseVoidStatement(tn);
break;
}
case Token.WHILE: {
statement = this.parseWhileStatement(tn);
break;
}
case Token.TYPE: { // also identifier
if (tn.peek(false, IdentifierHandling.PREFER) == Token.IDENTIFIER) {
statement = this.parseTypeDeclaration(tn, CommonFlags.NONE, null, tn.tokenPos);
break;
}
// fall-through
}
default: {
tn.reset(state);
statement = this.parseExpressionStatement(tn);
break;
}
}
if (!statement) { // has been reported
tn.reset(state);
this.skipStatement(tn);
} else {
tn.discard(state);
}
return statement;
}
parseBlockStatement(
tn: Tokenizer,
topLevel: bool
): BlockStatement | null {
// at '{': Statement* '}' ';'?
var startPos = tn.tokenPos;
var statements = new Array<Statement>();
while (!tn.skip(Token.CLOSEBRACE)) {
let state = tn.mark();
let statement = this.parseStatement(tn, topLevel);
if (!statement) {
if (tn.token == Token.ENDOFFILE) return null;
tn.reset(state);
this.skipStatement(tn);
} else {
tn.discard(state);
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(IdentifierHandling.PREFER);
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(IdentifierHandling.PREFER);
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)) {
let condition = this.parseExpression(tn);
if (!condition) return null;
if (tn.skip(Token.CLOSEPAREN)) {
let ret = Node.createDoStatement(<Statement>statement, <Expression>condition, 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(), "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)) {
let initializer: Statement | null = null;
if (tn.skip(Token.CONST)) {
initializer = this.parseVariable(tn, CommonFlags.CONST, null, tn.tokenPos);
} else if (tn.skip(Token.LET)) {
initializer = this.parseVariable(tn, CommonFlags.LET, null, tn.tokenPos);
} else if (tn.skip(Token.VAR)) {
initializer = this.parseVariable(tn, CommonFlags.NONE, null, tn.tokenPos);
} else if (!tn.skip(Token.SEMICOLON)) {
initializer = this.parseExpressionStatement(tn);
if (!initializer) return null;
}
if (tn.token == Token.SEMICOLON) {
let condition: ExpressionStatement | null = null;
if (!tn.skip(Token.SEMICOLON)) {
condition = this.parseExpressionStatement(tn);
if (!condition) return null;
}
if (tn.token == Token.SEMICOLON) {
let 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;
}
}
let 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)) {
let condition = this.parseExpression(tn);
if (!condition) return null;
if (tn.skip(Token.CLOSEPAREN)) {
let statement = this.parseStatement(tn);
if (!statement) return null;
let 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)
);
} else {
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)) {
let condition = this.parseExpression(tn);
if (!condition) return null;
if (tn.skip(Token.CLOSEPAREN)) {
if (tn.skip(Token.OPENBRACE)) {
let cases = new Array<SwitchCase>();
while (!tn.skip(Token.CLOSEBRACE)) {
let case_ = this.parseSwitchCase(tn);
if (!case_) return null;
cases.push(<SwitchCase>case_);
}
let 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)) {
let 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);
}
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<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)) {
let statements = new Array<Statement>();
while (!tn.skip(Token.CLOSEBRACE)) {
stmt = this.parseStatement(tn);
if (!stmt) return null;
statements.push(<Statement>stmt);
}
let catchVariable: IdentifierExpression | null = null;
let catchStatements: Statement[] | null = null;
let 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.skipIdentifier()) {
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 = [];
while (!tn.skip(Token.CLOSEBRACE)) {
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 = [];
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;
}
let 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,
flags: CommonFlags,
decorators: DecoratorNode[] | null,
startPos: i32
): TypeDeclaration | null {
// at 'type': Identifier ('<' TypeParameters '>')? '=' Type ';'?
if (tn.skipIdentifier()) {
let name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
let typeParameters: TypeParameterNode[] | null = null;
if (tn.skip(Token.LESSTHAN)) {
typeParameters = this.parseTypeParameters(tn);
if (!typeParameters) return null;
flags |= CommonFlags.GENERIC;
}
if (tn.skip(Token.EQUALS)) {
let type = this.parseType(tn);
if (!type) return null;
let ret = Node.createTypeDeclaration(
name,
typeParameters,
type,
decorators,
flags,
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;
}
parseVoidStatement(
tn: Tokenizer
): VoidStatement | null {
// at 'void': Expression ';'?
var startPos = tn.tokenPos;
var expression = this.parseExpression(tn, Precedence.GROUPING);
if (!expression) return null;
var ret = Node.createVoidStatement(expression, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
return ret;
}
parseWhileStatement(
tn: Tokenizer
): WhileStatement | null {
// at 'while': '(' Expression ')' Statement ';'?
var startPos = tn.tokenPos;
if (tn.skip(Token.OPENPAREN)) {
let expression = this.parseExpression(tn);
if (!expression) return null;
if (tn.skip(Token.CLOSEPAREN)) {
let statement = this.parseStatement(tn);
if (!statement) return null;
let 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(IdentifierHandling.PREFER);
var startPos = tn.tokenPos;
var precedence = determinePrecedenceStart(token);
if (precedence != Precedence.NONE) {
let 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)
);
} else {
this.error(
DiagnosticCode.Operation_not_supported,
tn.range()
);
}
return null;
} else {
operand = this.parseExpression(tn, precedence);
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));
}
var expr: Expression | null = null;
switch (token) {
case Token.NULL: return Node.createNullExpression(tn.range());
case Token.TRUE: return Node.createTrueExpression(tn.range());
case Token.FALSE: return Node.createFalseExpression(tn.range());
// ParenthesizedExpression
// FunctionExpression
case Token.OPENPAREN: {
// determine whether this is a function expression
if (tn.skip(Token.CLOSEPAREN)) { // must be a function expression (fast route)
return this.parseFunctionExpressionCommon(
tn,
Node.createEmptyIdentifierExpression(tn.range(startPos)),
[],
true
);
}
let state = tn.mark();
let again = true;
do {
switch (tn.next(IdentifierHandling.PREFER)) {
// function expression
case Token.DOT_DOT_DOT: {
tn.reset(state);
return this.parseFunctionExpression(tn);
}
// can be both
case Token.IDENTIFIER: {
tn.readIdentifier();
switch (tn.next()) {
// if we got here, check for arrow
case Token.CLOSEPAREN: {
if (
!tn.skip(Token.COLON) &&
!tn.skip(Token.EQUALS_GREATERTHAN)
) {
again = false;
break;
}
// fall-through
}
// function expression
case Token.COLON: { // type annotation
tn.reset(state);
return this.parseFunctionExpression(tn);
}
// optional parameter or parenthesized
case Token.QUESTION: {
if (
tn.skip(Token.COLON) || // optional parameter with type
tn.skip(Token.COMMA) || // optional parameter without type
tn.skip(Token.CLOSEPAREN) // last optional parameter without type
) {
tn.reset(state);
return this.parseFunctionExpression(tn);
}
again = false; // parenthesized
break;
}
case Token.COMMA: {
break; // continue
}
// parenthesized expression
// case Token.EQUALS: // missing type annotation for simplicity
default: {
again = false;
break;
}
}
break;
}
// parenthesized expression
default: {
again = false;
break;
}
}
} while (again);
tn.reset(state);
// parse parenthesized
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: {
let elementExpressions = new Array<Expression | null>();
while (!tn.skip(Token.CLOSEBRACKET)) {
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.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEBRACKET)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "]"
);
return null;
}
}
}
return Node.createArrayLiteralExpression(elementExpressions, tn.range(startPos, tn.pos));
}
// ObjectLiteralExpression
case Token.OPENBRACE: {
let startPos = tn.tokenPos;
let names = new Array<IdentifierExpression>();
let values = new Array<Expression>();
let name: IdentifierExpression;
while (!tn.skip(Token.CLOSEBRACE)) {
if (!tn.skipIdentifier()) {
if (!tn.skip(Token.STRINGLITERAL)) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range(),
);
return null;
}
name = Node.createIdentifierExpression(tn.readString(), tn.range());
name.isQuoted = true;
} else {
name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
}
names.push(name);
if (tn.skip(Token.COLON)) {
let value = this.parseExpression(tn, Precedence.COMMA + 1);
if (!value) return null;
values.push(value);
} else if (!name.isQuoted) {
values.push(name);
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ":"
);
return null;
}
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEBRACE)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
return null;
}
}
}
return Node.createObjectLiteralExpression(names, values, tn.range(startPos, tn.pos));
}
// AssertionExpression (unary prefix)
case Token.LESSTHAN: {
let 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)
);
}
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: {
if (tn.peek() != Token.DOT && tn.nextToken != Token.OPENPAREN) {
this.error(
DiagnosticCode._super_must_be_followed_by_an_argument_list_or_member_access,
tn.range()
);
}
return Node.createSuperExpression(tn.range(startPos, tn.pos));
}
case Token.STRINGLITERAL: {
return Node.createStringLiteralExpression(tn.readString(), tn.range(startPos, tn.pos));
}
case Token.INTEGERLITERAL: {
return Node.createIntegerLiteralExpression(tn.readInteger(), tn.range(startPos, tn.pos));
}
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: {
let 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)
);
}
case Token.FUNCTION: {
return this.parseFunctionExpression(tn);
}
case Token.CLASS: {
return this.parseClassExpression(tn);
}
default: {
if (token == Token.ENDOFFILE) {
this.error(
DiagnosticCode.Unexpected_end_of_text,
tn.range(startPos)
);
} else {
this.error(
DiagnosticCode.Expression_expected,
tn.range()
);
}
return null;
}
}
}
tryParseTypeArgumentsBeforeArguments(
tn: Tokenizer
): CommonTypeNode[] | null {
// at '<': Type (',' Type)* '>' '('
var state = tn.mark();
if (!tn.skip(Token.LESSTHAN)) return null;
var typeArguments = new Array<CommonTypeNode>();
do {
if (tn.peek() === Token.GREATERTHAN) {
break;
}
let type = this.parseType(tn, true, true);
if (!type) {
tn.reset(state);
return null;
}
typeArguments.push(type);
} while (tn.skip(Token.COMMA));
if (tn.skip(Token.GREATERTHAN) && tn.skip(Token.OPENPAREN)) {
return typeArguments;
}
tn.reset(state);
return null;
}
parseArguments(
tn: Tokenizer
): Expression[] | null {
// at '(': (Expression (',' Expression)*)? ')'
var args = new Array<Expression>();
while (!tn.skip(Token.CLOSEPAREN)) {
let expr = this.parseExpression(tn, Precedence.COMMA + 1);
if (!expr) return null;
args.push(expr);
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEPAREN)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
return null;
}
}
}
return args;
}
parseExpression(
tn: Tokenizer,
precedence: Precedence = Precedence.COMMA
): Expression | null {
assert(precedence != Precedence.NONE);
var expr = this.parseExpressionStart(tn);
if (!expr) return null;
expr = this.maybeParseCallExpression(tn, expr); // simple call like on an Identifier
var startPos = expr.range.start;
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: {
let toType = this.parseType(tn); // reports
if (!toType) return null;
expr = Node.createAssertionExpression(
AssertionKind.AS,
expr,
toType,
tn.range(startPos, tn.pos)
);
break;
}
case Token.EXCLAMATION: {
expr = Node.createAssertionExpression(
AssertionKind.NONNULL,
expr,
null,
tn.range(startPos, tn.pos)
);
break;
}
// InstanceOfExpression
case Token.INSTANCEOF: {
let isType = this.parseType(tn); // reports
if (!isType) return null;
expr = Node.createInstanceOfExpression(
expr,
isType,
tn.range(startPos, tn.pos)
);
break;
}
// ElementAccessExpression
case Token.OPENBRACKET: {
next = this.parseExpression(tn); // reports
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: {
let ifThen = this.parseExpression(tn);
if (!ifThen) return null;
if (!tn.skip(Token.COLON)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ":"
);
return null;
}
let ifElse = this.parseExpression(tn, precedence > Precedence.COMMA
? Precedence.COMMA + 1
: Precedence.COMMA
);
if (!ifElse) return null;
expr = Node.createTernaryExpression(
expr,
ifThen,
ifElse,
tn.range(startPos, tn.pos)
);
break;
}
// CommaExpression
case Token.COMMA: {
let commaExprs: 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));
break;
}
default: {
// PropertyAccessExpression
if (token == Token.DOT) {
if (tn.skipIdentifier()) {
next = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
} else {
next = this.parseExpression(tn,
isRightAssociative(token)
? nextPrecedence
: nextPrecedence + 1
);
if (!next) return null;
}
if (next.kind == NodeKind.IDENTIFIER) { // expr '.' Identifier
expr = Node.createPropertyAccessExpression(
expr,
<IdentifierExpression>next,
tn.range(startPos, tn.pos)
);
} else if (next.kind == NodeKind.CALL) { // expr '.' CallExpression
expr = this.joinPropertyCall(tn, startPos, expr, <CallExpression>next);
if (!expr) return null;
} else {
this.error(
DiagnosticCode.Identifier_expected,
next.range
);
return null;
}
// BinaryExpression
} else {
next = this.parseExpression(tn,
isRightAssociative(token)
? nextPrecedence
: nextPrecedence + 1
);
if (!next) return null;
expr = Node.createBinaryExpression(token, expr, next, tn.range(startPos, tn.pos));
}
break;
}
}
expr = this.maybeParseCallExpression(tn, expr); // compound call like on an ElementAccess
}
return expr;
}
private joinPropertyCall(
tn: Tokenizer,
startPos: i32,
expr: Expression,
call: CallExpression
): Expression | null {
var callee = call.expression;
switch (callee.kind) {
case NodeKind.IDENTIFIER: { // join property access and use as call target
call.expression = Node.createPropertyAccessExpression(
expr,
<IdentifierExpression>callee,
tn.range(startPos, tn.pos)
);
break;
}
case NodeKind.CALL: { // join call target und wrap the original call around it
let inner = this.joinPropertyCall(tn, startPos, expr, <CallExpression>callee);
if (!inner) return null;
call.expression = inner;
call.range = tn.range(startPos, tn.pos);
break;
}
default: {
this.error(
DiagnosticCode.Identifier_expected,
call.range
);
return null;
}
}
return call;
}
private maybeParseCallExpression(
tn: Tokenizer,
expr: Expression
): Expression {
if (nodeIsCallable(expr.kind)) {
let typeArguments: CommonTypeNode[] | null = null;
while (
tn.skip(Token.OPENPAREN)
||
nodeIsGenericCallable(expr.kind) && (typeArguments = this.tryParseTypeArgumentsBeforeArguments(tn)) !== null
) {
let args = this.parseArguments(tn);
if (!args) break;
expr = Node.createCallExpression( // is again callable
expr,
typeArguments,
args,
tn.range(expr.range.start, tn.pos)
);
}
}
return expr;
}
/** Skips over a statement on errors in an attempt to reduce unnecessary diagnostic noise. */
skipStatement(tn: Tokenizer): void {
tn.peek(true);
if (tn.nextTokenOnNewLine) tn.next(); // if reset() to the previous line
do {
let nextToken = tn.peek(true);
if (
nextToken == Token.ENDOFFILE || // next step should handle this
nextToken == Token.SEMICOLON // end of the statement for sure
) {
tn.next();
break;
}
if (tn.nextTokenOnNewLine) break; // end of the statement maybe
switch (tn.next()) {
case Token.IDENTIFIER: {
tn.readIdentifier();
break;
}
case Token.STRINGLITERAL: {
tn.readString();
break;
}
case Token.INTEGERLITERAL: {
tn.readInteger();
break;
}
case Token.FLOATLITERAL: {
tn.readFloat();
break;
}
}
} while (true);
}
/** Skips over a block on errors in an attempt to reduce unnecessary diagnostic noise. */
skipBlock(tn: Tokenizer): void {
// at '{': ... '}'
var depth = 1;
var again = true;
do {
switch (tn.next()) {
case Token.ENDOFFILE: {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
again = false;
break;
}
case Token.OPENBRACE: {
++depth;
break;
}
case Token.CLOSEBRACE: {
--depth;
if (!depth) again = false;
break;
}
case Token.IDENTIFIER: {
tn.readIdentifier();
break;
}
case Token.STRINGLITERAL: {
tn.readString();
break;
}
case Token.INTEGERLITERAL: {
tn.readInteger();
break;
}
case Token.FLOATLITERAL: {
tn.readFloat();
break;
}
}
} while (again);
}
}
/** Operator precedence from least to largest. */
export const enum Precedence {
NONE,
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
}
/** Determines the precedence of a starting token. */
function determinePrecedenceStart(kind: Token): Precedence {
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;
}
return Precedence.NONE;
}
/** Determines the precende of a non-starting token. */
function determinePrecedence(kind: Token): Precedence {
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:
case Token.EXCLAMATION: return Precedence.MEMBERACCESS;
}
return Precedence.NONE;
}
/** 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;
}
}