@ibyar/expressions
Version:
Aurora expression, an template expression and evaluation, An 100% spec compliant ES2022 JavaScript toolchain,
962 lines • 47.9 kB
JavaScript
import { Class } from '../api/class/class.js';
import { Literal } from '../api/definition/values.js';
import { ClassLiteralProperty, ClassLiteralPropertyKind, classPropertyKindFor, createClassInfo, FunctionKind, FunctionSyntaxKind, isAccessor, ParseFunctionFlag, PropertyKind, PropertyKindInfo, PropertyPosition, VariableDeclarationContext } from './enums.js';
import { JavaScriptInlineParser } from './inline.js';
import { TokenStream } from './stream.js';
import { Token } from './token.js';
import { ExpressionNodeFactory, ExpressionNodeSourcePosition } from './factory.js';
import { isStrict, LanguageMode } from './language.js';
export class JavaScriptParser extends JavaScriptInlineParser {
/**
* parser js with
* @param source
* @returns
*/
static parse(source, { mode, factory, addLocation } = {}) {
mode ??= LanguageMode.Strict;
const stream = (typeof source === 'string') || Array.isArray(source)
? TokenStream.getTokenStream(source, mode) : source;
if (factory == undefined) {
const sourcePositionFactory = addLocation && typeof source === 'string' ? new ExpressionNodeSourcePosition(source) : undefined;
factory = new ExpressionNodeFactory(sourcePositionFactory);
}
const parser = new JavaScriptParser(stream, factory, false);
return parser.scan();
}
/**
* parse inline code like that used in html 2 or 1 way binding
*/
static parseScript(source, options) {
return JavaScriptInlineParser.parse(source, options);
}
scan() {
const range = this.createStartPosition();
const isModule = isStrict(this.languageMode);
if (isModule) {
this.setFunctionKind(FunctionKind.Module);
}
const body = isModule ? this.parseModuleItemList() : this.parseStatementList(Token.EOS);
this.updateRangeEnd(range);
return this.factory.createProgram(isModule ? 'module' : 'script', body, range);
}
parseNewTargetExpression(start) {
this.consume(Token.PERIOD);
const target = this.parsePropertyOrPrivatePropertyName();
if (target.toString() !== 'target') {
throw new Error(this.errorMessage(`Expression (new.${target.toString()}) not supported.`));
}
return this.factory.createMetaProperty(this.factory.createIdentifier('new', start?.range), this.factory.createIdentifier('target', target.range), this.createRange(start));
}
parseClassExpression() {
this.consume(Token.CLASS);
let name;
let isStrictReserved = false;
if (this.peekAnyIdentifier()) {
name = this.parseAndClassifyIdentifier(this.next());
isStrictReserved = Token.isStrictReservedWord(this.current().token);
}
const classLiteral = this.parseClassLiteral(name, isStrictReserved);
return this.factory.createClassExpression(classLiteral.getBody(), classLiteral.getDecorators(), classLiteral.getId(), classLiteral.getSuperClass());
}
parseClassDeclaration(names, defaultExport, start) {
// ClassDeclaration ::
// 'class' Identifier ('extends' LeftHandExpression)? '{' ClassBody '}'
// 'class' ('extends' LeftHandExpression)? '{' ClassBody '}'
//
// The anonymous form is allowed iff [default_export] is true.
//
// 'class' is expected to be consumed by the caller.
//
// A ClassDeclaration
//
// class C { ... }
//
// has the same semantics as:
//
// let C = class C { ... };
//
// so rewrite it as such.
const nextToken = this.peek().token;
const isStrictReserved = Token.isStrictReservedWord(nextToken);
let name;
let variableName;
if (defaultExport && (nextToken == Token.EXTENDS || nextToken == Token.LBRACE)) {
name = new Literal('default');
variableName = '.default';
}
else {
const identifier = this.parseIdentifier();
variableName = identifier.getName();
name = identifier;
}
const classLiteral = this.parseClassLiteral(name, isStrictReserved);
const range = this.createRange(start);
return this.factory.createClassDeclaration(classLiteral.getBody(), classLiteral.getDecorators(), classLiteral.getId(), classLiteral.getSuperClass(), range);
}
parseClassLiteral(name, nameIsStrictReserved) {
const isAnonymous = !!!name;
// All parts of a ClassDeclaration and ClassExpression are strict code.
if (!isAnonymous) {
if (nameIsStrictReserved) {
throw new Error(this.errorMessage(`Unexpected Strict Reserved class name`));
}
if (this.isEvalOrArguments(name)) {
throw new Error(this.errorMessage(`Strict Eval Arguments not allowed for class name`));
}
}
const classInfo = createClassInfo();
classInfo.isAnonymous = isAnonymous;
if (this.check(Token.EXTENDS)) {
classInfo.extends = this.parseLeftHandSideExpression();
}
const bodyStart = this.expect(Token.LBRACE);
const hasExtends = !!classInfo.extends;
while (this.peek().isNotType(Token.RBRACE)) {
if (this.check(Token.SEMICOLON))
continue;
// Either we're parsing a `static { }` initialization block or a property.
if (this.peek().isType(Token.STATIC) && this.peekAhead().isType(Token.LBRACE)) {
const staticBlock = this.parseClassStaticBlock(classInfo);
classInfo.staticElements.push(staticBlock);
classInfo.hasStaticBlocks = true;
continue;
}
// FuncNameInferrerState fni_state(& fni_);
// If we haven't seen the constructor yet, it potentially is the next
// property.
let isConstructor = !classInfo.hasSeenConstructor;
const propInfo = new PropertyKindInfo();
propInfo.position = PropertyPosition.ClassLiteral;
const property = this.parseClassPropertyDefinition(classInfo, propInfo, hasExtends);
// if (has_error()) return impl() . FailureExpression();
const propertyKind = classPropertyKindFor(propInfo.kind);
if (!classInfo.hasStaticComputedNames && propInfo.isStatic && propInfo.isComputedName) {
classInfo.hasStaticComputedNames = true;
}
isConstructor &&= classInfo.hasSeenConstructor;
const isField = propertyKind == ClassLiteralPropertyKind.FIELD;
if (propInfo.isPrivate) {
if (isConstructor) {
throw new Error(this.errorMessage('private constructor is not allowed'));
}
classInfo.requiresBrand ||= (!isField && !propInfo.isStatic);
const isMethod = propertyKind == ClassLiteralPropertyKind.METHOD;
classInfo.hasPrivateMethods ||= isMethod;
classInfo.hasStaticPrivateMethods ||= isMethod && propInfo.isStatic;
this.declarePrivateClassMember(propInfo.name, property, propertyKind, propInfo.isStatic, classInfo);
continue;
}
if (isField) {
if (propInfo.isComputedName) {
classInfo.computedFieldCount++;
}
this.declarePublicClassField(property, propInfo.isStatic, propInfo.isComputedName, classInfo);
continue;
}
this.declarePublicClassMethod(name, property, isConstructor, classInfo);
}
const bodyEnd = this.expect(Token.RBRACE);
const classBody = this.rewriteClassLiteral(classInfo, name, [bodyStart.range[0], bodyEnd.range[1]]);
return new Class(classBody, [], name, classInfo.extends);
}
declarePublicClassMethod(name, property, isConstructor, classInfo) {
// throw new Error('Method not implemented.');
if (isConstructor) {
if (classInfo.constructor) {
throw new SyntaxError(this.errorMessage('A class may only have one constructor.'));
}
classInfo.constructor = property;
// set the class name as the constructor name
Reflect.set(classInfo.constructor, 'id', name);
return;
}
classInfo.publicMembers.push(property);
}
declarePublicClassField(property, isStatic, isComputedName, classInfo) {
if (isStatic) {
classInfo.staticElements.push(property);
}
else {
classInfo.instanceFields.push(property);
}
// if (isComputedName) {
// classInfo.publicMembers.push(property);
// }
}
declarePrivateClassMember(propertyName, property, kind, isStatic, classInfo) {
classInfo.privateMembers.push(property);
}
// protected createPrivateNameVariable(mode: VariableMode, staticFlag: StaticFlag, propertyName: string) {
// throw new Error('Method not implemented.');
// }
parseClassPropertyDefinition(classInfo, propInfo, hasExtends) {
if (!classInfo) {
throw new Error(this.errorMessage('class info is undefined'));
}
if (propInfo.position !== PropertyPosition.ClassLiteral) {
throw new Error(this.errorMessage('expected property position ClassLiteral'));
}
const nameToken = this.peek();
let nameExpression;
if (nameToken.isType(Token.STATIC)) {
this.consume(Token.STATIC);
if (this.peek().isType(Token.LPAREN)) {
propInfo.kind = PropertyKind.Method;
nameExpression = this.parseIdentifier();
propInfo.name = nameExpression.getName();
}
else if (this.peek().isType(Token.ASSIGN)
|| this.peek().isType(Token.SEMICOLON)
|| this.peek().isType(Token.RBRACK)) {
nameExpression = this.parseIdentifier();
propInfo.name = nameExpression.getName();
}
else {
propInfo.isStatic = true;
nameExpression = this.parseProperty(propInfo);
}
}
else {
nameExpression = this.parseProperty(propInfo);
}
switch (propInfo.kind) {
case PropertyKind.Assign:
case PropertyKind.ClassField:
case PropertyKind.ShorthandOrClassField:
case PropertyKind.NotSet: {
// This case is a name followed by a
// name or other property. Here we have
// to assume that's an uninitialized
// field followed by a line break
// followed by a property, with ASI
// adding the semicolon. If not, there
// will be a syntax error after parsing
// the first name as an uninitialized
// field.
propInfo.kind = PropertyKind.ClassField;
// if (!propInfo.isComputedName) {
// this.checkClassFieldName(propInfo.name, propInfo.isStatic);
// }
const initializer = this.parseMemberInitializer(classInfo, propInfo.isStatic);
this.expectSemicolon();
const result = this.newClassLiteralProperty(nameExpression, initializer, ClassLiteralPropertyKind.FIELD, propInfo.isStatic, propInfo.isComputedName, propInfo.isPrivate, propInfo.rangeStart);
this.setFunctionNameFromPropertyName(result, propInfo.name);
return result;
}
case PropertyKind.Method: {
// MethodDefinition
// PropertyName '(' StrictFormalParameters ')' '{' FunctionBody '}'
// '*' PropertyName '(' StrictFormalParameters ')' '{' FunctionBody '}'
// async PropertyName '(' StrictFormalParameters ')'
// '{' FunctionBody '}'
// async '*' PropertyName '(' StrictFormalParameters ')'
// '{' FunctionBody '}'
if (!propInfo.isComputedName) {
this.checkClassMethodName(propInfo, classInfo);
}
let kind = this.methodKindFor(propInfo.isStatic, propInfo.funcFlag);
if (!propInfo.isStatic && propInfo.name.toString() === 'constructor') {
classInfo.hasSeenConstructor = true;
kind = hasExtends ? FunctionKind.DerivedConstructor : FunctionKind.BaseConstructor;
}
const value = this.parseFunctionLiteral(kind, FunctionSyntaxKind.AccessorOrMethod, nameExpression, propInfo.rangeStart);
const result = this.newClassLiteralProperty(nameExpression, value, ClassLiteralProperty.Kind.METHOD, propInfo.isStatic, propInfo.isComputedName, propInfo.isPrivate, propInfo.rangeStart);
this.setFunctionNameFromPropertyName(result, propInfo.name);
return result;
}
case PropertyKind.AccessorGetter:
case PropertyKind.AccessorSetter: {
if (propInfo.funcFlag !== ParseFunctionFlag.IsNormal) {
throw new Error(this.errorMessage('accessor is not normal function'));
}
const isGet = propInfo.kind == PropertyKind.AccessorGetter;
if (!propInfo.isComputedName) {
this.checkClassMethodName(propInfo, classInfo);
// Make sure the name expression is a string since we need a Name for
// Runtime_DefineAccessorPropertyUnchecked and since we can determine
// this statically we can skip the extra runtime check.
nameExpression = new Literal(propInfo.name);
}
let kind;
if (propInfo.isStatic) {
kind = isGet ? FunctionKind.StaticGetterFunction
: FunctionKind.StaticSetterFunction;
}
else {
kind = isGet ? FunctionKind.GetterFunction
: FunctionKind.SetterFunction;
}
const value = this.parseFunctionLiteral(kind, FunctionSyntaxKind.AccessorOrMethod, nameExpression, propInfo.rangeStart);
const propertyKind = isGet ? ClassLiteralProperty.Kind.GETTER : ClassLiteralProperty.Kind.SETTER;
const result = this.newClassLiteralProperty(nameExpression, value, propertyKind, propInfo.isStatic, propInfo.isComputedName, propInfo.isPrivate, propInfo.rangeStart);
const prefix = isGet ? 'get ' : 'set ';
this.setFunctionNameFromPropertyName(result, propInfo.name, prefix);
return result;
}
case PropertyKind.Value:
case PropertyKind.Shorthand:
case PropertyKind.Spread:
// throw new Error(this.errorMessage('Report Unexpected Token'));
return this.factory.createNull(propInfo.rangeStart?.range);
}
throw new Error(this.errorMessage('UNREACHABLE'));
}
checkClassMethodName(propInfo, classInfo) {
if (!(propInfo.kind == PropertyKind.Method || isAccessor(propInfo.kind))) {
throw new Error(this.errorMessage('not kind of method or setter or getter'));
}
if (propInfo.isPrivate && propInfo.name.toString() === 'constructor') {
throw new Error(this.errorMessage('constructor is private'));
}
else if (propInfo.isStatic && propInfo.name.toString() === 'prototype') {
throw new Error(this.errorMessage('static prototype'));
}
else if (propInfo.name.toString() === 'constructor') {
if (propInfo.funcFlag !== ParseFunctionFlag.IsNormal || isAccessor(propInfo.kind)) {
if (propInfo.funcFlag === ParseFunctionFlag.IsGenerator) {
throw new Error(this.errorMessage('constructor is generator'));
}
else if (propInfo.funcFlag === ParseFunctionFlag.IsAsync) {
throw new Error(this.errorMessage('constructor is async'));
}
if (classInfo.hasSeenConstructor) {
throw new Error(this.errorMessage('duplicate constructor'));
}
}
classInfo.hasSeenConstructor = true;
}
}
parseClassStaticBlock(classInfo) {
this.consume(Token.STATIC);
// Each static block has its own var and lexical scope, so make a new var
// block scope instead of using the synthetic members initializer function
// scope.
this.setStatue(true, FunctionKind.ClassStaticInitializerFunction);
const block = this.parseBlock();
this.restoreStatue();
classInfo.hasStaticElements = true;
return this.factory.createClassStaticBlockDeclaration(block.getBody(), block.range);
}
newClassLiteralProperty(nameExpression, initializer, kind, isStatic, isComputedName, isPrivate, start) {
const range = this.createRange(start);
switch (kind) {
case ClassLiteralPropertyKind.METHOD:
if (nameExpression.toString() === 'constructor') {
return this.factory.createMethodSignature('constructor', nameExpression, initializer, [], isComputedName, isStatic, range);
}
return this.factory.createMethodSignature('method', nameExpression, initializer, [], isComputedName, isStatic, range);
case ClassLiteralPropertyKind.GETTER:
return this.factory.createMethodSignature('get', nameExpression, initializer, [], isComputedName, isStatic, range);
case ClassLiteralPropertyKind.SETTER:
return this.factory.createMethodSignature('set', nameExpression, initializer, [], isComputedName, isStatic, range);
case ClassLiteralPropertyKind.FIELD:
return this.factory.createPropertySignature(nameExpression, [], isComputedName, isStatic, initializer, range);
default:
break;
}
throw new Error(this.errorMessage('UNREACHABLE'));
}
parseMemberInitializer(classInfo, isStatic) {
const kind = isStatic
? FunctionKind.ClassStaticInitializerFunction
: FunctionKind.ClassMembersInitializerFunction;
let initializer = undefined;
if (this.check(Token.ASSIGN)) {
this.setStatue(true, kind);
initializer = this.parseAssignmentExpression();
this.restoreStatue();
}
if (isStatic) {
classInfo.hasStaticElements = true;
}
else {
classInfo.hasInstanceMembers = true;
}
return initializer;
}
setFunctionNameFromPropertyName(property, name, prefix) {
// TODO: no need for this now
// check later when needed
// the ClassExpression* handle this
}
parseSuperExpression() {
const superToken = this.consume(Token.SUPER);
if (Token.isProperty(this.peek().token)) {
if (this.peek().isType(Token.PERIOD) && this.peekAhead().isType(Token.PRIVATE_NAME)) {
this.consume(Token.PERIOD);
this.consume(Token.PRIVATE_NAME);
throw new Error(this.errorMessage('Unexpected Private Field'));
}
if (this.peek().isType(Token.QUESTION_PERIOD)) {
this.consume(Token.QUESTION_PERIOD);
throw new Error(this.errorMessage('Optional Chaining No Super'));
}
return this.factory.createSuper(superToken.range);
}
throw new Error(this.errorMessage('Unexpected Super'));
}
rewriteClassLiteral(classInfo, name, range) {
const body = [];
if (classInfo.hasSeenConstructor) {
body.push(classInfo.constructor);
}
body.push(...classInfo.staticElements);
body.push(...classInfo.instanceFields);
body.push(...classInfo.publicMembers);
body.push(...classInfo.privateMembers);
return this.factory.createClassBody(body, range);
}
parseModuleItemList() {
// ecma262/#prod-Module
// Module :
// ModuleBody?
//
// ecma262/#prod-ModuleItemList
// ModuleBody :
// ModuleItem*
const body = [];
while (this.peek().isNotType(Token.EOS)) {
const stat = this.parseModuleItem();
if (!stat) {
break;
}
if (this.factory.isEmptyStatement(stat)) {
continue;
}
body.push(stat);
}
return body;
}
parseModuleItem() {
// ecma262/#prod-ModuleItem
// ModuleItem :
// ImportDeclaration
// ExportDeclaration
// StatementListItem
const next = this.peek();
if (next.isType(Token.EXPORT)) {
return this.parseExportDeclaration();
}
if (next.isType(Token.IMPORT)) {
// We must be careful not to parse a dynamic import expression as an import
// declaration. Same for import.meta expressions.
const peekAhead = this.peekAhead();
if (peekAhead.isNotType(Token.LPAREN) && peekAhead.isNotType(Token.PERIOD)) {
return this.parseImportDeclaration();
// return factory() -> EmptyStatement();
}
}
return this.parseStatementListItem();
}
parseExportDeclaration() {
// ExportDeclaration:
// 'export' '*' 'from' ModuleSpecifier ';'
// 'export' '*' 'from' ModuleSpecifier [no LineTerminator here]
// AssertClause ';'
// 'export' '*' 'as' IdentifierName 'from' ModuleSpecifier ';'
// 'export' '*' 'as' IdentifierName 'from' ModuleSpecifier
// [no LineTerminator here] AssertClause ';'
// 'export' '*' 'as' ModuleExportName 'from' ModuleSpecifier ';'
// 'export' '*' 'as' ModuleExportName 'from' ModuleSpecifier ';'
// [no LineTerminator here] AssertClause ';'
// 'export' ExportClause ('from' ModuleSpecifier)? ';'
// 'export' ExportClause ('from' ModuleSpecifier [no LineTerminator here]
// AssertClause)? ';'
// 'export' VariableStatement
// 'export' Declaration
// 'export' 'default' ... (handled in ParseExportDefault)
//
// ModuleExportName :
// StringLiteral
const start = this.expect(Token.EXPORT);
let result;
const names = [];
// Statement * result = nullptr;
// ZonePtrList <const AstRawString> names(1, zone());
// Scanner::Location loc = scanner() -> peek_location();
switch (this.peek().token) {
case Token.DEFAULT:
return this.parseExportDefault(start);
case Token.MUL:
return this.parseExportStar(start);
case Token.LBRACE: {
// There are two cases here:
//
// 'export' ExportClause ';'
// and
// 'export' ExportClause FromClause ';'
//
// In the first case, the exported identifiers in ExportClause must
// not be reserved words, while in the latter they may be. We
// pass in a location that gets filled with the first reserved word
// encountered, and then throw a SyntaxError if we are in the
// non-FromClause case.
// Scanner::Location reserved_loc = Scanner:: Location:: invalid();
// Scanner::Location string_literal_local_name_loc =
// Scanner:: Location:: invalid();
// ZoneChunkList < ExportClauseData >* export_data =
// ParseExportClause(& reserved_loc, & string_literal_local_name_loc);
const exportData = this.parseExportClause();
let moduleSpecifier;
let importAssertions;
if (this.checkContextualKeyword('from')) {
// Scanner::Location specifier_loc = scanner() -> peek_location();
moduleSpecifier = this.parseModuleSpecifier();
importAssertions = this.parseImportAssertClause();
this.expectSemicolon();
// if (exportData.isEmpty()) {
// // module() -> AddEmptyImport(module_specifier, import_assertions, specifier_loc, zone());
// } else {
// // for (const ExportClauseData& data : * export_data) {
// // module() -> AddExport(data.local_name, data.export_name,
// // module_specifier, import_assertions,
// // data.location, specifier_loc, zone());
// // }
// }
}
else {
// if (reserved_loc.IsValid()) {
// // No FromClause, so reserved words are invalid in ExportClause.
// ReportMessageAt(reserved_loc, MessageTemplate:: kUnexpectedReserved);
// return nullptr;
// } else if (string_literal_local_name_loc.IsValid()) {
// ReportMessageAt(string_literal_local_name_loc,
// MessageTemplate:: kModuleExportNameWithoutFromClause);
// return nullptr;
// }
this.expectSemicolon();
// for (const ExportClauseData& data : * export_data) {
// module() -> AddExport(data.local_name, data.export_name, data.location,
// zone());
// }
}
return this.factory.createNamespaceExportDeclaration(exportData, undefined, moduleSpecifier, importAssertions, this.createRange(start));
// return factory() -> EmptyStatement();
}
case Token.FUNCTION: {
const declaration = this.parseFunctionDeclaration();
const identifier = declaration.getId();
const specifier = this.factory.createExportSpecifier(identifier, identifier, identifier.range);
result = this.factory.createNamespaceExportDeclaration([specifier], declaration, undefined, undefined, this.createRange(start));
break;
}
case Token.CLASS: {
const classToken = this.consume(Token.CLASS);
const declaration = this.parseClassDeclaration(names, false, classToken);
const identifier = declaration.getId();
const specifier = this.factory.createExportSpecifier(identifier, identifier, identifier.range);
result = this.factory.createNamespaceExportDeclaration([specifier], declaration, undefined, undefined, this.createRange(start));
break;
}
case Token.VAR:
case Token.LET:
case Token.CONST:
const declaration = this.parseVariableStatement(VariableDeclarationContext.StatementListItem, names);
const specifiers = declaration.getDeclarations()
.map(node => this.factory.createExportSpecifier(node.getId(), node.getId(), node.getId().range));
result = this.factory.createNamespaceExportDeclaration(specifiers, declaration, undefined, undefined, this.createRange(start));
break;
case Token.ASYNC:
const asyncToken = this.consume(Token.ASYNC);
if (this.peek().isType(Token.FUNCTION) && !this.scanner.hasLineTerminatorBeforeNext()) {
const declaration = this.parseAsyncFunctionDeclaration(names, false, asyncToken);
const identifier = declaration.getId();
const specifier = this.factory.createExportSpecifier(identifier, identifier, identifier.range);
result = this.factory.createNamespaceExportDeclaration([specifier], declaration, undefined, undefined, this.createRange(start));
break;
}
default:
throw new SyntaxError(this.errorMessage('Unexpected Token'));
}
// loc.end_pos = scanner() -> location().end_pos;
// SourceTextModuleDescriptor * descriptor = module();
// for (const AstRawString* name : names) {
// descriptor -> AddExport(name, name, loc, zone());
// }
return result;
}
parseImportDeclaration() {
// ImportDeclaration :
// 'import' ImportClause 'from' ModuleSpecifier ';'
// 'import' ModuleSpecifier ';'
// 'import' ImportClause 'from' ModuleSpecifier [no LineTerminator here]
// AssertClause ';'
// 'import' ModuleSpecifier [no LineTerminator here] AssertClause';'
//
// ImportClause :
// ImportedDefaultBinding
// NameSpaceImport
// NamedImports
// ImportedDefaultBinding ',' NameSpaceImport
// ImportedDefaultBinding ',' NamedImports
//
// NameSpaceImport :
// '*' 'as' ImportedBinding
const start = this.expect(Token.IMPORT);
const tok = this.peek();
// 'import' ModuleSpecifier ';'
if (tok.isType(Token.STRING)) {
// Scanner::Location specifier_loc = scanner() -> peek_location();
const moduleSpecifier = this.parseModuleSpecifier();
const importAssertions = this.parseImportAssertClause();
this.expectSemicolon();
// module() -> AddEmptyImport(module_specifier, import_assertions, specifier_loc,zone());
return this.factory.createImportDeclaration(moduleSpecifier, undefined, importAssertions, this.createRange(start));
}
// Parse ImportedDefaultBinding if present.
let importDefaultBinding;
// Scanner::Location import_default_binding_loc;
if (tok.isNotType(Token.MUL) && tok.isNotType(Token.LBRACE)) {
importDefaultBinding = this.parseNonRestrictedIdentifier();
// DeclareUnboundVariable(import_default_binding, VariableMode:: kConst,kNeedsInitialization, pos);
}
// Parse NameSpaceImport or NamedImports if present.
let moduleNamespaceBinding;
let namedImports;
// Scanner::Location module_namespace_binding_loc;
// const ZonePtrList<const NamedImport >* named_imports = nullptr;
if (importDefaultBinding == undefined || this.check(Token.COMMA)) {
switch (this.peek().token) {
case Token.MUL: {
this.consume(Token.MUL);
this.expectContextualKeyword('as');
moduleNamespaceBinding = this.parseNonRestrictedIdentifier();
// ExpectContextualKeyword(ast_value_factory() -> as_string());
// module_namespace_binding = ParseNonRestrictedIdentifier();
// module_namespace_binding_loc = scanner() -> location();
// DeclareUnboundVariable(module_namespace_binding, VariableMode:: kConst, kCreatedInitialized, pos);
break;
}
case Token.LBRACE:
namedImports = this.parseNamedImports();
break;
default:
throw new SyntaxError(this.errorMessage('Unexpected Token'));
}
}
this.expectContextualKeyword('from');
// ExpectContextualKeyword(ast_value_factory() -> from_string());
// Scanner::Location specifier_loc = scanner() -> peek_location();
const moduleSpecifier = this.parseModuleSpecifier();
const importAssertions = this.parseImportAssertClause();
this.expectSemicolon();
// Now that we have all the information, we can make the appropriate
// declarations.
// TODO(neis): Would prefer to call DeclareVariable for each case below rather
// than above and in ParseNamedImports, but then a possible error message
// would point to the wrong location. Maybe have a DeclareAt version of
// Declare that takes a location?
const specifiers = [];
if (moduleNamespaceBinding) {
specifiers.push(this.factory.createImportNamespaceSpecifier(moduleNamespaceBinding, moduleNamespaceBinding.range));
}
if (importDefaultBinding) {
specifiers.push(this.factory.createImportDefaultSpecifier(importDefaultBinding, importDefaultBinding.range));
}
if (namedImports?.length) {
specifiers.push(...namedImports);
}
return this.factory.createImportDeclaration(moduleSpecifier, specifiers.length ? specifiers : undefined, importAssertions, this.createRange(start));
}
parseImportExpressions() {
const start = this.consume(Token.IMPORT);
if (this.check(Token.PERIOD)) {
const peek = this.peek();
this.expectContextualKeyword('meta');
return this.factory.createMetaProperty(this.factory.createIdentifier('import', start.range), this.factory.createIdentifier('meta', peek.range), this.createRange(start));
}
if (this.peek().isNotType(Token.LPAREN)) {
throw new SyntaxError(this.errorMessage('Unexpected Token'));
}
this.consume(Token.LPAREN);
if (this.peek().isType(Token.RPAREN)) {
throw new SyntaxError(this.errorMessage('Import Missing Specifier'));
}
this.setAcceptIN(true);
const specifier = this.parseAssignmentExpressionCoverGrammar();
if (this.check(Token.COMMA)) {
if (this.check(Token.RPAREN)) {
// A trailing comma allowed after the specifier.
return this.factory.createImportExpression(specifier, undefined, this.createRange(start));
}
else {
const importAssertions = this.parseAssignmentExpressionCoverGrammar();
this.check(Token.COMMA); // A trailing comma is allowed after the import
// assertions.
this.expect(Token.RPAREN);
return this.factory.createImportExpression(specifier, importAssertions, this.createRange(start));
}
}
this.expect(Token.RPAREN);
this.restoreAcceptIN();
return this.factory.createImportExpression(specifier, undefined, this.createRange(start));
}
parseVariableStatement(varContext, names) {
// VariableStatement ::
// VariableDeclarations ';'
// The scope of a var declared variable anywhere inside a function
// is the entire function (ECMA-262, 3rd, 10.1.3, and 12.2). Thus we can
// transform a source-level var declaration into a (Function) Scope
// declaration, and rewrite the source-level initialization into an assignment
// statement. We use a block to collect multiple assignments.
//
// We mark the block as initializer block because we don't want the
// rewriter to add a '.result' assignment to such a block (to get compliant
// behavior for code such as print(eval('var x = 7')), and for cosmetic
// reasons when pretty-printing. Also, unless an assignment (initialization)
// is inside an initializer block, it is ignored.
const declarations = this.parseVariableDeclarations(varContext);
this.expectSemicolon();
names.push(...declarations.getDeclarations().map(d => d.getId().toString()));
return declarations;
}
parseImportAssertClause() {
// AssertClause :
// assert '{' '}'
// assert '{' AssertEntries '}'
// AssertEntries :
// IdentifierName: AssertionKey
// IdentifierName: AssertionKey , AssertEntries
// AssertionKey :
// IdentifierName
// StringLiteral
// auto import_assertions = zone() -> New<ImportAssertions>(zone());
// if (!FLAG_harmony_import_assertions) {
// return import_assertions;
// }
// Assert clause is optional, and cannot be preceded by a LineTerminator.
if (this.scanner.hasLineTerminatorBeforeNext() || !this.checkContextualKeyword('assert')) {
return undefined;
}
this.expect(Token.LBRACE);
const counts = {};
const importAssertions = [];
while (this.peek().isNotType(Token.RBRACE)) {
let attributeKey = this.checkAndGetValue(Token.STRING);
if (!attributeKey) {
attributeKey = this.parsePropertyOrPrivatePropertyName();
}
this.expect(Token.COLON);
const attributeValue = this.expectAndGetValue(Token.STRING);
importAssertions.push(this.factory.createAssertEntry(attributeKey, attributeValue, this.createRange(attributeKey)));
counts[attributeKey.toString()] = (counts[attributeKey.toString()] ?? 0) + 1;
if (counts[attributeKey.toString()] > 1) {
// // It is a syntax error if two AssertEntries have the same key.
throw new SyntaxError(this.errorMessage('Import Assertion Duplicate Key'));
}
if (this.peek().isType(Token.RBRACE))
break;
if (!this.check(Token.COMMA)) {
throw new SyntaxError('Unexpected Token');
}
}
this.expect(Token.RBRACE);
return importAssertions;
}
parseModuleSpecifier() {
// ModuleSpecifier :
// StringLiteral
return this.expectAndGetValue(Token.STRING);
}
parseExportClause() {
// ExportClause :
// '{' '}'
// '{' ExportsList '}'
// '{' ExportsList ',' '}'
//
// ExportsList :
// ExportSpecifier
// ExportsList ',' ExportSpecifier
//
// ExportSpecifier :
// IdentifierName
// IdentifierName 'as' IdentifierName
// IdentifierName 'as' ModuleExportName
// ModuleExportName
// ModuleExportName 'as' ModuleExportName
//
// ModuleExportName :
// StringLiteral
// ZoneChunkList < ExportClauseData >* export_data = zone() -> New<ZoneChunkList<ExportClauseData>>(zone());
this.expect(Token.LBRACE);
const exportData = [];
let nameTok = this.peek();
while (nameTok.isNotType(Token.RBRACE)) {
const localName = this.parseExportSpecifierName();
let exportName;
if (this.checkContextualKeyword('as')) {
exportName = this.parseExportSpecifierName();
}
else {
exportName = localName;
}
exportData.push(this.factory.createExportSpecifier(exportName, localName));
if (this.peek().isType(Token.RBRACE))
break;
if (!this.check(Token.COMMA)) {
throw new SyntaxError('Unexpected Token');
}
}
this.expect(Token.RBRACE);
return exportData;
}
parseExportSpecifierName() {
const next = this.next();
// IdentifierName
if (next.isType(Token.IDENTIFIER) || Token.isPropertyName(next.token)) {
return next.getValue();
}
// ModuleExportName
if (next.isType(Token.STRING)) {
const exportName = next.getValue();
return exportName;
}
throw new SyntaxError(this.errorMessage('Unexpected Token'));
}
parseNamedImports() {
// NamedImports :
// '{' '}'
// '{' ImportsList '}'
// '{' ImportsList ',' '}'
//
// ImportsList :
// ImportSpecifier
// ImportsList ',' ImportSpecifier
//
// ImportSpecifier :
// BindingIdentifier
// IdentifierName 'as' BindingIdentifier
// ModuleExportName 'as' BindingIdentifier
this.expect(Token.LBRACE);
const result = [];
// auto result = zone() -> New < ZonePtrList <const NamedImport>> (1, zone());
while (this.peek().isNotType(Token.RBRACE)) {
const importName = this.parseExportSpecifierName();
let localName = importName;
// In the presence of 'as', the left-side of the 'as' can
// be any IdentifierName. But without 'as', it must be a valid
// BindingIdentifier.
if (this.checkContextualKeyword('as')) {
localName = this.parsePropertyOrPrivatePropertyName();
}
if (!Token.isValidIdentifier(this.current().token, LanguageMode.Strict, false, isStrict(this.languageMode))) {
throw new SyntaxError(this.errorMessage('Unexpected Reserved Keyword'));
}
else if (this.isEvalOrArguments(localName)) {
throw new SyntaxError(this.errorMessage('Strict Eval Arguments'));
}
result.push(this.factory.createImportSpecifier(localName, importName, this.createRange(importName)));
// DeclareUnboundVariable(localName, VariableMode:: kConst, kNeedsInitialization, position());
// NamedImport * import = zone() -> New<NamedImport>(import_name, local_name, location);
// result-> Add(import, zone());
if (this.peek().isType(Token.RBRACE))
break;
this.expect(Token.COMMA);
}
this.expect(Token.RBRACE);
return result;
}
parseExportDefault(start) {
// Supports the following productions, starting after the 'default' token:
// 'export' 'default' HoistableDeclaration
// 'export' 'default' ClassDeclaration
// 'export' 'default' AssignmentExpression[In] ';'
this.expect(Token.DEFAULT);
// Scanner::Location default_loc = scanner() -> location();
// ZonePtrList <const AstRawString> localNames(1, zone());
const localNames = [];
let result;
switch (this.peek().token) {
case Token.FUNCTION:
result = this.parseHoistableDeclaration(localNames, true, this.peek());
break;
case Token.CLASS:
const classToken = this.consume(Token.CLASS);
result = this.parseClassDeclaration(localNames, true, classToken);
break;
case Token.ASYNC:
if (this.peekAhead().isType(Token.FUNCTION) && !this.scanner.hasLineTerminatorBeforeNext()) {
const asyncStart = this.consume(Token.ASYNC);
result = this.parseHoistableDeclaration(localNames, true, asyncStart);
break;
}
default: {
// int pos = position();
// AcceptINScope scope(this, true);
this.setAcceptIN(true);
result = this.parseAssignmentExpression();
// SetFunctionName(value, ast_value_factory() -> default_string());
// const AstRawString* local_name = ast_value_factory() -> dot_default_string();
// localNames.Add(local_name, zone());
// It's fine to declare this as VariableMode::kConst because the user has
// no way of writing to it.
// VariableProxy * proxy = DeclareBoundVariable(local_name, VariableMode:: kConst, pos);
// proxy ->var() -> set_initializer_position(position());
// Assignment * assignment = factory() -> NewAssignment(Token.INIT, proxy, value, kNoSourcePosition);
// result = IgnoreCompletion( factory() -> NewExpressionStatement(assignment, kNoSourcePosition));
this.expectSemicolon();
this.restoreAcceptIN();
break;
}
}
// if (!result) {
// DCHECK_EQ(localNames.length(), 1);
// module() -> AddExport(localNames.first(), ast_value_factory() -> default_string(), default_loc, zone());
// }
return this.factory.createExportDefault(result, this.createRange(start));
}
parseExportStar(start) {
this.consume(Token.MUL);
if (!this.peekContextualKeyword('as')) {
// 'export' '*' 'from' ModuleSpecifier ';'
// Scanner::Location loc = scanner() -> location();
this.expectContextualKeyword('from');
// Scanner::Location specifier_loc = scanner() -> peek_location();
const moduleSpecifier = this.parseModuleSpecifier();
const importAssertions = this.parseImportAssertClause();
this.expectSemicolon();
// module() -> AddStarExport(module_specifier, import_assertions, loc,specifier_loc, zone());
return this.factory.createExportAllDeclaration(moduleSpecifier, undefined, importAssertions, this.createRange(start));
}
// 'export' '*' 'as' IdentifierName 'from' ModuleSpecifier ';'
//
// Desugaring:
// export * as x from "...";
// ~>
// import * as .x from "..."; export {.x as x};
//
// Note that the desugared internal namespace export name (.x above) will
// never conflict with a string literal export name, as literal string export
// names in local name positions (i.e. left of 'as' or in a clause without
// 'as') are disallowed without a following 'from' clause.
this.expectContextualKeyword('as');
const exportName = this.parseExportSpecifierName();
// Scanner::Location export_name_loc = scanner() -> location();
// const localName = this.nextInternalNamespaceExportName();
// Scanner::Location local_name_loc = Scanner:: Location:: invalid();
// DeclareUnboundVariable(local_name, VariableMode:: kConst, kCreatedInitialized,pos);
this.expectContextualKeyword('from');
const moduleSpecifier = this.parseModuleSpecifier();
const importAssertions = this.parseImportAssertClause();
this.expectSemicolon();
// const specifiers = [new ExportSpecifier(exportName as Identifier, exportName as Identifier)];
// module() -> AddStarImport(local_name, module_specifier, import_assertions,local_name_loc, specifier_loc, zone());
// module() -> AddExport(local_name, export_name, export_name_loc, zone());
return this.factory.createExportAllDeclaration(moduleSpecifier, exportName, importAssertions, this.createRange(start));
}
parseWithStatement() {
// WithStatement ::
// 'with' '(' Expression ')' Statement
const start = this.consume(Token.WITH);
if (isStrict(this.languageMode)) {
throw new SyntaxError(this.errorMessage('With is not allowed in Strict mode.'));
}
this.expect(Token.LPAREN);
const object = this.parseExpression();
this.expect(Token.RPAREN);
const body = this.parseStatement();
const range = this.createRange(start);
return this.factory.createWithStatement(object, body, range);
}
}
//# sourceMappingURL=parser.js.map