/**
* @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,
Range,
CommentHandler,
IdentifierHandling,
isIllegalVariableIdentifier
} from "./tokenizer";
import {
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,
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[];
/** Constructs a new parser. */
constructor(
diagnostics: DiagnosticMessage[] | null = null,
sources: Source[] | null = null
) {
super(diagnostics);
this.sources = sources ? sources : new Array();
}
/** 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
var normalizedPath = normalizePath(path);
var internalPath = mangleInternalPath(normalizedPath);
// check if already processed
if (this.donelog.has(internalPath)) return;
this.donelog.add(internalPath); // do not parse again
this.seenlog.add(internalPath); // do not request again
// 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
var source = new Source(
isEntry
? SourceKind.USER_ENTRY
: path.startsWith(LIBRARY_PREFIX)
? path.indexOf(PATH_DELIMITER, LIBRARY_PREFIX.length) < 0
? SourceKind.LIBRARY_ENTRY
: SourceKind.LIBRARY
: SourceKind.USER,
normalizedPath,
text
);
this.sources.push(source);
this.currentSource = source;
// tokenize and parse
var tn = new Tokenizer(source, this.diagnostics);
tn.onComment = this.onComment;
var 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 {
var flags = CommonFlags.NONE;
var startPos = -1;
// check decorators
var decorators: DecoratorNode[] | null = null;
while (tn.skip(Token.AT)) {
if (startPos < 0) startPos = tn.tokenPos;
let decorator = this.parseDecorator(tn);
if (!decorator) {
this.skipStatement(tn);
continue;
}
if (!decorators) decorators = [decorator];
else decorators.push(decorator);
}
// check modifiers
var exportStart = 0;
var exportEnd = 0;
var defaultStart = 0;
var 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;
}
}
var declareStart = 0;
var declareEnd = 0;
var 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
var statement: Statement | null = null;
// handle declarations
var first = tn.peek();
if (startPos < 0) startPos = tn.nextTokenPos;
switch (first) {
case Token.CONST: {
tn.next();
flags |= CommonFlags.CONST;
if (tn.skip(Token.ENUM)) {
statement = this.parseEnum(tn, flags, decorators, startPos);
} else {
statement = this.parseVariable(tn, flags, decorators, startPos);
}
decorators = null;
break;
}
case Token.LET: flags |= CommonFlags.LET;
case Token.VAR: {
tn.next();
statement = this.parseVariable(tn, flags, decorators, startPos);
decorators = null;
break;
}
case Token.ENUM: {
tn.next();
statement = this.parseEnum(tn, flags, decorators, startPos);
decorators = null;
break;
}
case Token.FUNCTION: {
tn.next();
statement = this.parseFunction(tn, flags, decorators, startPos);
decorators = null;
break;
}
case Token.ABSTRACT: {
let state = tn.mark();
tn.next();
let abstractStart = tn.tokenPos;
let abstractEnd = tn.pos;
let next = tn.peek(true);
if (tn.nextTokenOnNewLine) {
tn.reset(state);
statement = this.parseStatement(tn, true);
break;
}
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(false, IdentifierHandling.PREFER) == Token.IDENTIFIER) {
tn.discard(state);
statement = this.parseNamespace(tn, flags, decorators, startPos);
decorators = null;
} else {
tn.reset(state);
statement = this.parseStatement(tn, true);
}
break;
}
case Token.IMPORT: {
tn.next();
flags |= CommonFlags.IMPORT;
if (flags & CommonFlags.EXPORT) {
statement = this.parseExportImport(tn, startPos);
} else {
statement = this.parseImport(tn);
}
break;
}
case Token.TYPE: { // also identifier
let state = tn.mark();
tn.next();
if (tn.peek(false, IdentifierHandling.PREFER) == Token.IDENTIFIER) {
tn.discard(state);
statement = this.parseTypeDeclaration(tn, flags, decorators, startPos);
decorators = null;
} else {
tn.reset(state);
statement = this.parseStatement(tn, true);
}
break;
}
default: {
// handle plain exports
if (flags & CommonFlags.EXPORT) {
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 {
var 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 {
var 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)*
var first = Node.createSimpleTypeName(tn.readIdentifier(), tn.range());
var 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
var token = tn.next();
var startPos = tn.tokenPos;
var type: TypeNode;
// '(' ...
if (token == Token.OPENPAREN) {
// '(' FunctionSignature ')' '|' 'null'?
let isNullableSignature = tn.skip(Token.OPENPAREN);
// FunctionSignature?
let signature = this.tryParseFunctionType(tn);
if (signature) {
if (isNullableSignature) {
if (!tn.skip(Token.CLOSEPAREN)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
return null;
}
if (!tn.skip(Token.BAR)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "|"
);
return null;
}
if (!tn.skip(Token.NULL)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "null"
);
}
signature.isNullable = true;
}
return signature;
} else if (isNullableSignature || this.tryParseSignatureIsSignature) {
this.error(
DiagnosticCode.Unexpected_token,
tn.range()
);
return null;
}
// Type (',' Type)* ')'
if (acceptParenthesized) {
let innerType = this.parseType(tn, false, suppressErrors);
if (!innerType) return null;
if (!tn.skip(Token.CLOSEPAREN)) {
if (!suppressErrors) {
this.error(
DiagnosticCode._0_expected,
tn.range(tn.pos), ")"
);
}
return null;
}
type = innerType;
type.range.start = startPos;
type.range.end = tn.pos;
} else {
this.error(
DiagnosticCode.Unexpected_token,
tn.range()
);
return null;
}
// 'void'
} else if (token == Token.VOID) {
type = Node.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;
}
// ... | null
while (tn.skip(Token.BAR)) {
if (tn.skip(Token.NULL)) {
type.isNullable = true;
} else {
let notNullStart = tn.pos;
let notNull = this.parseType(tn, false, true);
if (!suppressErrors) {
this.error(
DiagnosticCode._0_expected,
notNull ? notNull.range : tn.range(notNullStart), "null"
);
}
return null;
}
}
// ... [][]
while (tn.skip(Token.OPENBRACKET)) {
let bracketStart = tn.tokenPos;
if (!tn.skip(Token.CLOSEBRACKET)) {
if (!suppressErrors) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "]"
);
}
return null;
}
let bracketRange = tn.range(bracketStart, tn.pos);
// ...[] | null
let nullable = false;
if (tn.skip(Token.BAR)) {
if (tn.skip(Token.NULL)) {
nullable = true;
} else {
if (!suppressErrors) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "null"
);
}
return null;
}
}
type = Node.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
var state = tn.mark();
var startPos = tn.tokenPos;
var parameters: ParameterNode[] | null = null;
var thisType: NamedTypeNode | null = null;
var isSignature: bool = false;
var firstParamNameNoType: IdentifierExpression | null = null;
var 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) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
} else {
tn.reset(state);
}
this.tryParseSignatureIsSignature = isSignature;
return null;
}
} while (tn.skip(Token.COMMA));
if (!tn.skip(Token.CLOSEPAREN)) {
if (isSignature) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
} else {
tn.reset(state);
}
this.tryParseSignatureIsSignature = isSignature;
return null;
}
}
var returnType: 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
var startPos = tn.tokenPos;
if (tn.skipIdentifier()) {
let name = tn.readIdentifier();
let expression: Expression = Node.createIdentifierExpression(name, tn.range(startPos, tn.pos));
while (tn.skip(Token.DOT)) {
if (tn.skipIdentifier(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)* ';'?
var declarations = new Array();
do {
let declaration = this.parseVariableDeclaration(tn, flags, decorators, isFor);
if (!declaration) return null;
declarations.push(declaration);
} while (tn.skip(Token.COMMA));
var ret = Node.createVariableStatement(decorators, declarations, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
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;
}
var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
if (isIllegalVariableIdentifier(identifier.text)) {
this.error(
DiagnosticCode.Identifier_expected,
identifier.range
);
}
var flags = parentFlags;
if (tn.skip(Token.EXCLAMATION)) {
flags |= CommonFlags.DEFINITELY_ASSIGNED;
}
var type: TypeNode | null = null;
if (tn.skip(Token.COLON)) {
type = this.parseType(tn, true);
}
var initializer: Expression | null = null;
if (tn.skip(Token.EQUALS)) {
if (flags & CommonFlags.AMBIENT) {
this.error(
DiagnosticCode.Initializers_are_not_allowed_in_ambient_contexts,
tn.range()
); // recoverable
}
initializer = this.parseExpression(tn, Precedence.COMMA + 1);
if (!initializer) return null;
} else if (!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
}
}
var range = Range.join(identifier.range, tn.range());
if (initializer !== null && (flags & CommonFlags.DEFINITELY_ASSIGNED) != 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;
}
var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
if (tn.next() != Token.OPENBRACE) {
this.error(
DiagnosticCode._0_expected,
tn.range(), "{"
);
return null;
}
var members = new Array();
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;
}
}
}
var ret = Node.createEnumDeclaration(
identifier,
decorators,
flags,
members,
tn.range(startPos, tn.pos)
);
tn.skip(Token.SEMICOLON);
return ret;
}
parseEnumValue(
tn: Tokenizer,
parentFlags: CommonFlags
): EnumValueDeclaration | null {
// before: Identifier ('=' Expression)?
if (!tn.skipIdentifier()) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
return null;
}
var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
var value: Expression | null = null;
if (tn.skip(Token.EQUALS)) {
value = this.parseExpression(tn, Precedence.COMMA + 1);
if (!value) return null;
}
return Node.createEnumValueDeclaration(
identifier,
parentFlags,
value,
Range.join(identifier.range, tn.range())
);
}
parseReturn(
tn: Tokenizer
): ReturnStatement | null {
// at 'return': Expression | (';' | '}' | ...'\n')
var startPos = tn.tokenPos;
var expr: Expression | null = null;
if (
tn.peek(true) != Token.SEMICOLON &&
tn.nextToken != Token.CLOSEBRACE &&
!tn.nextTokenOnNewLine
) {
if (!(expr = this.parseExpression(tn))) return null;
}
var ret = Node.createReturnStatement(expr, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
return ret;
}
parseTypeParameters(
tn: Tokenizer
): TypeParameterNode[] | null {
// at ''
var typeParameters = new Array();
var seenOptional = false;
var start = tn.tokenPos;
while (!tn.skip(Token.GREATERTHAN)) {
let typeParameter = this.parseTypeParameter(tn);
if (!typeParameter) return null;
if (typeParameter.defaultType !== null) {
seenOptional = true;
} else if (seenOptional) {
this.error(
DiagnosticCode.Required_type_parameters_may_not_follow_optional_type_parameters,
typeParameter.range
);
typeParameter.defaultType = null;
}
typeParameters.push(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)*)? ')'
var parameters = new Array();
var seenRest: ParameterNode | null = null;
var seenOptional = false;
var reportedRest = false;
var 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 !== null && !reportedRest) {
this.error(
DiagnosticCode.A_rest_parameter_must_be_last_in_a_parameter_list,
seenRest.name.range
);
reportedRest = true;
}
switch (param.parameterKind) {
default: {
if (seenOptional) {
this.error(
DiagnosticCode.A_required_parameter_cannot_follow_an_optional_parameter,
param.name.range
);
}
break;
}
case ParameterKind.OPTIONAL: {
seenOptional = true;
break;
}
case ParameterKind.REST: {
seenRest = param;
break;
}
}
parameters.push(param);
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEPAREN)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
return null;
}
}
}
return parameters;
}
parseParameter(
tn: Tokenizer,
isConstructor: bool = false
): ParameterNode | null {
// before: ('public' | 'private' | 'protected' | '...')? Identifier '?'? (':' Type)? ('=' Expression)?
var isRest = false;
var isOptional = false;
var startRange: Range | null = null;
var accessFlags: CommonFlags = CommonFlags.NONE;
if (isConstructor) {
if (tn.skip(Token.PUBLIC)) {
startRange = tn.range();
accessFlags |= CommonFlags.PUBLIC;
} else if (tn.skip(Token.PROTECTED)) {
startRange = tn.range();
accessFlags |= CommonFlags.PROTECTED;
} else if (tn.skip(Token.PRIVATE)) {
startRange = tn.range();
accessFlags |= CommonFlags.PRIVATE;
}
if (tn.peek() == Token.READONLY) {
let state = tn.mark();
tn.next();
if (tn.peek() != Token.COLON) { // modifier
tn.discard(state);
if (!startRange) startRange = tn.range();
accessFlags |= CommonFlags.READONLY;
} else { // identifier
tn.reset(state);
}
}
}
if (tn.skip(Token.DOT_DOT_DOT)) {
if (accessFlags) {
this.error(
DiagnosticCode.A_parameter_property_cannot_be_declared_using_a_rest_parameter,
tn.range()
);
} else {
startRange = tn.range();
}
isRest = true;
}
if (tn.skipIdentifier()) {
if (!isRest) startRange = tn.range();
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
let type: 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
// (' 0 && parameters[0].initializer !== null) {
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.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.DEFINITELY_ASSIGNED;
}
if (tn.skip(Token.COLON)) {
type = this.parseType(tn);
if (!type) return null;
} else {
this.error(
DiagnosticCode.Type_expected,
tn.range()
); // recoverable
}
let initializer: Expression | null = null;
if (tn.skip(Token.EQUALS)) {
initializer = this.parseExpression(tn);
if (!initializer) return null;
}
let range = tn.range(startPos, tn.pos);
if (
(flags & CommonFlags.DEFINITELY_ASSIGNED) != 0 &&
(isInterface || initializer !== null || (flags & CommonFlags.STATIC) != 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 !== null && decorators.length > 0) {
this.error(
DiagnosticCode.Decorators_are_not_valid_here,
Range.join(decorators[0].range, decorators[decorators.length - 1].range)
); // recoverable
}
var start = tn.tokenPos;
if (tn.skipIdentifier()) {
let id = tn.readIdentifier();
if (id == "key") {
if (tn.skip(Token.COLON)) {
let keyType = this.parseType(tn);
if (!keyType) return null;
if (keyType.kind != NodeKind.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) 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;
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)? ';'?
var path: StringLiteralExpression | null = null;
var 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 !== null) {
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
var name = tn.readIdentifier();
var range = tn.range();
var 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 ';'?
var startPos = tn.tokenPos;
var members: ImportDeclaration[] | null = null;
var namespaceName: IdentifierExpression | null = null;
var 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
var state = tn.mark();
var token = tn.next();
var statement: Statement | null = null;
switch (token) {
case Token.BREAK: {
statement = this.parseBreak(tn);
break;
}
case Token.CONST: {
statement = this.parseVariable(tn, CommonFlags.CONST, null, tn.tokenPos);
break;
}
case Token.CONTINUE: {
statement = this.parseContinue(tn);
break;
}
case Token.DO: {
statement = this.parseDoStatement(tn);
break;
}
case Token.FOR: {
statement = this.parseForStatement(tn);
break;
}
case Token.IF: {
statement = this.parseIfStatement(tn);
break;
}
case Token.LET: {
statement = this.parseVariable(tn, CommonFlags.LET, null, tn.tokenPos);
break;
}
case Token.VAR: {
statement = this.parseVariable(tn, CommonFlags.NONE, null, tn.tokenPos);
break;
}
case Token.OPENBRACE: {
statement = this.parseBlockStatement(tn, topLevel);
break;
}
case Token.RETURN: {
if (topLevel) {
this.error(
DiagnosticCode.A_return_statement_can_only_be_used_within_a_function_body,
tn.range()
); // recoverable
}
statement = this.parseReturn(tn);
break;
}
case Token.SEMICOLON: {
return Node.createEmptyStatement(tn.range(tn.tokenPos));
}
case Token.SWITCH: {
statement = this.parseSwitchStatement(tn);
break;
}
case Token.THROW: {
statement = this.parseThrowStatement(tn);
break;
}
case Token.TRY: {
statement = this.parseTryStatement(tn);
break;
}
case Token.VOID: {
statement = this.parseVoidStatement(tn);
break;
}
case Token.WHILE: {
statement = this.parseWhileStatement(tn);
break;
}
case Token.TYPE: { // also identifier
if (tn.peek(false, IdentifierHandling.PREFER) == Token.IDENTIFIER) {
statement = this.parseTypeDeclaration(tn, CommonFlags.NONE, null, tn.tokenPos);
break;
}
// fall-through
}
default: {
tn.reset(state);
statement = this.parseExpressionStatement(tn);
break;
}
}
if (!statement) { // has been reported
tn.reset(state);
this.skipStatement(tn);
} else {
tn.discard(state);
}
return statement;
}
parseBlockStatement(
tn: Tokenizer,
topLevel: bool
): BlockStatement | null {
// at '{': Statement* '}' ';'?
var startPos = tn.tokenPos;
var statements = new Array();
while (!tn.skip(Token.CLOSEBRACE)) {
let state = tn.mark();
let statement = this.parseStatement(tn, topLevel);
if (!statement) {
if (tn.token == Token.ENDOFFILE) return null;
tn.reset(state);
this.skipStatement(tn);
} else {
tn.discard(state);
statements.push(statement);
}
}
var ret = Node.createBlockStatement(statements, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
return ret;
}
parseBreak(
tn: Tokenizer
): BreakStatement | null {
// at 'break': Identifier? ';'?
var identifier: IdentifierExpression | null = null;
if (tn.peek(true) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) {
tn.next(IdentifierHandling.PREFER);
identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
}
var ret = Node.createBreakStatement(identifier, tn.range());
tn.skip(Token.SEMICOLON);
return ret;
}
parseContinue(
tn: Tokenizer
): ContinueStatement | null {
// at 'continue': Identifier? ';'?
var identifier: IdentifierExpression | null = null;
if (tn.peek(true) == Token.IDENTIFIER && !tn.nextTokenOnNewLine) {
tn.next(IdentifierHandling.PREFER);
identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
}
var ret = Node.createContinueStatement(identifier, tn.range());
tn.skip(Token.SEMICOLON);
return ret;
}
parseDoStatement(
tn: Tokenizer
): DoStatement | null {
// at 'do': Statement 'while' '(' Expression ')' ';'?
var startPos = tn.tokenPos;
var statement = this.parseStatement(tn);
if (!statement) return null;
if (tn.skip(Token.WHILE)) {
if (tn.skip(Token.OPENPAREN)) {
let condition = this.parseExpression(tn);
if (!condition) return null;
if (tn.skip(Token.CLOSEPAREN)) {
let ret = Node.createDoStatement(statement, condition, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
return ret;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
}
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "("
);
}
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "while"
);
}
return null;
}
parseExpressionStatement(
tn: Tokenizer
): ExpressionStatement | null {
// at previous token
var expr = this.parseExpression(tn);
if (!expr) return null;
var ret = Node.createExpressionStatement(expr);
tn.skip(Token.SEMICOLON);
return ret;
}
parseForStatement(
tn: Tokenizer
): Statement | null {
// at 'for': '(' Statement? Expression? ';' Expression? ')' Statement
var startPos = tn.tokenPos;
if (tn.skip(Token.OPENPAREN)) {
let initializer: Statement | null = null;
if (tn.skip(Token.CONST)) {
initializer = this.parseVariable(tn, CommonFlags.CONST, null, tn.tokenPos, 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
var iterable = this.parseExpression(tn);
if (!iterable) return null;
if (!tn.skip(Token.CLOSEPAREN)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
return null;
}
var statement = this.parseStatement(tn);
if (!statement) return null;
return Node.createForOfStatement(
variable,
iterable,
statement,
tn.range(startPos, tn.pos)
);
}
parseIfStatement(
tn: Tokenizer
): IfStatement | null {
// at 'if': '(' Expression ')' Statement ('else' Statement)?
var startPos = tn.tokenPos;
if (tn.skip(Token.OPENPAREN)) {
let condition = this.parseExpression(tn);
if (!condition) return null;
if (tn.skip(Token.CLOSEPAREN)) {
let statement = this.parseStatement(tn);
if (!statement) return null;
let elseStatement: Statement | null = null;
if (tn.skip(Token.ELSE)) {
elseStatement = this.parseStatement(tn);
if (!elseStatement) return null;
}
return Node.createIfStatement(
condition,
statement,
elseStatement,
tn.range(startPos, tn.pos)
);
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
}
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "("
);
}
return null;
}
parseSwitchStatement(
tn: Tokenizer
): SwitchStatement | null {
// at 'switch': '(' Expression ')' '{' SwitchCase* '}' ';'?
var startPos = tn.tokenPos;
if (tn.skip(Token.OPENPAREN)) {
let condition = this.parseExpression(tn);
if (!condition) return null;
if (tn.skip(Token.CLOSEPAREN)) {
if (tn.skip(Token.OPENBRACE)) {
let 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 {
var startPos = tn.tokenPos;
var statements: Statement[],
statement: Statement | null;
// 'case' Expression ':' Statement*
if (tn.skip(Token.CASE)) {
let label = this.parseExpression(tn);
if (!label) return null;
if (tn.skip(Token.COLON)) {
statements = new Array();
while (
tn.peek() != Token.CASE &&
tn.nextToken != Token.DEFAULT &&
tn.nextToken != Token.CLOSEBRACE
) {
statement = this.parseStatement(tn);
if (!statement) return null;
statements.push(statement);
}
return Node.createSwitchCase(label, statements, tn.range(startPos, tn.pos));
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ":"
);
}
// 'default' ':' Statement*
} else if (tn.skip(Token.DEFAULT)) {
if (tn.skip(Token.COLON)) {
statements = new Array();
while (
tn.peek() != Token.CASE &&
tn.nextToken != Token.DEFAULT &&
tn.nextToken != Token.CLOSEBRACE
) {
statement = this.parseStatement(tn);
if (!statement) return null;
statements.push(statement);
}
return Node.createSwitchCase(null, statements, tn.range(startPos, tn.pos));
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ":"
);
}
} else {
this.error(
DiagnosticCode._case_or_default_expected,
tn.range()
);
}
return null;
}
parseThrowStatement(
tn: Tokenizer
): ThrowStatement | null {
// at 'throw': Expression ';'?
var startPos = tn.tokenPos;
var expression = this.parseExpression(tn);
if (!expression) return null;
var ret = Node.createThrowStatement(expression, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
return ret;
}
parseTryStatement(
tn: Tokenizer
): TryStatement | null {
// at 'try':
// '{' Statement* '}'
// ('catch' '(' VariableMember ')' '{' Statement* '}')?
// ('finally' '{' Statement* '}'? ';'?
var startPos = tn.tokenPos;
var stmt: Statement | null;
if (tn.skip(Token.OPENBRACE)) {
let statements = new Array();
while (!tn.skip(Token.CLOSEBRACE)) {
stmt = this.parseStatement(tn);
if (!stmt) return null;
statements.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(
statements,
catchVariable,
catchStatements,
finallyStatements,
tn.range(startPos, tn.pos)
);
tn.skip(Token.SEMICOLON);
return ret;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "{"
);
}
return null;
}
parseTypeDeclaration(
tn: Tokenizer,
flags: CommonFlags,
decorators: DecoratorNode[] | null,
startPos: i32
): TypeDeclaration | null {
// at 'type': Identifier ('')? '=' Type ';'?
if (tn.skipIdentifier()) {
let name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
let typeParameters: TypeParameterNode[] | null = null;
if (tn.skip(Token.LESSTHAN)) {
typeParameters = this.parseTypeParameters(tn);
if (!typeParameters) return null;
flags |= CommonFlags.GENERIC;
}
if (tn.skip(Token.EQUALS)) {
let type = this.parseType(tn);
if (!type) return null;
let ret = Node.createTypeDeclaration(
name,
decorators,
flags,
typeParameters,
type,
tn.range(startPos, tn.pos)
);
tn.skip(Token.SEMICOLON);
return ret;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "="
);
}
} else {
this.error(
DiagnosticCode.Identifier_expected,
tn.range()
);
}
return null;
}
parseVoidStatement(
tn: Tokenizer
): VoidStatement | null {
// at 'void': Expression ';'?
var startPos = tn.tokenPos;
var expression = this.parseExpression(tn, Precedence.GROUPING);
if (!expression) return null;
var ret = Node.createVoidStatement(expression, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
return ret;
}
parseWhileStatement(
tn: Tokenizer
): WhileStatement | null {
// at 'while': '(' Expression ')' Statement ';'?
var startPos = tn.tokenPos;
if (tn.skip(Token.OPENPAREN)) {
let expression = this.parseExpression(tn);
if (!expression) return null;
if (tn.skip(Token.CLOSEPAREN)) {
let statement = this.parseStatement(tn);
if (!statement) return null;
let ret = Node.createWhileStatement(expression, statement, tn.range(startPos, tn.pos));
tn.skip(Token.SEMICOLON);
return ret;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), ")"
);
}
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "("
);
}
return null;
}
// expressions
parseExpressionStart(
tn: Tokenizer
): Expression | null {
var token = tn.next(IdentifierHandling.PREFER);
var 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.UNARY_PREFIX);
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.UNARY_PREFIX);
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)) !== null
) {
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.ARROW_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: {
if (
!tn.skip(Token.COLON) &&
!tn.skip(Token.EQUALS_GREATERTHAN)
) {
again = false;
break;
}
// fall-through
}
// function expression
case Token.COLON: { // type annotation
tn.reset(state);
return this.parseFunctionExpression(tn);
}
// optional parameter or parenthesized
case Token.QUESTION: {
if (
tn.skip(Token.COLON) || // optional parameter with type
tn.skip(Token.COMMA) || // optional parameter without type
tn.skip(Token.CLOSEPAREN) // last optional parameter without type
) {
tn.reset(state);
return this.parseFunctionExpression(tn);
}
again = false; // parenthesized
break;
}
case Token.COMMA: {
break; // continue
}
// parenthesized expression
// case Token.EQUALS: // missing type annotation for simplicity
default: {
again = false;
break;
}
}
break;
}
// parenthesized expression
default: {
again = false;
break;
}
}
} while (again);
tn.reset(state);
// parse parenthesized
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(true) == Token.EQUALS_GREATERTHAN && !tn.nextTokenOnNewLine) {
return this.parseFunctionExpressionCommon(
tn,
Node.createEmptyIdentifierExpression(tn.range(startPos)),
[
Node.createParameter(
ParameterKind.DEFAULT,
identifier,
Node.createOmittedType(identifier.range.atEnd),
null,
identifier.range
)
],
null,
ArrowKind.ARROW_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 '' '('
var state = tn.mark();
if (!tn.skip(Token.LESSTHAN)) return null;
var start = tn.tokenPos;
var 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)*)? ')'
var 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);
var expr = this.parseExpressionStart(tn);
if (!expr) return null;
var startPos = expr.range.start;
// precedence climbing
// see: http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing
var 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: {
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 '`': ... '`'
var startPos = tag ? tag.range.start : tn.tokenPos;
var parts = new Array();
var rawParts = new Array();
var 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 {
var 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 {
var typeArguments: TypeNode[] | null = null;
while (
tn.skip(Token.OPENPAREN) ||
potentiallyGeneric &&
(typeArguments = this.tryParseTypeArgumentsBeforeArguments(tn)) !== null
) {
let args = this.parseArguments(tn);
if (!args) break;
expr = Node.createCallExpression( // is again callable
expr,
typeArguments,
args,
tn.range(expr.range.start, tn.pos)
);
potentiallyGeneric = false;
}
return expr;
}
/** Skips over a statement on errors in an attempt to reduce unnecessary diagnostic noise. */
skipStatement(tn: Tokenizer): void {
tn.peek(true);
if (tn.nextTokenOnNewLine) tn.next(); // if reset() to the previous line
do {
let nextToken = tn.peek(true);
if (
nextToken == Token.ENDOFFILE || // next step should handle this
nextToken == Token.SEMICOLON // end of the statement for sure
) {
tn.next();
break;
}
if (tn.nextTokenOnNewLine) break; // end of the statement maybe
switch (tn.next()) {
case Token.IDENTIFIER: {
tn.readIdentifier();
break;
}
case Token.STRINGLITERAL:
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 '{': ... '}'
var depth = 1;
var again = true;
do {
switch (tn.next()) {
case Token.ENDOFFILE: {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
again = false;
break;
}
case Token.OPENBRACE: {
++depth;
break;
}
case Token.CLOSEBRACE: {
--depth;
if (!depth) again = false;
break;
}
case Token.IDENTIFIER: {
tn.readIdentifier();
break;
}
case Token.STRINGLITERAL:{
tn.readString();
break;
}
case Token.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,
LOGICAL_OR,
LOGICAL_AND,
BITWISE_OR,
BITWISE_XOR,
BITWISE_AND,
EQUALITY,
RELATIONAL,
SHIFT,
ADDITIVE,
MULTIPLICATIVE,
EXPONENTIATED,
UNARY_PREFIX,
UNARY_POSTFIX,
CALL,
MEMBERACCESS,
GROUPING
}
/** Determines the precende of a non-starting token. */
function determinePrecedence(kind: Token): Precedence {
switch (kind) {
case Token.COMMA: return Precedence.COMMA;
case Token.EQUALS:
case Token.PLUS_EQUALS:
case Token.MINUS_EQUALS:
case Token.ASTERISK_ASTERISK_EQUALS:
case Token.ASTERISK_EQUALS:
case Token.SLASH_EQUALS:
case Token.PERCENT_EQUALS:
case Token.LESSTHAN_LESSTHAN_EQUALS:
case Token.GREATERTHAN_GREATERTHAN_EQUALS:
case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS:
case Token.AMPERSAND_EQUALS:
case Token.CARET_EQUALS:
case Token.BAR_EQUALS: return Precedence.ASSIGNMENT;
case Token.QUESTION: return Precedence.CONDITIONAL;
case Token.BAR_BAR: return Precedence.LOGICAL_OR;
case Token.AMPERSAND_AMPERSAND: return Precedence.LOGICAL_AND;
case Token.BAR: return Precedence.BITWISE_OR;
case Token.CARET: return Precedence.BITWISE_XOR;
case Token.AMPERSAND: return Precedence.BITWISE_AND;
case Token.EQUALS_EQUALS:
case Token.EXCLAMATION_EQUALS:
case Token.EQUALS_EQUALS_EQUALS:
case Token.EXCLAMATION_EQUALS_EQUALS: return Precedence.EQUALITY;
case Token.AS:
case Token.IN:
case Token.INSTANCEOF:
case Token.LESSTHAN:
case Token.GREATERTHAN:
case Token.LESSTHAN_EQUALS:
case Token.GREATERTHAN_EQUALS: return Precedence.RELATIONAL;
case Token.LESSTHAN_LESSTHAN:
case Token.GREATERTHAN_GREATERTHAN:
case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: return Precedence.SHIFT;
case Token.PLUS:
case Token.MINUS: return Precedence.ADDITIVE;
case Token.ASTERISK:
case Token.SLASH:
case Token.PERCENT: return Precedence.MULTIPLICATIVE;
case Token.ASTERISK_ASTERISK: return Precedence.EXPONENTIATED;
case Token.PLUS_PLUS:
case Token.MINUS_MINUS: return Precedence.UNARY_POSTFIX;
case Token.DOT:
case Token.NEW:
case Token.OPENBRACKET:
case Token.EXCLAMATION: return Precedence.MEMBERACCESS;
}
return Precedence.NONE;
}