Files
completion-viz/engine262/src/parser/FunctionParser.mjs
T

323 lines
11 KiB
JavaScript

import { IsSimpleParameterList } from '../static-semantics/all.mjs';
import { getDeclarations } from './Scope.mjs';
import { Token } from './tokens.mjs';
import { IdentifierParser } from './IdentifierParser.mjs';
export const FunctionKind = {
NORMAL: 0,
ASYNC: 1,
};
export class FunctionParser extends IdentifierParser {
// FunctionDeclaration :
// `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}`
// [+Default] `function` `(` FormalParameters `)` `{` FunctionBody `}`
// FunctionExpression :
// `function` BindingIdentifier? `(` FormalParameters `)` `{` FunctionBody `}`
// GeneratorDeclaration :
// `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}`
// [+Default] `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}`
// GeneratorExpression :
// `function` BindingIdentifier? `(` FormalParameters `)` `{` GeneratorBody `}`
// AsyncGeneratorDeclaration :
// `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncGeneratorBody `}`
// [+Default] `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}`
// AsyncGeneratorExpression :
// `async` `function` BindingIdentifier? `(` FormalParameters `)` `{` AsyncGeneratorBody `}`
// AsyncFunctionDeclaration :
// `async` `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}`
// [+Default] `async` `function` `(` FormalParameters `)` `{` AsyncFunctionBody `}`
// Async`FunctionExpression :
// `async` `function` BindingIdentifier? `(` FormalParameters `)` `{` AsyncFunctionBody `}`
parseFunction(isExpression, kind) {
const isAsync = kind === FunctionKind.ASYNC;
const node = this.startNode();
if (isAsync) {
this.expect('async');
}
this.expect(Token.FUNCTION);
const isGenerator = this.eat(Token.MUL);
if (!this.test(Token.LPAREN)) {
this.scope.with({
await: isExpression ? false : undefined,
yield: isExpression ? false : undefined,
}, () => {
node.BindingIdentifier = this.parseBindingIdentifier();
});
if (!isExpression) {
this.scope.declare(node.BindingIdentifier, 'function');
}
} else if (isExpression === false && !this.scope.isDefault()) {
this.unexpected();
} else {
node.BindingIdentifier = null;
}
this.scope.with({
default: false,
await: isAsync,
yield: isGenerator,
lexical: true,
variable: true,
variableFunctions: true,
}, () => {
node.FormalParameters = this.parseFormalParameters();
const body = this.parseFunctionBody(isAsync, isGenerator, false);
node[body.type] = body;
if (node.BindingIdentifier) {
if (body.strict && (node.BindingIdentifier.name === 'eval' || node.BindingIdentifier.name === 'arguments')) {
this.raiseEarly('UnexpectedToken', node.BindingIdentifier);
}
if (isExpression) {
if (this.scope.hasYield() && node.BindingIdentifier.name === 'yield') {
this.raiseEarly('UnexpectedToken', node.BindingIdentifier);
}
if (this.scope.hasAwait() && node.BindingIdentifier.name === 'await') {
this.raiseEarly('UnexpectedToken', node.BindingIdentifier);
}
}
}
this.validateFormalParameters(node.FormalParameters, body);
});
const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}${isExpression ? 'Expression' : 'Declaration'}`;
return this.finishNode(node, name);
}
validateFormalParameters(parameters, body, wantsUnique = false) {
const isStrict = body.strict;
const hasStrictDirective = body.directives && body.directives.includes('use strict');
if (wantsUnique === false && !IsSimpleParameterList(parameters)) {
wantsUnique = true;
}
if (hasStrictDirective) {
parameters.forEach((p) => {
if (p.type !== 'SingleNameBinding' || p.Initializer) {
this.raiseEarly('UseStrictNonSimpleParameter', p);
}
});
}
const names = new Set();
getDeclarations(parameters)
.forEach((d) => {
if (isStrict) {
if (d.name === 'arguments' || d.name === 'eval') {
this.raiseEarly('UnexpectedToken', d.node);
}
}
if (isStrict || wantsUnique) {
if (names.has(d.name)) {
this.raiseEarly('AlreadyDeclared', d.node, d.name);
} else {
names.add(d.name);
}
}
});
}
convertArrowParameter(node) {
switch (node.type) {
case 'IdentifierReference': {
node.type = 'BindingIdentifier';
const container = this.startNode();
container.BindingIdentifier = node;
container.Initializer = null;
this.scope.declare(node, 'parameter');
return this.finishNode(container, 'SingleNameBinding');
}
case 'BindingRestElement':
this.scope.declare(node, 'parameter');
return node;
case 'Elision':
return node;
case 'ArrayLiteral': {
const wrap = this.startNode();
node.BindingElementList = [];
node.ElementList.forEach((p, i) => {
const c = this.convertArrowParameter(p);
if (c.type === 'BindingRestElement') {
if (i !== node.ElementList.length - 1) {
this.raiseEarly('UnexpectedToken', c);
}
node.BindingRestElement = c;
} else {
node.BindingElementList.push(c);
}
});
delete node.ElementList;
node.type = 'ArrayBindingPattern';
wrap.BindingPattern = node;
wrap.Initializer = null;
return this.finishNode(wrap, 'BindingElement');
}
case 'ObjectLiteral': {
const wrap = this.startNode();
node.BindingPropertyList = [];
node.PropertyDefinitionList.forEach((p) => {
const c = this.convertArrowParameter(p);
if (c.type === 'BindingRestProperty') {
node.BindingRestProperty = c;
} else {
node.BindingPropertyList.push(c);
}
});
delete node.PropertyDefinitionList;
node.type = 'ObjectBindingPattern';
wrap.BindingPattern = node;
wrap.Initializer = null;
return this.finishNode(wrap, 'BindingElement');
}
case 'AssignmentExpression': {
const result = this.convertArrowParameter(node.LeftHandSideExpression);
result.Initializer = node.AssignmentExpression;
return result;
}
case 'CoverInitializedName':
node.type = 'SingleNameBinding';
node.BindingIdentifier = node.IdentifierReference;
node.BindingIdentifier.type = 'BindingIdentifier';
delete node.IdentifierReference;
this.scope.declare(node, 'parameter');
return node;
case 'PropertyDefinition':
if (node.PropertyName === null) {
node.type = 'BindingRestProperty';
node.BindingIdentifier = node.AssignmentExpression;
node.BindingIdentifier.type = 'BindingIdentifier';
} else {
node.type = 'BindingProperty';
node.BindingElement = this.convertArrowParameter(node.AssignmentExpression);
}
this.scope.declare(node, 'parameter');
delete node.AssignmentExpression;
return node;
case 'SpreadElement':
case 'AssignmentRestElement':
node.type = 'BindingRestElement';
if (node.AssignmentExpression.type === 'AssignmentExpression') {
this.raiseEarly('UnexpectedToken', node);
} else if (node.AssignmentExpression.type === 'IdentifierReference') {
node.BindingIdentifier = node.AssignmentExpression;
node.BindingIdentifier.type = 'BindingIdentifier';
} else {
node.BindingPattern = this.convertArrowParameter(node.AssignmentExpression).BindingPattern;
}
this.scope.declare(node, 'parameter');
delete node.AssignmentExpression;
return node;
default:
this.raiseEarly('UnexpectedToken', node);
return node;
}
}
parseArrowFunction(node, { arrowInfo, Arguments }, kind) {
const isAsync = kind === FunctionKind.ASYNC;
this.expect(Token.ARROW);
if (arrowInfo) {
arrowInfo.awaitExpressions.forEach((e) => {
this.raiseEarly('AwaitInFormalParameters', e);
});
arrowInfo.yieldExpressions.forEach((e) => {
this.raiseEarly('YieldInFormalParameters', e);
});
if (isAsync) {
arrowInfo.awaitIdentifiers.forEach((e) => {
this.raiseEarly('AwaitInFormalParameters', e);
});
}
}
this.scope.with({
default: false,
lexical: true,
variable: true,
}, () => {
this.scope.with({
parameters: true,
}, () => {
node.ArrowParameters = Arguments.map((p) => this.convertArrowParameter(p));
});
const body = this.parseConciseBody(isAsync);
this.validateFormalParameters(node.ArrowParameters, body, true);
node[`${isAsync ? 'Async' : ''}ConciseBody`] = body;
});
return this.finishNode(node, `${isAsync ? 'Async' : ''}ArrowFunction`);
}
parseConciseBody(isAsync) {
if (this.test(Token.LBRACE)) {
return this.parseFunctionBody(isAsync, false, true);
}
const asyncBody = this.startNode();
const exprBody = this.startNode();
this.scope.with({ await: isAsync }, () => {
exprBody.AssignmentExpression = this.parseAssignmentExpression();
});
asyncBody.ExpressionBody = this.finishNode(exprBody, 'ExpressionBody');
return this.finishNode(asyncBody, `${isAsync ? 'Async' : ''}ConciseBody`);
}
// FormalParameter : BindingElement
parseFormalParameter() {
return this.parseBindingElement();
}
parseFormalParameters() {
this.expect(Token.LPAREN);
if (this.eat(Token.RPAREN)) {
return [];
}
const params = [];
this.scope.with({ parameters: true }, () => {
while (true) {
if (this.test(Token.ELLIPSIS)) {
const element = this.parseBindingRestElement();
this.scope.declare(element, 'parameter');
params.push(element);
this.expect(Token.RPAREN);
break;
} else {
const formal = this.parseFormalParameter();
this.scope.declare(formal, 'parameter');
params.push(formal);
}
if (this.eat(Token.RPAREN)) {
break;
}
this.expect(Token.COMMA);
if (this.eat(Token.RPAREN)) {
break;
}
}
});
return params;
}
parseUniqueFormalParameters() {
return this.parseFormalParameters();
}
parseFunctionBody(isAsync, isGenerator, isArrow) {
const node = this.startNode();
this.expect(Token.LBRACE);
this.scope.with({
newTarget: isArrow ? undefined : true,
return: true,
await: isAsync,
yield: isGenerator,
label: 'boundary',
}, () => {
node.directives = [];
node.FunctionStatementList = this.parseStatementList(Token.RBRACE, node.directives);
node.strict = node.strict || node.directives.includes('use strict');
});
const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}Body`;
return this.finishNode(node, name);
}
}