bug 533874, r=jimb: expose the parser as a JS API
authorDave Herman <dherman@mozilla.com>
Tue, 15 Jun 2010 13:32:32 -0700
changeset 51110 842ca3e81a78
parent 51109 a009d47505f5
child 51113 7245265eea80
push id15219
push userrsayre@mozilla.com
push date2010-08-20 21:10 +0000
treeherdermozilla-central@7fb1bd5802f0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs533874
milestone2.0b4pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 533874, r=jimb: expose the parser as a JS API
js/src/Makefile.in
js/src/js.msg
js/src/jsast.tbl
js/src/jsatom.h
js/src/jsparse.cpp
js/src/jsparse.h
js/src/jsproto.tbl
js/src/jsreflect.cpp
js/src/jsreflect.h
js/src/jsval.h
js/src/shell/js.cpp
js/src/tests/js1_8_5/extensions/jstests.list
js/src/tests/js1_8_5/extensions/reflect-parse.js
js/src/tests/js1_8_5/extensions/shell.js
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -152,16 +152,17 @@ CPPSRCS		= \
 		jsobj.cpp \
 		json.cpp \
 		jsopcode.cpp \
 		jsparse.cpp \
 		jsproxy.cpp \
 		jsprf.cpp \
 		jspropertycache.cpp \
 		jspropertytree.cpp \
+		jsreflect.cpp \
 		jsregexp.cpp \
 		jsscan.cpp \
 		jsscope.cpp \
 		jsscript.cpp \
 		jsstr.cpp \
 		jstask.cpp \
 		jstypedarray.cpp \
 		jsutil.cpp \
@@ -215,16 +216,17 @@ INSTALLED_HEADERS = \
 		jsproxy.h \
 		jsprf.h \
 		jspropertycache.h \
 		jspropertycacheinlines.h \
 		jspropertytree.h \
 		jsproto.tbl \
 		jsprvtd.h \
 		jspubtd.h \
+		jsreflect.h \
 		jsregexp.h \
 		jsscan.h \
 		jsscope.h \
 		jsscript.h \
 		jsscriptinlines.h \
 		jsstaticcheck.h \
 		jsstdint.h \
 		jsstr.h \
@@ -628,17 +630,17 @@ endif # SOLARIS_SUNPRO_CXX
 # Allow building jsinterp.c with special optimization flags
 ifdef INTERP_OPTIMIZER
 jsinterp.$(OBJ_SUFFIX): MODULE_OPTIMIZE_FLAGS=$(INTERP_OPTIMIZER)
 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) $<
 endif
 endif
 
 # An AIX Optimization bug causes PR_dtoa() & JS_dtoa to produce wrong result.
 # This suppresses optimization for this single compilation unit.
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -328,8 +328,9 @@ MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG, 
 MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS,   246, 0, JSEXN_ERR, "invalid arguments")
 MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION,   247, 0, JSEXN_ERR, "call to Function() blocked by CSP")
 MSG_DEF(JSMSG_BAD_GET_SET_FIELD,      248, 1, JSEXN_TYPEERR, "property descriptor's {0} field is neither undefined nor a function")
 MSG_DEF(JSMSG_BAD_PROXY_FIX,          249, 0, JSEXN_TYPEERR, "proxy was fixed while executing the handler")
 MSG_DEF(JSMSG_INVALID_EVAL_SCOPE_ARG, 250, 0, JSEXN_EVALERR, "invalid eval scope argument")
 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")
new file mode 100644
--- /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 */
--- a/js/src/jsatom.h
+++ b/js/src/jsatom.h
@@ -55,17 +55,17 @@
 
 #define ATOM_PINNED     0x1       /* atom is pinned against GC */
 #define ATOM_INTERNED   0x2       /* pinned variant for JS_Intern* API */
 #define ATOM_NOCOPY     0x4       /* don't copy atom string bytes */
 #define ATOM_TMPSTR     0x8       /* internal, to avoid extra string */
 
 #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 */
 
 static JS_ALWAYS_INLINE jsid
 JSID_FROM_BITS(size_t bits)
 {
     jsid id;
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -2881,20 +2881,19 @@ Parser::functionDef(JSAtom *funAtom, Fun
             if (funtc.callsEval())
                 outertc->noteCallsEval();
         }
     }
 
 #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) {
             JSParseNode *block;
 
             block = ListNode::create(outertc);
             if (!block)
                 return NULL;
