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
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 };
+
+})();