diff --git a/js/src/Makefile.in b/js/src/Makefile.in index d37873459a12..fe494c4a0897 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -157,6 +157,7 @@ CPPSRCS = \ jsprf.cpp \ jspropertycache.cpp \ jspropertytree.cpp \ + jsreflect.cpp \ jsregexp.cpp \ jsscan.cpp \ jsscope.cpp \ @@ -220,6 +221,7 @@ INSTALLED_HEADERS = \ jsproto.tbl \ jsprvtd.h \ jspubtd.h \ + jsreflect.h \ jsregexp.h \ jsscan.h \ jsscope.h \ @@ -633,7 +635,7 @@ endif ifeq ($(OS_ARCH),IRIX) ifndef GNU_CC _COMPILE_CFLAGS = $(patsubst -O%,-O1,$(COMPILE_CFLAGS)) -jsapi.o jsxdrapi.o jsarena.o jsarray.o jsatom.o jsemit.o jsfun.o jsinterp.o jsregexp.o jsparse.o jsopcode.o jsscript.o: %.o: %.cpp Makefile.in +jsapi.o jsxdrapi.o jsarena.o jsarray.o jsatom.o jsemit.o jsfun.o jsinterp.o jsreflect.o jsregexp.o jsparse.o jsopcode.o jsscript.o: %.o: %.cpp Makefile.in $(REPORT_BUILD) @$(MAKE_DEPS_AUTO_CXX) $(CXX) -o $@ -c $(_COMPILE_CFLAGS) $< diff --git a/js/src/js.msg b/js/src/js.msg index 1c8c8c7700a2..e7e4223c51d8 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -333,3 +333,4 @@ MSG_DEF(JSMSG_INVALID_EVAL_SCOPE_ARG, 250, 0, JSEXN_EVALERR, "invalid eval scope MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS, 251, 3, JSEXN_SYNTAXERR, "{0} functions must have {1} argument{2}") MSG_DEF(JSMSG_THROW_TYPE_ERROR, 252, 0, JSEXN_TYPEERR, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them") MSG_DEF(JSMSG_BAD_TOISOSTRING_PROP, 253, 0, JSEXN_TYPEERR, "toISOString property is not callable") +MSG_DEF(JSMSG_BAD_PARSE_NODE, 254, 0, JSEXN_INTERNALERR, "bad parse node") diff --git a/js/src/jsast.tbl b/js/src/jsast.tbl new file mode 100644 index 000000000000..1007c33ea1f0 --- /dev/null +++ b/js/src/jsast.tbl @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* AST_ERROR = -1 */ + +ASTDEF(AST_PROGRAM, "Program") + +ASTDEF(AST_IDENTIFIER, "Identifier") +ASTDEF(AST_LITERAL, "Literal") +ASTDEF(AST_PROPERTY, "Property") + +ASTDEF(AST_FUNC_DECL, "FunctionDeclaration") +ASTDEF(AST_VAR_DECL, "VariableDeclaration") +ASTDEF(AST_VAR_DTOR, "VariableDeclarator") + +ASTDEF(AST_LIST_EXPR, "SequenceExpression") +ASTDEF(AST_COND_EXPR, "ConditionalExpression") +ASTDEF(AST_UNARY_EXPR, "UnaryExpression") +ASTDEF(AST_BINARY_EXPR, "BinaryExpression") +ASTDEF(AST_ASSIGN_EXPR, "AssignmentExpression") +ASTDEF(AST_LOGICAL_EXPR, "LogicalExpression") +ASTDEF(AST_UPDATE_EXPR, "UpdateExpression") +ASTDEF(AST_NEW_EXPR, "NewExpression") +ASTDEF(AST_CALL_EXPR, "CallExpression") +ASTDEF(AST_MEMBER_EXPR, "MemberExpression") +ASTDEF(AST_FUNC_EXPR, "FunctionExpression") +ASTDEF(AST_ARRAY_EXPR, "ArrayExpression") +ASTDEF(AST_OBJECT_EXPR, "ObjectExpression") +ASTDEF(AST_THIS_EXPR, "ThisExpression") +ASTDEF(AST_GRAPH_EXPR, "GraphExpression") +ASTDEF(AST_GRAPH_IDX_EXPR, "GraphIndexExpression") +ASTDEF(AST_COMP_EXPR, "ComprehensionExpression") +ASTDEF(AST_GENERATOR_EXPR, "GeneratorExpression") +ASTDEF(AST_YIELD_EXPR, "YieldExpression") + +ASTDEF(AST_EMPTY_STMT, "EmptyStatement") +ASTDEF(AST_BLOCK_STMT, "BlockStatement") +ASTDEF(AST_EXPR_STMT, "ExpressionStatement") +ASTDEF(AST_LAB_STMT, "LabeledStatement") +ASTDEF(AST_IF_STMT, "IfStatement") +ASTDEF(AST_SWITCH_STMT, "SwitchStatement") +ASTDEF(AST_WHILE_STMT, "WhileStatement") +ASTDEF(AST_DO_STMT, "DoWhileStatement") +ASTDEF(AST_FOR_STMT, "ForStatement") +ASTDEF(AST_FOR_IN_STMT, "ForInStatement") +ASTDEF(AST_BREAK_STMT, "BreakStatement") +ASTDEF(AST_CONTINUE_STMT, "ContinueStatement") +ASTDEF(AST_WITH_STMT, "WithStatement") +ASTDEF(AST_RETURN_STMT, "ReturnStatement") +ASTDEF(AST_TRY_STMT, "TryStatement") +ASTDEF(AST_THROW_STMT, "ThrowStatement") +ASTDEF(AST_DEBUGGER_STMT, "DebuggerStatement") + +ASTDEF(AST_CASE, "SwitchCase") +ASTDEF(AST_CATCH, "CatchClause") +ASTDEF(AST_COMP_BLOCK, "ComprehensionBlock") + +ASTDEF(AST_ARRAY_PATT, "ArrayPattern") +ASTDEF(AST_OBJECT_PATT, "ObjectPattern") + +ASTDEF(AST_XMLANYNAME, "XMLAnyName") +ASTDEF(AST_XMLATTR_SEL, "XMLAttributeSelector") +ASTDEF(AST_XMLESCAPE, "XMLEscape") +ASTDEF(AST_XMLFILTER, "XMLFilterExpression") +ASTDEF(AST_XMLDEFAULT, "XMLDefaultDeclaration") +ASTDEF(AST_XMLQUAL, "XMLQualifiedIdentifier") +ASTDEF(AST_XMLELEM, "XMLElement") +ASTDEF(AST_XMLTEXT, "XMLText") +ASTDEF(AST_XMLLIST, "XMLList") +ASTDEF(AST_XMLSTART, "XMLStartTag") +ASTDEF(AST_XMLEND, "XMLEndTag") +ASTDEF(AST_XMLPOINT, "XMLPointTag") +ASTDEF(AST_XMLNAME, "XMLName") +ASTDEF(AST_XMLATTR, "XMLAttribute") +ASTDEF(AST_XMLCDATA, "XMLCdata") +ASTDEF(AST_XMLCOMMENT, "XMLComment") +ASTDEF(AST_XMLPI, "XMLProcessingInstruction") + +/* AST_LIMIT = last + 1 */ diff --git a/js/src/jsatom.h b/js/src/jsatom.h index 8fcf54b35468..f74cd0e01715 100644 --- a/js/src/jsatom.h +++ b/js/src/jsatom.h @@ -60,7 +60,7 @@ #define STRING_TO_ATOM(str) (JS_ASSERT(str->isAtomized()), \ (JSAtom *)str) -#define ATOM_TO_STRING(atom) ((JSString *)atom) +#define ATOM_TO_STRING(atom) ((JSString *)(atom)) #define ATOM_TO_JSVAL(atom) STRING_TO_JSVAL(ATOM_TO_STRING(atom)) /* Engine-internal extensions of jsid */ diff --git a/js/src/jsparse.cpp b/js/src/jsparse.cpp index cca0c61ab376..2ff28146b2b7 100644 --- a/js/src/jsparse.cpp +++ b/js/src/jsparse.cpp @@ -2886,10 +2886,9 @@ Parser::functionDef(JSAtom *funAtom, FunctionType type, uintN lambda) #if JS_HAS_DESTRUCTURING /* * If there were destructuring formal parameters, prepend the initializing - * comma expression that we synthesized to body. If the body is a lexical - * scope node, we must make a special TOK_SEQ node, to prepend the formal - * parameter destructuring code without bracing the decompilation of the - * function body's lexical scope. + * comma expression that we synthesized to body. If the body is a return + * node, we must make a special TOK_SEQ node, to prepend the destructuring + * code without bracing the decompilation of the function body. */ if (prolog) { if (body->pn_arity != PN_LIST) { @@ -7881,10 +7880,9 @@ Parser::primaryExpr(TokenKind tt, JSBool afterDot) * ARRAYPUSH node after we parse the rest of the comprehension. */ pnexp = pn->last(); - JS_ASSERT(pn->pn_count == 1 || pn->pn_count == 2); - pn->pn_tail = (--pn->pn_count == 1) - ? &pn->pn_head->pn_next - : &pn->pn_head; + JS_ASSERT(pn->pn_count == 1); + pn->pn_count = 0; + pn->pn_tail = &pn->pn_head; *pn->pn_tail = NULL; pntop = comprehensionTail(pnexp, pn->pn_blockid, diff --git a/js/src/jsparse.h b/js/src/jsparse.h index 658bb071b38a..f366468a591b 100644 --- a/js/src/jsparse.h +++ b/js/src/jsparse.h @@ -69,7 +69,10 @@ JS_BEGIN_EXTERN_C * pn_body: TOK_UPVARS if the function's source body * depends on outer names, else TOK_ARGSBODY * if formal parameters, else TOK_LC node for - * function body statements + * function body statements, else TOK_RETURN + * for expression closure, else TOK_SEQ for + * expression closure with destructured + * formal parameters * pn_cookie: static level and var index for function * pn_dflags: PND_* definition/use flags (see below) * pn_blockid: block id number @@ -84,7 +87,10 @@ JS_BEGIN_EXTERN_C * * * TOK_LC list pn_head: list of pn_count statements - * TOK_IF ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null + * TOK_IF ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null. + * In body of a comprehension or desugared generator + * expression, pn_kid2 is TOK_YIELD, TOK_ARRAYPUSH, + * or (if the push was optimized away) empty TOK_LC. * TOK_SWITCH binary pn_left: discriminant * pn_right: list of TOK_CASE nodes, with at most one * TOK_DEFAULT node, or if there are let bindings @@ -128,7 +134,7 @@ JS_BEGIN_EXTERN_C * pn_expr: initializer or null * each assignment node has * pn_left: TOK_NAME with pn_used true and -* pn_lexdef (NOT pn_expr) set + * pn_lexdef (NOT pn_expr) set * pn_right: initializer * TOK_RETURN unary pn_kid: return expr or null * TOK_SEMI unary pn_kid: expr or null statement @@ -207,6 +213,9 @@ JS_BEGIN_EXTERN_C * TOK_PRIMARY nullary pn_op: JSOp bytecode * * + * TOK_DEFAULT name pn_atom: default XML namespace string literal + * TOK_FILTER binary pn_left: container expr, pn_right: filter expr + * TOK_DBLDOT binary pn_left: container expr, pn_right: selector expr * TOK_ANYNAME nullary pn_op: JSOP_ANYNAME * pn_atom: cx->runtime->atomState.starAtom * TOK_AT unary pn_op: JSOP_TOATTRNAME; pn_kid attribute id/expr @@ -220,11 +229,13 @@ JS_BEGIN_EXTERN_C * pn_head: start tag, content1, ... contentN, end tag * pn_count: 2 + N where N is number of content nodes * N may be > x.length() if {expr} embedded + * After constant folding, these contents may be + * concatenated into string nodes. * TOK_XMLLIST list XML list node * pn_head: content1, ... contentN * TOK_XMLSTAGO, list XML start, end, and point tag contents - * TOK_XMLETAGC, pn_head: tag name or {expr}, ... XML attrs ... - * TOK_XMLPTAGO + * TOK_XMLETAGO, pn_head: tag name or {expr}, ... XML attrs ... + * TOK_XMLPTAGC * TOK_XMLNAME nullary pn_atom: XML name, with no {expr} embedded * TOK_XMLNAME list pn_head: tag name or {expr}, ... name or {expr} * TOK_XMLATTR, nullary pn_atom: attribute value string; pn_op: JSOP_STRING @@ -267,10 +278,10 @@ JS_BEGIN_EXTERN_C * TOK_LEXICALSCOPE name pn_op: JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR * pn_objbox: block object in JSObjectBox holder * pn_expr: block body - * TOK_ARRAYCOMP list pn_head: list of pn_count (1 or 2) elements - * if pn_count is 2, first element is #n=[...] - * last element is block enclosing for loop(s) - * and optionally if-guarded TOK_ARRAYPUSH + * TOK_ARRAYCOMP list pn_count: 1 + * pn_head: list of 1 element, which is block + * enclosing for loop(s) and optionally + * if-guarded TOK_ARRAYPUSH * TOK_ARRAYPUSH unary pn_op: JSOP_ARRAYCOMP * pn_kid: array comprehension expression */ @@ -532,6 +543,35 @@ public: pn_pos.begin.index + str->length() + 2 == pn_pos.end.index); } +#ifdef JS_HAS_GENERATOR_EXPRS + /* + * True if this node is a desugared generator expression. + */ + bool isGeneratorExpr() const { + if (PN_TYPE(this) == js::TOK_LP) { + JSParseNode *callee = this->pn_head; + if (PN_TYPE(callee) == js::TOK_FUNCTION) { + JSParseNode *body = (PN_TYPE(callee->pn_body) == js::TOK_UPVARS) + ? callee->pn_body->pn_tree + : callee->pn_body; + if (PN_TYPE(body) == js::TOK_LEXICALSCOPE) + return true; + } + } + return false; + } + + JSParseNode *generatorExpr() const { + JS_ASSERT(isGeneratorExpr()); + JSParseNode *callee = this->pn_head; + JSParseNode *body = PN_TYPE(callee->pn_body) == js::TOK_UPVARS + ? callee->pn_body->pn_tree + : callee->pn_body; + JS_ASSERT(PN_TYPE(body) == js::TOK_LEXICALSCOPE); + return body->pn_expr; + } +#endif + /* * Compute a pointer to the last element in a singly-linked list. NB: list * must be non-empty for correct PN_LAST usage -- this is asserted! @@ -959,6 +999,11 @@ struct Parser : private js::AutoGCRooter void setPrincipals(JSPrincipals *prin); + const char *getFilename() + { + return tokenStream.getFilename(); + } + /* * Parse a top-level JS script. */ diff --git a/js/src/jsproto.tbl b/js/src/jsproto.tbl index e90e0391ed83..ff5f3fa00d8a 100644 --- a/js/src/jsproto.tbl +++ b/js/src/jsproto.tbl @@ -103,6 +103,8 @@ JS_PROTO(Float32Array, 34, js_InitTypedArrayClasses) JS_PROTO(Float64Array, 35, js_InitTypedArrayClasses) JS_PROTO(Uint8ClampedArray, 36, js_InitTypedArrayClasses) JS_PROTO(Proxy, 37, js_InitProxyClass) +JS_PROTO(Reflect, 38, js_InitReflectClass) +JS_PROTO(ASTNode, 39, js_InitReflectClass) #undef XML_INIT #undef NAMESPACE_INIT diff --git a/js/src/jsreflect.cpp b/js/src/jsreflect.cpp new file mode 100644 index 000000000000..0026c387173f --- /dev/null +++ b/js/src/jsreflect.cpp @@ -0,0 +1,2691 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99 ft=cpp: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * June 12, 2009. + * + * The Initial Developer of the Original Code is + * the Mozilla Corporation. + * + * Contributor(s): + * Dave Herman + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS reflection package. + */ +#include +#include /* for jsparse.h */ +#include "jspubtd.h" +#include "jsatom.h" +#include "jsobj.h" +#include "jsreflect.h" +#include "jscntxt.h" /* for jsparse.h */ +#include "jsbit.h" /* for jsparse.h */ +#include "jsscript.h" /* for jsparse.h */ +#include "jsinterp.h" /* for jsparse.h */ +#include "jsparse.h" +#include "jsregexp.h" +#include "jsvector.h" +#include "jsemit.h" +#include "jsscan.h" +#include "jsprf.h" +#include "jsiter.h" +#include "jsbool.h" +#include "jsval.h" +#include "jsvalue.h" +#include "jsobjinlines.h" +#include "jsarray.h" +#include "jsnum.h" + +using namespace js; + +namespace js { + +char const *aopNames[] = { + "=", /* AOP_ASSIGN */ + "+=", /* AOP_PLUS */ + "-=", /* AOP_MINUS */ + "*=", /* AOP_STAR */ + "/=", /* AOP_DIV */ + "%=", /* AOP_MOD */ + "<<=", /* AOP_LSH */ + ">>=", /* AOP_RSH */ + ">>>=", /* AOP_URSH */ + "|=", /* AOP_BITOR */ + "^=", /* AOP_BITXOR */ + "&=" /* AOP_BITAND */ +}; + +char const *binopNames[] = { + "==", /* BINOP_EQ */ + "!=", /* BINOP_NE */ + "===", /* BINOP_STRICTEQ */ + "!==", /* BINOP_STRICTNE */ + "<", /* BINOP_LT */ + "<=", /* BINOP_LE */ + ">", /* BINOP_GT */ + ">=", /* BINOP_GE */ + "<<", /* BINOP_LSH */ + ">>", /* BINOP_RSH */ + ">>>", /* BINOP_URSH */ + "+", /* BINOP_PLUS */ + "-", /* BINOP_MINUS */ + "*", /* BINOP_STAR */ + "/", /* BINOP_DIV */ + "%", /* BINOP_MOD */ + "|", /* BINOP_BITOR */ + "^", /* BINOP_BITXOR */ + "&", /* BINOP_BITAND */ + "in", /* BINOP_IN */ + "instanceof", /* BINOP_INSTANCEOF */ + "..", /* BINOP_DBLDOT */ +}; + +char const *unopNames[] = { + "delete", /* UNOP_DELETE */ + "-", /* UNOP_NEG */ + "+", /* UNOP_POS */ + "!", /* UNOP_NOT */ + "~", /* UNOP_BITNOT */ + "typeof", /* UNOP_TYPEOF */ + "void" /* UNOP_VOID */ +}; + +char const *nodeTypeNames[] = { +#define ASTDEF(ast, str) str, +#include "jsast.tbl" +#undef ASTDEF + NULL +}; + +typedef Vector NodeVector; + +/* + * JSParseNode is a somewhat intricate data structure, and its invariants have + * evolved, making it more likely that there could be a disconnect between the + * parser and the AST serializer. We use these macros to check invariants on a + * parse node and raise a dynamic error on failure. + */ +#define LOCAL_ASSERT(expr) \ + JS_BEGIN_MACRO \ + JS_ASSERT(expr); \ + if (!(expr)) { \ + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_PARSE_NODE); \ + return false; \ + } \ + JS_END_MACRO + +#define LOCAL_NOT_REACHED(expr) \ + JS_BEGIN_MACRO \ + JS_NOT_REACHED(expr); \ + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_PARSE_NODE); \ + return false; \ + JS_END_MACRO + + +/* + * Builder class that constructs JavaScript AST node objects. See: + * + * https://developer.mozilla.org/en/SpiderMonkey/Parser_API + * + * Bug 569487: generalize builder interface + */ +class NodeBuilder +{ + JSContext *cx; + char const *src; /* source filename or null */ + Value srcval; /* source filename JS value or null */ + + public: + NodeBuilder(JSContext *c, char const *s) + : cx(c), src(s) { + } + + bool init() { + if (src) + return atomValue(src, &srcval); + + srcval.setNull(); + return true; + } + + private: + bool atomValue(const char *s, Value *dst) { + /* + * Bug 575416: instead of js_Atomize, lookup constant atoms in tbl file + */ + JSAtom *atom = js_Atomize(cx, s, strlen(s), 0); + if (!atom) + return false; + + *dst = Valueify(ATOM_TO_JSVAL(atom)); + return true; + } + + bool newObject(JSObject **dst) { + JSObject *nobj = NewNonFunction(cx, &js_ObjectClass, NULL, NULL); + if (!nobj) + return false; + + *dst = nobj; + return true; + } + + bool newArray(NodeVector &elts, Value *dst); + + bool newNode(ASTType type, TokenPos *pos, JSObject **dst); + + bool newNode(ASTType type, TokenPos *pos, Value *dst) { + JSObject *node; + return newNode(type, pos, &node) && + setResult(node, dst); + } + + bool newNode(ASTType type, TokenPos *pos, const char *childName, Value child, Value *dst) { + JSObject *node; + return newNode(type, pos, &node) && + setProperty(node, childName, child) && + setResult(node, dst); + } + + bool newNode(ASTType type, TokenPos *pos, + const char *childName1, Value child1, + const char *childName2, Value child2, + Value *dst) { + JSObject *node; + return newNode(type, pos, &node) && + setProperty(node, childName1, child1) && + setProperty(node, childName2, child2) && + setResult(node, dst); + } + + bool newNode(ASTType type, TokenPos *pos, + const char *childName1, Value child1, + const char *childName2, Value child2, + const char *childName3, Value child3, + Value *dst) { + JSObject *node; + return newNode(type, pos, &node) && + setProperty(node, childName1, child1) && + setProperty(node, childName2, child2) && + setProperty(node, childName3, child3) && + setResult(node, dst); + } + + bool newNode(ASTType type, TokenPos *pos, + const char *childName1, Value child1, + const char *childName2, Value child2, + const char *childName3, Value child3, + const char *childName4, Value child4, + Value *dst) { + JSObject *node; + return newNode(type, pos, &node) && + setProperty(node, childName1, child1) && + setProperty(node, childName2, child2) && + setProperty(node, childName3, child3) && + setProperty(node, childName4, child4) && + setResult(node, dst); + } + + bool newNode(ASTType type, TokenPos *pos, + const char *childName1, Value child1, + const char *childName2, Value child2, + const char *childName3, Value child3, + const char *childName4, Value child4, + const char *childName5, Value child5, + Value *dst) { + JSObject *node; + return newNode(type, pos, &node) && + setProperty(node, childName1, child1) && + setProperty(node, childName2, child2) && + setProperty(node, childName3, child3) && + setProperty(node, childName4, child4) && + setProperty(node, childName5, child5) && + setResult(node, dst); + } + + bool newListNode(ASTType type, TokenPos *pos, const char *propName, + NodeVector &elts, Value *dst) { + Value array; + return newArray(elts, &array) && + newNode(type, pos, propName, array, dst); + } + + bool setProperty(JSObject *obj, const char *name, Value val) { + JS_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE); + + /* Represent "no node" as null and ensure users are not exposed to magic values. */ + if (val.isMagic(JS_SERIALIZE_NO_NODE)) + val.setNull(); + + /* + * Bug 575416: instead of js_Atomize, lookup constant atoms in tbl file + */ + JSAtom *atom = js_Atomize(cx, name, strlen(name), 0); + if (!atom) + return false; + + return obj->defineProperty(cx, ATOM_TO_JSID(atom), val); + } + + bool setNodeLoc(JSObject *obj, TokenPos *pos); + + bool setResult(JSObject *obj, Value *dst) { + JS_ASSERT(obj); + dst->setObject(*obj); + return true; + } + + public: + /* + * All of the public builder methods take as their last two + * arguments a nullable token position and a non-nullable, rooted + * outparam. + * + * All Value arguments are rooted. Any Value arguments representing + * optional subnodes may be a JS_SERIALIZE_NO_NODE magic value. + */ + + /* + * misc nodes + */ + + bool program(NodeVector &elts, TokenPos *pos, Value *dst); + + bool literal(Value val, TokenPos *pos, Value *dst); + + bool identifier(Value name, TokenPos *pos, Value *dst); + + bool function(ASTType type, TokenPos *pos, + Value id, NodeVector &args, Value body, + bool isGenerator, bool isExpression, Value *dst); + + bool variableDeclarator(Value id, Value init, TokenPos *pos, Value *dst); + + bool switchCase(Value expr, NodeVector &elts, TokenPos *pos, Value *dst); + + bool catchClause(Value var, Value guard, Value body, TokenPos *pos, Value *dst); + + bool propertyInitializer(Value key, Value val, PropKind kind, TokenPos *pos, Value *dst); + + + /* + * statements + */ + + bool blockStatement(NodeVector &elts, TokenPos *pos, Value *dst); + + bool expressionStatement(Value expr, TokenPos *pos, Value *dst); + + bool emptyStatement(TokenPos *pos, Value *dst); + + bool ifStatement(Value test, Value cons, Value alt, TokenPos *pos, Value *dst); + + bool breakStatement(Value label, TokenPos *pos, Value *dst); + + bool continueStatement(Value label, TokenPos *pos, Value *dst); + + bool labeledStatement(Value label, Value stmt, TokenPos *pos, Value *dst); + + bool throwStatement(Value arg, TokenPos *pos, Value *dst); + + bool returnStatement(Value arg, TokenPos *pos, Value *dst); + + bool forStatement(Value init, Value test, Value update, Value stmt, + TokenPos *pos, Value *dst); + + bool forInStatement(Value var, Value expr, Value stmt, + bool isForEach, TokenPos *pos, Value *dst); + + bool withStatement(Value expr, Value stmt, TokenPos *pos, Value *dst); + + bool whileStatement(Value test, Value stmt, TokenPos *pos, Value *dst); + + bool doWhileStatement(Value stmt, Value test, TokenPos *pos, Value *dst); + + bool switchStatement(Value disc, NodeVector &elts, bool lexical, TokenPos *pos, Value *dst); + + bool tryStatement(Value body, NodeVector &catches, Value finally, TokenPos *pos, Value *dst); + + bool debuggerStatement(TokenPos *pos, Value *dst); + + /* + * expressions + */ + + bool binaryExpression(BinaryOperator op, Value left, Value right, TokenPos *pos, Value *dst); + + bool unaryExpression(UnaryOperator op, Value expr, TokenPos *pos, Value *dst); + + bool assignmentExpression(AssignmentOperator op, Value lhs, Value rhs, + TokenPos *pos, Value *dst); + + bool updateExpression(Value expr, bool incr, bool prefix, TokenPos *pos, Value *dst); + + bool logicalExpression(bool lor, Value left, Value right, TokenPos *pos, Value *dst); + + bool conditionalExpression(Value test, Value cons, Value alt, TokenPos *pos, Value *dst); + + bool sequenceExpression(NodeVector &elts, TokenPos *pos, Value *dst); + + bool newExpression(Value callee, NodeVector &args, TokenPos *pos, Value *dst); + + bool callExpression(Value callee, NodeVector &args, TokenPos *pos, Value *dst); + + bool memberExpression(bool computed, Value expr, Value member, TokenPos *pos, Value *dst); + + bool arrayExpression(NodeVector &elts, TokenPos *pos, Value *dst); + + bool objectExpression(NodeVector &elts, TokenPos *pos, Value *dst); + + bool thisExpression(TokenPos *pos, Value *dst); + + bool yieldExpression(Value arg, TokenPos *pos, Value *dst); + + bool comprehensionBlock(Value patt, Value src, bool isForEach, TokenPos *pos, Value *dst); + + bool comprehensionExpression(Value body, NodeVector &blocks, Value filter, + TokenPos *pos, Value *dst); + + bool generatorExpression(Value body, NodeVector &blocks, Value filter, + TokenPos *pos, Value *dst); + + bool graphExpression(jsint idx, Value expr, TokenPos *pos, Value *dst); + + bool graphIndexExpression(jsint idx, TokenPos *pos, Value *dst); + + /* + * declarations + */ + + bool variableDeclaration(NodeVector &elts, VarDeclKind kind, TokenPos *pos, Value *dst); + + /* + * patterns + */ + + bool arrayPattern(NodeVector &elts, TokenPos *pos, Value *dst); + + bool objectPattern(NodeVector &elts, TokenPos *pos, Value *dst); + + bool propertyPattern(Value key, Value patt, TokenPos *pos, Value *dst); + + /* + * xml + */ + + bool xmlAnyName(TokenPos *pos, Value *dst); + + bool xmlEscapeExpression(Value expr, TokenPos *pos, Value *dst); + + bool xmlDefaultNamespace(Value ns, TokenPos *pos, Value *dst); + + bool xmlFilterExpression(Value left, Value right, TokenPos *pos, Value *dst); + + bool xmlAttributeSelector(Value expr, TokenPos *pos, Value *dst); + + bool xmlQualifiedIdentifier(Value left, Value right, bool computed, TokenPos *pos, Value *dst); + + bool xmlElement(NodeVector &elts, TokenPos *pos, Value *dst); + + bool xmlText(Value text, TokenPos *pos, Value *dst); + + bool xmlList(NodeVector &elts, TokenPos *pos, Value *dst); + + bool xmlStartTag(NodeVector &elts, TokenPos *pos, Value *dst); + + bool xmlEndTag(NodeVector &elts, TokenPos *pos, Value *dst); + + bool xmlPointTag(NodeVector &elts, TokenPos *pos, Value *dst); + + bool xmlName(Value text, TokenPos *pos, Value *dst); + + bool xmlName(NodeVector &elts, TokenPos *pos, Value *dst); + + bool xmlAttribute(Value text, TokenPos *pos, Value *dst); + + bool xmlCdata(Value text, TokenPos *pos, Value *dst); + + bool xmlComment(Value text, TokenPos *pos, Value *dst); + + bool xmlPI(Value target, TokenPos *pos, Value *dst); + + bool xmlPI(Value target, Value content, TokenPos *pos, Value *dst); +}; + +bool +NodeBuilder::newNode(ASTType type, TokenPos *pos, JSObject **dst) +{ + JS_ASSERT(type > AST_ERROR && type < AST_LIMIT); + + Value tv; + + JSObject *node = NewNonFunction(cx, &js_ObjectClass, NULL, NULL); + if (!node || + !setNodeLoc(node, pos) || + !atomValue(nodeTypeNames[type], &tv) || + !setProperty(node, "type", tv)) + return false; + + *dst = node; + return true; +} + +bool +NodeBuilder::newArray(NodeVector &elts, Value *dst) +{ + JSObject *array = js_NewArrayObject(cx, 0, NULL); + if (!array) + return false; + + const size_t len = elts.length(); + for (size_t i = 0; i < len; i++) { + Value val = elts[i]; + + JS_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE); + + /* Represent "no node" as null and ensure users are not exposed to magic values. */ + if (val.isMagic(JS_SERIALIZE_NO_NODE)) + val.setNull(); + + if (!js_ArrayCompPush(cx, array, val)) + return false; + } + + dst->setObject(*array); + return true; +} + +bool +NodeBuilder::setNodeLoc(JSObject *node, TokenPos *pos) +{ + if (!pos) + return setProperty(node, "loc", NullValue()); + + JSObject *loc, *to; + Value tv; + + return newObject(&loc) && + setProperty(node, "loc", ObjectValue(*loc)) && + setProperty(loc, "source", srcval) && + + newObject(&to) && + setProperty(loc, "start", ObjectValue(*to)) && + (tv.setNumber(pos->begin.lineno), true) && + setProperty(to, "line", tv) && + (tv.setNumber(pos->begin.index), true) && + setProperty(to, "column", tv) && + + newObject(&to) && + setProperty(loc, "end", ObjectValue(*to)) && + (tv.setNumber(pos->end.lineno), true) && + setProperty(to, "line", tv) && + (tv.setNumber(pos->end.index), true) && + setProperty(to, "column", tv); +} + +bool +NodeBuilder::program(NodeVector &elts, TokenPos *pos, Value *dst) +{ + Value array; + return newArray(elts, &array) && + newNode(AST_PROGRAM, pos, "body", array, dst); +} + +bool +NodeBuilder::blockStatement(NodeVector &elts, TokenPos *pos, Value *dst) +{ + Value array; + return newArray(elts, &array) && + newNode(AST_BLOCK_STMT, pos, "body", array, dst); +} + +bool +NodeBuilder::expressionStatement(Value expr, TokenPos *pos, Value *dst) +{ + return newNode(AST_EXPR_STMT, pos, "expression", expr, dst); +} + +bool +NodeBuilder::emptyStatement(TokenPos *pos, Value *dst) +{ + return newNode(AST_EMPTY_STMT, pos, dst); +} + +bool +NodeBuilder::ifStatement(Value test, Value cons, Value alt, TokenPos *pos, Value *dst) +{ + return newNode(AST_IF_STMT, pos, + "test", test, + "consequent", cons, + "alternate", alt, + dst); +} + +bool +NodeBuilder::breakStatement(Value label, TokenPos *pos, Value *dst) +{ + return newNode(AST_BREAK_STMT, pos, "label", label, dst); +} + +bool +NodeBuilder::continueStatement(Value label, TokenPos *pos, Value *dst) +{ + return newNode(AST_CONTINUE_STMT, pos, "label", label, dst); +} + +bool +NodeBuilder::labeledStatement(Value label, Value stmt, TokenPos *pos, Value *dst) +{ + return newNode(AST_LAB_STMT, pos, + "label", label, + "body", stmt, + dst); +} + +bool +NodeBuilder::throwStatement(Value arg, TokenPos *pos, Value *dst) +{ + return newNode(AST_THROW_STMT, pos, "argument", arg, dst); +} + +bool +NodeBuilder::returnStatement(Value arg, TokenPos *pos, Value *dst) +{ + return newNode(AST_RETURN_STMT, pos, "argument", arg, dst); +} + +bool +NodeBuilder::forStatement(Value init, Value test, Value update, Value stmt, + TokenPos *pos, Value *dst) +{ + return newNode(AST_FOR_STMT, pos, + "init", init, + "test", test, + "update", update, + "body", stmt, + dst); +} + +bool +NodeBuilder::forInStatement(Value var, Value expr, Value stmt, bool isForEach, + TokenPos *pos, Value *dst) +{ + return newNode(AST_FOR_IN_STMT, pos, + "left", var, + "right", expr, + "body", stmt, + "each", BooleanValue(isForEach), + dst); +} + +bool +NodeBuilder::withStatement(Value expr, Value stmt, TokenPos *pos, Value *dst) +{ + return newNode(AST_WITH_STMT, pos, + "object", expr, + "body", stmt, + dst); +} + +bool +NodeBuilder::whileStatement(Value test, Value stmt, TokenPos *pos, Value *dst) +{ + return newNode(AST_WHILE_STMT, pos, + "test", test, + "body", stmt, + dst); +} + +bool +NodeBuilder::doWhileStatement(Value stmt, Value test, TokenPos *pos, Value *dst) +{ + return newNode(AST_DO_STMT, pos, + "body", stmt, + "test", test, + dst); +} + +bool +NodeBuilder::switchStatement(Value disc, NodeVector &elts, bool lexical, TokenPos *pos, Value *dst) +{ + Value array; + return newArray(elts, &array) && + newNode(AST_SWITCH_STMT, pos, + "discriminant", disc, + "cases", array, + "lexical", BooleanValue(lexical), + dst); +} + +bool +NodeBuilder::tryStatement(Value body, NodeVector &catches, Value finally, + TokenPos *pos, Value *dst) +{ + Value handler; + if (catches.empty()) + handler.setNull(); + else if (catches.length() == 1) + handler = catches[0]; + else if (!newArray(catches, &handler)) + return false; + + return newNode(AST_TRY_STMT, pos, + "block", body, + "handler", handler, + "finalizer", finally, + dst); +} + +bool +NodeBuilder::debuggerStatement(TokenPos *pos, Value *dst) +{ + return newNode(AST_DEBUGGER_STMT, pos, dst); +} + +bool +NodeBuilder::binaryExpression(BinaryOperator op, Value left, Value right, TokenPos *pos, Value *dst) +{ + JS_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT); + + Value opName; + + return atomValue(binopNames[op], &opName) && + newNode(AST_BINARY_EXPR, pos, + "operator", opName, + "left", left, + "right", right, + dst); +} + +bool +NodeBuilder::unaryExpression(UnaryOperator unop, Value expr, TokenPos *pos, Value *dst) +{ + JS_ASSERT(unop > UNOP_ERR && unop < UNOP_LIMIT); + + Value opName; + + return atomValue(unopNames[unop], &opName) && + newNode(AST_UNARY_EXPR, pos, + "operator", opName, + "argument", expr, + "prefix", BooleanValue(true), + dst); +} + +bool +NodeBuilder::assignmentExpression(AssignmentOperator aop, Value lhs, Value rhs, + TokenPos *pos, Value *dst) +{ + JS_ASSERT(aop > AOP_ERR && aop < AOP_LIMIT); + + Value opName; + + return atomValue(aopNames[aop], &opName) && + newNode(AST_ASSIGN_EXPR, pos, + "operator", opName, + "left", lhs, + "right", rhs, + dst); +} + +bool +NodeBuilder::updateExpression(Value expr, bool incr, bool prefix, TokenPos *pos, Value *dst) +{ + Value opName; + + return atomValue(incr ? "++" : "--", &opName) && + newNode(AST_UPDATE_EXPR, pos, + "operator", opName, + "argument", expr, + "prefix", BooleanValue(prefix), + dst); +} + +bool +NodeBuilder::logicalExpression(bool lor, Value left, Value right, TokenPos *pos, Value *dst) +{ + Value opName; + + return atomValue(lor ? "||" : "&&", &opName) && + newNode(AST_LOGICAL_EXPR, pos, + "operator", opName, + "left", left, + "right", right, + dst); +} + +bool +NodeBuilder::conditionalExpression(Value test, Value cons, Value alt, TokenPos *pos, Value *dst) +{ + return newNode(AST_COND_EXPR, pos, + "test", test, + "consequent", cons, + "alternate", alt, + dst); +} + +bool +NodeBuilder::sequenceExpression(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_LIST_EXPR, pos, + "expressions", elts, + dst); +} + +bool +NodeBuilder::callExpression(Value callee, NodeVector &args, TokenPos *pos, Value *dst) +{ + Value array; + return newArray(args, &array) && + newNode(AST_CALL_EXPR, pos, + "callee", callee, + "arguments", array, + dst); +} + +bool +NodeBuilder::newExpression(Value callee, NodeVector &args, TokenPos *pos, Value *dst) +{ + Value array; + return newArray(args, &array) && + newNode(AST_NEW_EXPR, pos, + "callee", callee, + "arguments", array, + dst); +} + +bool +NodeBuilder::memberExpression(bool computed, Value expr, Value member, TokenPos *pos, Value *dst) +{ + return newNode(AST_MEMBER_EXPR, pos, + "object", expr, + "property", member, + "computed", BooleanValue(computed), + dst); +} + +bool +NodeBuilder::arrayExpression(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_ARRAY_EXPR, pos, + "elements", elts, + dst); +} + +bool +NodeBuilder::propertyPattern(Value key, Value patt, TokenPos *pos, Value *dst) +{ + Value kindName; + + return atomValue("init", &kindName) && + newNode(AST_PROPERTY, pos, + "key", key, + "value", patt, + "kind", kindName, + dst); +} + +bool +NodeBuilder::propertyInitializer(Value key, Value val, PropKind kind, TokenPos *pos, Value *dst) +{ + Value kindName; + + return atomValue(kind == PROP_INIT + ? "init" + : kind == PROP_GETTER + ? "get" + : "set", &kindName) && + newNode(AST_PROPERTY, pos, + "key", key, + "value", val, + "kind", kindName, + dst); +} + +bool +NodeBuilder::objectExpression(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_OBJECT_EXPR, pos, "properties", elts, dst); +} + +bool +NodeBuilder::thisExpression(TokenPos *pos, Value *dst) +{ + return newNode(AST_THIS_EXPR, pos, dst); +} + +bool +NodeBuilder::yieldExpression(Value arg, TokenPos *pos, Value *dst) +{ + return newNode(AST_YIELD_EXPR, pos, "argument", arg, dst); +} + +bool +NodeBuilder::comprehensionBlock(Value patt, Value src, bool isForEach, TokenPos *pos, Value *dst) +{ + return newNode(AST_COMP_BLOCK, pos, + "left", patt, + "right", src, + "each", BooleanValue(isForEach), + dst); +} + +bool +NodeBuilder::comprehensionExpression(Value body, NodeVector &blocks, Value filter, + TokenPos *pos, Value *dst) +{ + Value array; + + return newArray(blocks, &array) && + newNode(AST_COMP_EXPR, pos, + "body", body, + "blocks", array, + "filter", filter, + dst); +} + +bool +NodeBuilder::generatorExpression(Value body, NodeVector &blocks, Value filter, TokenPos *pos, Value *dst) +{ + Value array; + + return newArray(blocks, &array) && + newNode(AST_GENERATOR_EXPR, pos, + "body", body, + "blocks", array, + "filter", filter, + dst); +} + +bool +NodeBuilder::graphExpression(jsint idx, Value expr, TokenPos *pos, Value *dst) +{ + return newNode(AST_GRAPH_EXPR, pos, + "index", NumberValue(idx), + "expression", expr, + dst); +} + +bool +NodeBuilder::graphIndexExpression(jsint idx, TokenPos *pos, Value *dst) +{ + return newNode(AST_GRAPH_IDX_EXPR, pos, "index", NumberValue(idx), dst); +} + +bool +NodeBuilder::variableDeclaration(NodeVector &elts, VarDeclKind kind, TokenPos *pos, Value *dst) +{ + JS_ASSERT(kind > VARDECL_ERR && kind < VARDECL_LIMIT); + + Value array, kindName; + + return atomValue(kind == VARDECL_CONST + ? "const" + : kind == VARDECL_LET + ? "let" + : "var", &kindName) && + newArray(elts, &array) && + newNode(AST_VAR_DECL, pos, + "declarations", array, + "kind", kindName, + dst); +} + +bool +NodeBuilder::variableDeclarator(Value id, Value init, TokenPos *pos, Value *dst) +{ + return newNode(AST_VAR_DTOR, pos, "id", id, "init", init, dst); +} + +bool +NodeBuilder::switchCase(Value expr, NodeVector &elts, TokenPos *pos, Value *dst) +{ + Value array; + + return newArray(elts, &array) && + newNode(AST_CASE, pos, + "test", expr, + "consequent", array, + dst); +} + +bool +NodeBuilder::catchClause(Value var, Value guard, Value body, TokenPos *pos, Value *dst) +{ + return newNode(AST_CATCH, pos, + "param", var, + "guard", guard, + "body", body, + dst); +} + +bool +NodeBuilder::literal(Value val, TokenPos *pos, Value *dst) +{ + return newNode(AST_LITERAL, pos, "value", val, dst); +} + +bool +NodeBuilder::identifier(Value name, TokenPos *pos, Value *dst) +{ + return newNode(AST_IDENTIFIER, pos, "name", name, dst); +} + +bool +NodeBuilder::objectPattern(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_OBJECT_PATT, pos, "properties", elts, dst); +} + +bool +NodeBuilder::arrayPattern(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_ARRAY_PATT, pos, "elements", elts, dst); +} + +bool +NodeBuilder::function(ASTType type, TokenPos *pos, + Value id, NodeVector &args, Value body, + bool isGenerator, bool isExpression, + Value *dst) +{ + Value array; + + return newArray(args, &array) && + newNode(type, pos, + "id", id, + "params", array, + "body", body, + "generator", BooleanValue(isGenerator), + "expression", BooleanValue(isExpression), + dst); +} + +bool +NodeBuilder::xmlAnyName(TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLANYNAME, pos, dst); +} + +bool +NodeBuilder::xmlEscapeExpression(Value expr, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLESCAPE, pos, "expression", expr, dst); +} + +bool +NodeBuilder::xmlFilterExpression(Value left, Value right, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLFILTER, pos, "left", left, "right", right, dst); +} + +bool +NodeBuilder::xmlDefaultNamespace(Value ns, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLDEFAULT, pos, "namespace", ns, dst); +} + +bool +NodeBuilder::xmlAttributeSelector(Value expr, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLATTR_SEL, pos, "attribute", expr, dst); +} + +bool +NodeBuilder::xmlQualifiedIdentifier(Value left, Value right, bool computed, + TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLQUAL, pos, + "left", left, + "right", right, + "computed", BooleanValue(computed), + dst); +} + +bool +NodeBuilder::xmlElement(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_XMLELEM, pos, "contents", elts, dst); +} + +bool +NodeBuilder::xmlText(Value text, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLTEXT, pos, "text", text, dst); +} + +bool +NodeBuilder::xmlList(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_XMLLIST, pos, "contents", elts, dst); +} + +bool +NodeBuilder::xmlStartTag(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_XMLSTART, pos, "contents", elts, dst); +} + +bool +NodeBuilder::xmlEndTag(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_XMLEND, pos, "contents", elts, dst); +} + +bool +NodeBuilder::xmlPointTag(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_XMLPOINT, pos, "contents", elts, dst); +} + +bool +NodeBuilder::xmlName(Value text, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLNAME, pos, "contents", text, dst); +} + +bool +NodeBuilder::xmlName(NodeVector &elts, TokenPos *pos, Value *dst) +{ + return newListNode(AST_XMLNAME, pos, "contents", elts, dst); +} + +bool +NodeBuilder::xmlAttribute(Value text, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLATTR, pos, "value", text, dst); +} + +bool +NodeBuilder::xmlCdata(Value text, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLCDATA, pos, "contents", text, dst); +} + +bool +NodeBuilder::xmlComment(Value text, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLCOMMENT, pos, "contents", text, dst); +} + +bool +NodeBuilder::xmlPI(Value target, TokenPos *pos, Value *dst) +{ + return xmlPI(target, NullValue(), pos, dst); +} + +bool +NodeBuilder::xmlPI(Value target, Value contents, TokenPos *pos, Value *dst) +{ + return newNode(AST_XMLPI, pos, + "target", target, + "contents", contents, + dst); +} + + +/* + * Serialization of parse nodes to JavaScript objects. + * + * All serialization methods take a non-nullable JSParseNode pointer. + */ + +class ASTSerializer +{ + JSContext *cx; + NodeBuilder builder; + uintN lineno; + + Value atomContents(JSAtom *atom) { + return Valueify(ATOM_TO_JSVAL(atom ? atom : cx->runtime->atomState.emptyAtom)); + } + + BinaryOperator binop(TokenKind tk, JSOp op); + UnaryOperator unop(TokenKind tk, JSOp op); + AssignmentOperator aop(JSOp op); + + bool statements(JSParseNode *pn, NodeVector &elts); + bool expressions(JSParseNode *pn, NodeVector &elts); + bool xmls(JSParseNode *pn, NodeVector &elts); + bool leftAssociate(JSParseNode *pn, Value *dst); + bool binaryOperands(JSParseNode *pn, NodeVector &elts); + bool functionArgs(JSParseNode *pn, JSParseNode *pnargs, JSParseNode *pndestruct, + JSParseNode *pnbody, NodeVector &args); + + bool sourceElement(JSParseNode *pn, Value *dst); + + bool declaration(JSParseNode *pn, Value *dst); + bool variableDeclaration(JSParseNode *pn, bool let, Value *dst); + bool variableDeclarator(JSParseNode *pn, VarDeclKind *pkind, Value *dst); + + bool optStatement(JSParseNode *pn, Value *dst) { + if (!pn) { + dst->setMagic(JS_SERIALIZE_NO_NODE); + return true; + } + return statement(pn, dst); + } + + bool forInit(JSParseNode *pn, Value *dst); + bool statement(JSParseNode *pn, Value *dst); + bool blockStatement(JSParseNode *pn, Value *dst); + bool switchStatement(JSParseNode *pn, Value *dst); + bool switchCase(JSParseNode *pn, Value *dst); + bool tryStatement(JSParseNode *pn, Value *dst); + bool catchClause(JSParseNode *pn, Value *dst); + + bool optExpression(JSParseNode *pn, Value *dst) { + if (!pn) { + dst->setMagic(JS_SERIALIZE_NO_NODE); + return true; + } + return expression(pn, dst); + } + + bool expression(JSParseNode *pn, Value *dst); + + bool propertyName(JSParseNode *pn, Value *dst); + bool property(JSParseNode *pn, Value *dst); + + bool optIdentifier(JSAtom *atom, TokenPos *pos, Value *dst) { + if (!atom) { + dst->setMagic(JS_SERIALIZE_NO_NODE); + return true; + } + return identifier(atom, pos, dst); + } + + bool identifier(JSAtom *atom, TokenPos *pos, Value *dst); + bool identifier(JSParseNode *pn, Value *dst); + bool literal(JSParseNode *pn, Value *dst); + + bool pattern(JSParseNode *pn, VarDeclKind *pkind, Value *dst); + bool arrayPattern(JSParseNode *pn, VarDeclKind *pkind, Value *dst); + bool objectPattern(JSParseNode *pn, VarDeclKind *pkind, Value *dst); + + bool function(JSParseNode *pn, ASTType type, Value *dst); + bool functionArgsAndBody(JSParseNode *pn, NodeVector &args, Value *body); + bool functionBody(JSParseNode *pn, TokenPos *pos, Value *dst); + + bool comprehensionBlock(JSParseNode *pn, Value *dst); + bool comprehension(JSParseNode *pn, Value *dst); + bool generatorExpression(JSParseNode *pn, Value *dst); + + bool xml(JSParseNode *pn, Value *dst); + + public: + ASTSerializer(JSContext *c, char const *src, uintN ln) + : cx(c), builder(c, src), lineno(ln) { + } + + bool init() { + return builder.init(); + } + + bool program(JSParseNode *pn, Value *dst); +}; + +AssignmentOperator +ASTSerializer::aop(JSOp op) +{ + switch (op) { + case JSOP_NOP: + return AOP_ASSIGN; + case JSOP_ADD: + return AOP_PLUS; + case JSOP_SUB: + return AOP_MINUS; + case JSOP_MUL: + return AOP_STAR; + case JSOP_DIV: + return AOP_DIV; + case JSOP_MOD: + return AOP_MOD; + case JSOP_LSH: + return AOP_LSH; + case JSOP_RSH: + return AOP_RSH; + case JSOP_URSH: + return AOP_URSH; + case JSOP_BITOR: + return AOP_BITOR; + case JSOP_BITXOR: + return AOP_BITXOR; + case JSOP_BITAND: + return AOP_BITAND; + default: + return AOP_ERR; + } +} + +UnaryOperator +ASTSerializer::unop(TokenKind tk, JSOp op) +{ + if (tk == TOK_DELETE) + return UNOP_DELETE; + + switch (op) { + case JSOP_NEG: + return UNOP_NEG; + case JSOP_POS: + return UNOP_POS; + case JSOP_NOT: + return UNOP_NOT; + case JSOP_BITNOT: + return UNOP_BITNOT; + case JSOP_TYPEOF: + case JSOP_TYPEOFEXPR: + return UNOP_TYPEOF; + case JSOP_VOID: + return UNOP_VOID; + default: + return UNOP_ERR; + } +} + +BinaryOperator +ASTSerializer::binop(TokenKind tk, JSOp op) +{ + switch (tk) { + case TOK_EQOP: + switch (op) { + case JSOP_EQ: + return BINOP_EQ; + case JSOP_NE: + return BINOP_NE; + case JSOP_STRICTEQ: + return BINOP_STRICTEQ; + case JSOP_STRICTNE: + return BINOP_STRICTNE; + default: + return BINOP_ERR; + } + + case TOK_RELOP: + switch (op) { + case JSOP_LT: + return BINOP_LT; + case JSOP_LE: + return BINOP_LE; + case JSOP_GT: + return BINOP_GT; + case JSOP_GE: + return BINOP_GE; + default: + return BINOP_ERR; + } + + case TOK_SHOP: + switch (op) { + case JSOP_LSH: + return BINOP_LSH; + case JSOP_RSH: + return BINOP_RSH; + case JSOP_URSH: + return BINOP_URSH; + default: + return BINOP_ERR; + } + + case TOK_PLUS: + return BINOP_PLUS; + case TOK_MINUS: + return BINOP_MINUS; + case TOK_STAR: + return BINOP_STAR; + case TOK_DIVOP: + return (op == JSOP_MOD) ? BINOP_MOD : BINOP_DIV; + case TOK_BITOR: + return BINOP_BITOR; + case TOK_BITXOR: + return BINOP_BITXOR; + case TOK_BITAND: + return BINOP_BITAND; + case TOK_IN: + return BINOP_IN; + case TOK_INSTANCEOF: + return BINOP_INSTANCEOF; + case TOK_DBLDOT: + return BINOP_DBLDOT; + default: + return BINOP_ERR; + } +} + +bool +ASTSerializer::statements(JSParseNode *pn, NodeVector &elts) +{ + JS_ASSERT(PN_TYPE(pn) == TOK_LC && pn->pn_arity == PN_LIST); + + if (!elts.reserve(pn->pn_count)) + return false; + + for (JSParseNode *next = pn->pn_head; next; next = next->pn_next) { + Value elt; + if (!sourceElement(next, &elt)) + return false; + (void)elts.append(elt); /* space check above */ + } + + return true; +} + +bool +ASTSerializer::expressions(JSParseNode *pn, NodeVector &elts) +{ + if (!elts.reserve(pn->pn_count)) + return false; + + for (JSParseNode *next = pn->pn_head; next; next = next->pn_next) { + Value elt; + if (!expression(next, &elt)) + return false; + (void)elts.append(elt); /* space check above */ + } + + return true; +} + +bool +ASTSerializer::xmls(JSParseNode *pn, NodeVector &elts) +{ + if (!elts.reserve(pn->pn_count)) + return false; + + for (JSParseNode *next = pn->pn_head; next; next = next->pn_next) { + Value elt; + if (!xml(next, &elt)) + return false; + (void)elts.append(elt); /* space check above */ + } + + return true; +} + +bool +ASTSerializer::blockStatement(JSParseNode *pn, Value *dst) +{ + JS_ASSERT(PN_TYPE(pn) == TOK_LC); + + NodeVector stmts(cx); + return statements(pn, stmts) && + builder.blockStatement(stmts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::program(JSParseNode *pn, Value *dst) +{ + JS_ASSERT(pn); + + /* Workaround for bug 588061: parser's reported start position is always 0:0. */ + pn->pn_pos.begin.lineno = lineno; + + NodeVector stmts(cx); + return statements(pn, stmts) && + builder.program(stmts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::sourceElement(JSParseNode *pn, Value *dst) +{ + /* SpiderMonkey allows declarations even in pure statement contexts. */ + return statement(pn, dst); +} + +bool +ASTSerializer::declaration(JSParseNode *pn, Value *dst) +{ + JS_ASSERT(PN_TYPE(pn) == TOK_FUNCTION || + PN_TYPE(pn) == TOK_VAR || + PN_TYPE(pn) == TOK_LET); + + switch (PN_TYPE(pn)) { + case TOK_FUNCTION: + return function(pn, AST_FUNC_DECL, dst); + + case TOK_VAR: + return variableDeclaration(pn, false, dst); + + default: + JS_ASSERT(PN_TYPE(pn) == TOK_LET); + return variableDeclaration(pn, true, dst); + } +} + +bool +ASTSerializer::variableDeclaration(JSParseNode *pn, bool let, Value *dst) +{ + JS_ASSERT(let ? PN_TYPE(pn) == TOK_LET : PN_TYPE(pn) == TOK_VAR); + + /* Later updated to VARDECL_CONST if we find a PND_CONST declarator. */ + VarDeclKind kind = let ? VARDECL_LET : VARDECL_VAR; + + NodeVector dtors(cx); + if (!dtors.reserve(pn->pn_count)) + return false; + + /* In a for-in context, variable declarations contain just a single pattern. */ + if (pn->pn_xflags & PNX_FORINVAR) { + Value patt, child; + return pattern(pn->pn_head, &kind, &patt) && + builder.variableDeclarator(patt, NullValue(), &pn->pn_head->pn_pos, &child) && + dtors.append(child) && + builder.variableDeclaration(dtors, kind, &pn->pn_pos, dst); + } + + for (JSParseNode *next = pn->pn_head; next; next = next->pn_next) { + Value child; + if (!variableDeclarator(next, &kind, &child)) + return false; + (void)dtors.append(child); /* space check above */ + } + + return builder.variableDeclaration(dtors, kind, &pn->pn_pos, dst); +} + +bool +ASTSerializer::variableDeclarator(JSParseNode *pn, VarDeclKind *pkind, Value *dst) +{ + /* A destructuring declarator is always a TOK_ASSIGN. */ + JS_ASSERT(PN_TYPE(pn) == TOK_NAME || PN_TYPE(pn) == TOK_ASSIGN); + + JSParseNode *pnleft; + JSParseNode *pnright; + + if (PN_TYPE(pn) == TOK_NAME) { + pnleft = pn; + pnright = pn->pn_expr; + } else { + JS_ASSERT(PN_TYPE(pn) == TOK_ASSIGN); + pnleft = pn->pn_left; + pnright = pn->pn_right; + } + + Value left, right; + return pattern(pnleft, pkind, &left) && + optExpression(pnright, &right) && + builder.variableDeclarator(left, right, &pn->pn_pos, dst); +} + +bool +ASTSerializer::switchCase(JSParseNode *pn, Value *dst) +{ + NodeVector stmts(cx); + + Value expr; + + return optExpression(pn->pn_left, &expr) && + statements(pn->pn_right, stmts) && + builder.switchCase(expr, stmts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::switchStatement(JSParseNode *pn, Value *dst) +{ + Value disc; + + if (!expression(pn->pn_left, &disc)) + return false; + + JSParseNode *listNode; + bool lexical; + + if (PN_TYPE(pn->pn_right) == TOK_LEXICALSCOPE) { + listNode = pn->pn_right->pn_expr; + lexical = true; + } else { + listNode = pn->pn_right; + lexical = false; + } + + NodeVector cases(cx); + if (!cases.reserve(listNode->pn_count)) + return false; + + for (JSParseNode *next = listNode->pn_head; next; next = next->pn_next) { + Value child; + if (!switchCase(next, &child)) + return false; + (void)cases.append(child); /* space check above */ + } + + return builder.switchStatement(disc, cases, lexical, &pn->pn_pos, dst); +} + +bool +ASTSerializer::catchClause(JSParseNode *pn, Value *dst) +{ + Value var, guard, body; + + return pattern(pn->pn_kid1, NULL, &var) && + optExpression(pn->pn_kid2, &guard) && + statement(pn->pn_kid3, &body) && + builder.catchClause(var, guard, body, &pn->pn_pos, dst); +} + +bool +ASTSerializer::tryStatement(JSParseNode *pn, Value *dst) +{ + Value body; + if (!statement(pn->pn_kid1, &body)) + return false; + + NodeVector clauses(cx); + if (pn->pn_kid2) { + if (!clauses.reserve(pn->pn_kid2->pn_count)) + return false; + + for (JSParseNode *next = pn->pn_kid2->pn_head; next; next = next->pn_next) { + Value clause; + if (!catchClause(next->pn_expr, &clause)) + return false; + (void)clauses.append(clause); /* space check above */ + } + } + + Value finally; + return optStatement(pn->pn_kid3, &finally) && + builder.tryStatement(body, clauses, finally, &pn->pn_pos, dst); +} + +bool +ASTSerializer::forInit(JSParseNode *pn, Value *dst) +{ + if (!pn) { + dst->setMagic(JS_SERIALIZE_NO_NODE); + return true; + } + + return (PN_TYPE(pn) == TOK_VAR) + ? variableDeclaration(pn, false, dst) + : (PN_TYPE(pn) == TOK_LET) + ? variableDeclaration(pn, true, dst) + : expression(pn, dst); +} + +bool +ASTSerializer::statement(JSParseNode *pn, Value *dst) +{ + switch (PN_TYPE(pn)) { + case TOK_FUNCTION: + case TOK_VAR: + case TOK_LET: + return declaration(pn, dst); + + case TOK_SEMI: + if (pn->pn_kid) { + Value expr; + return expression(pn->pn_kid, &expr) && + builder.expressionStatement(expr, &pn->pn_pos, dst); + } + return builder.emptyStatement(&pn->pn_pos, dst); + + case TOK_LEXICALSCOPE: + pn = pn->pn_expr; + if (PN_TYPE(pn) != TOK_LC) + return statement(pn, dst); + /* FALL THROUGH */ + + case TOK_LC: + return blockStatement(pn, dst); + + case TOK_IF: + { + Value test, cons, alt; + + return expression(pn->pn_kid1, &test) && + statement(pn->pn_kid2, &cons) && + optStatement(pn->pn_kid3, &alt) && + builder.ifStatement(test, cons, alt, &pn->pn_pos, dst); + } + + case TOK_SWITCH: + return switchStatement(pn, dst); + + case TOK_TRY: + return tryStatement(pn, dst); + + case TOK_WITH: + case TOK_WHILE: + { + Value expr, stmt; + + return expression(pn->pn_left, &expr) && + statement(pn->pn_right, &stmt) && + (PN_TYPE(pn) == TOK_WITH) + ? builder.withStatement(expr, stmt, &pn->pn_pos, dst) + : builder.whileStatement(expr, stmt, &pn->pn_pos, dst); + } + + case TOK_DO: + { + Value stmt, test; + + return statement(pn->pn_left, &stmt) && + expression(pn->pn_right, &test) && + builder.doWhileStatement(stmt, test, &pn->pn_pos, dst); + } + + case TOK_FOR: + { + JSParseNode *head = pn->pn_left; + + Value stmt; + if (!statement(pn->pn_right, &stmt)) + return false; + + bool isForEach = pn->pn_iflags & JSITER_FOREACH; + + if (PN_TYPE(head) == TOK_IN) { + Value var, expr; + + return (PN_TYPE(head->pn_left) == TOK_VAR + ? variableDeclaration(head->pn_left, false, &var) + : PN_TYPE(head->pn_left) == TOK_LET + ? variableDeclaration(head->pn_left, true, &var) + : pattern(head->pn_left, NULL, &var)) && + expression(head->pn_right, &expr) && + builder.forInStatement(var, expr, stmt, isForEach, &pn->pn_pos, dst); + } + + Value init, test, update; + + return forInit(head->pn_kid1, &init) && + optExpression(head->pn_kid2, &test) && + optExpression(head->pn_kid3, &update) && + builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst); + } + + case TOK_BREAK: + case TOK_CONTINUE: + { + Value label; + + return optIdentifier(pn->pn_atom, NULL, &label) && + (PN_TYPE(pn) == TOK_BREAK + ? builder.breakStatement(label, &pn->pn_pos, dst) + : builder.continueStatement(label, &pn->pn_pos, dst)); + } + + case TOK_COLON: + { + Value label, stmt; + + return identifier(pn->pn_atom, NULL, &label) && + statement(pn->pn_expr, &stmt) && + builder.labeledStatement(label, stmt, &pn->pn_pos, dst); + } + + case TOK_THROW: + case TOK_RETURN: + { + Value arg; + + return optExpression(pn->pn_kid, &arg) && + (PN_TYPE(pn) == TOK_THROW + ? builder.throwStatement(arg, &pn->pn_pos, dst) + : builder.returnStatement(arg, &pn->pn_pos, dst)); + } + + case TOK_DEBUGGER: + return builder.debuggerStatement(&pn->pn_pos, dst); + +#if JS_HAS_XML_SUPPORT + case TOK_DEFAULT: + { + LOCAL_ASSERT(pn->pn_arity == PN_UNARY && PN_TYPE(pn->pn_kid) == TOK_STRING); + + Value ns; + + return literal(pn->pn_kid, &ns) && + builder.xmlDefaultNamespace(ns, &pn->pn_pos, dst); + } +#endif + + default: + LOCAL_NOT_REACHED("unexpected statement type"); + } +} + +bool +ASTSerializer::leftAssociate(JSParseNode *pn, Value *dst) +{ + JS_ASSERT(pn->pn_arity == PN_LIST); + + const size_t len = pn->pn_count; + JS_ASSERT(len >= 1); + + if (len == 1) + return expression(pn->pn_head, dst); + + JS_ASSERT(len >= 2); + + Vector list(cx); + if (!list.reserve(len)) + return false; + + for (JSParseNode *next = pn->pn_head; next; next = next->pn_next) { + (void)list.append(next); /* space check above */ + } + + TokenKind tk = PN_TYPE(pn); + + bool lor = tk == TOK_OR; + bool logop = lor || (tk == TOK_AND); + + Value right; + + if (!expression(list[len - 1], &right)) + return false; + + size_t i = len - 2; + + do { + JSParseNode *next = list[i]; + + Value left; + if (!expression(next, &left)) + return false; + + TokenPos subpos = { next->pn_pos.begin, pn->pn_pos.end }; + + if (logop) { + if (!builder.logicalExpression(lor, left, right, &subpos, &right)) + return false; + } else { + BinaryOperator op = binop(PN_TYPE(pn), PN_OP(pn)); + LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT); + + if (!builder.binaryExpression(op, left, right, &subpos, &right)) + return false; + } + } while (i-- != 0); + + *dst = right; + return true; +} + +bool +ASTSerializer::binaryOperands(JSParseNode *pn, NodeVector &elts) +{ + if (pn->pn_arity == PN_BINARY) { + Value left, right; + + return expression(pn->pn_left, &left) && + elts.append(left) && + expression(pn->pn_right, &right) && + elts.append(right); + } + + LOCAL_ASSERT(pn->pn_arity == PN_LIST); + + return expressions(pn, elts); +} + +bool +ASTSerializer::comprehensionBlock(JSParseNode *pn, Value *dst) +{ + LOCAL_ASSERT(pn->pn_arity == PN_BINARY); + + JSParseNode *in = pn->pn_left; + + LOCAL_ASSERT(in && PN_TYPE(in) == TOK_IN); + + bool isForEach = pn->pn_iflags & JSITER_FOREACH; + + Value patt, src; + return pattern(in->pn_left, NULL, &patt) && + expression(in->pn_right, &src) && + builder.comprehensionBlock(patt, src, isForEach, &in->pn_pos, dst); +} + +bool +ASTSerializer::comprehension(JSParseNode *pn, Value *dst) +{ + LOCAL_ASSERT(PN_TYPE(pn) == TOK_FOR); + + NodeVector blocks(cx); + + JSParseNode *next = pn; + while (PN_TYPE(next) == TOK_FOR) { + Value block; + if (!comprehensionBlock(next, &block) || + !blocks.append(block)) + return false; + next = next->pn_right; + } + + Value filter = MagicValue(JS_SERIALIZE_NO_NODE); + + if (PN_TYPE(next) == TOK_IF) { + if (!optExpression(next->pn_kid1, &filter)) + return false; + next = next->pn_kid2; + } else if (PN_TYPE(next) == TOK_LC && next->pn_count == 0) { + /* js_FoldConstants optimized away the push. */ + NodeVector empty(cx); + return builder.arrayExpression(empty, &pn->pn_pos, dst); + } + + LOCAL_ASSERT(PN_TYPE(next) == TOK_ARRAYPUSH); + + Value body; + + return expression(next->pn_kid, &body) && + builder.comprehensionExpression(body, blocks, filter, &pn->pn_pos, dst); +} + +bool +ASTSerializer::generatorExpression(JSParseNode *pn, Value *dst) +{ + LOCAL_ASSERT(PN_TYPE(pn) == TOK_FOR); + + NodeVector blocks(cx); + + JSParseNode *next = pn; + while (PN_TYPE(next) == TOK_FOR) { + Value block; + if (!comprehensionBlock(next, &block) || + !blocks.append(block)) + return false; + next = next->pn_right; + } + + Value filter = MagicValue(JS_SERIALIZE_NO_NODE); + + if (PN_TYPE(next) == TOK_IF) { + if (!optExpression(next->pn_kid1, &filter)) + return false; + next = next->pn_kid2; + } + + LOCAL_ASSERT(PN_TYPE(next) == TOK_SEMI && + PN_TYPE(next->pn_kid) == TOK_YIELD && + next->pn_kid->pn_kid); + + Value body; + + return expression(next->pn_kid->pn_kid, &body) && + builder.generatorExpression(body, blocks, filter, &pn->pn_pos, dst); +} + +bool +ASTSerializer::expression(JSParseNode *pn, Value *dst) +{ + switch (PN_TYPE(pn)) { + case TOK_FUNCTION: + return function(pn, AST_FUNC_EXPR, dst); + + case TOK_COMMA: + { + NodeVector exprs(cx); + return expressions(pn, exprs) && + builder.sequenceExpression(exprs, &pn->pn_pos, dst); + } + + case TOK_HOOK: + { + Value test, cons, alt; + + return expression(pn->pn_kid1, &test) && + expression(pn->pn_kid2, &cons) && + expression(pn->pn_kid3, &alt) && + builder.conditionalExpression(test, cons, alt, &pn->pn_pos, dst); + } + + case TOK_OR: + case TOK_AND: + { + if (pn->pn_arity == PN_BINARY) { + Value left, right; + return expression(pn->pn_left, &left) && + expression(pn->pn_right, &right) && + builder.logicalExpression(PN_TYPE(pn) == TOK_OR, left, right, &pn->pn_pos, dst); + } + return leftAssociate(pn, dst); + } + + case TOK_INC: + case TOK_DEC: + { + bool incr = PN_TYPE(pn) == TOK_INC; + bool prefix = PN_OP(pn) >= JSOP_INCNAME && PN_OP(pn) <= JSOP_DECELEM; + + Value expr; + return expression(pn->pn_kid, &expr) && + builder.updateExpression(expr, incr, prefix, &pn->pn_pos, dst); + } + + case TOK_ASSIGN: + { + AssignmentOperator op = aop(PN_OP(pn)); + LOCAL_ASSERT(op > AOP_ERR && op < AOP_LIMIT); + + Value lhs, rhs; + return pattern(pn->pn_left, NULL, &lhs) && + expression(pn->pn_right, &rhs) && + builder.assignmentExpression(op, lhs, rhs, &pn->pn_pos, dst); + } + + case TOK_EQOP: + case TOK_RELOP: + case TOK_SHOP: + case TOK_PLUS: + case TOK_MINUS: + case TOK_STAR: + case TOK_DIVOP: + case TOK_BITOR: + case TOK_BITXOR: + case TOK_BITAND: + case TOK_IN: + case TOK_INSTANCEOF: + case TOK_DBLDOT: + if (pn->pn_arity == PN_BINARY) { + BinaryOperator op = binop(PN_TYPE(pn), PN_OP(pn)); + LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT); + + Value left, right; + return expression(pn->pn_left, &left) && + expression(pn->pn_right, &right) && + builder.binaryExpression(op, left, right, &pn->pn_pos, dst); + } + return leftAssociate(pn, dst); + + case TOK_DELETE: + case TOK_UNARYOP: +#if JS_HAS_XML_SUPPORT + if (PN_OP(pn) == JSOP_XMLNAME) + return expression(pn->pn_kid, dst); +#endif + + { + UnaryOperator op = unop(PN_TYPE(pn), PN_OP(pn)); + LOCAL_ASSERT(op > UNOP_ERR && op < UNOP_LIMIT); + + Value expr; + return expression(pn->pn_kid, &expr) && + builder.unaryExpression(op, expr, &pn->pn_pos, dst); + } + + case TOK_NEW: + case TOK_LP: + { +#ifdef JS_HAS_GENERATOR_EXPRS + if (pn->isGeneratorExpr()) + return generatorExpression(pn->generatorExpr(), dst); +#endif + + JSParseNode *next = pn->pn_head; + + Value callee; + if (!expression(next, &callee)) + return false; + + NodeVector args(cx); + if (!args.reserve(pn->pn_count - 1)) + return false; + + for (next = next->pn_next; next; next = next->pn_next) { + Value arg; + if (!expression(next, &arg)) + return false; + (void)args.append(arg); /* space check above */ + } + + return PN_TYPE(pn) == TOK_NEW + ? builder.newExpression(callee, args, &pn->pn_pos, dst) + : builder.callExpression(callee, args, &pn->pn_pos, dst); + } + + case TOK_DOT: + { + Value expr, id; + return expression(pn->pn_expr, &expr) && + identifier(pn->pn_atom, NULL, &id) && + builder.memberExpression(false, expr, id, &pn->pn_pos, dst); + } + + case TOK_LB: + { + Value left, right; + return expression(pn->pn_left, &left) && + expression(pn->pn_right, &right) && + builder.memberExpression(true, left, right, &pn->pn_pos, dst); + } + + case TOK_RB: + { + NodeVector elts(cx); + if (!elts.reserve(pn->pn_count)) + return false; + + for (JSParseNode *next = pn->pn_head; next; next = next->pn_next) { + if (PN_TYPE(next) == TOK_COMMA) { + (void)elts.append(MagicValue(JS_SERIALIZE_NO_NODE)); /* space check above */ + } else { + Value expr; + if (!expression(next, &expr)) + return false; + (void)elts.append(expr); /* space check above */ + } + } + + return builder.arrayExpression(elts, &pn->pn_pos, dst); + } + + case TOK_RC: + { + NodeVector elts(cx); + if (!elts.reserve(pn->pn_count)) + return false; + + for (JSParseNode *next = pn->pn_head; next; next = next->pn_next) { + Value prop; + if (!property(next, &prop)) + return false; + (void)elts.append(prop); /* space check above */ + } + + return builder.objectExpression(elts, &pn->pn_pos, dst); + } + + case TOK_NAME: + return identifier(pn, dst); + + case TOK_STRING: + case TOK_REGEXP: + case TOK_NUMBER: + case TOK_PRIMARY: + return PN_OP(pn) == JSOP_THIS ? builder.thisExpression(&pn->pn_pos, dst) : literal(pn, dst); + + case TOK_YIELD: + { + Value arg; + return optExpression(pn->pn_kid, &arg) && + builder.yieldExpression(arg, &pn->pn_pos, dst); + } + + case TOK_DEFSHARP: + { + Value expr; + return expression(pn->pn_kid, &expr) && + builder.graphExpression(pn->pn_num, expr, &pn->pn_pos, dst); + } + + case TOK_USESHARP: + return builder.graphIndexExpression(pn->pn_num, &pn->pn_pos, dst); + + case TOK_ARRAYCOMP: + /* NB: it's no longer the case that pn_count could be 2. */ + LOCAL_ASSERT(pn->pn_count == 1); + LOCAL_ASSERT(PN_TYPE(pn->pn_head) == TOK_LEXICALSCOPE); + + return comprehension(pn->pn_head->pn_expr, dst); + +#ifdef JS_HAS_XML_SUPPORT + case TOK_ANYNAME: + return builder.xmlAnyName(&pn->pn_pos, dst); + + case TOK_DBLCOLON: + { + Value left, right; + + LOCAL_ASSERT(pn->pn_arity == PN_NAME || pn->pn_arity == PN_BINARY); + + bool computed = pn->pn_arity == PN_BINARY; + + return (computed + ? (expression(pn->pn_left, &left) && + expression(pn->pn_right, &right)) + : (expression(pn->pn_expr, &left) && + identifier(pn->pn_atom, NULL, &right))) && + builder.xmlQualifiedIdentifier(left, right, computed, + &pn->pn_pos, dst); + } + + case TOK_AT: + { + Value expr; + return expression(pn->pn_kid, &expr) && + builder.xmlAttributeSelector(expr, &pn->pn_pos, dst); + } + + case TOK_FILTER: + { + Value left, right; + return expression(pn->pn_left, &left) && + expression(pn->pn_right, &right) && + builder.xmlFilterExpression(left, right, &pn->pn_pos, dst); + } + + default: + return xml(pn, dst); + +#else + default: + LOCAL_NOT_REACHED("unexpected expression type"); +#endif + } +} + +bool +ASTSerializer::xml(JSParseNode *pn, Value *dst) +{ + switch (PN_TYPE(pn)) { +#ifdef JS_HAS_XML_SUPPORT + case TOK_LC: + { + Value expr; + return expression(pn->pn_kid, &expr) && + builder.xmlEscapeExpression(expr, &pn->pn_pos, dst); + } + + case TOK_XMLELEM: + { + NodeVector elts(cx); + if (!xmls(pn, elts)) + return false; + return builder.xmlElement(elts, &pn->pn_pos, dst); + } + + case TOK_XMLLIST: + { + NodeVector elts(cx); + if (!xmls(pn, elts)) + return false; + return builder.xmlList(elts, &pn->pn_pos, dst); + } + + case TOK_XMLSTAGO: + { + NodeVector elts(cx); + if (!xmls(pn, elts)) + return false; + return builder.xmlStartTag(elts, &pn->pn_pos, dst); + } + + case TOK_XMLETAGO: + { + NodeVector elts(cx); + if (!xmls(pn, elts)) + return false; + return builder.xmlEndTag(elts, &pn->pn_pos, dst); + } + + case TOK_XMLPTAGC: + { + NodeVector elts(cx); + if (!xmls(pn, elts)) + return false; + return builder.xmlPointTag(elts, &pn->pn_pos, dst); + } + + case TOK_XMLTEXT: + return builder.xmlText(atomContents(pn->pn_atom), &pn->pn_pos, dst); + + case TOK_XMLNAME: + if (pn->pn_arity == PN_NULLARY) + return builder.xmlName(atomContents(pn->pn_atom), &pn->pn_pos, dst); + + LOCAL_ASSERT(pn->pn_arity == PN_LIST); + + { + NodeVector elts(cx); + return xmls(pn, elts) && + builder.xmlName(elts, &pn->pn_pos, dst); + } + + case TOK_XMLATTR: + return builder.xmlAttribute(atomContents(pn->pn_atom), &pn->pn_pos, dst); + + case TOK_XMLCDATA: + return builder.xmlCdata(atomContents(pn->pn_atom), &pn->pn_pos, dst); + + case TOK_XMLCOMMENT: + return builder.xmlComment(atomContents(pn->pn_atom), &pn->pn_pos, dst); + + case TOK_XMLPI: + if (!pn->pn_atom2) + return builder.xmlPI(atomContents(pn->pn_atom), &pn->pn_pos, dst); + else + return builder.xmlPI(atomContents(pn->pn_atom), + atomContents(pn->pn_atom2), + &pn->pn_pos, + dst); +#endif + + default: + LOCAL_NOT_REACHED("unexpected XML node type"); + } +} + +bool +ASTSerializer::propertyName(JSParseNode *pn, Value *dst) +{ + if (PN_TYPE(pn) == TOK_NAME) + return identifier(pn, dst); + + LOCAL_ASSERT(PN_TYPE(pn) == TOK_STRING || PN_TYPE(pn) == TOK_NUMBER); + + return literal(pn, dst); +} + +bool +ASTSerializer::property(JSParseNode *pn, Value *dst) +{ + PropKind kind; + switch (PN_OP(pn)) { + case JSOP_INITPROP: + kind = PROP_INIT; + break; + + case JSOP_GETTER: + kind = PROP_GETTER; + break; + + case JSOP_SETTER: + kind = PROP_SETTER; + break; + + default: + LOCAL_NOT_REACHED("unexpected object-literal property"); + } + + Value key, val; + return propertyName(pn->pn_left, &key) && + expression(pn->pn_right, &val) && + builder.propertyInitializer(key, val, kind, &pn->pn_pos, dst); +} + +bool +ASTSerializer::literal(JSParseNode *pn, Value *dst) +{ + Value val; + switch (PN_TYPE(pn)) { + case TOK_STRING: + val = Valueify(ATOM_TO_JSVAL(pn->pn_atom)); + break; + + case TOK_REGEXP: + { + JSObject *re1 = pn->pn_objbox ? pn->pn_objbox->object : NULL; + LOCAL_ASSERT(re1 && re1->isRegExp()); + + JSObject *proto; + if (!js_GetClassPrototype(cx, cx->fp->getScopeChain(), JSProto_RegExp, &proto)) + return false; + + JSObject *re2 = js_CloneRegExpObject(cx, re1, proto); + if (!re2) + return false; + + val.setObject(*re2); + break; + } + + case TOK_NUMBER: + val.setNumber(pn->pn_dval); + break; + + case TOK_PRIMARY: + if (PN_OP(pn) == JSOP_NULL) + val.setNull(); + else + val.setBoolean(PN_OP(pn) == JSOP_TRUE); + break; + + default: + LOCAL_NOT_REACHED("unexpected literal type"); + } + + return builder.literal(val, &pn->pn_pos, dst); +} + +bool +ASTSerializer::arrayPattern(JSParseNode *pn, VarDeclKind *pkind, Value *dst) +{ + JS_ASSERT(PN_TYPE(pn) == TOK_RB); + + NodeVector elts(cx); + if (!elts.reserve(pn->pn_count)) + return false; + + for (JSParseNode *next = pn->pn_head; next; next = next->pn_next) { + if (PN_TYPE(next) == TOK_COMMA) { + (void)elts.append(MagicValue(JS_SERIALIZE_NO_NODE)); /* space check above */ + } else { + Value patt; + if (!pattern(next, pkind, &patt)) + return false; + (void)elts.append(patt); /* space check above */ + } + } + + return builder.arrayPattern(elts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::objectPattern(JSParseNode *pn, VarDeclKind *pkind, Value *dst) +{ + JS_ASSERT(PN_TYPE(pn) == TOK_RC); + + NodeVector elts(cx); + if (!elts.reserve(pn->pn_count)) + return false; + + for (JSParseNode *next = pn->pn_head; next; next = next->pn_next) { + LOCAL_ASSERT(PN_OP(next) == JSOP_INITPROP); + + Value key, patt, prop; + if (!propertyName(next->pn_left, &key) || + !pattern(next->pn_right, pkind, &patt) || + !builder.propertyPattern(key, patt, &next->pn_pos, &prop)) + return false; + + (void)elts.append(prop); /* space check above */ + } + + return builder.objectPattern(elts, &pn->pn_pos, dst); +} + +bool +ASTSerializer::pattern(JSParseNode *pn, VarDeclKind *pkind, Value *dst) +{ + switch (PN_TYPE(pn)) { + case TOK_RC: + return objectPattern(pn, pkind, dst); + + case TOK_RB: + return arrayPattern(pn, pkind, dst); + + case TOK_NAME: + if (pkind && (pn->pn_dflags & PND_CONST)) + *pkind = VARDECL_CONST; + /* FALL THROUGH */ + + default: + return expression(pn, dst); + } +} + +bool +ASTSerializer::identifier(JSAtom *atom, TokenPos *pos, Value *dst) +{ + return builder.identifier(atomContents(atom), pos, dst); +} + +bool +ASTSerializer::identifier(JSParseNode *pn, Value *dst) +{ + LOCAL_ASSERT(pn->pn_arity == PN_NAME || pn->pn_arity == PN_NULLARY); + LOCAL_ASSERT(pn->pn_atom); + + return identifier(pn->pn_atom, &pn->pn_pos, dst); +} + +bool +ASTSerializer::function(JSParseNode *pn, ASTType type, Value *dst) +{ + JSFunction *func = (JSFunction *)pn->pn_funbox->object; + + bool isGenerator = +#ifdef JS_HAS_GENERATORS + pn->pn_funbox->tcflags & TCF_FUN_IS_GENERATOR; +#else + false; +#endif + + bool isExpression = +#ifdef JS_HAS_EXPR_CLOSURES + func->flags & JSFUN_EXPR_CLOSURE; +#else + false; +#endif + + Value id; + if (!optIdentifier(func->atom, NULL, &id)) + return false; + + NodeVector args(cx); + + JSParseNode *argsAndBody = (PN_TYPE(pn->pn_body) == TOK_UPVARS) + ? pn->pn_body->pn_tree + : pn->pn_body; + + Value body; + return functionArgsAndBody(argsAndBody, args, &body) && + builder.function(type, &pn->pn_pos, id, args, body, isGenerator, isExpression, dst); +} + +bool +ASTSerializer::functionArgsAndBody(JSParseNode *pn, NodeVector &args, Value *body) +{ + JSParseNode *pnargs; + JSParseNode *pnbody; + + /* Extract the args and body separately. */ + if (PN_TYPE(pn) == TOK_ARGSBODY) { + pnargs = pn; + pnbody = pn->last(); + } else { + pnargs = NULL; + pnbody = pn; + } + + JSParseNode *pndestruct; + + /* Extract the destructuring assignments. */ + if (pnbody->pn_arity == PN_LIST && (pnbody->pn_xflags & PNX_DESTRUCT)) { + JSParseNode *head = pnbody->pn_head; + LOCAL_ASSERT(head && PN_TYPE(head) == TOK_SEMI); + + pndestruct = head->pn_kid; + LOCAL_ASSERT(pndestruct && PN_TYPE(pndestruct) == TOK_COMMA); + } else { + pndestruct = NULL; + } + + /* Serialize the arguments and body. */ + switch (PN_TYPE(pnbody)) { + case TOK_RETURN: /* expression closure, no destructured args */ + return functionArgs(pn, pnargs, NULL, pnbody, args) && + expression(pnbody->pn_kid, body); + + case TOK_SEQ: /* expression closure with destructured args */ + { + JSParseNode *pnstart = pnbody->pn_head->pn_next; + LOCAL_ASSERT(pnstart && PN_TYPE(pnstart) == TOK_RETURN); + + return functionArgs(pn, pnargs, pndestruct, pnbody, args) && + expression(pnstart->pn_kid, body); + } + + case TOK_LC: /* statement closure */ + { + JSParseNode *pnstart = (pnbody->pn_xflags & PNX_DESTRUCT) + ? pnbody->pn_head->pn_next + : pnbody->pn_head; + + return functionArgs(pn, pnargs, pndestruct, pnbody, args) && + functionBody(pnstart, &pnbody->pn_pos, body); + } + + default: + LOCAL_NOT_REACHED("unexpected function contents"); + } +} + +bool +ASTSerializer::functionArgs(JSParseNode *pn, JSParseNode *pnargs, JSParseNode *pndestruct, + JSParseNode *pnbody, NodeVector &args) +{ + uintN i = 0; + JSParseNode *arg = pnargs ? pnargs->pn_head : NULL; + JSParseNode *destruct = pndestruct ? pndestruct->pn_head : NULL; + Value node; + + /* + * Arguments are found in potentially two different places: 1) the + * argsbody sequence (which ends with the body node), or 2) a + * destructuring initialization at the beginning of the body. Loop + * |arg| through the argsbody and |destruct| through the initial + * destructuring assignments, stopping only when we've exhausted + * both. + */ + while ((arg && arg != pnbody) || destruct) { + if (arg && arg != pnbody && arg->frameSlot() == i) { + if (!identifier(arg, &node) || + !args.append(node)) + return false; + arg = arg->pn_next; + } else if (destruct && destruct->pn_right->frameSlot() == i) { + if (!pattern(destruct->pn_left, NULL, &node) || + !args.append(node)) + return false; + destruct = destruct->pn_next; + } else { + LOCAL_NOT_REACHED("missing function argument"); + } + ++i; + } + + return true; +} + +bool +ASTSerializer::functionBody(JSParseNode *pn, TokenPos *pos, Value *dst) +{ + NodeVector elts(cx); + + /* We aren't sure how many elements there are up front, so we'll check each append. */ + for (JSParseNode *next = pn; next; next = next->pn_next) { + Value child; + if (!sourceElement(next, &child) || + !elts.append(child)) + return false; + } + + return builder.blockStatement(elts, pos, dst); +} + +} /* namespace js */ + +/* Reflect class */ + +Class js_ReflectClass = { + js_Reflect_str, + JSCLASS_HAS_CACHED_PROTO(JSProto_Reflect), + PropertyStub, + PropertyStub, + PropertyStub, + PropertyStub, + EnumerateStub, + ResolveStub, + ConvertStub +}; + +static JSBool +reflect_parse(JSContext *cx, uintN argc, jsval *vp) +{ + if (argc < 1) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, + "Reflect.parse", "0", "s"); + return JS_FALSE; + } + + JSString *src = js_ValueToString(cx, Valueify(JS_ARGV(cx, vp)[0])); + if (!src) + return JS_FALSE; + + const char *filename = NULL; + if (argc > 1) { + JSString *str = js_ValueToString(cx, Valueify(JS_ARGV(cx, vp)[1])); + if (!str) + return JS_FALSE; + filename = js_GetStringBytes(NULL, str); + } + + uintN lineno = 1; + if (argc > 2) { + if (!ValueToECMAUint32(cx, Valueify(JS_ARGV(cx, vp)[2]), &lineno)) + return JS_FALSE; + } + + const jschar *chars; + size_t length; + + src->getCharsAndLength(chars, length); + + Parser parser(cx); + + if (!parser.init(chars, length, NULL, filename, lineno)) + return JS_FALSE; + + JSParseNode *pn = parser.parse(NULL); + if (!pn) + return JS_FALSE; + + ASTSerializer serialize(cx, filename, lineno); + if (!serialize.init()) + return JS_FALSE; + + Value val; + if (!serialize.program(pn, &val)) { + JS_SET_RVAL(cx, vp, JSVAL_NULL); + return JS_FALSE; + } + + JS_SET_RVAL(cx, vp, Jsvalify(val)); + return JS_TRUE; +} + +static JSFunctionSpec static_methods[] = { + JS_FN("parse", reflect_parse, 1, 0), + JS_FS_END +}; + + +JSObject * +js_InitReflectClass(JSContext *cx, JSObject *obj) +{ + JSObject *Reflect = NewNonFunction(cx, &js_ReflectClass, NULL, obj); + if (!Reflect) + return NULL; + + if (!JS_DefineProperty(cx, obj, js_Reflect_str, OBJECT_TO_JSVAL(Reflect), + JS_PropertyStub, JS_PropertyStub, 0)) + return NULL; + + if (!JS_DefineFunctions(cx, Reflect, static_methods)) + return NULL; + + return Reflect; +} diff --git a/js/src/jsreflect.h b/js/src/jsreflect.h new file mode 100644 index 000000000000..2117b98bdc78 --- /dev/null +++ b/js/src/jsreflect.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99 ft=cpp: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * June 12, 2009. + * + * The Initial Developer of the Original Code is + * the Mozilla Corporation. + * + * Contributor(s): + * Dave Herman + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS reflection package. + */ +#ifndef jsreflect_h___ +#define jsreflect_h___ + +#include +#include "jspubtd.h" + +namespace js { + +enum ASTType { + AST_ERROR = -1, +#define ASTDEF(ast, str) ast, +#include "jsast.tbl" +#undef ASTDEF + AST_LIMIT +}; + +enum AssignmentOperator { + AOP_ERR = -1, + + /* assign */ + AOP_ASSIGN = 0, + /* operator-assign */ + AOP_PLUS, AOP_MINUS, AOP_STAR, AOP_DIV, AOP_MOD, + /* shift-assign */ + AOP_LSH, AOP_RSH, AOP_URSH, + /* binary */ + AOP_BITOR, AOP_BITXOR, AOP_BITAND, + + AOP_LIMIT +}; + +enum BinaryOperator { + BINOP_ERR = -1, + + /* eq */ + BINOP_EQ = 0, BINOP_NE, BINOP_STRICTEQ, BINOP_STRICTNE, + /* rel */ + BINOP_LT, BINOP_LE, BINOP_GT, BINOP_GE, + /* shift */ + BINOP_LSH, BINOP_RSH, BINOP_URSH, + /* arithmetic */ + BINOP_PLUS, BINOP_MINUS, BINOP_STAR, BINOP_DIV, BINOP_MOD, + /* binary */ + BINOP_BITOR, BINOP_BITXOR, BINOP_BITAND, + /* misc */ + BINOP_IN, BINOP_INSTANCEOF, + /* xml */ + BINOP_DBLDOT, + + BINOP_LIMIT +}; + +enum UnaryOperator { + UNOP_ERR = -1, + + UNOP_DELETE = 0, + UNOP_NEG, + UNOP_POS, + UNOP_NOT, + UNOP_BITNOT, + UNOP_TYPEOF, + UNOP_VOID, + + UNOP_LIMIT +}; + +enum VarDeclKind { + VARDECL_ERR = -1, + VARDECL_VAR = 0, + VARDECL_CONST, + VARDECL_LET, + VARDECL_LIMIT +}; + +enum PropKind { + PROP_ERR = -1, + PROP_INIT = 0, + PROP_GETTER, + PROP_SETTER, + PROP_LIMIT +}; + +extern char const *aopNames[]; +extern char const *binopNames[]; +extern char const *unopNames[]; +extern char const *nodeTypeNames[]; + +} /* namespace js */ + +extern js::Class js_ReflectClass; + +extern JSObject * +js_InitReflectClass(JSContext *cx, JSObject *obj); + + +#endif /* jsreflect_h___ */ diff --git a/js/src/jsval.h b/js/src/jsval.h index bfaf90bb7f4e..ac58c6af747a 100644 --- a/js/src/jsval.h +++ b/js/src/jsval.h @@ -261,6 +261,7 @@ typedef enum JSWhyMagic JS_FAST_CONSTRUCTOR, /* 'this' value for fast natives invoked with 'new' */ JS_NO_CONSTANT, /* compiler sentinel value */ JS_THIS_POISON, /* used in debug builds to catch tracing errors */ + JS_SERIALIZE_NO_NODE, /* an empty subnode in the AST serializer */ JS_GENERIC_MAGIC /* for local use */ } JSWhyMagic; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index f62485e00903..9736e7a6c233 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -71,6 +71,7 @@ #include "jsnum.h" #include "jsobj.h" #include "jsparse.h" +#include "jsreflect.h" #include "jsscope.h" #include "jsscript.h" #include "jstracer.h" @@ -2706,6 +2707,23 @@ split_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, return JS_TRUE; } +static JSBool +ResolveClass(JSContext *cx, JSObject *obj, jsid id, JSBool *resolved) +{ + if (!JS_ResolveStandardClass(cx, obj, id, resolved)) + return JS_FALSE; + + if (!*resolved) { + if (JSID_IS_ATOM(id, CLASS_ATOM(cx, Reflect))) { + if (!js_InitReflectClass(cx, obj)) + return JS_FALSE; + *resolved = JS_TRUE; + } + } + + return JS_TRUE; +} + static JSBool split_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags, JSObject **objp) { @@ -2736,7 +2754,7 @@ split_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags, JSObject **obj if (!(flags & JSRESOLVE_ASSIGNING)) { JSBool resolved; - if (!JS_ResolveStandardClass(cx, obj, id, &resolved)) + if (!ResolveClass(cx, obj, id, &resolved)) return JS_FALSE; if (resolved) { @@ -2962,7 +2980,7 @@ sandbox_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags, JS_ValueToBoolean(cx, v, &b); if (b && (flags & JSRESOLVE_ASSIGNING) == 0) { - if (!JS_ResolveStandardClass(cx, obj, id, &resolved)) + if (!ResolveClass(cx, obj, id, &resolved)) return JS_FALSE; if (resolved) { *objp = obj; @@ -4711,7 +4729,7 @@ global_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags, #ifdef LAZY_STANDARD_CLASSES JSBool resolved; - if (!JS_ResolveStandardClass(cx, obj, id, &resolved)) + if (!ResolveClass(cx, obj, id, &resolved)) return JS_FALSE; if (resolved) { *objp = obj; diff --git a/js/src/tests/js1_8_5/extensions/jstests.list b/js/src/tests/js1_8_5/extensions/jstests.list index f53743683743..e971e320602b 100644 --- a/js/src/tests/js1_8_5/extensions/jstests.list +++ b/js/src/tests/js1_8_5/extensions/jstests.list @@ -12,4 +12,5 @@ script scripted-proxies.js script array-length-protochange.js script parseInt-octal.js script proxy-enumerateOwn-duplicates.js +skip-if(!xulRuntime.shell) script reflect-parse.js script destructure-accessor.js diff --git a/js/src/tests/js1_8_5/extensions/reflect-parse.js b/js/src/tests/js1_8_5/extensions/reflect-parse.js new file mode 100644 index 000000000000..74583026bd57 --- /dev/null +++ b/js/src/tests/js1_8_5/extensions/reflect-parse.js @@ -0,0 +1,703 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var { Pattern, MatchError } = Match; + +var _ = Pattern.ANY; + +function program(elts) Pattern({ type: "Program", body: elts }) +function exprStmt(expr) Pattern({ type: "ExpressionStatement", expression: expr }) +function throwStmt(expr) Pattern({ type: "ThrowStatement", argument: expr }) +function returnStmt(expr) Pattern({ type: "ReturnStatement", argument: expr }) +function yieldExpr(expr) Pattern({ type: "YieldExpression", argument: expr }) +function lit(val) Pattern({ type: "Literal", value: val }) +var thisExpr = Pattern({ type: "ThisExpression" }); +function funDecl(id, params, body) Pattern({ type: "FunctionDeclaration", + id: id, + params: params, + body: body, + generator: false }) +function genFunDecl(id, params, body) Pattern({ type: "FunctionDeclaration", + id: id, + params: params, + body: body, + generator: true }) +function varDecl(decls) Pattern({ type: "VariableDeclaration", declarations: decls, kind: "var" }) +function letDecl(decls) Pattern({ type: "VariableDeclaration", declarations: decls, kind: "let" }) +function constDecl(decls) Pattern({ type: "VariableDeclaration", declarations: decls, kind: "const" }) +function blockStmt(body) Pattern({ type: "BlockStatement", body: body }) +function ident(name) Pattern({ type: "Identifier", name: name }) +function dotExpr(obj, id) Pattern({ type: "MemberExpression", computed: false, object: obj, property: id }) +function memExpr(obj, id) Pattern({ type: "MemberExpression", computed: true, object: obj, property: id }) +function forStmt(init, test, update, body) Pattern({ type: "ForStatement", init: init, test: test, update: update, body: body }) +function forInStmt(lhs, rhs, body) Pattern({ type: "ForInStatement", left: lhs, right: rhs, body: body, each: false }) +function forEachInStmt(lhs, rhs, body) Pattern({ type: "ForInStatement", left: lhs, right: rhs, body: body, each: true }) +function breakStmt(lab) Pattern({ type: "BreakStatement", label: lab }) +function continueStmt(lab) Pattern({ type: "ContinueStatement", label: lab }) +function blockStmt(stmts) Pattern({ type: "BlockStatement", body: stmts }) +var emptyStmt = Pattern({ type: "EmptyStatement" }) +function ifStmt(test, cons, alt) Pattern({ type: "IfStatement", test: test, alternate: alt, consequent: cons }) +function labStmt(lab, stmt) Pattern({ type: "LabeledStatement", label: lab, body: stmt }) +function withStmt(obj, stmt) Pattern({ type: "WithStatement", object: obj, body: stmt }) +function whileStmt(test, stmt) Pattern({ type: "WhileStatement", test: test, body: stmt }) +function doStmt(stmt, test) Pattern({ type: "DoWhileStatement", test: test, body: stmt }) +function switchStmt(disc, cases) Pattern({ type: "SwitchStatement", discriminant: disc, cases: cases }) +function caseClause(test, stmts) Pattern({ type: "SwitchCase", test: test, consequent: stmts }) +function defaultClause(stmts) Pattern({ type: "SwitchCase", test: null, consequent: stmts }) +function catchClause(id, guard, body) Pattern({ type: "CatchClause", param: id, guard: guard, body: body }) +function tryStmt(body, catches, fin) Pattern({ type: "TryStatement", block: body, handler: catches, finalizer: fin }) +function funExpr(id, args, body, gen) Pattern({ type: "FunctionExpression", + id: id, + params: args, + body: body, + generator: false }) +function genFunExpr(id, args, body) Pattern({ type: "FunctionExpression", + id: id, + params: args, + body: body, + generator: true }) + +function unExpr(op, arg) Pattern({ type: "UnaryExpression", operator: op, argument: arg }) +function binExpr(op, left, right) Pattern({ type: "BinaryExpression", operator: op, left: left, right: right }) +function aExpr(op, left, right) Pattern({ type: "AssignmentExpression", operator: op, left: left, right: right }) +function updExpr(op, arg, prefix) Pattern({ type: "UpdateExpression", operator: op, argument: arg, prefix: prefix }) +function logExpr(op, left, right) Pattern({ type: "LogicalExpression", operator: op, left: left, right: right }) + +function condExpr(test, cons, alt) Pattern({ type: "ConditionalExpression", test: test, consequent: cons, alternate: alt }) +function seqExpr(exprs) Pattern({ type: "SequenceExpression", expressions: exprs }) +function newExpr(callee, args) Pattern({ type: "NewExpression", callee: callee, arguments: args }) +function callExpr(callee, args) Pattern({ type: "CallExpression", callee: callee, arguments: args }) +function arrExpr(elts) Pattern({ type: "ArrayExpression", elements: elts }) +function objExpr(elts) Pattern({ type: "ObjectExpression", properties: elts }) +function compExpr(body, blocks, filter) Pattern({ type: "ComprehensionExpression", body: body, blocks: blocks, filter: filter }) +function genExpr(body, blocks, filter) Pattern({ type: "GeneratorExpression", body: body, blocks: blocks, filter: filter }) +function graphExpr(idx, body) Pattern({ type: "GraphExpression", index: idx, expression: body }) +function idxExpr(idx) Pattern({ type: "GraphIndexExpression", index: idx }) + +function compBlock(left, right) Pattern({ type: "ComprehensionBlock", left: left, right: right, each: false }) +function compEachBlock(left, right) Pattern({ type: "ComprehensionBlock", left: left, right: right, each: true }) + +function arrPatt(elts) Pattern({ type: "ArrayPattern", elements: elts }) +function objPatt(elts) Pattern({ type: "ObjectPattern", properties: elts }) + +function localSrc(src) "(function(){ " + src + " })" +function localPatt(patt) program([exprStmt(funExpr(null, [], blockStmt([patt])))]) +function blockSrc(src) "(function(){ { " + src + " } })" +function blockPatt(patt) program([exprStmt(funExpr(null, [], blockStmt([blockStmt([patt])])))]) + +var xmlAnyName = Pattern({ type: "XMLAnyName" }); + +function xmlQualId(left, right, computed) Pattern({ type: "XMLQualifiedIdentifier", left: left, right: right, computed: computed }) +function xmlAttrSel(id) Pattern({ type: "XMLAttributeSelector", attribute: id }) +function xmlFilter(left, right) Pattern({ type: "XMLFilterExpression", left: left, right: right }) +function xmlPointTag(contents) Pattern({ type: "XMLPointTag", contents: contents }) +function xmlStartTag(contents) Pattern({ type: "XMLStartTag", contents: contents }) +function xmlEndTag(contents) Pattern({ type: "XMLEndTag", contents: contents }) +function xmlEscape(expr) Pattern({ type: "XMLEscape", expression: expr }) +function xmlElt(contents) Pattern({ type: "XMLElement", contents: contents }) +function xmlAttr(value) Pattern({ type: "XMLAttribute", value: value }) +function xmlText(text) Pattern({ type: "XMLText", text: text }) +function xmlPI(target, contents) Pattern({ type: "XMLProcessingInstruction", target: target, contents: contents }) + +function assertBlockStmt(src, patt) { + blockPatt(patt).assert(Reflect.parse(blockSrc(src))); +} + +function assertBlockExpr(src, patt) { + assertBlockStmt(src, exprStmt(patt)); +} + +function assertBlockDecl(src, patt) { + blockPatt(patt).assert(Reflect.parse(blockSrc(src))); +} + +function assertLocalStmt(src, patt) { + localPatt(patt).assert(Reflect.parse(localSrc(src))); +} + +function assertLocalExpr(src, patt) { + assertLocalStmt(src, exprStmt(patt)); +} + +function assertLocalDecl(src, patt) { + localPatt(patt).assert(Reflect.parse(localSrc(src))); +} + +function assertGlobalStmt(src, patt) { + program([patt]).assert(Reflect.parse(src)); +} + +function assertGlobalExpr(src, patt) { + assertStmt(src, exprStmt(patt)); +} + +function assertGlobalDecl(src, patt) { + program([patt]).assert(Reflect.parse(src)); +} + +function assertStmt(src, patt) { + assertLocalStmt(src, patt); + assertGlobalStmt(src, patt); + assertBlockStmt(src, patt); +} + +function assertExpr(src, patt) { + assertLocalExpr(src, patt); + assertGlobalExpr(src, patt); + assertBlockExpr(src, patt); +} + +function assertDecl(src, patt) { + assertLocalDecl(src, patt); + assertGlobalDecl(src, patt); + assertBlockDecl(src, patt); +} + +// general tests + +// NB: These are useful but for now trace-test doesn't do I/O reliably. + +//program(_).assert(Reflect.parse(snarf('data/flapjax.txt'))); +//program(_).assert(Reflect.parse(snarf('data/jquery-1.4.2.txt'))); +//program(_).assert(Reflect.parse(snarf('data/prototype.js'))); +//program(_).assert(Reflect.parse(snarf('data/dojo.js.uncompressed.js'))); +//program(_).assert(Reflect.parse(snarf('data/mootools-1.2.4-core-nc.js'))); + + +// declarations + +assertDecl("var x = 1, y = 2, z = 3", + varDecl([{ id: ident("x"), init: lit(1) }, + { id: ident("y"), init: lit(2) }, + { id: ident("z"), init: lit(3) }])); +assertDecl("var x, y, z", + varDecl([{ id: ident("x"), init: null }, + { id: ident("y"), init: null }, + { id: ident("z"), init: null }])); +assertDecl("function foo() { }", + funDecl(ident("foo"), [], blockStmt([]))); +assertDecl("function foo() { return 42 }", + funDecl(ident("foo"), [], blockStmt([returnStmt(lit(42))]))); + + +// expressions + +assertExpr("true", lit(true)); +assertExpr("false", lit(false)); +assertExpr("42", lit(42)); +assertExpr("(/asdf/)", lit(/asdf/)); +assertExpr("this", thisExpr); +assertExpr("foo", ident("foo")); +assertExpr("foo.bar", dotExpr(ident("foo"), ident("bar"))); +assertExpr("foo[bar]", memExpr(ident("foo"), ident("bar"))); +assertExpr("(function(){})", funExpr(null, [], blockStmt([]))); +assertExpr("(function f() {})", funExpr(ident("f"), [], blockStmt([]))); +assertExpr("(function f(x,y,z) {})", funExpr(ident("f"), [ident("x"),ident("y"),ident("z")], blockStmt([]))); +assertExpr("(++x)", updExpr("++", ident("x"), true)); +assertExpr("(x++)", updExpr("++", ident("x"), false)); +assertExpr("(+x)", unExpr("+", ident("x"))); +assertExpr("(-x)", unExpr("-", ident("x"))); +assertExpr("(!x)", unExpr("!", ident("x"))); +assertExpr("(~x)", unExpr("~", ident("x"))); +assertExpr("(delete x)", unExpr("delete", ident("x"))); +assertExpr("(typeof x)", unExpr("typeof", ident("x"))); +assertExpr("(void x)", unExpr("void", ident("x"))); +assertExpr("(x == y)", binExpr("==", ident("x"), ident("y"))); +assertExpr("(x != y)", binExpr("!=", ident("x"), ident("y"))); +assertExpr("(x === y)", binExpr("===", ident("x"), ident("y"))); +assertExpr("(x !== y)", binExpr("!==", ident("x"), ident("y"))); +assertExpr("(x < y)", binExpr("<", ident("x"), ident("y"))); +assertExpr("(x <= y)", binExpr("<=", ident("x"), ident("y"))); +assertExpr("(x > y)", binExpr(">", ident("x"), ident("y"))); +assertExpr("(x >= y)", binExpr(">=", ident("x"), ident("y"))); +assertExpr("(x << y)", binExpr("<<", ident("x"), ident("y"))); +assertExpr("(x >> y)", binExpr(">>", ident("x"), ident("y"))); +assertExpr("(x >>> y)", binExpr(">>>", ident("x"), ident("y"))); +assertExpr("(x + y)", binExpr("+", ident("x"), ident("y"))); +assertExpr("(w + x + y + z)", binExpr("+", ident("w"), binExpr("+", ident("x", binExpr("+", ident("y"), ident("z")))))) +assertExpr("(x - y)", binExpr("-", ident("x"), ident("y"))); +assertExpr("(x * y)", binExpr("*", ident("x"), ident("y"))); +assertExpr("(x / y)", binExpr("/", ident("x"), ident("y"))); +assertExpr("(x % y)", binExpr("%", ident("x"), ident("y"))); +assertExpr("(x | y)", binExpr("|", ident("x"), ident("y"))); +assertExpr("(x ^ y)", binExpr("^", ident("x"), ident("y"))); +assertExpr("(x & y)", binExpr("&", ident("x"), ident("y"))); +assertExpr("(x in y)", binExpr("in", ident("x"), ident("y"))); +assertExpr("(x instanceof y)", binExpr("instanceof", ident("x"), ident("y"))); +assertExpr("(x = y)", aExpr("=", ident("x"), ident("y"))); +assertExpr("(x += y)", aExpr("+=", ident("x"), ident("y"))); +assertExpr("(x -= y)", aExpr("-=", ident("x"), ident("y"))); +assertExpr("(x *= y)", aExpr("*=", ident("x"), ident("y"))); +assertExpr("(x /= y)", aExpr("/=", ident("x"), ident("y"))); +assertExpr("(x %= y)", aExpr("%=", ident("x"), ident("y"))); +assertExpr("(x <<= y)", aExpr("<<=", ident("x"), ident("y"))); +assertExpr("(x >>= y)", aExpr(">>=", ident("x"), ident("y"))); +assertExpr("(x >>>= y)", aExpr(">>>=", ident("x"), ident("y"))); +assertExpr("(x |= y)", aExpr("|=", ident("x"), ident("y"))); +assertExpr("(x ^= y)", aExpr("^=", ident("x"), ident("y"))); +assertExpr("(x &= y)", aExpr("&=", ident("x"), ident("y"))); +assertExpr("(x || y)", logExpr("||", ident("x"), ident("y"))); +assertExpr("(x && y)", logExpr("&&", ident("x"), ident("y"))); +assertExpr("(w || x || y || z)", logExpr("||", ident("w"), logExpr("||", ident("x", logExpr("||", ident("y"), ident("z")))))) +assertExpr("(x ? y : z)", condExpr(ident("x"), ident("y"), ident("z"))); +assertExpr("(x,y)", seqExpr([ident("x"),ident("y")])) +assertExpr("(x,y,z)", seqExpr([ident("x"),ident("y"),ident("z")])) +assertExpr("(a,b,c,d,e,f,g)", seqExpr([ident("a"),ident("b"),ident("c"),ident("d"),ident("e"),ident("f"),ident("g")])); +assertExpr("(new Object)", newExpr(ident("Object"), [])); +assertExpr("(new Object())", newExpr(ident("Object"), [])); +assertExpr("(new Object(42))", newExpr(ident("Object"), [lit(42)])); +assertExpr("(new Object(1,2,3))", newExpr(ident("Object"), [lit(1),lit(2),lit(3)])); +assertExpr("(String())", callExpr(ident("String"), [])); +assertExpr("(String(42))", callExpr(ident("String"), [lit(42)])); +assertExpr("(String(1,2,3))", callExpr(ident("String"), [lit(1),lit(2),lit(3)])); +assertExpr("[]", arrExpr([])); +assertExpr("[1]", arrExpr([lit(1)])); +assertExpr("[1,2]", arrExpr([lit(1),lit(2)])); +assertExpr("[1,2,3]", arrExpr([lit(1),lit(2),lit(3)])); +assertExpr("[1,,2,3]", arrExpr([lit(1),null,lit(2),lit(3)])); +assertExpr("[1,,,2,3]", arrExpr([lit(1),null,null,lit(2),lit(3)])); +assertExpr("[1,,,2,,3]", arrExpr([lit(1),null,null,lit(2),null,lit(3)])); +assertExpr("[1,,,2,,,3]", arrExpr([lit(1),null,null,lit(2),null,null,lit(3)])); +assertExpr("[,1,2,3]", arrExpr([null,lit(1),lit(2),lit(3)])); +assertExpr("[,,1,2,3]", arrExpr([null,null,lit(1),lit(2),lit(3)])); +assertExpr("[,,,1,2,3]", arrExpr([null,null,null,lit(1),lit(2),lit(3)])); +assertExpr("[,,,1,2,3,]", arrExpr([null,null,null,lit(1),lit(2),lit(3)])); +assertExpr("[,,,1,2,3,,]", arrExpr([null,null,null,lit(1),lit(2),lit(3),null])); +assertExpr("[,,,1,2,3,,,]", arrExpr([null,null,null,lit(1),lit(2),lit(3),null,null])); +assertExpr("[,,,,,]", arrExpr([null,null,null,null,null])); +assertExpr("({})", objExpr([])); +assertExpr("({x:1})", objExpr([{ key: ident("x"), value: lit(1) }])); +assertExpr("({x:1, y:2})", objExpr([{ key: ident("x"), value: lit(1) }, + { key: ident("y"), value: lit(2) } ])); +assertExpr("({x:1, y:2, z:3})", objExpr([{ key: ident("x"), value: lit(1) }, + { key: ident("y"), value: lit(2) }, + { key: ident("z"), value: lit(3) } ])); +assertExpr("({x:1, 'y':2, z:3})", objExpr([{ key: ident("x"), value: lit(1) }, + { key: lit("y"), value: lit(2) }, + { key: ident("z"), value: lit(3) } ])); +assertExpr("({'x':1, 'y':2, z:3})", objExpr([{ key: lit("x"), value: lit(1) }, + { key: lit("y"), value: lit(2) }, + { key: ident("z"), value: lit(3) } ])); +assertExpr("({'x':1, 'y':2, 3:3})", objExpr([{ key: lit("x"), value: lit(1) }, + { key: lit("y"), value: lit(2) }, + { key: lit(3), value: lit(3) } ])); + +// statements + +assertStmt("throw 42", throwStmt(lit(42))); +assertStmt("for (;;) break", forStmt(null, null, null, breakStmt(null))); +assertStmt("for (x; y; z) break", forStmt(ident("x"), ident("y"), ident("z"), breakStmt(null))); +assertStmt("for (var x; y; z) break", forStmt(varDecl([{ id: ident("x"), init: null }]), ident("y"), ident("z"))); +assertStmt("for (var x = 42; y; z) break", forStmt(varDecl([{ id: ident("x"), init: lit(42) }]), ident("y"), ident("z"))); +assertStmt("for (x; ; z) break", forStmt(ident("x"), null, ident("z"), breakStmt(null))); +assertStmt("for (var x; ; z) break", forStmt(varDecl([{ id: ident("x"), init: null }]), null, ident("z"))); +assertStmt("for (var x = 42; ; z) break", forStmt(varDecl([{ id: ident("x"), init: lit(42) }]), null, ident("z"))); +assertStmt("for (x; y; ) break", forStmt(ident("x"), ident("y"), null, breakStmt(null))); +assertStmt("for (var x; y; ) break", forStmt(varDecl([{ id: ident("x"), init: null }]), ident("y"), null, breakStmt(null))); +assertStmt("for (var x = 42; y; ) break", forStmt(varDecl([{ id: ident("x"), init: lit(42) }]), ident("y"), null, breakStmt(null))); +assertStmt("for (var x in y) break", forInStmt(varDecl([{ id: ident("x"), init: null }]), ident("y"), breakStmt(null))); +assertStmt("for (x in y) break", forInStmt(ident("x"), ident("y"), breakStmt(null))); +assertStmt("{ }", blockStmt([])); +assertStmt("{ throw 1; throw 2; throw 3; }", blockStmt([ throwStmt(lit(1)), throwStmt(lit(2)), throwStmt(lit(3))])); +assertStmt(";", emptyStmt); +assertStmt("if (foo) throw 42;", ifStmt(ident("foo"), throwStmt(lit(42)), null)); +assertStmt("if (foo) throw 42; else true;", ifStmt(ident("foo"), throwStmt(lit(42)), exprStmt(lit(true)))); +assertStmt("if (foo) { throw 1; throw 2; throw 3; }", + ifStmt(ident("foo"), + blockStmt([throwStmt(lit(1)), throwStmt(lit(2)), throwStmt(lit(3))]), + null)); +assertStmt("if (foo) { throw 1; throw 2; throw 3; } else true;", + ifStmt(ident("foo"), + blockStmt([throwStmt(lit(1)), throwStmt(lit(2)), throwStmt(lit(3))]), + exprStmt(lit(true)))); +assertStmt("foo: for(;;) break foo;", labStmt(ident("foo"), forStmt(null, null, null, breakStmt(ident("foo"))))); +assertStmt("foo: for(;;) continue foo;", labStmt(ident("foo"), forStmt(null, null, null, continueStmt(ident("foo"))))); +assertStmt("with (obj) { }", withStmt(ident("obj"), blockStmt([]))); +assertStmt("with (obj) { obj; }", withStmt(ident("obj"), blockStmt([exprStmt(ident("obj"))]))); +assertStmt("while (foo) { }", whileStmt(ident("foo"), blockStmt([]))); +assertStmt("while (foo) { foo; }", whileStmt(ident("foo"), blockStmt([exprStmt(ident("foo"))]))); +assertStmt("do { } while (foo);", doStmt(blockStmt([]), ident("foo"))); +assertStmt("do { foo; } while (foo)", doStmt(blockStmt([exprStmt(ident("foo"))]), ident("foo"))); +assertStmt("switch (foo) { case 1: 1; break; case 2: 2; break; default: 3; }", + switchStmt(ident("foo"), + [ caseClause(lit(1), [ exprStmt(lit(1)), breakStmt(null) ]), + caseClause(lit(2), [ exprStmt(lit(2)), breakStmt(null) ]), + defaultClause([ exprStmt(lit(3)) ]) ])); +assertStmt("switch (foo) { case 1: 1; break; case 2: 2; break; default: 3; case 42: 42; }", + switchStmt(ident("foo"), + [ caseClause(lit(1), [ exprStmt(lit(1)), breakStmt(null) ]), + caseClause(lit(2), [ exprStmt(lit(2)), breakStmt(null) ]), + defaultClause([ exprStmt(lit(3)) ]), + caseClause(lit(42), [ exprStmt(lit(42)) ]) ])); +assertStmt("try { } catch (e) { }", + tryStmt(blockStmt([]), + catchClause(ident("e"), null, blockStmt([])), + null)); +assertStmt("try { } catch (e) { } finally { }", + tryStmt(blockStmt([]), + catchClause(ident("e"), null, blockStmt([])), + blockStmt([]))); +assertStmt("try { } finally { }", + tryStmt(blockStmt([]), + null, + blockStmt([]))); +assertStmt("try { } catch (e if foo) { } catch (e if bar) { } finally { }", + tryStmt(blockStmt([]), + [ catchClause(ident("e"), ident("foo"), blockStmt([])), + catchClause(ident("e"), ident("bar"), blockStmt([])) ], + blockStmt([]))); +assertStmt("try { } catch (e if foo) { } catch (e if bar) { } catch (e) { } finally { }", + tryStmt(blockStmt([]), + [ catchClause(ident("e"), ident("foo"), blockStmt([])), + catchClause(ident("e"), ident("bar"), blockStmt([])), + catchClause(ident("e"), null, blockStmt([])) ], + blockStmt([]))); + +assertDecl("var {x:y} = foo;", varDecl([{ id: objPatt([{ key: ident("x"), value: ident("y") }]), + init: ident("foo") }])); + +// global let is var +assertGlobalDecl("let {x:y} = foo;", varDecl([{ id: objPatt([{ key: ident("x"), value: ident("y") }]), + init: ident("foo") }])); +// function-global let is var +assertLocalDecl("let {x:y} = foo;", varDecl([{ id: objPatt([{ key: ident("x"), value: ident("y") }]), + init: ident("foo") }])); +// block-local let is let +assertBlockDecl("let {x:y} = foo;", letDecl([{ id: objPatt([{ key: ident("x"), value: ident("y") }]), + init: ident("foo") }])); + +assertDecl("const {x:y} = foo;", constDecl([{ id: objPatt([{ key: ident("x"), value: ident("y") }]), + init: ident("foo") }])); + + +// various combinations of identifiers and destructuring patterns: +function makePatternCombinations(id, destr) + [ + [ id(1) ], + [ id(1), id(2) ], + [ id(1), id(2), id(3) ], + [ id(1), id(2), id(3), id(4) ], + [ id(1), id(2), id(3), id(4), id(5) ], + + [ destr(1) ], + [ destr(1), destr(2) ], + [ destr(1), destr(2), destr(3) ], + [ destr(1), destr(2), destr(3), destr(4) ], + [ destr(1), destr(2), destr(3), destr(4), destr(5) ], + + [ destr(1), id(2) ], + + [ destr(1), id(2), id(3) ], + [ destr(1), id(2), id(3), id(4) ], + [ destr(1), id(2), id(3), id(4), id(5) ], + [ destr(1), id(2), id(3), id(4), destr(5) ], + [ destr(1), id(2), id(3), destr(4) ], + [ destr(1), id(2), id(3), destr(4), id(5) ], + [ destr(1), id(2), id(3), destr(4), destr(5) ], + + [ destr(1), id(2), destr(3) ], + [ destr(1), id(2), destr(3), id(4) ], + [ destr(1), id(2), destr(3), id(4), id(5) ], + [ destr(1), id(2), destr(3), id(4), destr(5) ], + [ destr(1), id(2), destr(3), destr(4) ], + [ destr(1), id(2), destr(3), destr(4), id(5) ], + [ destr(1), id(2), destr(3), destr(4), destr(5) ], + + [ id(1), destr(2) ], + + [ id(1), destr(2), id(3) ], + [ id(1), destr(2), id(3), id(4) ], + [ id(1), destr(2), id(3), id(4), id(5) ], + [ id(1), destr(2), id(3), id(4), destr(5) ], + [ id(1), destr(2), id(3), destr(4) ], + [ id(1), destr(2), id(3), destr(4), id(5) ], + [ id(1), destr(2), id(3), destr(4), destr(5) ], + + [ id(1), destr(2), destr(3) ], + [ id(1), destr(2), destr(3), id(4) ], + [ id(1), destr(2), destr(3), id(4), id(5) ], + [ id(1), destr(2), destr(3), id(4), destr(5) ], + [ id(1), destr(2), destr(3), destr(4) ], + [ id(1), destr(2), destr(3), destr(4), id(5) ], + [ id(1), destr(2), destr(3), destr(4), destr(5) ] + ] + +// destructuring function parameters + +function testParamPatternCombinations(makePattSrc, makePattPatt) { + var pattSrcs = makePatternCombinations(function(n) ("x" + n), makePattSrc); + var pattPatts = makePatternCombinations(function(n) (ident("x" + n)), makePattPatt); + + for (var i = 0; i < pattSrcs.length; i++) { + function makeSrc(body) ("(function(" + pattSrcs[i].join(",") + ") " + body + ")") + function makePatt(body) (funExpr(null, pattPatts[i], body)) + + // no upvars, block body + assertExpr(makeSrc("{ }", makePatt(blockStmt([])))); + // upvars, block body + assertExpr(makeSrc("{ return [x1,x2,x3,x4,x5]; }"), + makePatt(blockStmt([returnStmt(arrExpr([ident("x1"), ident("x2"), ident("x3"), ident("x4"), ident("x5")]))]))); + // no upvars, expression body + assertExpr(makeSrc("(0)"), makePatt(lit(0))); + // upvars, expression body + assertExpr(makeSrc("[x1,x2,x3,x4,x5]"), + makePatt(arrExpr([ident("x1"), ident("x2"), ident("x3"), ident("x4"), ident("x5")]))); + } +} + +testParamPatternCombinations(function(n) ("{a" + n + ":x" + n + "," + "b" + n + ":y" + n + "," + "c" + n + ":z" + n + "}"), + function(n) (objPatt([{ key: ident("a" + n), value: ident("x" + n) }, + { key: ident("b" + n), value: ident("y" + n) }, + { key: ident("c" + n), value: ident("z" + n) }]))); + +testParamPatternCombinations(function(n) ("[x" + n + "," + "y" + n + "," + "z" + n + "]"), + function(n) (arrPatt([ident("x" + n), ident("y" + n), ident("z" + n)]))); + + +// destructuring variable declarations + +function testVarPatternCombinations(makePattSrc, makePattPatt) { + var pattSrcs = makePatternCombinations(function(n) ("x" + n), makePattSrc); + var pattPatts = makePatternCombinations(function(n) ({ id: ident("x" + n), init: null }), makePattPatt); + + for (var i = 0; i < pattSrcs.length; i++) { + // variable declarations in blocks + assertDecl("var " + pattSrcs[i].join(",") + ";", varDecl(pattPatts[i])); + + assertGlobalDecl("let " + pattSrcs[i].join(",") + ";", varDecl(pattPatts[i])); + assertLocalDecl("let " + pattSrcs[i].join(",") + ";", varDecl(pattPatts[i])); + assertBlockDecl("let " + pattSrcs[i].join(",") + ";", letDecl(pattPatts[i])); + + assertDecl("const " + pattSrcs[i].join(",") + ";", constDecl(pattPatts[i])); + + // variable declarations in for-loop heads + assertStmt("for (var " + pattSrcs[i].join(",") + "; foo; bar);", + forStmt(varDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); + assertStmt("for (let " + pattSrcs[i].join(",") + "; foo; bar);", + forStmt(letDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); + assertStmt("for (const " + pattSrcs[i].join(",") + "; foo; bar);", + forStmt(constDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); + } +} + +testVarPatternCombinations(function (n) ("{a" + n + ":x" + n + "," + "b" + n + ":y" + n + "," + "c" + n + ":z" + n + "} = 0"), + function (n) ({ id: objPatt([{ key: ident("a" + n), value: ident("x" + n) }, + { key: ident("b" + n), value: ident("y" + n) }, + { key: ident("c" + n), value: ident("z" + n) }]), + init: lit(0) })); + +testVarPatternCombinations(function(n) ("[x" + n + "," + "y" + n + "," + "z" + n + "] = 0"), + function(n) ({ id: arrPatt([ident("x" + n), ident("y" + n), ident("z" + n)]), + init: lit(0) })); + +// destructuring assignment + +function testAssignmentCombinations(makePattSrc, makePattPatt) { + var pattSrcs = makePatternCombinations(function(n) ("x" + n + " = 0"), makePattSrc); + var pattPatts = makePatternCombinations(function(n) (aExpr("=", ident("x" + n), lit(0))), makePattPatt); + + for (var i = 0; i < pattSrcs.length; i++) { + var src = pattSrcs[i].join(","); + var patt = pattPatts[i].length === 1 ? pattPatts[i][0] : seqExpr(pattPatts[i]); + + // assignment expression statement + assertExpr("(" + src + ")", patt); + + // for-loop head assignment + assertStmt("for (" + src + "; foo; bar);", + forStmt(patt, ident("foo"), ident("bar"), emptyStmt)); + } +} + +testAssignmentCombinations(function (n) ("{a" + n + ":x" + n + "," + "b" + n + ":y" + n + "," + "c" + n + ":z" + n + "} = 0"), + function (n) (aExpr("=", + objPatt([{ key: ident("a" + n), value: ident("x" + n) }, + { key: ident("b" + n), value: ident("y" + n) }, + { key: ident("c" + n), value: ident("z" + n) }]), + lit(0)))); + + +// destructuring in for-in and for-each-in loop heads + +var axbycz = objPatt([{ key: ident("a"), value: ident("x") }, + { key: ident("b"), value: ident("y") }, + { key: ident("c"), value: ident("z") }]); +var xyz = arrPatt([ident("x"), ident("y"), ident("z")]); + +assertStmt("for (var {a:x,b:y,c:z} in foo);", forInStmt(varDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for (let {a:x,b:y,c:z} in foo);", forInStmt(letDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for ({a:x,b:y,c:z} in foo);", forInStmt(axbycz, ident("foo"), emptyStmt)); +assertStmt("for (var [x,y,z] in foo);", forInStmt(varDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for (let [x,y,z] in foo);", forInStmt(letDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for ([x,y,z] in foo);", forInStmt(xyz, ident("foo"), emptyStmt)); +assertStmt("for each (var {a:x,b:y,c:z} in foo);", forEachInStmt(varDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for each (let {a:x,b:y,c:z} in foo);", forEachInStmt(letDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for each ({a:x,b:y,c:z} in foo);", forEachInStmt(axbycz, ident("foo"), emptyStmt)); +assertStmt("for each (var [x,y,z] in foo);", forEachInStmt(varDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for each (let [x,y,z] in foo);", forEachInStmt(letDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for each ([x,y,z] in foo);", forEachInStmt(xyz, ident("foo"), emptyStmt)); + +// expression closures + +assertDecl("function inc(x) (x + 1)", funDecl(ident("inc"), [ident("x")], binExpr("+", ident("x"), lit(1)))); +assertExpr("(function(x) (x+1))", funExpr(null, [ident("x")], binExpr("+"), ident("x"), lit(1))); + +// generators + +assertDecl("function gen(x) { yield }", genFunDecl(ident("gen"), [ident("x")], blockStmt([exprStmt(yieldExpr(null))]))); +assertExpr("(function(x) { yield })", genFunExpr(null, [ident("x")], blockStmt([exprStmt(yieldExpr(null))]))); +assertDecl("function gen(x) { yield 42 }", genFunDecl(ident("gen"), [ident("x")], blockStmt([exprStmt(yieldExpr(lit(42)))]))); +assertExpr("(function(x) { yield 42 })", genFunExpr(null, [ident("x")], blockStmt([exprStmt(yieldExpr(lit(42)))]))); + +// getters and setters + +assertExpr("({ get x() { return 42 } })", + objExpr([ { key: ident("x"), + value: funExpr(null, [], blockStmt([returnStmt(lit(42))])), + kind: "get" } ])); +assertExpr("({ set x(v) { return 42 } })", + objExpr([ { key: ident("x"), + value: funExpr(null, [ident("v")], blockStmt([returnStmt(lit(42))])), + kind: "set" } ])); + +// comprehensions + +assertExpr("[ x for (x in foo)]", + compExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], null)); +assertExpr("[ [x,y] for (x in foo) for (y in bar)]", + compExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], null)); +assertExpr("[ [x,y,z] for (x in foo) for (y in bar) for (z in baz)]", + compExpr(arrExpr([ident("x"), ident("y"), ident("z")]), + [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar")), compBlock(ident("z"), ident("baz"))], + null)); + +assertExpr("[ x for (x in foo) if (p)]", + compExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], ident("p"))); +assertExpr("[ [x,y] for (x in foo) for (y in bar) if (p)]", + compExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], ident("p"))); +assertExpr("[ [x,y,z] for (x in foo) for (y in bar) for (z in baz) if (p) ]", + compExpr(arrExpr([ident("x"), ident("y"), ident("z")]), + [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar")), compBlock(ident("z"), ident("baz"))], + ident("p"))); + +assertExpr("[ x for each (x in foo)]", + compExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], null)); +assertExpr("[ [x,y] for each (x in foo) for each (y in bar)]", + compExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], null)); +assertExpr("[ [x,y,z] for each (x in foo) for each (y in bar) for each (z in baz)]", + compExpr(arrExpr([ident("x"), ident("y"), ident("z")]), + [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar")), compEachBlock(ident("z"), ident("baz"))], + null)); + +assertExpr("[ x for each (x in foo) if (p)]", + compExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], ident("p"))); +assertExpr("[ [x,y] for each (x in foo) for each (y in bar) if (p)]", + compExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], ident("p"))); +assertExpr("[ [x,y,z] for each (x in foo) for each (y in bar) for each (z in baz) if (p) ]", + compExpr(arrExpr([ident("x"), ident("y"), ident("z")]), + [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar")), compEachBlock(ident("z"), ident("baz"))], + ident("p"))); + +// generator expressions + +assertExpr("( x for (x in foo))", + genExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], null)); +assertExpr("( [x,y] for (x in foo) for (y in bar))", + genExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], null)); +assertExpr("( [x,y,z] for (x in foo) for (y in bar) for (z in baz))", + genExpr(arrExpr([ident("x"), ident("y"), ident("z")]), + [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar")), compBlock(ident("z"), ident("baz"))], + null)); + +assertExpr("( x for (x in foo) if (p))", + genExpr(ident("x"), [compBlock(ident("x"), ident("foo"))], ident("p"))); +assertExpr("( [x,y] for (x in foo) for (y in bar) if (p))", + genExpr(arrExpr([ident("x"), ident("y")]), [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar"))], ident("p"))); +assertExpr("( [x,y,z] for (x in foo) for (y in bar) for (z in baz) if (p) )", + genExpr(arrExpr([ident("x"), ident("y"), ident("z")]), + [compBlock(ident("x"), ident("foo")), compBlock(ident("y"), ident("bar")), compBlock(ident("z"), ident("baz"))], + ident("p"))); + +assertExpr("( x for each (x in foo))", + genExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], null)); +assertExpr("( [x,y] for each (x in foo) for each (y in bar))", + genExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], null)); +assertExpr("( [x,y,z] for each (x in foo) for each (y in bar) for each (z in baz))", + genExpr(arrExpr([ident("x"), ident("y"), ident("z")]), + [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar")), compEachBlock(ident("z"), ident("baz"))], + null)); + +assertExpr("( x for each (x in foo) if (p))", + genExpr(ident("x"), [compEachBlock(ident("x"), ident("foo"))], ident("p"))); +assertExpr("( [x,y] for each (x in foo) for each (y in bar) if (p))", + genExpr(arrExpr([ident("x"), ident("y")]), [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar"))], ident("p"))); +assertExpr("( [x,y,z] for each (x in foo) for each (y in bar) for each (z in baz) if (p) )", + genExpr(arrExpr([ident("x"), ident("y"), ident("z")]), + [compEachBlock(ident("x"), ident("foo")), compEachBlock(ident("y"), ident("bar")), compEachBlock(ident("z"), ident("baz"))], + ident("p"))); + +// NOTE: it would be good to test generator expressions both with and without upvars, just like functions above. + + +// sharp variables + +assertExpr("#1={me:#1#}", graphExpr(1, objExpr([{ key: ident("me"), value: idxExpr(1) }]))) + + +// E4X + +assertExpr("x..tagName", binExpr("..", ident("x"), lit("tagName"))); +assertExpr("x.*", memExpr(ident("x"), xmlAnyName)); +assertExpr("x[*]", memExpr(ident("x"), xmlAnyName)); +assertExpr("x::y", xmlQualId(ident("x"), ident("y"), false)); +assertExpr("x::[foo]", xmlQualId(ident("x"), ident("foo"), true)); +assertExpr("x::[foo()]", xmlQualId(ident("x"), callExpr(ident("foo"), []), true)); +assertExpr("*::*", xmlQualId(xmlAnyName, ident("*"), false)); +assertExpr("*::[foo]", xmlQualId(xmlAnyName, ident("foo"), true)); +assertExpr("*::[foo()]", xmlQualId(xmlAnyName, callExpr(ident("foo"), []), true)); +assertExpr("@foo", xmlAttrSel(ident("foo"))); +assertExpr("x.(p)", xmlFilter(ident("x"), ident("p"))); +assertExpr("<{foo}/>", xmlPointTag([xmlEscape(ident("foo"))])); +assertExpr("<{foo}>", xmlElt([xmlStartTag([xmlEscape(ident("foo"))]), + xmlEndTag([xmlEscape(ident("foo"))])])); +assertExpr("<{foo} {attr}='attr'/>", xmlPointTag([xmlEscape(ident("foo")), + xmlEscape(ident("attr")), + xmlAttr("attr")])); +assertExpr("<{foo}>text", xmlElt([xmlStartTag([xmlEscape(ident("foo"))]), + xmlText("text"), + xmlEndTag([xmlEscape(ident("foo"))])])); +assertExpr("", xmlPI("xml", "")); +assertExpr("", xmlPI("xml", "version='1.0'")); + +// NOTE: We appear to be unable to test XMLNAME, XMLCDATA, and XMLCOMMENT. + + +// Source location information + + +var withoutFileOrLine = Reflect.parse("42"); +var withFile = Reflect.parse("42", "foo.js"); +var withFileAndLine = Reflect.parse("42", "foo.js", 111); + +Pattern({ source: null, start: { line: 1, column: 0 }, end: { line: 1, column: 2 } }).match(withoutFileOrLine.loc); +Pattern({ source: "foo.js", start: { line: 1, column: 0 }, end: { line: 1, column: 2 } }).match(withFile.loc); +Pattern({ source: "foo.js", start: { line: 111, column: 0 }, end: { line: 111, column: 2 } }).match(withFileAndLine.loc); + +var withoutFileOrLine2 = Reflect.parse("foo +\nbar"); +var withFile2 = Reflect.parse("foo +\nbar", "foo.js"); +var withFileAndLine2 = Reflect.parse("foo +\nbar", "foo.js", 111); + +Pattern({ source: null, start: { line: 1, column: 0 }, end: { line: 2, column: 3 } }).match(withoutFileOrLine2.loc); +Pattern({ source: "foo.js", start: { line: 1, column: 0 }, end: { line: 2, column: 3 } }).match(withFile2.loc); +Pattern({ source: "foo.js", start: { line: 111, column: 0 }, end: { line: 112, column: 3 } }).match(withFileAndLine2.loc); + +var nested = Reflect.parse("(-b + sqrt(sqr(b) - 4 * a * c)) / (2 * a)", "quad.js"); +var fourAC = nested.body[0].expression.left.right.arguments[0].right; + +Pattern({ source: "quad.js", start: { line: 1, column: 20 }, end: { line: 1, column: 29 } }).match(fourAC.loc); + + +reportCompare(true, true); diff --git a/js/src/tests/js1_8_5/extensions/shell.js b/js/src/tests/js1_8_5/extensions/shell.js index 1b22a34f1094..9cf458ee20b0 100644 --- a/js/src/tests/js1_8_5/extensions/shell.js +++ b/js/src/tests/js1_8_5/extensions/shell.js @@ -17,3 +17,156 @@ if (typeof version != 'undefined') version(185); } +// A little pattern-matching library. +var Match = + +(function() { + + function Pattern(template) { + // act like a constructor even as a function + if (!(this instanceof Pattern)) + return new Pattern(template); + + this.template = template; + } + + Pattern.prototype = { + match: function(act) { + return match(act, this.template); + }, + + matches: function(act) { + try { + return this.match(act); + } + catch (e if e instanceof MatchError) { + return false; + } + }, + + assert: function(act, message) { + try { + return this.match(act); + } + catch (e if e instanceof MatchError) { + throw new Error((message || "failed match") + ": " + e.message); + } + }, + + toString: function() "[object Pattern]" + }; + + Pattern.ANY = new Pattern; + Pattern.ANY.template = Pattern.ANY; + + var quote = uneval; + + function MatchError(msg) { + this.message = msg; + } + + MatchError.prototype = { + toString: function() { + return "match error: " + this.message; + } + }; + + function isAtom(x) { + return (typeof x === "number") || + (typeof x === "string") || + (typeof x === "boolean") || + (x === null) || + (typeof x === "object" && x instanceof RegExp); + } + + function isObject(x) { + return (x !== null) && (typeof x === "object"); + } + + function isArrayLike(x) { + return isObject(x) && ("length" in x); + } + + function matchAtom(act, exp) { + if ((typeof exp) === "number" && isNaN(exp)) { + if ((typeof act) !== "number" || !isNaN(act)) + throw new MatchError("expected NaN, got: " + quote(act)); + return true; + } + + if (exp === null) { + if (act !== null) + throw new MatchError("expected null, got: " + quote(act)); + return true; + } + + if (exp instanceof RegExp) { + if (!(act instanceof RegExp) || exp.source !== act.source) + throw new MatchError("expected " + quote(exp) + ", got: " + quote(act)); + return true; + } + + switch (typeof exp) { + case "string": + if (act !== exp) + throw new MatchError("expected " + exp.quote() + ", got " + quote(act)); + return true; + case "boolean": + case "number": + if (exp !== act) + throw new MatchError("expected " + exp + ", got " + quote(act)); + return true; + } + + throw new Error("bad pattern: " + exp.toSource()); + } + + function matchObject(act, exp) { + if (!isObject(act)) + throw new MatchError("expected object, got " + quote(act)); + + for (var key in exp) { + if (!(key in act)) + throw new MatchError("expected property " + key.quote() + " not found in " + quote(act)); + match(act[key], exp[key]); + } + + return true; + } + + function matchArray(act, exp) { + if (!isObject(act) || !("length" in act)) + throw new MatchError("expected array-like object, got " + quote(act)); + + var length = exp.length; + for (var i = 0; i < length; i++) { + if (i in exp) { + if (!(i in act)) + throw new MatchError("expected array property " + i + " not found in " + quote(act)); + match(act[i], exp[i]); + } + } + + return true; + } + + function match(act, exp) { + if (exp === Pattern.ANY) + return true; + + if (exp instanceof Pattern) + return exp.match(act); + + if (isAtom(exp)) + return matchAtom(act, exp); + + if (isArrayLike(exp)) + return matchArray(act, exp); + + return matchObject(act, exp); + } + + return { Pattern: Pattern, + MatchError: MatchError }; + +})();