@@ -7876,20 +7875,19 @@ Parser::primaryExpr(TokenKind tt, JSBool
                 pn->pn_type = TOK_ARRAYCOMP;
 
                 /*
                  * Remove the comprehension expression from pn's linked list
                  * and save it via pnexp.  We'll re-install it underneath the
                  * 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,
                                           TOK_ARRAYPUSH, JSOP_ARRAYPUSH);
                 if (!pntop)
                     return NULL;
                 pn->append(pntop);
             }
--- a/js/src/jsparse.h
+++ b/js/src/jsparse.h
@@ -64,32 +64,38 @@ JS_BEGIN_EXTERN_C
  * <Definitions>
  * TOK_FUNCTION name        pn_funbox: ptr to JSFunctionBox holding function
  *                            object containing arg and var properties.  We
  *                            create the function object at parse (not emit)
  *                            time to specialize arg and var bytecodes early.
  *                          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
  * TOK_ARGSBODY list        list of formal parameters followed by TOK_LC node
  *                            for function body statements as final element
  *                          pn_count: 1 + number of formal parameters
  * TOK_UPVARS   nameset     pn_names: lexical dependencies (JSDefinitions)
  *                            defined in enclosing scopes, or ultimately not
  *                            defined (free variables, either global property
  *                            references or reference errors).
  *                          pn_tree: TOK_ARGSBODY or TOK_LC node
  *
  * <Statements>
  * 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
  *                            in the top level of the switch body's cases, a
  *                            TOK_LEXICALSCOPE node that contains the list of
  *                            TOK_CASE nodes.
  * TOK_CASE,    binary      pn_left: case expr or null if TOK_DEFAULT
  * TOK_DEFAULT              pn_right: TOK_LC node for this case's statements
@@ -123,17 +129,17 @@ JS_BEGIN_EXTERN_C
  * TOK_WITH     binary      pn_left: head expr, pn_right: body
  * TOK_VAR      list        pn_head: list of TOK_NAME or TOK_ASSIGN nodes
  *                                   each name node has
  *                                     pn_used: false
  *                                     pn_atom: variable name
  *                                     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
  * TOK_COLON    name        pn_atom: label, pn_expr: labeled statement
  *
  * <Expressions>
  * All left-associated binary trees of the same type are optimized into lists
  * to avoid recursion when processing expression chains.
@@ -202,34 +208,39 @@ JS_BEGIN_EXTERN_C
  *                          jsscript.h's UPVAR macros) and pn_dflags telling
  *                          const-ness and static analysis results
  * TOK_NAME     name        If pn_used, TOK_NAME uses the lexdef member instead
  *                          of the expr member it overlays
  * TOK_NUMBER   dval        pn_dval: double value of numeric literal
  * TOK_PRIMARY  nullary     pn_op: JSOp bytecode
  *
  * <E4X node descriptions>
+ * 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
  * TOK_DBLCOLON binary      pn_op: JSOP_QNAME
  *                          pn_left: TOK_ANYNAME or TOK_NAME node
  *                          pn_right: TOK_STRING "*" node, or expr within []
  *              name        pn_op: JSOP_QNAMECONST
  *                          pn_expr: TOK_ANYNAME or TOK_NAME left operand
  *                          pn_atom: name on right of ::
  * TOK_XMLELEM  list        XML element node
  *                          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
  * TOK_XMLCDATA,
  * TOK_XMLCOMMENT
  * TOK_XMLPI    nullary     pn_atom: XML processing instruction target
  *                          pn_atom2: XML PI content, or null if no content
  * TOK_XMLTEXT  nullary     pn_atom: marked-up text, or null if empty string
@@ -262,20 +273,20 @@ JS_BEGIN_EXTERN_C
  *
  * <Non-E4X node descriptions, continued>
  *
  * Label              Variant   Members
  * -----              -------   -------
  * 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
  */
 typedef enum JSParseNodeArity {
     PN_NULLARY,                         /* 0 kids, only pn_atom/pn_dval/etc. */
     PN_UNARY,                           /* one kid, plus a couple of scalars */
     PN_BINARY,                          /* two kids, plus a couple of scalars */
     PN_TERNARY,                         /* three kids */
@@ -527,16 +538,45 @@ public:
          * Directives must contain no EscapeSequences or LineContinuations.
          * If the string's length in the source code is its length as a value,
          * accounting for the quotes, then it qualifies.
          */
         return (pn_pos.begin.lineno == pn_pos.end.lineno &&
                 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!
      */
     JSParseNode *last() const {
         JS_ASSERT(pn_arity == PN_LIST);
         JS_ASSERT(pn_count != 0);
         return (JSParseNode *)((char *)pn_tail - offsetof(JSParseNode, pn_next));
@@ -954,16 +994,21 @@ struct Parser : private js::AutoGCRooter
      * JSContext.tempPool mark. This means you cannot allocate from tempPool
      * and save the pointer beyond the next Parser destructor invocation.
      */
     bool init(const jschar *base, size_t length,
               FILE *fp, const char *filename, uintN lineno);
 
     void setPrincipals(JSPrincipals *prin);
 
+    const char *getFilename()
+    {
+        return tokenStream.getFilename();
+    }
+
     /*
      * Parse a top-level JS script.
      */
     JSParseNode *parse(JSObject *chain);
 
 #if JS_HAS_XML_SUPPORT
     JSParseNode *parseXMLText(JSObject *chain, bool allowList);
 #endif
--- a/js/src/jsproto.tbl
+++ b/js/src/jsproto.tbl
@@ -98,15 +98,17 @@ JS_PROTO(Uint8Array,            29,     
 JS_PROTO(Int16Array,            30,     js_InitTypedArrayClasses)
 JS_PROTO(Uint16Array,           31,     js_InitTypedArrayClasses)
 JS_PROTO(Int32Array,            32,     js_InitTypedArrayClasses)
 JS_PROTO(Uint32Array,           33,     js_InitTypedArrayClasses)
 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
 #undef QNAME_INIT
 #undef ANYNAME_INIT
 #undef ATTRIBUTE_INIT
 #undef GENERATOR_INIT
new file mode 100644
--- /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 <dherman@mozilla.com>
+ *
+ * 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 <stdlib.h>
+#include <string.h>     /* 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<Value, 8> 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<WithProto::Class>(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<WithProto::Class>(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<JSParseNode *, 8> 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<WithProto::Class>(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;
+}
new file mode 100644
--- /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 <dherman@mozilla.com>
+ *
+ * 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 <stdlib.h>
+#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___ */
--- a/js/src/jsval.h
+++ b/js/src/jsval.h
@@ -256,16 +256,17 @@ typedef enum JSWhyMagic
     JS_NATIVE_ENUMERATE,         /* indicates that a custom enumerate hook forwarded
                                   * to js_Enumerate, which really means the object can be
                                   * enumerated like a native object. */
     JS_NO_ITER_VALUE,            /* there is not a pending iterator value */
     JS_GENERATOR_CLOSING,        /* exception value thrown when closing a generator */
     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;
 
 typedef struct JSString JSString;
 typedef struct JSObject JSObject;
 
 #if defined(IS_LITTLE_ENDIAN)
 # if JS_BITS_PER_WORD == 32
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -66,16 +66,17 @@
 #include "jsemit.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsiter.h"
 #include "jslock.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsparse.h"
+#include "jsreflect.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jstracer.h"
 #include "jsxml.h"
 #include "jsperf.h"
 
 #include "prmjtime.h"
 
@@ -2702,16 +2703,33 @@ split_enumerate(JSContext *cx, JSObject 
         *statep = JSVAL_NULL;
         break;
     }
 
     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)
 {
     ComplexObject *cpx;
 
     if (JSID_IS_ATOM(id) &&
         !strcmp(JS_GetStringBytes(JSID_TO_STRING(id)), "isInner")) {
         *objp = obj;
         return JS_DefineProperty(cx, obj, "isInner", JSVAL_VOID, NULL, NULL,
@@ -2731,17 +2749,17 @@ split_resolve(JSContext *cx, JSObject *o
 
         return JS_TRUE;
     }
 
 #ifdef LAZY_STANDARD_CLASSES
     if (!(flags & JSRESOLVE_ASSIGNING)) {
         JSBool resolved;
 
-        if (!JS_ResolveStandardClass(cx, obj, id, &resolved))
+        if (!ResolveClass(cx, obj, id, &resolved))
             return JS_FALSE;
 
         if (resolved) {
             *objp = obj;
             return JS_TRUE;
         }
     }
 #endif
@@ -2957,17 +2975,17 @@ sandbox_resolve(JSContext *cx, JSObject 
     jsval v;
     JSBool b, resolved;
 
     if (!JS_GetProperty(cx, obj, "lazy", &v))
         return JS_FALSE;
 
     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;
             return JS_TRUE;
         }
     }
     *objp = NULL;
     return JS_TRUE;
@@ -4706,17 +4724,17 @@ global_enumerate(JSContext *cx, JSObject
 
 static JSBool
 global_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
                JSObject **objp)
 {
 #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;
         return JS_TRUE;
     }
 #endif
 
 #if defined(SHELL_HACK) && defined(DEBUG) && defined(XP_UNIX)
--- a/js/src/tests/js1_8_5/extensions/jstests.list
+++ b/js/src/tests/js1_8_5/extensions/jstests.list
@@ -7,9 +7,10 @@ skip-if(!xulRuntime.shell) script worker
 skip-if(!xulRuntime.shell) script worker-init.js
 skip-if(!xulRuntime.shell) script worker-simple.js
 skip-if(!xulRuntime.shell) script worker-terminate.js
 skip-if(!xulRuntime.shell) script worker-timeout.js
 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
new file mode 100644
--- /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}></{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</{foo}>", xmlElt([xmlStartTag([xmlEscape(ident("foo"))]),
+                                          xmlText("text"),
+                                          xmlEndTag([xmlEscape(ident("foo"))])]));
+assertExpr("<?xml?>", xmlPI("xml", ""));
+assertExpr("<?xml version='1.0'?>", 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);
--- a/js/src/tests/js1_8_5/extensions/shell.js
+++ b/js/src/tests/js1_8_5/extensions/shell.js
@@ -12,8 +12,161 @@ var workerDir = '';
 
 // explicitly turn on js185
 // XXX: The browser currently only supports up to version 1.8
 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 };
+
+})();