Added support for named function parameters. Fixed several minor variable definition bugs.

This commit is contained in:
waldemar%netscape.com 2001-12-04 02:25:03 +00:00
parent 03bcfe58b9
commit 7742f17c6f
8 changed files with 142 additions and 55 deletions

View File

@ -2268,9 +2268,10 @@ BinaryOpEquals:
uint32 reqArgCount = 0;
uint32 optArgCount = 0;
uint32 namedArgCount = 0;
VariableBinding *b = f->function.parameters;
while ((b != f->function.optParameters) && (b != f->function.restParameter)) {
while (b != f->function.optParameters) {
reqArgCount++;
b = b->next;
}
@ -2278,7 +2279,13 @@ BinaryOpEquals:
optArgCount++;
b = b->next;
}
fnc->setArgCounts(m_cx, reqArgCount, optArgCount, (f->function.restParameter != NULL));
b = f->function.namedParameters;
while (b) {
namedArgCount++;
b = b->next;
}
fnc->setArgCounts(m_cx, reqArgCount, optArgCount, namedArgCount,
f->function.restParameter != f->function.namedParameters, f->function.restIsNamed);
if (mScopeChain->isPossibleUncheckedFunction(&f->function))
fnc->setIsPrototype(true);

View File

@ -952,7 +952,6 @@ bool ScopeChain::isPossibleUncheckedFunction(FunctionDefinition *f)
{
bool result = false;
if ((f->resultType == NULL)
&& (f->restParameter == NULL)
&& (f->optParameters == NULL)
&& (f->prefix == FunctionName::normal)
&& (topClass() == NULL)) {
@ -1107,9 +1106,10 @@ void ScopeChain::collectNames(StmtNode *p)
uint32 reqArgCount = 0;
uint32 optArgCount = 0;
uint32 namedArgCount = 0;
VariableBinding *b = f->function.parameters;
while ((b != f->function.optParameters) && (b != f->function.restParameter)) {
while (b != f->function.optParameters) {
reqArgCount++;
b = b->next;
}
@ -1117,7 +1117,13 @@ void ScopeChain::collectNames(StmtNode *p)
optArgCount++;
b = b->next;
}
fnc->setArgCounts(m_cx, reqArgCount, optArgCount, (f->function.restParameter != NULL));
b = f->function.namedParameters;
while (b) {
namedArgCount++;
b = b->next;
}
fnc->setArgCounts(m_cx, reqArgCount, optArgCount, namedArgCount,
f->function.restParameter != f->function.namedParameters, f->function.restIsNamed);
if (isOperator) {
// no need to do anything yet, all operators are 'pre-declared'
@ -1922,9 +1928,10 @@ static JSValue Function_Constructor(Context *cx, const JSValue& thisValue, JSVal
uint32 reqArgCount = 0;
uint32 optArgCount = 0;
uint32 namedArgCount = 0;
VariableBinding *b = f->function.parameters;
while ((b != f->function.optParameters) && (b != f->function.restParameter)) {
while (b != f->function.optParameters) {
reqArgCount++;
b = b->next;
}
@ -1932,7 +1939,13 @@ static JSValue Function_Constructor(Context *cx, const JSValue& thisValue, JSVal
optArgCount++;
b = b->next;
}
fnc->setArgCounts(cx, reqArgCount, optArgCount, (f->function.restParameter != NULL));
b = f->function.namedParameters;
while (b) {
namedArgCount++;
b = b->next;
}
fnc->setArgCounts(cx, reqArgCount, optArgCount, namedArgCount,
f->function.restParameter != f->function.namedParameters, f->function.restIsNamed);
if (cx->mScopeChain->isPossibleUncheckedFunction(&f->function)) {
fnc->setIsPrototype(true);
@ -2497,15 +2510,17 @@ JSFunction::JSFunction(Context *, NativeCode *code, JSType *resultType)
JSValue JSFunction::runArgInitializer(Context *cx, uint32 a, const JSValue& thisValue, JSValue *argv, uint32 argc)
{
ASSERT(mArguments && (a < (mRequiredArgs + mOptionalArgs)));
return cx->interpret(getByteCode(), (int32)mArguments[a].mInitializer, getScopeChain(), thisValue, argv, argc);
return cx->interpret(getByteCode(), (int32)mArguments[a].mInitializer, getScopeChain(), thisValue, argv, argc);
}
void JSFunction::setArgCounts(Context *cx, uint32 r, uint32 o, bool hasRest)
void JSFunction::setArgCounts(Context *cx, uint32 r, uint32 o, uint32 n, bool hasRest, bool restIsNamed)
{
mHasRestParameter = hasRest;
mRequiredArgs = r;
mOptionalArgs = o;
mArguments = new ArgumentData[mRequiredArgs + mOptionalArgs + ((hasRest) ? 1 : 0)];
mHasRestParameter = hasRest;
mRestIsNamed = restIsNamed;
mRequiredArgs = r;
mOptionalArgs = o;
mNamedArgs = n;
mArguments = new ArgumentData[mRequiredArgs + mOptionalArgs + ((hasRest) ? 1 : 0)];
defineVariable(cx, cx->Length_StringAtom, (NamespaceList *)NULL, Property::DontDelete | Property::ReadOnly, Number_Type, JSValue((float64)mRequiredArgs));
}
@ -2553,7 +2568,7 @@ void Context::initClass(JSType *type, ClassDef *cdef, PrototypeFunctions *pdef)
// fun->setClass(type); don't do this, it makes the function a method
StringAtom *name = &mWorld.identifiers[widenCString(pdef->mDef[i].name)];
fun->setFunctionName(name);
fun->setArgCounts(this, pdef->mDef[i].length, 0, false);
fun->setArgCounts(this, pdef->mDef[i].length, 0, 0, false, false);
type->mPrototypeObject->defineVariable(this, *name,
(NamespaceList *)(NULL),
Property::NoAttribute,
@ -2963,7 +2978,7 @@ Context::Context(JSObject **global, World &world, Arena &a, Pragma::Flags flags)
for (i = 0; i < (sizeof(globalObjectFunctions) / sizeof(ProtoFunDef)); i++) {
x = new JSFunction(this, globalObjectFunctions[i].imp, globalObjectFunctions[i].result);
x->setArgCounts(this, globalObjectFunctions[i].length, 0, false);
x->setArgCounts(this, globalObjectFunctions[i].length, 0, 0, false, false);
x->setIsPrototype(true);
getGlobalObject()->defineVariable(this, widenCString(globalObjectFunctions[i].name), (NamespaceList *)(NULL), Property::NoAttribute, globalObjectFunctions[i].result, JSValue(x));
}

View File

@ -1212,7 +1212,7 @@ XXX ...couldn't get this to work...
void setByteCode(ByteCodeModule *b) { ASSERT(!isNative()); mByteCode = b; }
void setResultType(JSType *r) { mResultType = r; }
void setArgCounts(Context *cx, uint32 r, uint32 o, bool hasRest);
void setArgCounts(Context *cx, uint32 r, uint32 o, uint32 n, bool hasRest, bool restIsNamed);
void setArgument(uint32 index, const String *n, JSType *t)
{ ASSERT(mArguments && (index < (mRequiredArgs + mOptionalArgs + ((mHasRestParameter) ? 1 : 0) ))); mArguments[index].mType = t; mArguments[index].mName = n; }
void setArgumentInitializer(uint32 index, uint32 offset)
@ -1269,12 +1269,14 @@ XXX ...couldn't get this to work...
JSType *mResultType;
uint32 mRequiredArgs; // total # parameters
uint32 mOptionalArgs;
uint32 mNamedArgs;
ArgumentData *mArguments;
ScopeChain *mScopeChain;
bool mIsPrototype; // set for functions with prototype attribute
bool mIsConstructor;
bool mIsChecked;
bool mHasRestParameter;
bool mRestIsNamed;
const String *mRestParameterName;
JSType *mClass; // pointer to owning class if this function is a method
FunctionName *mFunctionName;

View File

@ -256,7 +256,7 @@ void initMathObject(Context *cx, JSObject *mathObj)
for (i = 0; i < sizeof(MathObjectFunctions) / sizeof(MathObjectFunctionDef); i++) {
JSFunction *f = new JSFunction(cx, MathObjectFunctions[i].imp, Number_Type);
f->setArgCounts(cx, MathObjectFunctions[i].length, 0, false);
f->setArgCounts(cx, MathObjectFunctions[i].length, 0, 0, false, false);
mathObj->defineVariable(cx, widenCString(MathObjectFunctions[i].name),
(NamespaceList *)(NULL), Property::ReadOnly | Property::DontDelete,
Number_Type, JSValue(f));

View File

@ -787,6 +787,7 @@ const JS::Parser::BinaryOperatorInfo JS::Parser::tokenBinaryOperatorInfos[Token:
{ExprNode::none, pExpression, pNone, false}, // Token::Get
{ExprNode::none, pExpression, pNone, false}, // Token::Include
{ExprNode::none, pExpression, pNone, false}, // Token::Javascript
{ExprNode::none, pExpression, pNone, false}, // Token::Named
{ExprNode::none, pExpression, pNone, false}, // Token::Set
{ExprNode::none, pExpression, pNone, false}, // Token::Strict
@ -1053,32 +1054,40 @@ bool JS::Parser::doubleColonFollows()
}
// Parse and return a VariableBinding (UntypedVariableBinding if untyped is true).
// Parse and return a VariableBinding or, depnding on the flags given, one of the related productions
// UntypedVariableBinding, TypedIdentifier, TypedInitialiser, or Identifier.
// pos is the position of the binding or its first attribute, if any.
// If noIn is false, allow the in operator.
// If noIn is true, don't allow the in operator.
// If noType is true, don't allow a type annotation.
// If noInitializer is true, don't allow any initializers.
// If noAttributes is true, don't allow initializers that are not expressions.
// The value of the constant parameter is stored in the returned VariableBinding.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// If the first or second token was peeked, it should be have been done with preferRegExp set to true.
// After parseVariableBinding finishes, the next token might have been peeked with preferRegExp set to true.
// The reason preferRegExp is true is to correctly parse the following case of semicolon insertion:
// var a
// /regexp/
JS::VariableBinding *JS::Parser::parseVariableBinding(size_t pos, bool noIn, bool untyped, bool constant)
JS::VariableBinding *JS::Parser::parseVariableBinding(size_t pos, bool noIn, bool noType, bool noInitializer, bool noAttributes, bool constant)
{
const StringAtom &name = parseIdentifier();
ExprNode *type = 0;
if (lexer.eat(true, Token::colon))
if (untyped)
syntaxError("Type annotation not allowed in a var inside a substatement; enclose the var in a block");
if (noType)
syntaxError(noInitializer ? "Type annotation not allowed on a named ... parameter" :
"Type annotation not allowed in a var inside a substatement; enclose the var in a block");
else
type = parseTypeExpression(noIn);
ExprNode *initializer = 0;
if (lexer.eat(true, Token::assignment)) {
if (noInitializer)
syntaxError("Initializer not allowed on a ... parameter");
const Token &t = lexer.get(true);
size_t tPos = t.getPos();
if (!untyped && (t.getFlag(Token::isNonExpressionAttribute) || t.hasKind(Token::Private) && !doubleColonFollows())) {
if (!noAttributes && (t.getFlag(Token::isNonExpressionAttribute) || t.hasKind(Token::Private) && !doubleColonFollows())) {
initializer = new(arena) IdentifierExprNode(t);
makeAttribute:
initializer = parseAttributes(tPos, initializer);
@ -1087,7 +1096,7 @@ JS::VariableBinding *JS::Parser::parseVariableBinding(size_t pos, bool noIn, boo
initializer = parseAssignmentExpression(noIn);
lexer.redesignate(true); // Safe: a '/' or a '/=' would have been interpreted as an operator,
// so it can't be the next token.
if (!untyped && expressionIsAttribute(initializer)) {
if (!noAttributes && expressionIsAttribute(initializer)) {
const Token &t2 = lexer.peek(true);
if (!lineBreakBefore(t2) && t2.getFlag(Token::canFollowAttribute))
goto makeAttribute;
@ -1100,20 +1109,37 @@ JS::VariableBinding *JS::Parser::parseVariableBinding(size_t pos, bool noIn, boo
// Parse and return a VariableBinding for a function parameter. The parameter may optionally be
// preceded by the const attribute.
// preceded by the const and/or named attributes. If named is true on entry to this function, then
// the named attribute is required and named will remain true on exit. If named is false on entry,
// then the named attribute is optional and named will be true only if the named attribute was given.
// If rest is true, then no initializer is allowed and, if the named attribute is also found, no type
// is allowed. rest and named must not be simultaneously true on entry to this function.
// This function does not check that named parameters have initializers; the caller should make this
// check.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseParameter finishes, the next token might have been peeked with preferRegExp set to true.
JS::VariableBinding *JS::Parser::parseParameter()
JS::VariableBinding *JS::Parser::parseParameter(bool rest, bool &named)
{
const Token &t = lexer.peek(true);
size_t pos = t.getPos();
bool constant = false;
if (t.hasKind(Token::Const)) {
lexer.skip();
constant = true;
const Token *t = &lexer.get(true);
size_t pos = t->getPos();
bool constToken = false;
bool namedToken = false;
while (true) {
if (t->hasKind(Token::Const) && !constToken)
constToken = true;
// If the named attribute is required then assume that any Token::Named is the attribute. If it
// isn't, then the parse will generate an error further down.
else if (t->hasKind(Token::Named) && !namedToken && (named || lexer.peek(true).getFlag(Token::canFollowAttribute)))
namedToken = true;
else break;
t = &lexer.get(true);
}
return parseVariableBinding(pos, false, false, constant);
lexer.unget();
if (named && !namedToken)
syntaxError("'named' expected", 0);
named = namedToken;
return parseVariableBinding(pos, false, rest && namedToken, rest, true, constToken);
}
@ -1157,40 +1183,58 @@ void JS::Parser::parseFunctionSignature(FunctionDefinition &fd)
NodeQueue<VariableBinding> parameters;
VariableBinding *optParameters = 0;
VariableBinding *restParameter = 0;
VariableBinding *namedParameters = 0;
bool restIsNamed = false;
bool named = false;
// The code below is very tricky because it discovers syntax errors on the earliest
// possible token. Be careful to account for all possible error cases.
if (!lexer.eat(true, Token::closeParenthesis)) {
while (true) {
if (lexer.eat(true, Token::tripleDot)) {
if (restParameter)
syntaxError("A ... parameter can't follow a named or ... parameter");
const Token &t1 = lexer.peek(true);
if (t1.hasKind(Token::closeParenthesis))
if (t1.hasKind(Token::closeParenthesis) || t1.hasKind(Token::comma))
restParameter = new(arena) VariableBinding(t1.getPos(), 0, 0, 0, false);
else
restParameter = parseParameter();
restParameter = parseParameter(true, named);
restIsNamed = named;
if (!optParameters)
optParameters = restParameter;
parameters += restParameter;
require(true, Token::closeParenthesis);
break;
named = true; // Only named parameters may follow.
} else {
VariableBinding *b = parseParameter();
VariableBinding *b = parseParameter(false, named);
if (named) {
if (!namedParameters)
namedParameters = b;
if (!restParameter)
restParameter = b;
}
if (b->initializer) {
if (!optParameters)
optParameters = b;
} else
if (optParameters)
if (optParameters || named)
syntaxError("'=' expected", 0);
parameters += b;
const Token &t = lexer.get(true);
if (!t.hasKind(Token::comma))
if (t.hasKind(Token::closeParenthesis))
break;
else
syntaxError("',' or ')' expected");
}
const Token &t = lexer.get(true);
if (!t.hasKind(Token::comma))
if (t.hasKind(Token::closeParenthesis))
break;
else
syntaxError("',' or ')' expected");
if (restIsNamed)
syntaxError("')' expected"); // Nothing may follow a named rest parameter.
}
}
fd.parameters = parameters.first;
fd.optParameters = optParameters;
fd.restParameter = restParameter;
fd.namedParameters = namedParameters;
fd.restIsNamed = restIsNamed;
fd.resultType = parseTypeBinding(Token::colon, false);
}
@ -1525,7 +1569,7 @@ JS::StmtNode *JS::Parser::parseAnnotatableDirective(size_t pos, ExprNode *attrib
{
NodeQueue<VariableBinding> bindings;
do bindings += parseVariableBinding(lexer.peek(true).getPos(), noIn, untyped, sKind == StmtNode::Const);
do bindings += parseVariableBinding(lexer.peek(true).getPos(), noIn, untyped, false, false, sKind == StmtNode::Const);
while (lexer.eat(true, Token::comma));
s = new(arena) VariableStmtNode(pos, sKind, attributes, bindings.first);
}
@ -2057,13 +2101,20 @@ void JS::FunctionDefinition::print(PrettyPrinter &f, const AttributeStmtNode *at
{
PrettyPrinter::Block b2(f);
const VariableBinding *p = parameters;
if (p)
if (p) {
bool named = false;
while (true) {
if (p == restParameter) {
if (p == namedParameters)
named = true;
else if (p == restParameter) {
f << "...";
if (p->name)
if (restIsNamed)
f << " named ";
else if (p->name)
f << ' ';
}
if (named)
f << "named ";
p->print(f, true);
p = p->next;
if (!p)
@ -2071,6 +2122,7 @@ void JS::FunctionDefinition::print(PrettyPrinter &f, const AttributeStmtNode *at
f << ',';
f.fillBreak(1);
}
}
f << ')';
}
if (resultType) {

View File

@ -274,9 +274,15 @@ namespace JavaScript {
struct FunctionDefinition: FunctionName {
VariableBinding *parameters; // Linked list of all parameters, including optional and rest parameters, if any
VariableBinding *optParameters; // Pointer to first non-required (or rest) parameter inside parameters list; nil if none
VariableBinding *restParameter; // Pointer to rest parameter inside parameters list; nil if none
// The parameters linked list includes all kinds of parameters. optParameters, restParameter, and namedParameters
// are pointers into the interior of the linked list. If any kind is empty, then that kind's pointer is equal to
// the following kind's pointer, so, for example, if there is no rest parameter then restParameter == namedParameters.
// A pointer is nil only if its kind and all subsequent kinds are empty.
VariableBinding *parameters; // Linked list of all parameters, including optional, rest, and named parameters
VariableBinding *optParameters; // Pointer to first optional parameter inside parameters list
VariableBinding *restParameter; // Pointer to rest parameter inside parameters list
VariableBinding *namedParameters;// Pointer to first named parameter inside parameters list
bool restIsNamed; // True if the rest parameter has the 'named' attribute
ExprNode *resultType; // Result type expression or nil if not provided
BlockStmtNode *body; // Body; nil if none
@ -827,8 +833,8 @@ namespace JavaScript {
ExprNode *parseTypeExpression(bool noIn=false);
ExprNode *parseTypeBinding(Token::Kind kind, bool noIn);
bool doubleColonFollows();
VariableBinding *parseVariableBinding(size_t pos, bool noIn, bool untyped, bool constant);
VariableBinding *parseParameter();
VariableBinding *parseVariableBinding(size_t pos, bool noIn, bool noType, bool noInitializer, bool noAttributes, bool constant);
VariableBinding *parseParameter(bool rest, bool &named);
void parseFunctionName(FunctionName &fn);
void parseFunctionSignature(FunctionDefinition &fd);
ExportBinding *parseExportBinding();

View File

@ -171,6 +171,7 @@ const char *const JS::Token::kindNames[kindsEnd] = {
"get", // Get
"include", // Include
"javascript", // Javascript
"named", // Named
"set", // Set
"strict" // Strict
};
@ -314,6 +315,7 @@ const uchar JS::Token::kindFlags[kindsEnd] = {
isAttr|nonreserved, // Get
isAttr|nonreserved, // Include
isAttr|nonreserved, // Javascript
isAttr|nonreserved, // Named
isAttr|nonreserved, // Set
isAttr|nonreserved, // Strict

View File

@ -186,6 +186,7 @@ namespace JavaScript
Get, // get
Include, // include
Javascript, // javascript
Named, // named
Set, // set
Strict, // strict
@ -208,6 +209,7 @@ namespace JavaScript
case Token::Get: \
case Token::Include: \
case Token::Javascript: \
case Token::Named: \
case Token::Set: \
case Token::Strict: \
case Token::identifier
@ -218,6 +220,7 @@ namespace JavaScript
case Token::Exclude: \
case Token::Get: \
case Token::Javascript: \
case Token::Named: \
case Token::Set: \
case Token::Strict: \
case Token::identifier