/**
* @fileoverview A TypeScript parser for the AssemblyScript subset.
*
* Takes the tokens produced by the `Tokenizer` and builds an abstract
* syntax tree composed of `Node`s wrapped in a `Source` out of it.
*
* @license Apache-2.0
*/
import {
CommonFlags,
LIBRARY_PREFIX,
PATH_DELIMITER
} from "./common";
import {
Tokenizer,
Token,
CommentHandler,
IdentifierHandling,
isIllegalVariableIdentifier
} from "./tokenizer";
import {
Range,
DiagnosticCode,
DiagnosticEmitter,
DiagnosticMessage
} from "./diagnostics";
import {
CharCode,
normalizePath
} from "./util";
import {
Node,
NodeKind,
Source,
SourceKind,
TypeNode,
TypeName,
NamedTypeNode,
FunctionTypeNode,
ArrowKind,
Expression,
AssertionKind,
CallExpression,
ClassExpression,
FunctionExpression,
IdentifierExpression,
StringLiteralExpression,
Statement,
BlockStatement,
BreakStatement,
ClassDeclaration,
ContinueStatement,
DeclarationStatement,
DecoratorNode,
DoStatement,
EnumDeclaration,
EnumValueDeclaration,
ExportImportStatement,
ExportMember,
ExportStatement,
ExpressionStatement,
ForOfStatement,
FunctionDeclaration,
IfStatement,
ImportDeclaration,
ImportStatement,
IndexSignatureNode,
NamespaceDeclaration,
ParameterNode,
ParameterKind,
ReturnStatement,
SwitchCase,
SwitchStatement,
ThrowStatement,
TryStatement,
TypeDeclaration,
TypeParameterNode,
VariableStatement,
VariableDeclaration,
VoidStatement,
WhileStatement,
ModuleDeclaration,
mangleInternalPath
} from "./ast";
/** Represents a dependee. */
class Dependee {
constructor(
public source: Source,
public reportNode: Node
) {}
}
/** Parser interface. */
export class Parser extends DiagnosticEmitter {
/** Source file names to be requested next. */
backlog: string[] = new Array();
/** Source file names already seen, that is processed or backlogged. */
seenlog: Set = new Set();
/** Source file names already completely processed. */
donelog: Set = new Set();
/** Optional handler to intercept comments while tokenizing. */
onComment: CommentHandler | null = null;
/** Current file being parsed. */
currentSource: Source | null = null;
/** Map of dependees being depended upon by a source, by path. */
dependees: Map = new Map();
/** An array of parsed sources. */
sources: Source[];
/** Current overridden module name. */
currentModuleName: string | null = null;
/** Constructs a new parser. */
constructor(
diagnostics: DiagnosticMessage[] | null = null,
sources: Source[] = []
) {
super(diagnostics);
this.sources = sources;
}
/** Parses a file and adds its definitions to the program. */
parseFile(
/** Source text of the file, or `null` to indicate not found. */
text: string | null,
/** Normalized path of the file. */
path: string,
/** Whether this is an entry file. */
isEntry: bool
): void {
// the frontend gives us paths with file extensions
let normalizedPath = normalizePath(path);
let 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
// check if this is an error
if (text == null) {
let dependees = this.dependees;
let dependee: Dependee | null = null;
if (dependees.has(internalPath)) dependee = assert(dependees.get(internalPath));
this.error(
DiagnosticCode.File_0_not_found,
dependee
? dependee.reportNode.range
: null,
path
);
return;
}
// create the source element
let source = new Source(
isEntry
? SourceKind.UserEntry
: path.startsWith(LIBRARY_PREFIX)
? path.indexOf(PATH_DELIMITER, LIBRARY_PREFIX.length) < 0
? SourceKind.LibraryEntry
: SourceKind.Library
: SourceKind.User,
normalizedPath,
text
);
this.sources.push(source);
this.currentSource = source;
this.currentModuleName = null;
// tokenize and parse
let tn = new Tokenizer(source, this.diagnostics);
tn.onComment = this.onComment;
let statements = source.statements;
while (!tn.skip(Token.EndOfFile)) {
let statement = this.parseTopLevelStatement(tn, null);
if (statement) {
statements.push(statement);
} else {
this.skipStatement(tn);
}
}
}
/** Parses a top-level statement. */
parseTopLevelStatement(
tn: Tokenizer,
namespace: NamespaceDeclaration | null = null
): Statement | null {
let flags = namespace ? namespace.flags & CommonFlags.Ambient : CommonFlags.None;
let startPos = -1;
// check decorators
let 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 = [decorator];
else decorators.push(decorator);
}
// check modifiers
let exportStart = 0;
let exportEnd = 0;
let defaultStart = 0;
let defaultEnd = 0;
if (tn.skip(Token.Export)) {
if (startPos < 0) startPos = tn.tokenPos;
flags |= CommonFlags.Export;
exportStart = tn.tokenPos;
exportEnd = tn.pos;
if (tn.skip(Token.Default)) {
defaultStart = tn.tokenPos;
defaultEnd = tn.pos;
}
}
let declareStart = 0;
let declareEnd = 0;
let contextIsAmbient = namespace != null && namespace.is(CommonFlags.Ambient);
if (tn.skip(Token.Declare)) {
if (contextIsAmbient) {
this.error(
DiagnosticCode.A_declare_modifier_cannot_be_used_in_an_already_ambient_context,
tn.range()
); // recoverable
} else {
if (startPos < 0) startPos = tn.tokenPos;
declareStart = startPos;
declareEnd = tn.pos;
flags |= CommonFlags.Declare | CommonFlags.Ambient;
}
} else if (contextIsAmbient) {
flags |= CommonFlags.Ambient;
}
// parse the statement
let statement: Statement | null = null;
// handle declarations
let 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();
let abstractStart = tn.tokenPos;
let abstractEnd = tn.pos;
if (tn.peekOnNewLine()) {
tn.reset(state);
statement = this.parseStatement(tn, true);
break;
}
let next = tn.peek();
if (next != Token.Class) {
if (next == Token.Interface) {
this.error(
DiagnosticCode._abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration,
tn.range(abstractStart, abstractEnd)
);
}
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(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(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;
}
case Token.Module: { // also identifier
let state = tn.mark();
tn.next();
if (tn.peek() == Token.StringLiteral && !tn.peekOnNewLine()) {
tn.discard(state);
statement = this.parseModuleDeclaration(tn, flags);
} else {
tn.reset(state);
statement = this.parseStatement(tn, true);
}
break;
}
default: {
// handle plain exports
if (flags & CommonFlags.Export) {
if (defaultEnd && tn.skipIdentifier(IdentifierHandling.Prefer)) {
if (declareEnd) {
this.error(
DiagnosticCode.An_export_assignment_cannot_have_modifiers,
tn.range(declareStart, declareEnd)
);
}
statement = this.parseExportDefaultAlias(tn, startPos, defaultStart, defaultEnd);
defaultStart = defaultEnd = 0; // consume
} else {
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
);
}
}
// check if this an `export default` declaration
if (defaultEnd && statement != null) {
switch (statement.kind) {
case NodeKind.EnumDeclaration:
case NodeKind.FunctionDeclaration:
case NodeKind.ClassDeclaration:
case NodeKind.InterfaceDeclaration:
case NodeKind.NamespaceDeclaration: {
return Node.createExportDefaultStatement(statement, tn.range(startPos, tn.pos));
}
default: {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(defaultStart, defaultEnd), "default"
);
}
}
}
return statement;
}
/** Obtains the next file to parse. */
nextFile(): string | null {
let backlog = this.backlog;
return backlog.length ? assert(backlog.shift()) : null;
}
/** Obtains the path of the dependee of the given imported file. */
getDependee(dependent: string): string | null {
let dependees = this.dependees;
if (dependees.has(dependent)) {
let dependee = assert(dependees.get(dependent));
return dependee.source.internalPath;
}
return null;
}
/** Finishes parsing. */
finish(): void {
if (this.backlog.length) throw new Error("backlog is not empty");
this.backlog = [];
this.seenlog.clear();
this.donelog.clear();
this.dependees.clear();
}
// types
/** Parses a type name. */
parseTypeName(
tn: Tokenizer
): TypeName | null {
// at: Identifier ('.' Identifier)*
let first = Node.createSimpleTypeName(tn.readIdentifier(), tn.range());
let current = first;
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;
}
}
return first;
}
/** Parses a type. */
parseType(
tn: Tokenizer,
acceptParenthesized: bool = true,
suppressErrors: bool = false
): TypeNode | null {
// before: Type
// NOTE: this parses our limited subset
let token = tn.next();
let startPos = tn.tokenPos;
let type: TypeNode;
// '(' ...
if (token == Token.OpenParen) {
// '(' FunctionSignature ')'
let isInnerParenthesized = tn.skip(Token.OpenParen);
// FunctionSignature?
let signature = this.tryParseFunctionType(tn);
if (signature) {
if (isInnerParenthesized) {
if (!tn.skip(Token.CloseParen)) {
if (!suppressErrors) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
}
return null;
}
}
type = signature;
} else if (isInnerParenthesized || this.tryParseSignatureIsSignature) {
if (!suppressErrors) {
this.error(
DiagnosticCode.Unexpected_token,
tn.range()
);
}
return null;
// Type (',' Type)* ')'
} else 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 {
if (!suppressErrors) {
this.error(
DiagnosticCode.Unexpected_token,
tn.range()
);
}
return null;
}
// 'void'
} else if (token == Token.Void) {
type = Node.createNamedType(
Node.createSimpleTypeName("void", tn.range()), [], false, tn.range(startPos, tn.pos)
);
// 'this'
} else if (token == Token.This) {
type = Node.createNamedType(
Node.createSimpleTypeName("this", tn.range()), [], false, tn.range(startPos, tn.pos)
);
// 'true'
} else if (token == Token.True || token == Token.False) {
type = Node.createNamedType(
Node.createSimpleTypeName("bool", tn.range()), [], false, tn.range(startPos, tn.pos)
);
// 'null'
} else if (token == Token.Null) {
type = Node.createNamedType(
Node.createSimpleTypeName("null", tn.range()), [], false, tn.range(startPos, tn.pos)
);
// StringLiteral
} else if (token == Token.StringLiteral) {
tn.readString();
type = Node.createNamedType(
Node.createSimpleTypeName("string", tn.range()), [], false, tn.range(startPos, tn.pos)
);
// Identifier
} else if (token == Token.Identifier) {
let name = this.parseTypeName(tn);
if (!name) return null;
let parameters: TypeNode[] | null = null;
// Name
if (tn.skip(Token.LessThan)) {
do {
let parameter = this.parseType(tn, true, suppressErrors);
if (!parameter) return null;
if (!parameters) parameters = [ parameter ];
else parameters.push(parameter);
} while (tn.skip(Token.Comma));
if (!tn.skip(Token.GreaterThan)) {
if (!suppressErrors) {
this.error(
DiagnosticCode._0_expected,
tn.range(tn.pos), ">"
);
}
return null;
}
}
if (!parameters) parameters = [];
type = Node.createNamedType(name, parameters, false, tn.range(startPos, tn.pos));
} else {
if (!suppressErrors) {
this.error(
DiagnosticCode.Type_expected,
tn.range()
);
}
return null;
}
// ... | type
while (tn.skip(Token.Bar)) {
let nextType = this.parseType(tn, false, true);
if (!nextType) return null;
let typeIsNull = type.kind == NodeKind.NamedType && (type).isNull;
let nextTypeIsNull = nextType.kind == NodeKind.NamedType && (nextType).isNull;
if (!typeIsNull && !nextTypeIsNull) {
if (!suppressErrors) {
this.error(
DiagnosticCode.Not_implemented_0, nextType.range, "union types"
);
}
return null;
} else if (nextTypeIsNull) {
type.isNullable = true;
type.range.end = nextType.range.end;
} else if (typeIsNull) {
nextType.range.start = type.range.start;
nextType.isNullable = true;
type = nextType;
} else {
// `null | null` still `null`
type.range.end = nextType.range.end;
}
}
// ... [][]
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.Not_implemented_0,
tn.range(), "union types"
);
}
return null;
}
}
type = Node.createNamedType(
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 type, as used in type declarations. */
tryParseFunctionType(
tn: Tokenizer
): FunctionTypeNode | null {
// at '(': ('...'? Identifier '?'? ':' Type (',' '...'? Identifier '?'? ':' Type)* )? ')' '=>' Type
let state = tn.mark();
let startPos = tn.tokenPos;
let parameters: ParameterNode[] | null = null;
let thisType: NamedTypeNode | null = null;
let isSignature: bool = false;
let firstParamNameNoType: IdentifierExpression | null = null;
let firstParamKind: ParameterKind = ParameterKind.Default;
if (tn.skip(Token.CloseParen)) {
isSignature = true;
tn.discard(state);
parameters = [];
} else {
isSignature = false; // not yet known
do {
let paramStart = -1;
let kind = ParameterKind.Default;
if (tn.skip(Token.Dot_Dot_Dot)) {
paramStart = tn.tokenPos;
isSignature = true;
tn.discard(state);
kind = ParameterKind.Rest;
}
if (tn.skip(Token.This)) {
if (paramStart < 0) paramStart = tn.tokenPos;
if (tn.skip(Token.Colon)) {
isSignature = true;
tn.discard(state);
let type = this.parseType(tn, false);
if (!type) return null;
if (type.kind != NodeKind.NamedType) {
this.error(
DiagnosticCode.Identifier_expected,
type.range
);
this.tryParseSignatureIsSignature = true;
return null;
}
thisType = type;
} else {
tn.reset(state);
this.tryParseSignatureIsSignature = false;
return null;
}
} else if (tn.skipIdentifier()) {
if (paramStart < 0) paramStart = tn.tokenPos;
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 = Node.createParameter(kind, name, type, null, tn.range(paramStart, tn.pos));
if (!parameters) parameters = [ param ];
else parameters.push(param);
} else {
if (!isSignature) {
if (tn.peek() == Token.Comma) {
isSignature = true;
tn.discard(state);
}
}
if (isSignature) {
let param = Node.createParameter(kind, name, Node.createOmittedType(tn.range(tn.pos)), null, tn.range(paramStart, tn.pos));
if (!parameters) parameters = [ param ];
else parameters.push(param);
this.error(
DiagnosticCode.Type_expected,
param.type.range
); // recoverable
} else if (!parameters) {
// on '(' Identifier ^',' we don't yet know whether this is a
// parenthesized or a function type, hence we have to delay the
// respective diagnostic until we know for sure.
firstParamNameNoType = name;
firstParamKind = kind;
}
}
} else {
if (isSignature) {
if (tn.peek() == Token.CloseParen) break; // allow trailing comma
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;
}
}
let returnType: TypeNode | null;
if (tn.skip(Token.Equals_GreaterThan)) {
if (!isSignature) {
isSignature = true;
tn.discard(state);
if (firstParamNameNoType) { // now we know
let param = Node.createParameter(
firstParamKind,
firstParamNameNoType,
Node.createOmittedType(firstParamNameNoType.range.atEnd),
null,
firstParamNameNoType.range
);
if (!parameters) parameters = [ param ];
else parameters.push(param);
this.error(
DiagnosticCode.Type_expected,
param.type.range
); // recoverable
}
}
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;
if (!parameters) parameters = [];
return Node.createFunctionType(
parameters,
returnType,
thisType,
false,
tn.range(startPos, tn.pos)
);
}
// statements
parseDecorator(
tn: Tokenizer
): DecoratorNode | null {
// at '@': Identifier ('.' Identifier)* '(' Arguments
let 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(IdentifierHandling.Prefer)) {
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,
isFor: bool = false
): VariableStatement | null {
// at ('const' | 'let' | 'var'): VariableDeclaration (',' VariableDeclaration)* ';'?
let declarations = new Array();
do {
let declaration = this.parseVariableDeclaration(tn, flags, decorators, isFor);
if (!declaration) return null;
declaration.overriddenModuleName = this.currentModuleName;
declarations.push(declaration);
} while (tn.skip(Token.Comma));
let ret = Node.createVariableStatement(decorators, declarations, tn.range(startPos, tn.pos));
if (!tn.skip(Token.Semicolon) && !isFor) this.checkASI(tn);
return ret;
}
parseVariableDeclaration(
tn: Tokenizer,
parentFlags: CommonFlags,
parentDecorators: DecoratorNode[] | null,
isFor: bool = false
): VariableDeclaration | null {
// before: Identifier (':' Type)? ('=' Expression)?
if (!tn.skipIdentifier()) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
return null;
}
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
if (isIllegalVariableIdentifier(identifier.text)) {
this.error(
DiagnosticCode.Identifier_expected,
identifier.range
);
}
let flags = parentFlags;
if (tn.skip(Token.Exclamation)) {
flags |= CommonFlags.DefinitelyAssigned;
}
let type: TypeNode | null = null;
if (tn.skip(Token.Colon)) {
type = this.parseType(tn, true);
}
let 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;
if (flags & CommonFlags.DefinitelyAssigned) {
this.error(
DiagnosticCode.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions,
initializer.range
);
}
} else if (!isFor) {
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
}
}
let range = Range.join(identifier.range, tn.range());
if ((flags & CommonFlags.DefinitelyAssigned) != 0 && (flags & CommonFlags.Ambient) != 0) {
this.error(
DiagnosticCode.A_definite_assignment_assertion_is_not_permitted_in_this_context,
range
);
}
return Node.createVariableDeclaration(
identifier,
parentDecorators,
flags,
type,
initializer,
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;
}
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
if (tn.next() != Token.OpenBrace) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "{"
);
return null;
}
let members = new Array();
while (!tn.skip(Token.CloseBrace)) {
let member = this.parseEnumValue(tn, CommonFlags.None);
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;
}
}
}
let ret = Node.createEnumDeclaration(
identifier,
decorators,
flags,
members,
tn.range(startPos, tn.pos)
);
ret.overriddenModuleName = this.currentModuleName;
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;
}
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
let value: Expression | null = null;
if (tn.skip(Token.Equals)) {
value = this.parseExpression(tn, Precedence.Comma + 1);
if (!value) return null;
}
return Node.createEnumValueDeclaration(
identifier,
parentFlags,
value,
Range.join(identifier.range, tn.range())
);
}
parseReturn(
tn: Tokenizer
): ReturnStatement | null {
// at 'return': Expression | (';' | '}' | ...'\n')
let startPos = tn.tokenPos;
let expr: Expression | null = null;
let nextToken = tn.peek();
if (
nextToken != Token.Semicolon &&
nextToken != Token.CloseBrace &&
!tn.peekOnNewLine()
) {
if (!(expr = this.parseExpression(tn))) return null;
}
let ret = Node.createReturnStatement(expr, tn.range(startPos, tn.pos));
if (!tn.skip(Token.Semicolon)) this.checkASI(tn);
return ret;
}
parseTypeParameters(
tn: Tokenizer
): TypeParameterNode[] | null {
// at '<': TypeParameter (',' TypeParameter)* '>'
let typeParameters = new Array();
let seenOptional = false;
let start = tn.tokenPos;
while (!tn.skip(Token.GreaterThan)) {
let typeParameter = this.parseTypeParameter(tn);
if (!typeParameter) return null;
if (typeParameter.defaultType) {
seenOptional = true;
} else if (seenOptional) {
this.error(
DiagnosticCode.Required_type_parameters_may_not_follow_optional_type_parameters,
typeParameter.range
);
typeParameter.defaultType = null;
}
typeParameters.push(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) {
this.error(
DiagnosticCode.Type_parameter_list_cannot_be_empty,
tn.range(start, tn.pos)
); // 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: NamedTypeNode | null = null;
if (tn.skip(Token.Extends)) {
let type = this.parseType(tn);
if (!type) return null;
if (type.kind != NodeKind.NamedType) {
this.error(
DiagnosticCode.Identifier_expected,
type.range
);
return null;
}
extendsType = type;
}
let defaultType: NamedTypeNode | null = null;
if (tn.skip(Token.Equals)) {
let type = this.parseType(tn);
if (!type) return null;
if (type.kind != NodeKind.NamedType) {
this.error(
DiagnosticCode.Identifier_expected,
type.range
);
return null;
}
defaultType = type;
}
return Node.createTypeParameter(
identifier,
extendsType,
defaultType,
Range.join(identifier.range, tn.range())
);
} else {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
}
return null;
}
private parseParametersThis: NamedTypeNode | null = null;
parseParameters(
tn: Tokenizer,
isConstructor: bool = false
): ParameterNode[] | null {
// at '(': (Parameter (',' Parameter)*)? ')'
let parameters = new Array();
let seenRest: ParameterNode | null = null;
let seenOptional = false;
let reportedRest = false;
let thisType: TypeNode | 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.NamedType) {
this.parseParametersThis = thisType;
} else {
this.error(
DiagnosticCode.Identifier_expected,
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)?
let isRest = false;
let isOptional = false;
let startRange: Range | null = null;
let 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: TypeNode | null = null;
if (isOptional = tn.skip(Token.Question)) {
if (isRest) {
this.error(
DiagnosticCode.A_rest_parameter_cannot_be_optional,
identifier.range
);
}
}
if (tn.skip(Token.Colon)) {
type = this.parseType(tn);
if (!type) return null;
} 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(
isRest
? ParameterKind.Rest
: isOptional
? ParameterKind.Optional
: ParameterKind.Default,
identifier,
type,
initializer,
Range.join(assert(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;
}
let name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
let signatureStart = -1;
let 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;
}
let parameters = this.parseParameters(tn);
if (!parameters) return null;
let thisType = this.parseParametersThis;
let 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 > 0 && 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
}
}
let returnType: TypeNode | 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
}
}
let signature = Node.createFunctionType(
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
}
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)
);
}
let ret = Node.createFunctionDeclaration(
name,
decorators,
flags,
typeParameters,
signature,
body,
ArrowKind.None,
tn.range(startPos, tn.pos)
);
ret.overriddenModuleName = this.currentModuleName;
tn.skip(Token.Semicolon);
return ret;
}
parseFunctionExpression(tn: Tokenizer): FunctionExpression | null {
let startPos = tn.tokenPos;
let name: IdentifierExpression;
let arrowKind = ArrowKind.None;
// 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 {
arrowKind = ArrowKind.Parenthesized;
assert(tn.token == Token.OpenParen);
name = Node.createEmptyIdentifierExpression(tn.range(tn.tokenPos));
}
// TODO: type parameters? doesn't seem worth it.
let signatureStart = tn.pos;
let parameters = this.parseParameters(tn);
if (!parameters) return null;
return this.parseFunctionExpressionCommon(tn, name, parameters, this.parseParametersThis, arrowKind, startPos, signatureStart);
}
private parseFunctionExpressionCommon(
tn: Tokenizer,
name: IdentifierExpression,
parameters: ParameterNode[],
explicitThis: NamedTypeNode | null,
arrowKind: ArrowKind,
startPos: i32 = -1,
signatureStart: i32 = -1
): FunctionExpression | null {
if (startPos < 0) startPos = name.range.start;
if (signatureStart < 0) signatureStart = startPos;
let returnType: TypeNode | null = null;
if (arrowKind != ArrowKind.Single && tn.skip(Token.Colon)) {
returnType = this.parseType(tn);
if (!returnType) return null;
} else {
returnType = Node.createOmittedType(tn.range(tn.pos));
}
if (arrowKind) {
if (!tn.skip(Token.Equals_GreaterThan)) {
this.error(
DiagnosticCode._0_expected,
tn.range(tn.pos), "=>"
);
return null;
}
}
let signature = Node.createFunctionType(
parameters,
returnType,
explicitThis,
false,
tn.range(signatureStart, tn.pos)
);
let body: Statement | null = null;
if (arrowKind) {
if (tn.skip(Token.OpenBrace)) {
body = this.parseBlockStatement(tn, false);
} else {
let bodyExpression = this.parseExpression(tn, Precedence.Comma + 1);
if (bodyExpression) body = Node.createExpressionStatement(bodyExpression);
}
} 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;
let declaration = Node.createFunctionDeclaration(
name,
null,
CommonFlags.None,
null,
signature,
body,
arrowKind,
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* '}'
let isInterface = tn.token == Token.Interface;
if (!tn.skipIdentifier()) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
return null;
}
let identifier = 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;
}
let extendsType: NamedTypeNode | null = null;
if (tn.skip(Token.Extends)) {
let type = this.parseType(tn);
if (!type) return null;
if (type.kind != NodeKind.NamedType) {
this.error(
DiagnosticCode.Identifier_expected,
type.range
);
return null;
}
extendsType = type;
}
let implementsTypes: NamedTypeNode[] | 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 (type.kind != NodeKind.NamedType) {
this.error(
DiagnosticCode.Identifier_expected,
type.range
);
return null;
}
if (!isInterface) {
if (!implementsTypes) implementsTypes = [];
implementsTypes.push(type);
}
} while (tn.skip(Token.Comma));
}
if (!tn.skip(Token.OpenBrace)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "{"
);
return null;
}
let members = new Array();
let declaration: ClassDeclaration;
if (isInterface) {
assert(!implementsTypes);
declaration = Node.createInterfaceDeclaration(
identifier,
decorators,
flags,
typeParameters,
extendsType,
null,
members,
tn.range(startPos, tn.pos)
);
} else {
declaration = Node.createClassDeclaration(
identifier,
decorators,
flags,
typeParameters,
extendsType,
implementsTypes,
members,
tn.range(startPos, tn.pos)
);
}
if (!tn.skip(Token.CloseBrace)) {
do {
let member = this.parseClassMember(tn, declaration);
if (member) {
if (member.kind == NodeKind.IndexSignature) {
declaration.indexSignature = member;
} else {
assert(member instanceof DeclarationStatement);
members.push(member);
}
} else {
this.skipStatement(tn);
if (tn.skip(Token.EndOfFile)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
return null;
}
}
} while (!tn.skip(Token.CloseBrace));
}
declaration.range.end = tn.pos;
declaration.overriddenModuleName = this.currentModuleName;
return declaration;
}
parseClassExpression(tn: Tokenizer): ClassExpression | null {
// at 'class': Identifier? '{' ... '}'
let startPos = tn.tokenPos;
let 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;
}
let members = new Array();
let declaration = Node.createClassDeclaration(
name,
null,
CommonFlags.None,
null,
null,
null,
members,
tn.range(startPos, tn.pos)
);
if (!tn.skip(Token.CloseBrace)) {
do {
let member = this.parseClassMember(tn, declaration);
if (member) {
if (member.kind == NodeKind.IndexSignature) {
declaration.indexSignature = member;
} else {
assert(declaration instanceof DeclarationStatement);
members.push(member);
}
} else {
this.skipStatement(tn);
if (tn.skip(Token.EndOfFile)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
return null;
}
}
} while (!tn.skip(Token.CloseBrace));
}
declaration.range.end = tn.pos;
return Node.createClassExpression(declaration);
}
parseClassMember(
tn: Tokenizer,
parent: ClassDeclaration
): Node | null {
// before:
// 'declare'?
// ('public' | 'private' | 'protected')?
// ('static' | 'abstract')?
// 'override'?
// 'readonly'?
// ('get' | 'set')?
// Identifier ...
let isInterface = parent.kind == NodeKind.InterfaceDeclaration;
let startPos = 0;
let decorators: DecoratorNode[] | null = null;
if (tn.skip(Token.At)) {
startPos = tn.tokenPos;
do {
let decorator = this.parseDecorator(tn);
if (!decorator) break;
if (!decorators) decorators = new Array();
decorators.push(decorator);
} while (tn.skip(Token.At));
if (isInterface && decorators) {
this.error(
DiagnosticCode.Decorators_are_not_valid_here,
Range.join(decorators[0].range, decorators[decorators.length - 1].range)
);
}
}
// inherit ambient status
let flags = parent.flags & CommonFlags.Ambient;
// interface methods are always overridden if used
if (isInterface) flags |= CommonFlags.Overridden;
let declareStart = 0;
let declareEnd = 0;
let contextIsAmbient = parent.is(CommonFlags.Ambient);
if (tn.skip(Token.Declare)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "declare"
);
} else {
if (contextIsAmbient) {
this.error(
DiagnosticCode.A_declare_modifier_cannot_be_used_in_an_already_ambient_context,
tn.range()
); // recoverable
} else {
flags |= CommonFlags.Declare | CommonFlags.Ambient;
declareStart = tn.tokenPos;
declareEnd = tn.pos;
}
}
if (!startPos) startPos = tn.tokenPos;
} else if (contextIsAmbient) {
flags |= CommonFlags.Ambient;
}
let accessStart = 0;
let accessEnd = 0;
if (tn.skip(Token.Public)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "public"
);
} else {
flags |= CommonFlags.Public;
accessStart = tn.tokenPos;
accessEnd = tn.pos;
}
if (!startPos) startPos = tn.tokenPos;
} else if (tn.skip(Token.Private)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "private"
);
} else {
flags |= CommonFlags.Private;
accessStart = tn.tokenPos;
accessEnd = tn.pos;
}
if (!startPos) startPos = tn.tokenPos;
} else if (tn.skip(Token.Protected)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "protected"
);
} else {
flags |= CommonFlags.Protected;
accessStart = tn.tokenPos;
accessEnd = tn.pos;
}
if (!startPos) startPos = tn.tokenPos;
}
let staticStart = 0;
let staticEnd = 0;
let abstractStart = 0;
let abstractEnd = 0;
if (tn.skip(Token.Static)) {
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "static"
);
} else {
flags |= CommonFlags.Static;
staticStart = tn.tokenPos;
staticEnd = tn.pos;
}
if (!startPos) startPos = tn.tokenPos;
} else {
flags |= CommonFlags.Instance;
if (tn.skip(Token.Abstract)) {
if (isInterface || !parent.is(CommonFlags.Abstract)) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "abstract"
);
} else {
flags |= CommonFlags.Abstract;
abstractStart = tn.tokenPos;
abstractEnd = tn.pos;
}
if (!startPos) startPos = tn.tokenPos;
}
if (parent.flags & CommonFlags.Generic) flags |= CommonFlags.GenericContext;
}
let overrideStart = 0;
let overrideEnd = 0;
if (tn.skip(Token.Override)) {
if (isInterface || parent.extendsType == null) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "override"
);
} else {
flags |= CommonFlags.Override;
overrideStart = tn.tokenPos;
overrideEnd = tn.pos;
}
if (!startPos) startPos = tn.tokenPos;
}
let readonlyStart = 0;
let readonlyEnd = 0;
if (tn.peek() == Token.Readonly) {
let state = tn.mark();
tn.next();
if (tn.peek() != Token.Colon) { // modifier
tn.discard(state);
flags |= CommonFlags.Readonly;
readonlyStart = tn.tokenPos;
readonlyEnd = tn.pos;
if (!startPos) startPos = readonlyStart;
} else { // identifier
tn.reset(state);
}
}
// check if accessor: ('get' | 'set') ^\n Identifier
let state = tn.mark();
let isConstructor = false;
let isGetter = false;
let getStart = 0;
let getEnd = 0;
let isSetter = false;
let setStart = 0;
let setEnd = 0;
if (!isInterface) {
if (tn.skip(Token.Get)) {
if (tn.peek(IdentifierHandling.Prefer) == Token.Identifier && !tn.peekOnNewLine()) {
flags |= CommonFlags.Get;
isGetter = true;
getStart = tn.tokenPos;
getEnd = tn.pos;
if (!startPos) startPos = getStart;
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(IdentifierHandling.Prefer) == Token.Identifier && !tn.peekOnNewLine()) {
flags |= CommonFlags.Set;
isSetter = true;
setStart = tn.tokenPos;
setEnd = tn.pos;
if (!startPos) startPos = setStart;
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 (!startPos) startPos = tn.tokenPos;
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 isGetterOrSetter = isGetter || isSetter;
let name: IdentifierExpression;
if (isConstructor) {
name = Node.createConstructorExpression(tn.range());
} else {
if (!isGetterOrSetter && tn.skip(Token.OpenBracket)) {
if (!startPos) startPos = tn.tokenPos;
// 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), "private"
); // recoverable
}
if (flags & CommonFlags.Static) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(staticStart, staticEnd), "static"
); // recoverable
}
if (flags & CommonFlags.Override) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(overrideStart, overrideEnd), "override"
);
}
if (flags & CommonFlags.Abstract) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(abstractStart, abstractEnd), "abstract"
); // recoverable
}
let retIndex = this.parseIndexSignature(tn, flags, decorators);
if (!retIndex) {
if (flags & CommonFlags.Readonly) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(readonlyStart, readonlyEnd), "readonly"
); // recoverable
}
return null;
}
tn.skip(Token.Semicolon);
return retIndex;
}
if (!tn.skipIdentifier(IdentifierHandling.Always)) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
return null;
}
if (!startPos) startPos = tn.tokenPos;
name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
}
let 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 (isGetterOrSetter) {
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)) {
if (flags & CommonFlags.Declare) {
this.error(
DiagnosticCode._0_modifier_cannot_appear_on_class_elements_of_this_kind,
tn.range(declareStart, declareEnd), "declare"
); // recoverable
}
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,
null,
parameter.flags | CommonFlags.Instance,
parameter.type,
null, // initialized via parameter
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 > 0 && parameters[0].initializer) {
this.error(
DiagnosticCode.A_set_accessor_parameter_cannot_have_an_initializer,
name.range
);
}
} else if (name.text == "constructor") {
this.error(
DiagnosticCode._0_keyword_cannot_be_used_here,
name.range, "constructor"
);
}
let returnType: TypeNode | 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, isSetter || name.kind == NodeKind.Constructor);
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.createFunctionType(
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
} else if (isInterface) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ";"
); // recoverable
}
body = this.parseBlockStatement(tn, false);
if (!body) return null;
} else if (!isInterface && !(flags & (CommonFlags.Ambient | CommonFlags.Abstract))) {
this.error(
DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration,
tn.range()
); // recoverable
}
let retMethod = Node.createMethodDeclaration(
name,
decorators,
flags,
typeParameters,
signature,
body,
tn.range(startPos, tn.pos)
);
if (!(isInterface && tn.skip(Token.Comma))) {
tn.skip(Token.Semicolon);
}
return retMethod;
} else if (isConstructor) {
this.error(
DiagnosticCode.Constructor_implementation_is_missing,
name.range
);
} else if (isGetterOrSetter) {
this.error(
DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration,
name.range
);
// field: (':' Type)? ('=' Expression)? ';'?
} else {
if (flags & CommonFlags.Declare) {
this.error(
DiagnosticCode.Not_implemented_0,
tn.range(declareStart, declareEnd), "Ambient fields"
); // recoverable
}
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: TypeNode | 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.DefinitelyAssigned;
}
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)) {
if (flags & CommonFlags.Ambient) {
this.error(
DiagnosticCode.Initializers_are_not_allowed_in_ambient_contexts,
tn.range()
); // recoverable
}
initializer = this.parseExpression(tn);
if (!initializer) return null;
if (flags & CommonFlags.DefinitelyAssigned) {
this.error(
DiagnosticCode.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions,
name.range
);
}
}
let range = tn.range(startPos, tn.pos);
if ((flags & CommonFlags.DefinitelyAssigned) != 0 && (isInterface || (flags & CommonFlags.Ambient) != 0)) {
this.error(
DiagnosticCode.A_definite_assignment_assertion_is_not_permitted_in_this_context,
range
);
}
let retField = Node.createFieldDeclaration(
name,
decorators,
flags,
type,
initializer,
range
);
if (!(isInterface && tn.skip(Token.Comma))) {
tn.skip(Token.Semicolon);
}
return retField;
}
return null;
}
parseIndexSignature(
tn: Tokenizer,
flags: CommonFlags,
decorators: DecoratorNode[] | null,
): IndexSignatureNode | null {
// at: '[': 'key' ':' Type ']' ':' Type
if (decorators && decorators.length > 0) {
this.error(
DiagnosticCode.Decorators_are_not_valid_here,
Range.join(decorators[0].range, decorators[decorators.length - 1].range)
); // recoverable
}
let 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.NamedType) {
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;
if (valueType.kind != NodeKind.NamedType) {
this.error(
DiagnosticCode.Identifier_expected,
valueType.range
);
return null;
}
return Node.createIndexSignature(keyType, valueType, flags, 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();
let declaration = Node.createNamespaceDeclaration(
identifier,
decorators,
flags,
members,
tn.range(startPos, tn.pos)
);
while (!tn.skip(Token.CloseBrace)) {
let member = this.parseTopLevelStatement(tn, declaration);
if (member) {
if (member.kind == NodeKind.Export) {
this.error(
DiagnosticCode.A_default_export_can_only_be_used_in_a_module,
member.range,
);
return null;
}
members.push(member);
} else {
this.skipStatement(tn);
if (tn.skip(Token.EndOfFile)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
return null;
}
}
}
declaration.range.end = tn.pos;
declaration.overriddenModuleName = this.currentModuleName;
tn.skip(Token.Semicolon);
return declaration;
} 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)? ';'?
let path: StringLiteralExpression | null = null;
let currentSource = assert(this.currentSource);
if (tn.skip(Token.OpenBrace)) {
let members = new Array();
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));
if (path) {
let internalPath = assert(ret.internalPath);
if (!this.seenlog.has(internalPath)) {
this.dependees.set(internalPath, new Dependee(currentSource, path));
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;
let exportPaths = source.exportPaths;
if (!exportPaths) source.exportPaths = [ internalPath ];
else if (!exportPaths.includes(internalPath)) exportPaths.push(internalPath);
if (!this.seenlog.has(internalPath)) {
this.dependees.set(internalPath, new Dependee(currentSource, path));
this.backlog.push(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(IdentifierHandling.Always)) {
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;
}
}
if (asIdentifier) {
return Node.createExportMember(
identifier,
asIdentifier,
Range.join(identifier.range, asIdentifier.range)
);
}
return Node.createExportMember(
identifier,
null,
identifier.range
);
} else {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
}
return null;
}
parseExportDefaultAlias(
tn: Tokenizer,
startPos: i32,
defaultStart: i32,
defaultEnd: i32
): ExportStatement {
// at 'export' 'default': [Known-To-Be-]Identifier
let name = tn.readIdentifier();
let range = tn.range();
let ret = Node.createExportStatement([
Node.createExportMember(
Node.createIdentifierExpression(name, range),
Node.createIdentifierExpression("default", tn.range(defaultStart, defaultEnd)),
range
)
], null, false, tn.range(startPos, tn.pos));
tn.skip(Token.Semicolon);
return ret;
}
parseImport(
tn: Tokenizer
): ImportStatement | null {
// at 'import':
// ('{' (ImportMember (',' ImportMember)* '}') | ('*' 'as' Identifier)?
// 'from' StringLiteral ';'?
let startPos = tn.tokenPos;
let members: ImportDeclaration[] | null = null;
let namespaceName: IdentifierExpression | null = null;
let skipFrom = false;
if (tn.skip(Token.OpenBrace)) { // import { ... } from "file"
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)) { // import * from "file"
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 if (tn.skip(Token.Identifier, IdentifierHandling.Prefer)) { // import Name from "file"
let name = tn.readIdentifier();
let range = tn.range();
members = [
Node.createImportDeclaration(
Node.createIdentifierExpression("default", range),
Node.createIdentifierExpression(name, range),
range
)
];
if (tn.skip(Token.Comma)) {
// TODO: default + star, default + members
this.error(
DiagnosticCode.Not_implemented_0,
tn.range(),
"Mixed default and named imports"
);
return null;
}
} else { // import "file"
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.createWildcardImportStatement(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.dependees.set(internalPath, new Dependee(assert(this.currentSource), path));
this.backlog.push(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;
}
}
if (asIdentifier) {
return Node.createImportDeclaration(
identifier,
asIdentifier,
Range.join(identifier.range, asIdentifier.range)
);
}
return Node.createImportDeclaration(
identifier,
null,
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
let state = tn.mark();
let token = tn.next();
let 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(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* '}' ';'?
let startPos = tn.tokenPos;
let statements = new Array();
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);
}
}
let ret = Node.createBlockStatement(statements, tn.range(startPos, tn.pos));
if (topLevel) tn.skip(Token.Semicolon);
return ret;
}
parseBreak(
tn: Tokenizer
): BreakStatement | null {
// at 'break': Identifier? ';'?
let identifier: IdentifierExpression | null = null;
if (tn.peek() == Token.Identifier && !tn.peekOnNewLine()) {
tn.next(IdentifierHandling.Prefer);
identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
}
let ret = Node.createBreakStatement(identifier, tn.range());
tn.skip(Token.Semicolon);
return ret;
}
parseContinue(
tn: Tokenizer
): ContinueStatement | null {
// at 'continue': Identifier? ';'?
let identifier: IdentifierExpression | null = null;
if (tn.peek() == Token.Identifier && !tn.peekOnNewLine()) {
tn.next(IdentifierHandling.Prefer);
identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
}
let ret = Node.createContinueStatement(identifier, tn.range());
tn.skip(Token.Semicolon);
return ret;
}
parseDoStatement(
tn: Tokenizer
): DoStatement | null {
// at 'do': Statement 'while' '(' Expression ')' ';'?
let startPos = tn.tokenPos;
let 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, 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
let expr = this.parseExpression(tn);
if (!expr) return null;
let ret = Node.createExpressionStatement(expr);
tn.skip(Token.Semicolon);
return ret;
}
parseForStatement(
tn: Tokenizer
): Statement | null {
// at 'for': '(' Statement? Expression? ';' Expression? ')' Statement
let 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, true);
} else if (tn.skip(Token.Let)) {
initializer = this.parseVariable(tn, CommonFlags.Let, null, tn.tokenPos, true);
} else if (tn.skip(Token.Var)) {
initializer = this.parseVariable(tn, CommonFlags.None, null, tn.tokenPos, true);
} else if (!tn.skip(Token.Semicolon)) {
initializer = this.parseExpressionStatement(tn);
if (!initializer) return null;
}
if (initializer) {
if (tn.skip(Token.Of)) {
// TODO: for (let [key, val] of ...)
if (initializer.kind == NodeKind.Expression) {
if ((initializer).expression.kind != NodeKind.Identifier) {
this.error(
DiagnosticCode.Identifier_expected,
initializer.range
);
return null;
}
return this.parseForOfStatement(tn, startPos, initializer);
}
if (initializer.kind == NodeKind.Variable) {
let declarations = (initializer).declarations;
for (let i = 0, k = declarations.length; i < k; ++i) {
let declaration = declarations[i];
let initializer = declaration.initializer;
if (initializer) {
this.error(
DiagnosticCode.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer,
initializer.range
); // recoverable
}
}
return this.parseForOfStatement(tn, startPos, initializer);
}
this.error(
DiagnosticCode.Identifier_expected,
initializer.range
);
return null;
}
// non-for..of needs type or initializer
if (initializer.kind == NodeKind.Variable) {
let declarations = (initializer).declarations;
for (let i = 0, k = declarations.length; i < k; ++i) {
let declaration = declarations[i];
if (!declaration.initializer) {
if (declaration.flags & CommonFlags.Const) {
this.error(
DiagnosticCode._const_declarations_must_be_initialized,
declaration.name.range
);
} else if (!declaration.type) {
this.error(
DiagnosticCode.Type_expected,
declaration.name.range.atEnd
);
}
}
}
}
}
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;
}
parseForOfStatement(
tn: Tokenizer,
startPos: i32,
variable: Statement,
): ForOfStatement | null {
// at 'of': Expression ')' Statement
let iterable = this.parseExpression(tn);
if (!iterable) 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.createForOfStatement(
variable,
iterable,
statement,
tn.range(startPos, tn.pos)
);
}
parseIfStatement(
tn: Tokenizer
): IfStatement | null {
// at 'if': '(' Expression ')' Statement ('else' Statement)?
let 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* '}' ';'?
let 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 switchCases = new Array();
while (!tn.skip(Token.CloseBrace)) {
let switchCase = this.parseSwitchCase(tn);
if (!switchCase) return null;
switchCases.push(switchCase);
}
let ret = Node.createSwitchStatement(condition, switchCases, 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 {
let startPos = tn.tokenPos;
let 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();
while (
tn.peek() != Token.Case &&
tn.nextToken != Token.Default &&
tn.nextToken != Token.CloseBrace
) {
statement = this.parseStatement(tn);
if (!statement) return null;
statements.push(statement);
}
return Node.createSwitchCase(label, statements, tn.range(startPos, tn.pos));
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ":"
);
}
// 'default' ':' Statement*
} else if (tn.skip(Token.Default)) {
if (tn.skip(Token.Colon)) {
statements = new Array();
while (
tn.peek() != Token.Case &&
tn.nextToken != Token.Default &&
tn.nextToken != Token.CloseBrace
) {
statement = this.parseStatement(tn);
if (!statement) return null;
statements.push(statement);
}
return Node.createSwitchCase(null, statements, tn.range(startPos, tn.pos));
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ":"
);
}
} else {
this.error(
DiagnosticCode._case_or_default_expected,
tn.range()
);
}
return null;
}
parseThrowStatement(
tn: Tokenizer
): ThrowStatement | null {
// at 'throw': Expression ';'?
let startPos = tn.tokenPos;
let expression = this.parseExpression(tn);
if (!expression) return null;
let ret = Node.createThrowStatement(expression, tn.range(startPos, tn.pos));
if (!tn.skip(Token.Semicolon)) this.checkASI(tn);
return ret;
}
parseTryStatement(
tn: Tokenizer
): TryStatement | null {
// at 'try':
// '{' Statement* '}'
// ('catch' '(' VariableMember ')' '{' Statement* '}')?
// ('finally' '{' Statement* '}'? ';'?
let startPos = tn.tokenPos;
let stmt: Statement | null;
if (tn.skip(Token.OpenBrace)) {
let bodyStatements = new Array();
while (!tn.skip(Token.CloseBrace)) {
stmt = this.parseStatement(tn);
if (!stmt) return null;
bodyStatements.push(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(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(stmt);
}
}
if (!(catchStatements || finallyStatements)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "catch"
);
return null;
}
let ret = Node.createTryStatement(
bodyStatements,
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)) {
tn.skip(Token.Bar);
let type = this.parseType(tn);
if (!type) return null;
if (isCircularTypeAlias(name.text, type)) {
this.error(
DiagnosticCode.Type_alias_0_circularly_references_itself,
name.range, name.text
);
return null;
}
let ret = Node.createTypeDeclaration(
name,
decorators,
flags,
typeParameters,
type,
tn.range(startPos, tn.pos)
);
tn.skip(Token.Semicolon);
ret.overriddenModuleName = this.currentModuleName;
return ret;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "="
);
}
} else {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
}
return null;
}
parseModuleDeclaration(
tn: Tokenizer,
flags: CommonFlags
): ModuleDeclaration | null {
// at 'module': StringLiteral ';'?
let startPos = tn.tokenPos;
assert(tn.next() == Token.StringLiteral); // checked earlier
let moduleName = tn.readString();
let ret = Node.createModuleDeclaration(moduleName, flags, tn.range(startPos, tn.pos));
this.currentModuleName = moduleName;
tn.skip(Token.Semicolon);
return ret;
}
parseVoidStatement(
tn: Tokenizer
): VoidStatement | null {
// at 'void': Expression ';'?
let startPos = tn.tokenPos;
let expression = this.parseExpression(tn, Precedence.Grouping);
if (!expression) return null;
let ret = Node.createVoidStatement(expression, tn.range(startPos, tn.pos));
tn.skip(Token.Semicolon);
return ret;
}
parseWhileStatement(
tn: Tokenizer
): WhileStatement | null {
// at 'while': '(' Expression ')' Statement ';'?
let 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
parseExpressionStart(
tn: Tokenizer
): Expression | null {
let token = tn.next(IdentifierHandling.Prefer);
let startPos = tn.tokenPos;
switch (token) {
// TODO: SpreadExpression, YieldExpression
case Token.Dot_Dot_Dot:
case Token.Yield: // fallthrough to unsupported UnaryPrefixExpression
// UnaryPrefixExpression
case Token.Exclamation:
case Token.Tilde:
case Token.Plus:
case Token.Minus:
case Token.TypeOf:
case Token.Void:
case Token.Delete: {
let operand = this.parseExpression(tn, Precedence.UnaryPrefix);
if (!operand) return null;
return Node.createUnaryPrefixExpression(token, operand, tn.range(startPos, tn.pos));
}
case Token.Plus_Plus:
case Token.Minus_Minus: {
let operand = this.parseExpression(tn, Precedence.UnaryPrefix);
if (!operand) return null;
switch (operand.kind) {
case NodeKind.Identifier:
case NodeKind.ElementAccess:
case NodeKind.PropertyAccess: break;
default: {
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));
}
// NewExpression
case Token.New: {
if (!tn.skipIdentifier()) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
return null;
}
let typeName = this.parseTypeName(tn);
if (!typeName) return null;
let typeArguments: TypeNode[] | null = null;
let arguments_: Expression[] | null = null;
if (
tn.skip(Token.OpenParen) ||
(typeArguments = this.tryParseTypeArgumentsBeforeArguments(tn))
) {
arguments_ = this.parseArguments(tn);
if (!arguments_) return null;
} else {
arguments_ = []; // new Type;
}
return Node.createNewExpression(
typeName,
typeArguments,
arguments_,
tn.range(startPos, tn.pos)
);
}
// Special IdentifierExpression
case Token.Null: return Node.createNullExpression(tn.range());
case Token.True: return Node.createTrueExpression(tn.range());
case Token.False: return Node.createFalseExpression(tn.range());
case Token.This: return Node.createThisExpression(tn.range());
case Token.Constructor: return Node.createConstructorExpression(tn.range());
// ParenthesizedExpression or 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)),
[],
null,
ArrowKind.Parenthesized
);
}
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: {
// `Identifier):Type =>` is function expression
if (tn.skip(Token.Colon)) {
let type = this.parseType(tn, true, true);
if (type == null) {
again = false;
break;
}
}
if (!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
let inner = this.parseExpression(tn);
if (!inner) return null;
if (!tn.skip(Token.CloseParen)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
return null;
}
inner = Node.createParenthesizedExpression(inner, tn.range(startPos, tn.pos));
return this.maybeParseCallExpression(tn, inner);
}
// ArrayLiteralExpression
case Token.OpenBracket: {
let elementExpressions = new Array();
while (!tn.skip(Token.CloseBracket)) {
let expr: Expression | null;
if (tn.peek() == Token.Comma) {
expr = Node.createOmittedExpression(tn.range(tn.pos));
} 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();
let values = new Array();
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;
}
let 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: {
let identifierText = tn.readIdentifier();
if (identifierText == "null") return Node.createNullExpression(tn.range()); // special
let identifier = Node.createIdentifierExpression(identifierText, tn.range(startPos, tn.pos));
if (tn.skip(Token.TemplateLiteral)) {
return this.parseTemplateLiteral(tn, identifier);
}
if (tn.peek() == Token.Equals_GreaterThan && !tn.peekOnNewLine()) {
return this.parseFunctionExpressionCommon(
tn,
Node.createEmptyIdentifierExpression(tn.range(startPos)),
[
Node.createParameter(
ParameterKind.Default,
identifier,
Node.createOmittedType(identifier.range.atEnd),
null,
identifier.range
)
],
null,
ArrowKind.Single,
startPos
);
}
return this.maybeParseCallExpression(tn, identifier, true);
}
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()
);
}
let expr = Node.createSuperExpression(tn.range(startPos, tn.pos));
return this.maybeParseCallExpression(tn, expr);
}
case Token.StringLiteral: {
return Node.createStringLiteralExpression(tn.readString(), tn.range(startPos, tn.pos));
}
case Token.TemplateLiteral: {
return this.parseTemplateLiteral(tn);
}
case Token.IntegerLiteral: {
let value = tn.readInteger();
tn.checkForIdentifierStartAfterNumericLiteral();
return Node.createIntegerLiteralExpression(value, tn.range(startPos, tn.pos));
}
case Token.FloatLiteral: {
let value = tn.readFloat();
tn.checkForIdentifierStartAfterNumericLiteral();
return Node.createFloatLiteralExpression(value, 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: {
let expr = this.parseFunctionExpression(tn);
if (!expr) return null;
return this.maybeParseCallExpression(tn, expr);
}
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
): TypeNode[] | null {
// at '<': Type (',' Type)* '>' '('
let state = tn.mark();
if (!tn.skip(Token.LessThan)) return null;
let start = tn.tokenPos;
let typeArguments: TypeNode[] | null = null;
do {
if (tn.peek() == Token.GreaterThan) {
break;
}
let type = this.parseType(tn, true, true);
if (!type) {
tn.reset(state);
return null;
}
if (!typeArguments) typeArguments = [ type ];
else typeArguments.push(type);
} while (tn.skip(Token.Comma));
if (tn.skip(Token.GreaterThan)) {
let end = tn.pos;
if (tn.skip(Token.OpenParen)) {
if (!typeArguments) {
this.error(
DiagnosticCode.Type_argument_list_cannot_be_empty,
tn.range(start, end)
);
}
return typeArguments;
}
}
tn.reset(state);
return null;
}
parseArguments(
tn: Tokenizer
): Expression[] | null {
// at '(': (Expression (',' Expression)*)? ')'
let args = new Array();
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);
let expr = this.parseExpressionStart(tn);
if (!expr) return null;
let startPos = expr.range.start;
// precedence climbing
// see: http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing
let nextPrecedence: Precedence;
while (
(nextPrecedence = determinePrecedence(tn.peek())) >= precedence
) {
let token = tn.next();
switch (token) {
// AssertionExpression
case Token.As: {
if (tn.skip(Token.Const)) {
expr = Node.createAssertionExpression(
AssertionKind.Const,
expr,
null,
tn.range(startPos, tn.pos)
);
} else {
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)
);
expr = this.maybeParseCallExpression(tn, expr);
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: {
let 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)
);
expr = this.maybeParseCallExpression(tn, expr);
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;
}
// PropertyAccessExpression
case Token.Dot: {
if (tn.skipIdentifier(IdentifierHandling.Always)) { // expr '.' Identifier
let next = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
expr = Node.createPropertyAccessExpression(
expr,
next,
tn.range(startPos, tn.pos)
);
} else {
let next = this.parseExpression(tn, nextPrecedence + 1);
if (!next) return null;
if (next.kind == NodeKind.Call) { // expr '.' CallExpression
expr = this.joinPropertyCall(tn, startPos, expr, next);
if (!expr) return null;
} else {
this.error(
DiagnosticCode.Identifier_expected,
next.range
);
return null;
}
}
if (tn.skip(Token.TemplateLiteral)) {
expr = this.parseTemplateLiteral(tn, expr);
if (!expr) return null;
} else {
expr = this.maybeParseCallExpression(tn, expr, true);
}
break;
}
// BinaryExpression (right associative)
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.Asterisk_Asterisk: {
let next = this.parseExpression(tn, nextPrecedence);
if (!next) return null;
expr = Node.createBinaryExpression(token, expr, next, tn.range(startPos, tn.pos));
break;
}
// BinaryExpression
case Token.LessThan:
case Token.GreaterThan:
case Token.LessThan_Equals:
case Token.GreaterThan_Equals:
case Token.Equals_Equals:
case Token.Equals_Equals_Equals:
case Token.Exclamation_Equals_Equals:
case Token.Exclamation_Equals:
case Token.Plus:
case Token.Minus:
case Token.Asterisk:
case Token.Slash:
case Token.Percent:
case Token.LessThan_LessThan:
case Token.GreaterThan_GreaterThan:
case Token.GreaterThan_GreaterThan_GreaterThan:
case Token.Ampersand:
case Token.Bar:
case Token.Caret:
case Token.Ampersand_Ampersand:
case Token.Bar_Bar:
case Token.In: {
let next = this.parseExpression(tn, nextPrecedence + 1);
if (!next) return null;
expr = Node.createBinaryExpression(token, expr, next, tn.range(startPos, tn.pos));
break;
}
default: assert(false); // filtered by determinePrecedence
}
}
return expr;
}
private parseTemplateLiteral(tn: Tokenizer, tag: Expression | null = null): Expression | null {
// at '`': ... '`'
let startPos = tag ? tag.range.start : tn.tokenPos;
let parts = new Array();
let rawParts = new Array();
let exprs = new Array();
parts.push(tn.readString(0, tag != null));
rawParts.push(tn.source.text.substring(tn.readStringStart, tn.readStringEnd));
while (tn.readingTemplateString) {
let expr = this.parseExpression(tn);
if (!expr) return null;
exprs.push(expr);
if (!tn.skip(Token.CloseBrace)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
return null;
}
parts.push(tn.readString(CharCode.Backtick, tag != null));
rawParts.push(tn.source.text.substring(tn.readStringStart, tn.readStringEnd));
}
return Node.createTemplateLiteralExpression(tag, parts, rawParts, exprs, tn.range(startPos, tn.pos));
}
private joinPropertyCall(
tn: Tokenizer,
startPos: i32,
expr: Expression,
call: CallExpression
): Expression | null {
let callee = call.expression;
switch (callee.kind) {
case NodeKind.Identifier: { // join property access and use as call target
call.expression = Node.createPropertyAccessExpression(
expr,
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, 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,
potentiallyGeneric: bool = false
): Expression {
let typeArguments: TypeNode[] | null = null;
while (
tn.skip(Token.OpenParen) ||
potentiallyGeneric &&
(typeArguments = this.tryParseTypeArgumentsBeforeArguments(tn))
) {
let args = this.parseArguments(tn);
if (!args) break;
expr = Node.createCallExpression( // is again callable
expr,
typeArguments,
args,
tn.range(expr.range.start, tn.pos)
);
potentiallyGeneric = false;
}
return expr;
}
private checkASI(
tn: Tokenizer
): void {
// see: https://tc39.es/ecma262/#sec-automatic-semicolon-insertion
let nextToken = tn.peek();
if (nextToken == Token.EndOfFile || nextToken == Token.CloseBrace || tn.peekOnNewLine()) return;
this.error(
DiagnosticCode.Unexpected_token,
tn.range(tn.nextTokenPos)
);
}
/** Skips over a statement on errors in an attempt to reduce unnecessary diagnostic noise. */
skipStatement(tn: Tokenizer): void {
if (tn.peekOnNewLine()) tn.next(); // if reset() to the previous line
do {
let nextToken = tn.peek();
if (
nextToken == Token.EndOfFile || // next step should handle this
nextToken == Token.Semicolon // end of the statement for sure
) {
tn.next();
break;
}
if (tn.peekOnNewLine()) break; // end of the statement maybe
switch (tn.next()) {
case Token.Identifier: {
tn.readIdentifier();
break;
}
case Token.StringLiteral:
case Token.TemplateLiteral: {
tn.readString();
break;
}
case Token.IntegerLiteral: {
tn.readInteger();
tn.checkForIdentifierStartAfterNumericLiteral();
break;
}
case Token.FloatLiteral: {
tn.readFloat();
tn.checkForIdentifierStartAfterNumericLiteral();
break;
}
case Token.OpenBrace: {
this.skipBlock(tn);
break;
}
}
} while (true);
tn.readingTemplateString = false;
}
/** Skips over a block on errors in an attempt to reduce unnecessary diagnostic noise. */
skipBlock(tn: Tokenizer): void {
// at '{': ... '}'
let depth = 1;
let 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.TemplateLiteral: {
tn.readString();
while(tn.readingTemplateString){
this.skipBlock(tn);
tn.readString(CharCode.Backtick);
}
break;
}
case Token.IntegerLiteral: {
tn.readInteger();
tn.checkForIdentifierStartAfterNumericLiteral();
break;
}
case Token.FloatLiteral: {
tn.readFloat();
tn.checkForIdentifierStartAfterNumericLiteral();
break;
}
}
} while (again);
}
}
/** Operator precedence from least to largest. */
export const enum Precedence {
None,
Comma,
Spread,
Yield,
Assignment,
Conditional,
LogicalOr,
LogicalAnd,
BitwiseOr,
BitwiseXor,
BitwiseAnd,
Equality,
Relational,
Shift,
Additive,
Multiplicative,
Exponentiated,
UnaryPrefix,
UnaryPostfix,
Call,
MemberAccess,
Grouping
}
/** Determines the precedence 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.LogicalOr;
case Token.Ampersand_Ampersand: return Precedence.LogicalAnd;
case Token.Bar: return Precedence.BitwiseOr;
case Token.Caret: return Precedence.BitwiseXor;
case Token.Ampersand: return Precedence.BitwiseAnd;
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.UnaryPostfix;
case Token.Dot:
case Token.OpenBracket:
case Token.Exclamation: return Precedence.MemberAccess;
}
return Precedence.None;
}
/** Checks if the type alias of the given name and type is circular. */
function isCircularTypeAlias(name: string, type: TypeNode): bool {
switch (type.kind) {
case NodeKind.NamedType: {
if ((type).name.identifier.text == name) {
return true;
}
let typeArguments = (type).typeArguments;
if (typeArguments) {
for (let i = 0, k = typeArguments.length; i < k; i++) {
if (isCircularTypeAlias(name, typeArguments[i])) return true;
}
}
break;
}
case NodeKind.FunctionType: {
let functionType = type;
if (isCircularTypeAlias(name, functionType.returnType)) return true;
let parameters = functionType.parameters;
for (let i = 0, k = parameters.length; i < k; i++) {
if (isCircularTypeAlias(name, parameters[i].type)) return true;
}
break;
}
default: assert(false);
}
return false;
}