diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4fb824e --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +all: build test + +build: move + jison lib/grammar.y lib/lexer.l + mv grammar.js dist/parser.js + +move: lib + cp lib/*.js dist/ + + +test: move dist + node test/reflect-parse.js + diff --git a/README.md b/README.md new file mode 100644 index 0000000..12fc652 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +![Reflect.js](reflectjs.png "Reflect.js") + +Reflect.js is a pure JavaScript implementation of [Mozilla's Parser API](https://developer.mozilla.org/en/SpiderMonkey/Parser_API). It does not currently support Mozilla specific extentions such as `let`, generators, list comprehensions, patterns, E4X, etc. but may eventually support ones that are/become Harmony proposals. *I'll probably add patterns next because those are pretty sweet.* + +Builders are also supported. + +Parsing large files can be slow, for reasons [articulated](http://www.andychu.net/ecmascript/RegExp-Enhancements.html) by Andy Chu. + +Install +======= + + npm install reflect + +MIT X Licensed. diff --git a/dist/.gitignore b/dist/.gitignore new file mode 100644 index 0000000..a6c7c28 --- /dev/null +++ b/dist/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/examples/jsonml-ast.js b/examples/jsonml-ast.js new file mode 100644 index 0000000..4298c88 --- /dev/null +++ b/examples/jsonml-ast.js @@ -0,0 +1,234 @@ +/* -*- 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 Reflect = require('reflect').Reflect; + +// A simple proof-of-concept that the builder API can be used to generate other +// formats, such as JsonMLAst: +// +// http://code.google.com/p/es-lab/wiki/JsonMLASTFormat +// +// It's incomplete (e.g., it doesn't convert source-location information and +// doesn't use all the direct-eval rules), but I think it proves the point. + +var JsonMLAst = (function() { +function reject() { + throw new SyntaxError("node type not supported"); +} + +function isDirectEval(expr) { + // an approximation to the actual rules. you get the idea + return (expr[0] === "IdExpr" && expr[1].name === "eval"); +} + +function functionNode(type) { + return function(id, args, body, isGenerator, isExpression) { + if (isExpression) + body = ["ReturnStmt", {}, body]; + + if (!id) + id = ["Empty"]; + + // Patch up the argument node types: s/IdExpr/IdPatt/g + for (var i = 0; i < args.length; i++) { + args[i][0] = "IdPatt"; + } + + args.unshift("ParamDecl", {}); + + return [type, {}, id, args, body]; + } +} + +return { + program: function(stmts) { + stmts.unshift("Program", {}); + return stmts; + }, + identifier: function(name) { + return ["IdExpr", { name: name }]; + }, + literal: function(val) { + return ["LiteralExpr", { value: val }]; + }, + expressionStatement: function(expr) {return expr}, + conditionalExpression: function(test, cons, alt) { + return ["ConditionalExpr", {}, test, cons, alt]; + }, + unaryExpression: function(op, expr) { + return ["UnaryExpr", {op: op}, expr]; + }, + binaryExpression: function(op, left, right) { + return ["BinaryExpr", {op: op}, left, right]; + }, + property: function(kind, key, val) { + return [kind === "init" + ? "DataProp" + : kind === "get" + ? "GetterProp" + : "SetterProp", + {name: key[1].name}, val]; + }, + functionDeclaration: functionNode("FunctionDecl"), + variableDeclaration: function(kind, elts) { + if (kind === "let" || kind === "const") + throw new SyntaxError("let and const not supported"); + elts.unshift("VarDecl", {}); + return elts; + }, + variableDeclarator: function(id, init) { + id[0] = "IdPatt"; + if (!init) + return id; + return ["InitPatt", {}, id, init]; + }, + sequenceExpression: function(exprs) { + var length = exprs.length; + var result = ["BinaryExpr", {op:","}, exprs[exprs.length - 2], exprs[exprs.length - 1]]; + for (var i = exprs.length - 3; i >= 0; i--) { + result = ["BinaryExpr", {op:","}, exprs[i], result]; + } + return result; + }, + assignmentExpression: function(op, lhs, expr) { + return ["AssignExpr", {op: op}, lhs, expr]; + }, + logicalExpression: function(op, left, right) { + return [op === "&&" ? "LogicalAndExpr" : "LogicalOrExpr", {}, left, right]; + }, + updateExpression: function(expr, op, isPrefix) { + return ["CountExpr", {isPrefix:isPrefix, op:op}, expr]; + }, + newExpression: function(callee, args) { + args.unshift("NewExpr", {}, callee); + return args; + }, + callExpression: function(callee, args) { + args.unshift(isDirectEval(callee) ? "EvalExpr" : "CallExpr", {}, callee); + return args; + }, + memberExpression: function(isComputed, expr, member) { + return ["MemberExpr", {}, expr, isComputed ? member : ["LiteralExpr", {type: "string", value: member[1].name}]]; + }, + functionExpression: functionNode("FunctionExpr"), + arrayExpression: function(elts) { + for (var i = 0; i < elts.length; i++) { + if (!elts[i]) + elts[i] = ["Empty"]; + } + elts.unshift("ArrayExpr", {}); + return elts; + }, + objectExpression: function(props) { + props.unshift("ObjectExpr", {}); + return props; + }, + thisExpression: function() { + return ["ThisExpr", {}]; + }, + + graphExpression: reject, + graphIndexExpression: reject, + comprehensionExpression: reject, + generatorExpression: reject, + yieldExpression: reject, + letExpression: reject, + + emptyStatement: function() {return ["EmptyStmt", {}] }, + blockStatement: function(stmts) { + stmts.unshift("BlockStmt", {}); + return stmts; + }, + labeledStatement: function(lab, stmt) { + return ["LabelledStmt", {label: lab}, stmt]; + }, + ifStatement: function(test, cons, alt) { + return ["IfStmt", {}, test, cons, alt || ["EmptyStmt", {}]]; + }, + switchStatement: function(test, clauses, isLexical) { + clauses.unshift("SwitchStmt", {}, test); + return clauses; + }, + whileStatement: function(expr, stmt) { + return ["WhileStmt", {}, expr, stmt]; + }, + doWhileStatement: function(stmt, expr) { + return ["DoWhileStmt", {}, stmt, expr]; + }, + forStatement: function(init, test, update, body) { + return ["ForStmt", {}, init || ["Empty"], test || ["Empty"], update || ["Empty"], body]; + }, + forInStatement: function(lhs, rhs, body) { + return ["ForInStmt", {}, lhs, rhs, body]; + }, + breakStatement: function(lab) { + return lab ? ["BreakStmt", {}, lab] : ["BreakStmt", {}]; + }, + continueStatement: function(lab) { + return lab ? ["ContinueStmt", {}, lab] : ["ContinueStmt", {}]; + }, + withStatement: function(expr, stmt) { + return ["WithStmt", {}, expr, stmt]; + }, + returnStatement: function(expr) { + return expr ? ["ReturnStmt", {}, expr] : ["ReturnStmt", {}]; + }, + tryStatement: function(body, catches, fin) { + if (catches.length > 1) + throw new SyntaxError("multiple catch clauses not supported"); + var node = ["TryStmt", body, catches[0] || ["Empty"]]; + if (fin) + node.push(fin); + return node; + }, + throwStatement: function(expr) { + return ["ThrowStmt", {}, expr]; + }, + debuggerStatement: function(){ return ["DebuggerStmt", {}] }, + letStatement: reject, + switchCase: function(expr, stmts) { + if (expr) + stmts.unshift("SwitchCase", {}, expr); + else + stmts.unshift("DefaultCase", {}); + return stmts; + }, + catchClause: function(param, guard, body) { + if (guard) + throw new SyntaxError("catch guards not supported"); + param[0] = "IdPatt"; + return ["CatchClause", {}, param, body]; + }, + comprehensionBlock: reject, + + arrayPattern: reject, + objectPattern: reject, + propertyPattern: reject, + + xmlAnyName: reject, + xmlAttributeSelector: reject, + xmlEscape: reject, + xmlFilterExpression: reject, + xmlDefaultDeclaration: reject, + xmlQualifiedIdentifier: reject, + xmlFunctionQualifiedIdentifier: reject, + xmlElement: reject, + xmlText: reject, + xmlList: reject, + xmlStartTag: reject, + xmlEndTag: reject, + xmlPointTag: reject, + xmlName: reject, + xmlAttribute: reject, + xmlCdata: reject, + xmlComment: reject, + xmlProcessingInstruction: reject +}; +})(); + +console.log(JSON.stringify(Reflect.parse("2 + (-x * y)", {loc: false, builder: JsonMLAst}),null," ")); + + diff --git a/lib/codegen.js b/lib/codegen.js new file mode 100644 index 0000000..590c815 --- /dev/null +++ b/lib/codegen.js @@ -0,0 +1,215 @@ + +var indentChar = ' '; //4 spaces + +function idt (lvl) { + if (lvl < 0) lvl = 0; + return Array(lvl+1).join(indentChar); +} + +var codegens = exports.nodes = { + 'EmptyStatement': function Empty_codegen () { + return ''; + }, + 'Literal': function LiteralExpr_codegen () { + switch (this.type) { + case 'null': + return 'null'; + case 'string': + return '"'+this.value+'"'; + default: + return this.value.toString(); + } + }, + 'ObjectExpression': function ObjectExpr_codegen (lvl) { + return this.children.length ? "{\n"+idt(lvl)+this.children.map(function(node){return node.toJS(lvl+1)}).join(',\n'+idt(lvl))+'\n'+idt(lvl-1)+'}': + '{}'; + }, + 'ThisExpression': function ThisExpression_codegen (lvl) { + return 'this'; + }, + 'ArrayExpression': function ObjectExpr_codegen (lvl) { + return "["+this.children.map(function(node){return node.toJS(lvl+1)}).join(', ') + +(this.children.length && this.children[this.children.length-1].nodeType === 'Empty' ? ',':'') + +"]"; + }, + 'MemberExpression': function MemberExpr_codegen (lvl) { + return this.children[0].toJS(lvl+1) + + (this.brackets ? + '['+this.children[1].toJS(lvl+1)+']' : + '.'+this.children[1].value); + }, + 'NewExpression': function MemberExpr_codegen (lvl) { + return 'new '+this.children[0].toJS(lvl+1) + + (this.children.length > 1 ? + '('+this.children.slice(1).map(function(node){return node.toJS(lvl+1)}).join(', ')+')' : '()'); + }, + 'CallExpression': function CappExpr_codegen (lvl) { + return this.children[0].toJS(lvl+1) + + (this.children.length > 1 ? + '('+this.children.slice(1).map(function(node){return node.toJS(lvl+1)}).join(', ')+')' : '()'); + }, + 'UpdateExpression': function CountExpr_codegen (lvl) { + return this.isPrefix ? + this.op + this.children[0].toJS(lvl+1) : + this.children[0].toJS(lvl+1) + this.op; + }, + //'DeleteExpression': function DeleteExpr_codegen (lvl) { + //return 'delete '+this.children[0].toJS(lvl+1); + //}, + //'VoidExpression': function VoidExpr_codegen (lvl) { + //return 'void '+this.children[0].toJS(lvl+1); + //}, + //'TypeofExpression': function TypeofExpr_codegen (lvl) { + //return 'typeof '+this.children[0].toJS(lvl+1); + //}, + 'UnaryExpression': function UnaryPlusExpr_codegen (lvl) { + return '+ '+this.children[0].toJS(lvl+1); + }, + 'ConditionalExpression': function ConditionalExpr_codegen (lvl) { + return this.children[0].toJS(lvl+1) + ' ? ' + + this.children[1].toJS(lvl+1) +' : '+ + this.children[2].toJS(lvl+1); + }, + 'AssignmentExpression': function AssignExpr_codegen (lvl) { + return this.children[0].toJS(lvl+1) +' '+ this.op +' '+ + this.children[1].toJS(lvl+1); + }, + 'BinaryExpression': function BinaryExpr_codegen (lvl) { + return this.children[0].toJS(lvl+1) + this.op +' '+ this.children[1].toJS(lvl+1); + }, + 'BlockStatement': function BlockStmt_codegen (lvl) { + return this.body.length ? "{\n"+idt(lvl)+genStmts(this.body, lvl)+"\n"+idt(lvl-1)+"}" : + '{ }'; + }, + 'VariableDeclaration': function VariableDeclaration_codegen (lvl) { + return "var "+this.children.map(function(node){return node.toJS(lvl);}).join(',\n'+idt(lvl+1)); + }, + 'InitPatt': function InitPatt_codegen (lvl) { + return this.children[0].toJS(lvl) +' = '+ + this.children[1].toJS(lvl); + }, + 'IfStatement': function IfStmt_codegen (lvl) { + return 'if (' + this.children[0].toJS(lvl+1) +') '+ this.children[1].blockgen(lvl+1) + + (this.children[2].nodeType === 'EmptyStatement' ? '' : + ' else '+this.children[2].blockgen(lvl)); + }, + 'DoWhileStatement': function DoWhileStmt_codegen (lvl) { + return 'do '+this.children[0].blockgen(lvl+1) + ' while (' + this.children[1].toJS(lvl+1) +')'; + }, + 'WhileStatement': function WhileStmt_codegen (lvl) { + return 'while (' + this.children[0].toJS(lvl+1) +') '+ this.children[1].blockgen(lvl+1); + }, + 'ForStatement': function ForStmt_codegen (lvl) { + return 'for (' + this.children[0].toJS(lvl+1) + + ';' + this.children[1].toJS(lvl+1) + + ';' + this.children[2].toJS(lvl+1) +') '+ this.children[3].blockgen(lvl+1); + }, + 'ForInStatement': function ForStmt_codegen (lvl) { + return 'for (' + this.children[0].toJS(lvl+1) + + ' in ' + this.children[1].toJS(lvl+1) +') '+ this.children[2].blockgen(lvl+1); + }, + 'ContinueStatement': function ContinueStmt_codegen (lvl) { + return 'continue' + (this.label ? ' '+this.label : ''); + }, + 'BreakStatement': function BreakStmt_codegen (lvl) { + return 'break' + (this.label ? ' '+this.label : ''); + }, + 'ReturnStatement': function ReturnStatement_codegen (lvl) { + return 'return' + (this.children.length ? ' '+this.children[0].toJS(lvl+1) : ''); + }, + 'WithStatement': function WithStmt_codegen (lvl) { + return 'with (' +this.children[0].toJS() + ') ' + this.children[1].blockgen(lvl); + }, + 'SwitchStatement': function SwitchStmt_codegen (lvl) { + return 'switch ('+this.children[0].toJS()+') {\n'+idt(lvl) + +this.children.slice(1).map(function(node){return node.toJS(lvl+1)}).join('\n'+idt(lvl)) + +'\n'+idt(lvl-1)+'}'; + }, + 'SwitchCase': function Case_codegen (lvl) { + return 'case ' +this.children[0].toJS()+':\n'+idt(lvl)+this.children.slice(1).map(function(node){return node.blockgen(lvl+1)}).join('\n'+idt(lvl)); + }, + 'LabeledStatement': function LabelledStmt_codegen (lvl) { + return 'labelled:\n'+idt(lvl) +this.children[0].blockgen(lvl+1); + }, + 'ThrowStatement': function ThrowStmt_codegen (lvl) { + return 'throw ' + this.children[0].toJS(lvl+1); + }, + 'TryStatement': function TryStmt_codegen (lvl) { + return 'try ' + this.children[0].toJS(lvl+1) + +this.children[1].toJS(lvl+1) + +(this.children.length === 3 ? + ' finally '+this.children[2].toJS(lvl+1) : ''); + }, + 'CatchClause': function CatchClause_codegen (lvl) { + return ' catch (' + this.children[0].toJS(lvl) +') '+this.children[1].toJS(lvl); + }, + 'DebuggerStatement': function DebuggerStmt_codegen (lvl) { + return 'debugger'; + }, + 'FunctionDeclaration': function FunctionDeclaration_codegen (lvl) { + return 'function ' + this.children[0].toJS() +' (' + +this.children[1].toJS(lvl+1) + ') ' + +'{\n'+idt(lvl)+genStmts(this.children.slice(2), lvl) +'\n'+idt(lvl-1)+'}'; + }, + 'FunctionExpression': function FunctionExpr_codegen (lvl) { + return 'function' + (this.children[0].nodeType === 'Empty' ? '' : ' '+this.children[0].toJS()) +' (' + +this.children[1].toJS() + ') ' + +'{\n'+idt(lvl)+genStmts(this.children.slice(2), lvl) +'\n'+idt(lvl-1)+'}'; + }, + 'Program': function Program_codegen (lvl) { + return this.children.map(function(node){return node.blockgen(lvl+1)}).join('\n')+'\n'; + } +}; + +function binaryExprCodegen (lvl) { + return this.children[0].toJS(lvl+1) +' '+ this.op +' '+ this.children[1].toJS(lvl+1); +} + +// convenience function for generating a list of statements +function genStmts (nodes, lvl) { + return nodes.map(function(node){return node.blockgen(lvl)}).join('\n'+idt(lvl)); +} + +// convenience function for generating code as a statement (default is expr) +function blockgen () { return this.stmtWrap(this.toJS.apply(this, arguments)); } + +// Most statements end with a semicolon +function defaultStmtWrap (source) { return source+';'; } + +// But some (like while-loop, if-then, etc) do not +function idWrap (source) { return source; } + +// Wrap epressions in parens if they were in original source +function curryParenWrap (fun) { return function (lvl) { return this.parens ? '('+fun.call(this, lvl)+')' : fun.call(this, lvl); }; } + +// Overwrite defaultStmtWrap for specific nodes +var wraps = { + // objects must be wrapped with parens to disambiguate from blocks + 'ObjectExpression': function (source) { return defaultStmtWrap(this.parens ? source : '('+source+')'); }, + 'BlockStatement': idWrap, + 'WhileStatement': idWrap, + 'WithStatement': idWrap, + 'ForStatement': idWrap, + 'ForInStatement': idWrap, + 'IfStatement': idWrap, + 'SwitchStatement': idWrap, + 'LabeledStatement': idWrap, + 'TryStatement': idWrap, + // func-expr must be wrapped with parens to disambiguate from func-decl + 'FunctionExpression': function (source) { return defaultStmtWrap(this.parens ? source : '('+source+')'); }, + 'FunctionDeclaration': idWrap, + 'Program': idWrap +}; + + +// Extend the node prototypes to include code generation functions +exports.extend = function extend (protos) { + var type; + protos.base.stmtWrap = defaultStmtWrap; + protos.base.blockgen = blockgen; + for (type in codegens) { + protos[type].toJS = curryParenWrap(codegens[type]); + if (type in wraps) protos[type].stmtWrap = wraps[type]; + } +}; + diff --git a/lib/grammar.y b/lib/grammar.y new file mode 100644 index 0000000..656d320 --- /dev/null +++ b/lib/grammar.y @@ -0,0 +1,910 @@ +%start Program + +%nonassoc IF_WITHOUT_ELSE +%nonassoc ELSE + +%% + +IdentifierName + : IDENT + | NULLTOKEN + | TRUETOKEN + | FALSETOKEN + | BREAK + | CASE + | CATCH + | CONTINUE + | DEBUGGER + | DEFAULT + | DELETETOKEN + | DO + | ELSE + | FINALLY + | FOR + | FUNCTION + | IF + | INTOKEN + | INSTANCEOF + | NEW + | RETURN + | SWITCH + | THIS + | THROW + | TRY + | TYPEOF + | VAR + | VOIDTOKEN + | WHILE + | WITH + ; + +Literal + : NULLTOKEN + { $$ = yy.Node('Literal', null, yy.loc(@1)); } + | TRUETOKEN + { $$ = yy.Node('Literal', true, yy.loc(@1)); } + | FALSETOKEN + { $$ = yy.Node('Literal', false, yy.loc(@1)); } + | NUMBER + { $$ = yy.Node('Literal', Number($1), yy.loc(@1)); } + | STRING + { $$ = yy.Node('Literal', yy.escapeString(String($1)), yy.loc(@1)); } + | RegularExpressionLiteralBegin REGEXP_BODY + {{ + var body = yytext.slice(1,yytext.lastIndexOf('/')); + var flags = yytext.slice(yytext.lastIndexOf('/')+1); + $$ = yy.Node('Literal', new RegExp(body, flags), yy.loc(yy.locComb(@$,@2))); + //$$ = yy.Node('RegExpExpression', {body:body,flags:flags}); + yy.inRegex = false; + }} + ; + +RegularExpressionLiteralBegin + : '/' + { yy.inRegex = true; yy.lexer.unput($1); $$ = $1; } + | DIVEQUAL + { yy.inRegex = true; yy.lexer.unput($1); $$ = $1; } + ; + +Property + : IdentifierName ':' AssignmentExpr + {{ yy.locComb(@$,@3);$$ = {key:yy.Node('Identifier', $1,yy.loc(@1)),value:$3,kind: "init"}; }} + | STRING ':' AssignmentExpr + {{ yy.locComb(@$,@3);$$ = {key:yy.Node('Literal', String($1),yy.loc(@1)),value:$3,kind: "init"}; }} + | NUMBER ':' AssignmentExpr + {{ yy.locComb(@$,@3);$$ = {key:yy.Node('Literal', Number($1),yy.loc(@1)),value:$3,kind: "init"}; }} + | IDENT IdentifierName '(' ')' Block + {{ + if ($1 !== 'get' && $1 !== 'set') throw new Error('Parse error, invalid set/get.'); // TODO: use jison ABORT when supported + @$ = yy.locComb(@1,@5); + var fun = yy.Node('FunctionExpression',null,[],$Block, false, false, yy.loc(@$)); + $$ = {key:yy.Node('Identifier', $2,yy.loc(@2)),value:fun,kind: $1}; + }} + | IDENT IdentifierName '(' FormalParameterList ')' Block + {{ + @$ = yy.locComb(@1,@6); + if ($1 !== 'get' && $1 !== 'set') throw new Error('Parse error, invalid set/get.'); // TODO: use jison ABORT when supported + var fun = yy.Node('FunctionExpression',null,$FormalParameterList,$Block,false,false,yy.loc(@$)); + $$ = {key:yy.Node('Identifier', $2,yy.loc(@2)),value:fun,kind: $1}; + }} + ; + +PropertyList + : Property + { $$ = [$1]; } + | PropertyList ',' Property + { $$ = $1; $$.push($3); } + ; + +PrimaryExpr + : PrimaryExprNoBrace + | OPENBRACE CLOSEBRACE + { $$ = yy.Node('ObjectExpression',[],yy.loc([@$,@2])); } + | OPENBRACE PropertyList CLOSEBRACE + { $$ = yy.Node('ObjectExpression',$2,yy.loc([@$,@3])); } + | OPENBRACE PropertyList ',' CLOSEBRACE + { $$ = yy.Node('ObjectExpression',$2,yy.loc([@$,@4])); } + ; + +PrimaryExprNoBrace + : THISTOKEN + { $$ = yy.Node('ThisExpression'); } + | Literal + | ArrayLiteral + | IDENT + { $$ = yy.Node('Identifier', String($1), yy.loc(@1)); } + | '(' Expr ')' + { $$ = $Expr; $$.parens = true; yy.locComb(@$,@3) } + ; + +ArrayLiteral + : '[' ']' + { $$ = yy.Node('ArrayExpression',[],yy.loc([@$,@2])); } + | '[' Elision ']' + { $$ = yy.Node('ArrayExpression',$2,yy.loc([@$,@3])); } + | '[' ElementList ']' + { $$ = yy.Node('ArrayExpression',$2,yy.loc([@$,@3])); } + | '[' ElementList ',' ElisionOpt ']' + { $$ = yy.Node('ArrayExpression',$2.concat($4),yy.loc([@$,@5]));} + ; + +ElementList + : AssignmentExpr + { $$ = [$1]; } + | Elision AssignmentExpr + { $$ = $1; $$.push($2); } + | ElementList ',' ElisionOpt AssignmentExpr + { $$ = $1.concat($3); $$.push($4); } + ; + +ElisionOpt + : + { $$ = []; } + | Elision + ; + +Elision + : ',' + { $$ = [,]; } + | Elision ',' + { $$ = $1; $$.length = $$.length+1; } + ; + +MemberExpr + : PrimaryExpr + | FunctionExpr + | MemberExpr '[' Expr ']' + { $$ = yy.Node('MemberExpression',$1,$3,true,yy.loc([@$,@4])); } + | MemberExpr '.' IdentifierName + { $$ = yy.Node('MemberExpression',$1,yy.Node('Identifier', String($3)),false,yy.loc([@$,@3])); } + | NEW MemberExpr Arguments + { $$ = yy.Node('NewExpression',$MemberExpr,$Arguments,yy.loc([@$,@3])); } + ; + +MemberExprNoBF + : PrimaryExprNoBrace + | MemberExprNoBF '[' Expr ']' + { $$ = yy.Node('MemberExpression',$1,$3,true,yy.loc([@$,@4])); } + | MemberExprNoBF '.' IdentifierName + { $$ = yy.Node('MemberExpression',$1,yy.Node('Identifier', String($3)),false,yy.loc([@$,@3])); } + | NEW MemberExpr Arguments + { $$ = yy.Node('NewExpression',$MemberExpr,$Arguments,yy.loc([@$,@3])); } + ; + +NewExpr + : MemberExpr + | NEW NewExpr + { $$ = yy.Node('NewExpression',$2,[],yy.loc([@$,@2])); } + ; + +NewExprNoBF + : MemberExprNoBF + | NEW NewExpr + { $$ = yy.Node('NewExpression',$2,[],yy.loc([@$,@2])); } + ; + +CallExpr + : MemberExpr Arguments + { $$ = yy.Node('CallExpression',$1,$2,yy.loc([@$,@2])); } + | CallExpr Arguments + { $$ = yy.Node('CallExpression',$1,$2,yy.loc([@$,@2])); } + | CallExpr '[' Expr ']' + { $$ = yy.Node('MemberExpression',$1,$3,true,yy.loc([@$,@4])); } + | CallExpr '.' IdentifierName + { $$ = yy.Node('MemberExpression',$1,yy.Node('Identifier', String($3)),false,yy.loc([@$,@3])); } + ; + +CallExprNoBF + : MemberExprNoBF Arguments + { $$ = yy.Node('CallExpression',$1,$2,yy.loc([@$,@2])); } + | CallExprNoBF Arguments + { $$ = yy.Node('CallExpression',$1,$2,yy.loc([@$,@2])); } + | CallExprNoBF '[' Expr ']' + { $$ = yy.Node('MemberExpression',$1,$3,true,yy.loc([@$,@4])); } + | CallExprNoBF '.' IdentifierName + { $$ = yy.Node('MemberExpression',$1,yy.Node('Identifier', String($3)),false,yy.loc([@$,@3])); } + ; + +Arguments + : '(' ')' + { $$ = []; } + | '(' ArgumentList ')' + { $$ = $ArgumentList; } + ; + +ArgumentList + : AssignmentExpr + { $$ = [$1]; } + | ArgumentList ',' AssignmentExpr + { $$ = $1; $$.push($3); } + ; + +LeftHandSideExpr + : NewExpr + | CallExpr + ; + +LeftHandSideExprNoBF + : NewExprNoBF + | CallExprNoBF + ; + +PostfixExpr + : LeftHandSideExpr + | LeftHandSideExpr PLUSPLUS + { $$ = yy.Node('UpdateExpression','++',$1,false,yy.loc([@$,@2])); } + | LeftHandSideExpr MINUSMINUS + { $$ = yy.Node('UpdateExpression','--',$1,false,yy.loc([@$,@2])); } + ; + +PostfixExprNoBF + : LeftHandSideExprNoBF + | LeftHandSideExprNoBF PLUSPLUS + { $$ = yy.Node('UpdateExpression','++',$1,false,yy.loc([@$,@2])); } + | LeftHandSideExprNoBF MINUSMINUS + { $$ = yy.Node('UpdateExpression','--',$1,false,yy.loc([@$,@2])); } + ; + +UnaryExprCommon + : DELETETOKEN UnaryExpr + { $$ = yy.Node('UnaryExpression','delete',$2,yy.loc([@$,@2])); } + | VOIDTOKEN UnaryExpr + { $$ = yy.Node('UnaryExpression','void',$2,yy.loc([@$,@2])); } + | TYPEOF UnaryExpr + { $$ = yy.Node('UnaryExpression','typeof',$2,yy.loc([@$,@2])); } + | PLUSPLUS UnaryExpr + { $$ = yy.Node('UpdateExpression','++',$2,true,yy.loc([@$,@2])); } + | MINUSMINUS UnaryExpr + { $$ = yy.Node('UpdateExpression','--',$2,true,yy.loc([@$,@2])); } + | '+' UnaryExpr + { $$ = yy.Node('UnaryExpression','+',$2,yy.loc([@$,@2])); } + | '-' UnaryExpr + { $$ = yy.Node('UnaryExpression','-',$2,yy.loc([@$,@2])); } + | '~' UnaryExpr + { $$ = yy.Node('UnaryExpression','~',$2,yy.loc([@$,@2])); } + | '!' UnaryExpr + { $$ = yy.Node('UnaryExpression','!',$2,yy.loc([@$,@2])); } + ; + +UnaryExpr + : PostfixExpr + | UnaryExprCommon + ; + +UnaryExprNoBF + : PostfixExprNoBF + | UnaryExprCommon + ; + +MultiplicativeExpr + : UnaryExpr + | MultiplicativeExpr '*' UnaryExpr + { $$ = yy.Node('BinaryExpression', '*', $1, $3, yy.loc([@$,@3])); } + | MultiplicativeExpr '/' UnaryExpr + { $$ = yy.Node('BinaryExpression', '/', $1, $3,yy.loc([@$,@3])); } + | MultiplicativeExpr '%' UnaryExpr + { $$ = yy.Node('BinaryExpression', '%', $1, $3,yy.loc([@$,@3])); } + ; + +MultiplicativeExprNoBF + : UnaryExprNoBF + | MultiplicativeExprNoBF '*' UnaryExpr + { $$ = yy.Node('BinaryExpression', '*', $1, $3,yy.loc([@$,@3])); } + | MultiplicativeExprNoBF '/' UnaryExpr + { $$ = yy.Node('BinaryExpression', '/', $1, $3,yy.loc([@$,@3])); } + | MultiplicativeExprNoBF '%' UnaryExpr + { $$ = yy.Node('BinaryExpression', '%', $1, $3,yy.loc([@$,@3])); } + ; + +AdditiveExpr + : MultiplicativeExpr + | AdditiveExpr '+' MultiplicativeExpr + { $$ = yy.Node('BinaryExpression', '+', $1, $3,yy.loc([@$,@3])); } + | AdditiveExpr '-' MultiplicativeExpr + { $$ = yy.Node('BinaryExpression', '-', $1, $3,yy.loc([@$,@3])); } + ; + +AdditiveExprNoBF + : MultiplicativeExprNoBF + | AdditiveExprNoBF '+' MultiplicativeExpr + { @$ = yy.locComb(@1,@3); + $$ = yy.Node('BinaryExpression', '+', $1, $3, yy.loc(@$)); } + | AdditiveExprNoBF '-' MultiplicativeExpr + { @$ = yy.locComb(@1,@3); + $$ = yy.Node('BinaryExpression', '-', $1, $3, yy.loc(@$)); } + ; + +ShiftExpr + : AdditiveExpr + | ShiftExpr LSHIFT AdditiveExpr + { $$ = yy.Node('BinaryExpression', '<<', $1, $3,yy.loc([@$,@3])); } + | ShiftExpr RSHIFT AdditiveExpr + { $$ = yy.Node('BinaryExpression', '>>', $1, $3,yy.loc([@$,@3])); } + | ShiftExpr URSHIFT AdditiveExpr + { $$ = yy.Node('BinaryExpression', '>>>', $1, $3,yy.loc([@$,@3])); } + ; + +ShiftExprNoBF + : AdditiveExprNoBF + | ShiftExprNoBF LSHIFT AdditiveExpr + { $$ = yy.Node('BinaryExpression', '<<', $1, $3,yy.loc([@$,@3])); } + | ShiftExprNoBF RSHIFT AdditiveExpr + { $$ = yy.Node('BinaryExpression', '>>', $1, $3,yy.loc([@$,@3])); } + | ShiftExprNoBF URSHIFT AdditiveExpr + { $$ = yy.Node('BinaryExpression', '>>>', $1, $3,yy.loc([@$,@3])); } + ; + +RelationalExpr + : ShiftExpr + | RelationalExpr '<' ShiftExpr + { $$ = yy.Node('BinaryExpression', '<', $1, $3,yy.loc([@$,@3])); } + | RelationalExpr '>' ShiftExpr + { $$ = yy.Node('BinaryExpression', '>', $1, $3,yy.loc([@$,@3])); } + | RelationalExpr LE ShiftExpr + { $$ = yy.Node('BinaryExpression', '<=', $1, $3,yy.loc([@$,@3])); } + | RelationalExpr GE ShiftExpr + { $$ = yy.Node('BinaryExpression', '>=', $1, $3,yy.loc([@$,@3])); } + | RelationalExpr INSTANCEOF ShiftExpr + { $$ = yy.Node('BinaryExpression', 'instanceof', $1, $3,yy.loc([@$,@3])); } + | RelationalExpr INTOKEN ShiftExpr + { $$ = yy.Node('BinaryExpression', 'in', $1, $3,yy.loc([@$,@3])); } + ; + +RelationalExprNoIn + : ShiftExpr + | RelationalExprNoIn '<' ShiftExpr + { $$ = yy.Node('BinaryExpression', '<', $1, $3,yy.loc([@$,@3])); } + | RelationalExprNoIn '>' ShiftExpr + { $$ = yy.Node('BinaryExpression', '>', $1, $3,yy.loc([@$,@3])); } + | RelationalExprNoIn LE ShiftExpr + { $$ = yy.Node('BinaryExpression', '<=', $1, $3,yy.loc([@$,@3])); } + | RelationalExprNoIn GE ShiftExpr + { $$ = yy.Node('BinaryExpression', '>=', $1, $3,yy.loc([@$,@3])); } + | RelationalExprNoIn INSTANCEOF ShiftExpr + { $$ = yy.Node('BinaryExpression', 'instanceof', $1, $3,yy.loc([@$,@3])); } + ; + +RelationalExprNoBF + : ShiftExprNoBF + | RelationalExprNoBF '<' ShiftExpr + { $$ = yy.Node('BinaryExpression', '<', $1, $3,yy.loc([@$,@3])); } + | RelationalExprNoBF '>' ShiftExpr + { $$ = yy.Node('BinaryExpression', '>', $1, $3,yy.loc([@$,@3])); } + | RelationalExprNoBF LE ShiftExpr + { $$ = yy.Node('BinaryExpression', '<=', $1, $3,yy.loc([@$,@3])); } + | RelationalExprNoBF GE ShiftExpr + { $$ = yy.Node('BinaryExpression', '>=', $1, $3,yy.loc([@$,@3])); } + | RelationalExprNoBF INSTANCEOF ShiftExpr + { $$ = yy.Node('BinaryExpression', 'instanceof', $1, $3,yy.loc([@$,@3])); } + | RelationalExprNoBF INTOKEN ShiftExpr + { $$ = yy.Node('BinaryExpression', 'in', $1, $3,yy.loc([@$,@3])); } + ; + +EqualityExpr + : RelationalExpr + | EqualityExpr EQEQ RelationalExpr + { $$ = yy.Node('BinaryExpression', '==', $1, $3,yy.loc([@$,@3])); } + | EqualityExpr NE RelationalExpr + { $$ = yy.Node('BinaryExpression', '!=', $1, $3,yy.loc([@$,@3])); } + | EqualityExpr STREQ RelationalExpr + { $$ = yy.Node('BinaryExpression', '===', $1, $3,yy.loc([@$,@3])); } + | EqualityExpr STRNEQ RelationalExpr + { $$ = yy.Node('BinaryExpression', '!==', $1, $3,yy.loc([@$,@3])); } + ; + +EqualityExprNoIn + : RelationalExprNoIn + | EqualityExprNoIn EQEQ RelationalExprNoIn + { $$ = yy.Node('BinaryExpression', '==', $1, $3,yy.loc([@$,@3])); } + | EqualityExprNoIn NE RelationalExprNoIn + { $$ = yy.Node('BinaryExpression', '!=', $1, $3,yy.loc([@$,@3])); } + | EqualityExprNoIn STREQ RelationalExprNoIn + { $$ = yy.Node('BinaryExpression', '===', $1, $3,yy.loc([@$,@3])); } + | EqualityExprNoIn STRNEQ RelationalExprNoIn + { $$ = yy.Node('BinaryExpression', '!==', $1, $3,yy.loc([@$,@3])); } + ; + +EqualityExprNoBF + : RelationalExprNoBF + | EqualityExprNoBF EQEQ RelationalExpr + { $$ = yy.Node('BinaryExpression', '==', $1, $3,yy.loc([@$,@3])); } + | EqualityExprNoBF NE RelationalExpr + { $$ = yy.Node('BinaryExpression', '!=', $1, $3,yy.loc([@$,@3])); } + | EqualityExprNoBF STREQ RelationalExpr + { $$ = yy.Node('BinaryExpression', '===', $1, $3,yy.loc([@$,@3])); } + | EqualityExprNoBF STRNEQ RelationalExpr + { $$ = yy.Node('BinaryExpression', '!==', $1, $3,yy.loc([@$,@3])); } + ; + +BitwiseANDExpr + : EqualityExpr + | BitwiseANDExpr '&' EqualityExpr + { $$ = yy.Node('BinaryExpression', '&', $1, $3,yy.loc([@$,@3])); } + ; + +BitwiseANDExprNoIn + : EqualityExprNoIn + | BitwiseANDExprNoIn '&' EqualityExprNoIn + { $$ = yy.Node('BinaryExpression', '&', $1, $3,yy.loc([@$,@3])); } + ; + +BitwiseANDExprNoBF + : EqualityExprNoBF + | BitwiseANDExprNoBF '&' EqualityExpr + { $$ = yy.Node('BinaryExpression', '&', $1, $3,yy.loc([@$,@3])); } + ; + +BitwiseXORExpr + : BitwiseANDExpr + | BitwiseXORExpr '^' BitwiseANDExpr + { $$ = yy.Node('BinaryExpression', '^', $1, $3,yy.loc([@$,@3])); } + ; + +BitwiseXORExprNoIn + : BitwiseANDExprNoIn + | BitwiseXORExprNoIn '^' BitwiseANDExprNoIn + { $$ = yy.Node('BinaryExpression', '^', $1, $3,yy.loc([@$,@3])); } + ; + +BitwiseXORExprNoBF + : BitwiseANDExprNoBF + | BitwiseXORExprNoBF '^' BitwiseANDExpr + { $$ = yy.Node('BinaryExpression', '^', $1, $3,yy.loc([@$,@3])); } + ; + +BitwiseORExpr + : BitwiseXORExpr + | BitwiseORExpr '|' BitwiseXORExpr + { $$ = yy.Node('BinaryExpression', '|', $1, $3,yy.loc([@$,@3])); } + ; + +BitwiseORExprNoIn + : BitwiseXORExprNoIn + | BitwiseORExprNoIn '|' BitwiseXORExprNoIn + { $$ = yy.Node('BinaryExpression', '|', $1, $3,yy.loc([@$,@3])); } + ; + +BitwiseORExprNoBF + : BitwiseXORExprNoBF + | BitwiseORExprNoBF '|' BitwiseXORExpr + { $$ = yy.Node('BinaryExpression', '|', $1, $3,yy.loc([@$,@3])); } + ; + +LogicalANDExpr + : BitwiseORExpr + | LogicalANDExpr AND BitwiseORExpr + { $$ = yy.Node('LogicalExpression', '&&', $1, $3,yy.loc([@$,@3])); } + ; + +LogicalANDExprNoIn + : BitwiseORExprNoIn + | LogicalANDExprNoIn AND BitwiseORExprNoIn + { $$ = yy.Node('LogicalExpression', '&&', $1, $3,yy.loc([@$,@3])); } + ; + +LogicalANDExprNoBF + : BitwiseORExprNoBF + | LogicalANDExprNoBF AND BitwiseORExpr + { $$ = yy.Node('LogicalExpression', '&&', $1, $3,yy.loc([@$,@3])); } + ; + +LogicalORExpr + : LogicalANDExpr + | LogicalORExpr OR LogicalANDExpr + { $$ = yy.Node('LogicalExpression', '||', $1, $3,yy.loc([@$,@3])); } + ; + +LogicalORExprNoIn + : LogicalANDExprNoIn + | LogicalORExprNoIn OR LogicalANDExprNoIn + { $$ = yy.Node('LogicalExpression', '||', $1, $3,yy.loc([@$,@3])); } + ; + +LogicalORExprNoBF + : LogicalANDExprNoBF + | LogicalORExprNoBF OR LogicalANDExpr + { $$ = yy.Node('LogicalExpression', '||', $1, $3,yy.loc([@$,@3])); } + ; + +ConditionalExpr + : LogicalORExpr + | LogicalORExpr '?' AssignmentExpr ':' AssignmentExpr + { $$ = yy.Node('ConditionalExpression', $1, $3, $5,yy.loc([@$,@5])); } + ; + +ConditionalExprNoIn + : LogicalORExprNoIn + | LogicalORExprNoIn '?' AssignmentExprNoIn ':' AssignmentExprNoIn + { $$ = yy.Node('ConditionalExpression', $1, $3, $5,yy.loc([@$,@5])); } + ; + +ConditionalExprNoBF + : LogicalORExprNoBF + | LogicalORExprNoBF '?' AssignmentExpr ':' AssignmentExpr + { $$ = yy.Node('ConditionalExpression', $1, $3, $5,yy.loc([@$,@5])); } + ; + +AssignmentExpr + : ConditionalExpr + | LeftHandSideExpr AssignmentOperator AssignmentExpr + { $$ = yy.Node('AssignmentExpression', $2, $1, $3,yy.loc([@$,@3])); } + ; + +AssignmentExprNoIn + : ConditionalExprNoIn + | LeftHandSideExpr AssignmentOperator AssignmentExprNoIn + { $$ = yy.Node('AssignmentExpression', $2, $1, $3,yy.loc([@$,@3])); } + ; + +AssignmentExprNoBF + : ConditionalExprNoBF + | LeftHandSideExprNoBF AssignmentOperator AssignmentExpr + { $$ = yy.Node('AssignmentExpression', $2, $1, $3,yy.loc([@$,@3])); } + ; + +AssignmentOperator + : '=' + | PLUSEQUAL + | MINUSEQUAL + | MULTEQUAL + | DIVEQUAL + | LSHIFTEQUAL + | RSHIFTEQUAL + | URSHIFTEQUAL + | ANDEQUAL + | XOREQUAL + | OREQUAL + | MODEQUAL + ; + +Expr + : AssignmentExpr + | Expr ',' AssignmentExpr + {{ + if ($1.type == 'SequenceExpression') { + $1.expressions.push($3); + $1.loc = yy.loc([@$,@3]); + $$ = $1; + } else + $$ = yy.Node('SequenceExpression',[$1, $3],yy.loc([@$,@3])); + }} + ; + +ExprNoIn + : AssignmentExprNoIn + | ExprNoIn ',' AssignmentExprNoIn + {{ + if ($1.type == 'SequenceExpression') { + $1.expressions.push($3); + $1.loc = yy.loc([@$,@3]); + $$ = $1; + } else + $$ = yy.Node('SequenceExpression',[$1, $3],yy.loc([@$,@3])); + }} + ; + +ExprNoBF + : AssignmentExprNoBF + | ExprNoBF ',' AssignmentExpr + {{ + if ($1.type == 'SequenceExpression') { + $1.expressions.push($3); + $1.loc = yy.loc([@$,@3]); + $$ = $1; + } else + $$ = yy.Node('SequenceExpression',[$1, $3],yy.loc([@$,@3])); + }} + ; + +Statement + : Block + | VariableStatement + | ConstStatement + | FunctionDeclarationaration + | EmptyStatement + | ExprStatement + | IfStatement + | IterationStatement + | ContinueStatement + | BreakStatement + | ReturnStatement + | WithStatement + | SwitchStatement + | LabeledStatement + | ThrowStatement + | TryStatement + | DebuggerStatement + ; + +Block + : OPENBRACE CLOSEBRACE + { $$ = yy.Node('BlockStatement',[],yy.loc([@$,@2])); } + | OPENBRACE SourceElements CLOSEBRACE + { $$ = yy.Node('BlockStatement',$2,yy.loc([@$,@3])); } + ; + +ConstStatement + : CONSTTOKEN ConstDecralarionList ';' + { $$ = yy.Node('VariableDeclaration',"const",$2,yy.loc([@$,@3])) } + | CONSTTOKEN ConstDecralarionList error + { $$ = yy.Node('VariableDeclaration',"const",$2,yy.loc([@$,@2])) } + ; + +ConstDecralarionList + : IDENT + { $$ = [yy.Node('InitPatt', yy.Node('Identifier', $1,yy.loc(@1)), null)]; } + | IDENT Initializer + { yy.locComb(@$,@2);$$ = [yy.Node('InitPatt', yy.Node('Identifier', $1,yy.loc(@1)), $2)]; } + | ConstDecralarionList ',' IDENT + { yy.locComb(@$,@3);$$ = $1; $1.push(yy.Node('InitPatt', yy.Node('Identifier', $3,yy.loc(@3)), null)); } + | ConstDecralarionList ',' IDENT Initializer + { yy.locComb(@$,@4);$$ = $1; $1.push(yy.Node('InitPatt', yy.Node('Identifier', $3,yy.loc(@3)), $4)); } + ; + +VariableStatement + : VAR VariableDeclarationList ';' + { $$ = yy.Node('VariableDeclaration',"var",$2,yy.loc([@$,@3])) } + | VAR VariableDeclarationList error + { $$ = yy.Node('VariableDeclaration',"var",$2,yy.loc([@$,@2])) } + ; + +VariableDeclarationList + : IDENT + { $$ = [yy.Node('InitPatt', yy.Node('Identifier', $1,yy.loc(@1)), null)]; } + | IDENT Initializer + { yy.locComb(@$,@2);$$ = [yy.Node('InitPatt', yy.Node('Identifier', $1,yy.loc(@1)), $2)]; } + | VariableDeclarationList ',' IDENT + { yy.locComb(@$,@3);$$ = $1; $1.push(yy.Node('InitPatt', yy.Node('Identifier', $3,yy.loc(@3)), null)); } + | VariableDeclarationList ',' IDENT Initializer + { yy.locComb(@$,@4);$$ = $1; $1.push(yy.Node('InitPatt', yy.Node('Identifier', $3,yy.loc(@3)), $4)); } + ; + +VariableDeclarationListNoIn + : IDENT + { $$ = [yy.Node('InitPatt', yy.Node('Identifier', $1,yy.loc(@1)), null)]; } + | IDENT InitializerNoIn + { yy.locComb(@$,@2); + $$ = [yy.Node('InitPatt', yy.Node('Identifier', $1,yy.loc(@1)), $2)]; } + | VariableDeclarationListNoIn ',' IDENT + { yy.locComb(@$,@3); + $$ = $1; $1.push(yy.Node('InitPatt', yy.Node('Identifier', $3,yy.loc(@3)), null)); } + | VariableDeclarationListNoIn ',' IDENT InitializerNoIn + { yy.locComb(@$,@4); + $$ = $1; $1.push(yy.Node('InitPatt', yy.Node('Identifier', $3,yy.loc(@3)), $4)); } + ; + +Initializer + : '=' AssignmentExpr + { $$ = $2; yy.locComb(@$,@2) } + ; + +InitializerNoIn + : '=' AssignmentExprNoIn + { $$ = $2; yy.locComb(@$,@2) } + ; + +EmptyStatement + : ';' + { $$ = yy.Node('EmptyStatement',yy.loc(@1)); } + ; + +ExprStatement + : ExprNoBF ';' + { $$ = yy.Node('ExpressionStatement', $1,yy.loc([@$,@2])); } + | ExprNoBF error + { $$ = yy.Node('ExpressionStatement', $1,yy.loc(@1)); } + ; + +IfStatement + : IF '(' Expr ')' Statement %prec IF_WITHOUT_ELSE + { $$ = yy.Node('IfStatement', $Expr, $Statement, null, yy.loc([@$,@5])); } + | IF '(' Expr ')' Statement ELSE Statement + { $$ = yy.Node('IfStatement', $Expr, $Statement1, $Statement2, yy.loc([@$,@7])); } + ; + +IterationStatement + : DO Statement WHILE '(' Expr ')' ';' + { $$ = yy.Node('DoWhileStatement', $Statement, $Expr,yy.loc([@$,@7])); } + | DO Statement WHILE '(' Expr ')' error + { $$ = yy.Node('DoWhileStatement', $Statement, $Expr,yy.loc([@$,@6])); } + | WHILE '(' Expr ')' Statement + { $$ = yy.Node('WhileStatement', $Expr, $Statement,yy.loc([@$,@5])); } + | FOR '(' ExprNoInOpt ';' ExprOpt ';' ExprOpt ')' Statement + { $$ = yy.Node('ForStatement', $ExprNoInOpt, $ExprOpt1, $ExprOpt2, $Statement,yy.loc([@$,@9])); } + | FOR '(' VAR VariableDeclarationListNoIn ';' ExprOpt ';' ExprOpt ')' Statement + { $$ = yy.Node('ForStatement', + yy.Node('VariableDeclaration',"var", $4, yy.loc([@3,@4])), + $ExprOpt1, $ExprOpt2, $Statement, yy.loc([@$,@10])); } + | FOR '(' LeftHandSideExpr INTOKEN Expr ')' Statement + { $$ = yy.Node('ForInStatement', $LeftHandSideExpr, $Expr, $Statement, false, yy.loc([@$,@7])); } + | FOR '(' VAR IDENT INTOKEN Expr ')' Statement + { $$ = yy.Node('ForInStatement', + yy.Node('VariableDeclaration',"var", + [yy.Node('InitPatt',yy.Node('Identifier', $4,yy.loc(@4)),null)], + yy.loc([@3,@4])), + $Expr, $Statement, false, yy.loc([@$,@8])); } + | FOR '(' VAR IDENT InitializerNoIn INTOKEN Expr ')' Statement + { $$ = yy.Node('ForInStatement', + yy.Node('VariableDeclaration',"var", + [yy.Node('InitPatt',yy.Node('Identifier', $4,yy.loc(@4)), $5)], + yy.loc([@3,@5])), + $Expr, $Statement, false, yy.loc([@$,@9])); } + ; + +ExprOpt + : + { $$ = null } + | Expr + ; + +ExprNoInOpt + : + { $$ = null } + | ExprNoIn + ; + +ContinueStatement + : CONTINUE ';' + { $$ = yy.Node('ContinueStatement',null,yy.loc([@$,@2])); } + | CONTINUE error + { $$ = yy.Node('ContinueStatement',null,yy.loc(@$)); } + | CONTINUE IDENT ';' + { $$ = yy.Node('ContinueStatement',yy.Node('Identifier', $2,yy.loc(@2)),yy.loc([@$,@3])); } + | CONTINUE IDENT error + { $$ = yy.Node('ContinueStatement',yy.Node('Identifier', $2,yy.loc(@2)),yy.loc([@$,@2])); } + ; + +BreakStatement + : BREAK ';' + { $$ = yy.Node('BreakStatement',null,yy.loc([@$,@2])); } + | BREAK error + { $$ = yy.Node('BreakStatement',null,yy.loc(@$)); } + | BREAK IDENT ';' + { $$ = yy.Node('BreakStatement',yy.Node('Identifier', $2,yy.loc(@$)),yy.loc([@$,@3])); } + | BREAK IDENT error + { $$ = yy.Node('BreakStatement',yy.Node('Identifier', $2,yy.loc(@$)),yy.loc([@$,@2])); } + ; + +ReturnStatement + : RETURN ';' + { $$ = yy.Node('ReturnStatement',null,yy.loc([@$,@3])); } + | RETURN error + { $$ = yy.Node('ReturnStatement',null,yy.loc([@$,@3])); } + | RETURN Expr ';' + { $$ = yy.Node('ReturnStatement',$2,yy.loc([@$,@3])); } + | RETURN Expr error + { $$ = yy.Node('ReturnStatement',$2,yy.loc([@$,@2])); } + ; + +WithStatement + : WITH '(' Expr ')' Statement + { $$ = yy.Node('WithStatement',$Expr,$Statement,yy.loc([@$,@5])); } + ; + +SwitchStatement + : SWITCH '(' Expr ')' CaseBlock + { $$ = yy.Node('SwitchStatement',$Expr,$CaseBlock,yy.loc([@$,@5])); } + ; + +CaseBlock + : OPENBRACE CaseClausesOpt CLOSEBRACE + { $$ = $2; yy.locComb(@$,@3) } + | OPENBRACE CaseClausesOpt DefaultClause CaseClausesOpt CLOSEBRACE + { $2.push($DefaultClause); $$ = $2.concat($CaseClausesOpt2); yy.locComb(@$,@5) } + ; + +CaseClausesOpt + : + { $$ = []; } + | CaseClauses + ; + +CaseClauses + : CaseClause + { $$ = [$1]; } + | CaseClauses CaseClause + { $1.push($2); $$ = $1; } + ; + +CaseClause + : CASE Expr ':' + { $$ = yy.Node('SwitchCase',$Expr,[], yy.loc([@$,@3])); } + | CASE Expr ':' SourceElements + { $$ = yy.Node('SwitchCase',$Expr,$4, yy.loc([@$,@4])); } + ; + +DefaultClause + : DEFAULT ':' + { $$ = yy.Node('SwitchCase',null,[], yy.loc([@$,@2])); } + | DEFAULT ':' SourceElements + { $$ = yy.Node('SwitchCase',null,$3, yy.loc([@$,@3])); } + ; + +LabeledStatement + : IDENT ':' Statement + { $$ = yy.Node('LabeledStatement',yy.Node('Identifier', $1,yy.loc(@1)),$3, yy.loc([@$,@3])); } + ; + +ThrowStatement + : THROW Expr ';' + { $$ = yy.Node('ThrowStatement', $Expr, @2, yy.loc([@$,@3])); } + | THROW Expr error + { $$ = yy.Node('ThrowStatement', $Expr, @2, yy.loc([@$,@2])); } + ; + +TryStatement + : TRY Block FINALLY Block + { $$ = yy.Node('TryStatement', $Block1, null, $Block2, yy.loc([@$,@4])); } + | TRY Block CATCH '(' IDENT ')' Block + { $$ = yy.Node('TryStatement', $Block1, + yy.Node('CatchClause',yy.Node('Identifier', $5,yy.loc(@5)),null, $Block2, yy.loc([@3,@7])), null, yy.loc([@$,@7])); } + | TRY Block CATCH '(' IDENT ')' Block FINALLY Block + { $$ = yy.Node('TryStatement', $Block1, + yy.Node('CatchClause',yy.Node('Identifier', $5,yy.loc(@5)),null, $Block2, yy.loc([@3,@7])), + $Block3, yy.loc([@$,@9])); } + ; + +DebuggerStatement + : DEBUGGER ';' + { $$ = yy.Node('DebuggerStatement', yy.loc([@$,@2])); } + | DEBUGGER error + { $$ = yy.Node('DebuggerStatement', yy.loc(@1)); } + ; + +FunctionDeclarationaration + : FUNCTION IDENT '(' ')' Block + { $$ = yy.Node('FunctionDeclaration', + yy.Node('Identifier', $2,yy.loc(@2)), [], $Block, false, false, yy.loc([@$,@5])) + } + | FUNCTION IDENT '(' FormalParameterList ')' Block + { $$ = yy.Node('FunctionDeclaration', + yy.Node('Identifier', $2,yy.loc(@2)), + $FormalParameterList, $Block, false, false, yy.loc([@$,@6])) + } + ; + +FunctionExpr + : FUNCTION '(' ')' Block + { $$ = yy.Node('FunctionExpression', null, [], $Block, false, false, yy.loc([@$,@4])); } + | FUNCTION '(' FormalParameterList ')' Block + { $$ = yy.Node('FunctionExpression', null, + $FormalParameterList, $Block, false, false, yy.loc([@$,@5])); } + + | FUNCTION IDENT '(' ')' Block + { $$ = yy.Node('FunctionExpression', + yy.Node('Identifier', $2,yy.loc(@2)), + [], $Block, false, false, yy.loc([@$,@5])); } + | FUNCTION IDENT '(' FormalParameterList ')' Block + { $$ = yy.Node('FunctionExpression', + yy.Node('Identifier', $2,yy.loc(@2)), + $FormalParameterList, $Block, false, false, yy.loc([@$,@6])); } + ; + +FormalParameterList + : IDENT + { $$ = [yy.Node('Identifier', $1)]; } + | FormalParameterList ',' IDENT + { $$ = $1; $$.push(yy.Node('Identifier', $3,yy.loc(@3))); } + ; + +FunctionBody + : + { $$ = []; } + | SourceElements + ; + +Program + : + { return yy.Node('Program'); } + | SourceElements + { return yy.Node('Program',$1,yy.loc(@1)); } + ; + +SourceElements + : Statement + { $$ = [$1]; } + | SourceElements Statement + { yy.locComb(@$,@2); + $$ = $1;$1.push($2); } + ; + diff --git a/lib/lexer.l b/lib/lexer.l new file mode 100644 index 0000000..ee36d7b --- /dev/null +++ b/lib/lexer.l @@ -0,0 +1,157 @@ +esc "\\" +int ([0-9]|[1-9][0-9]+) +exp ([eE][-+]?[0-9]+) +frac ("."[0-9]+) +IDS [a-zA-Z$_]|"\u"[a-fA-F0-9]{4} +BSL "\\". +RCLASS "["({BSL}|[^\\[])*"]" + +%% + +";"\s+/("++"|"--") {return ';'} +\n(\s|\n)*/("++"|"--") {return ';'} +\n(\s|\n)* %{ if(yy.ASI){ yy.ASI=false; return ';';} else yy.lineBreak = true; %} +\s+ {yy.ASI=false; /* skip whitespace */} +"//".* {/* skip comment */} +"#".* {/* skip comment */} +"/*"(.|\n)*?"*/" {yy.ASI=false; /* skip comment */} + +"0"[xX][a-fA-F0-9]+/([^a-zA-Z$_]{0,1}) {return 'NUMBER';} +([1-9][0-9]+|[0-9]){frac}?{exp}?/([^a-zA-Z$_]{0,1}) {return 'NUMBER';} + +'"'("\\x"[a-fA-F0-9]{2}|"\\u"[a-fA-F0-9]{4}|"\\"[^xu]|[^"{esc}\n])*'"' {yytext = yytext.substr(1,yyleng-2); return 'STRING';} +"'"("\\"['bfvnrt/{esc}]|"\\u"[a-fA-F0-9]{4}|[^'{esc}])*"'" {yytext = yytext.substr(1,yyleng-2); return 'STRING';} + +/("/="({BSL}|{RCLASS}|[^[\\\/])*"/"({IDS})*) %{ + if(!!yy.inRegex) { + var s = yy.lexer.matches[1]; + for(var i=0;i>=" {return 'RSHIFTEQUAL'} +">>>=" {return 'URSHIFTEQUAL'} +"/=" {return 'DIVEQUAL'} +"<=" {return 'LE'} +">=" {return 'GE'} +"===" {return 'STREQ'} +"!==" {return 'STRNEQ'} +"==" {return 'EQEQ'} +"!=" {return 'NE'} +"&&" {return 'AND'} +"||" {return 'OR'} +"++" {return 'PLUSPLUS'} +"--" {return 'MINUSMINUS'} +">>>" {return 'URSHIFT'} +"<<" {return 'LSHIFT'} +">>" {return 'RSHIFT'} +"+" {return '+'} +"-" {return '-'} +"*" {return '*'} +"%" {return '%'} +"<" {return '<'} +">" {return '>'} +"&" {return '&'} +"|" {return '|'} +"^" {return '^'} +"!" {return '!'} +"~" {return '~'} +"?" {return '?'} +"/" {return '/'} +"=" {return '='} + +"break" {yy.ASI = true; return 'BREAK'} +"case" {return 'CASE'} +"continue" {yy.ASI = true; return 'CONTINUE'} +"debugger" {return 'DEBUGGER'} +"default" {return 'DEFAULT'} +"delete" {return 'DELETETOKEN'} +"do" {return 'DO'} +"else" {return 'ELSE'} +"finally" {return 'FINALLY'} +"for" {return 'FOR'} +"function" {return 'FUNCTION'} +"if" {return 'IF'} +"in" {return 'INTOKEN'} +"instanceof" {return 'INSTANCEOF'} +"new" {return 'NEW'} +"return" {yy.ASI = true; return 'RETURN'} +"switch" {return 'SWITCH'} +"try" {return 'TRY'} +"catch" {return 'CATCH'} +"throw" {yy.ASI = true; return 'THROW'} +"typeof" {return 'TYPEOF'} +"var" {return 'VAR'} +"void" {return 'VOIDTOKEN'} +"while" {return 'WHILE'} +"with" {return 'WITH'} + +"class" {return 'WITH'} +"const" {return 'CONSTTOKEN'} +"enum" {return 'ENUM'} +"export" {return 'EXPORT'} +"extends" {return 'EXTENDS'} +"import" {return 'IMPORT'} +"super" {return 'SUPERTOKEN'} + +"implements" {return 'IMPLEMENTS'} +"interface" {return 'INTERFACE'} +"let" {return 'LET'} +"package" {return 'PACKAGE'} +"private" {return 'PRIVATE'} +"protected" {return 'PROTECTED'} +"public" {return 'PUBLIC'} +"static" {return 'STATIC'} +"yield" {return 'YIELD'} + +"this" {return 'THISTOKEN'} +"true" {return 'TRUETOKEN'} +"false" {return 'FALSETOKEN'} +"null" {return 'NULLTOKEN'} + +({IDS})+({IDS}|[0-9])* {return 'IDENT';} + +. {return 'INVALID'} + +%% + diff --git a/lib/nodes.js b/lib/nodes.js new file mode 100644 index 0000000..98e2454 --- /dev/null +++ b/lib/nodes.js @@ -0,0 +1,268 @@ + +exports.defineNodes = function (builder) { + +var defaultIni = function (loc) { + this.loc = loc; + return this; +} + +var def = function def(name, ini) { + builder[name[0].toLowerCase()+name.slice(1)] = function (a,b,c,d,e,f,g,h) { + var obj = {}; + ini.call(obj,a,b,c,d,e,f,g,h); + obj.type = name; + return obj; + }; +}; + +/* Nodes +*/ + +// Program node +def('Program', function (elements,loc) { + this.body = elements; + this.loc = loc; +}); + +def('ExpressionStatement', function (expression, loc) { + this.expression = expression; + this.loc = loc; +}); + +def('BlockStatement', function (body, loc) { + this.body = body; + this.loc = loc; +}); + +def('EmptyStatement', defaultIni); + + +// Identifier node +def('Identifier', function (name,loc) { + this.name = name; + this.loc = loc; +}); + +// Literal expression node +def('Literal', function (val, loc) { + this.value = val; + this.loc = loc; +}); + +// "this" expression node +def('ThisExpression', defaultIni); + +// Var statement node +def('VariableDeclaration', function (kind, declarations, loc) { + this.kind = kind; + this.declarations = declarations; + this.loc = loc; +}); + +// Init pattern node +def('InitPatt', function (id, init, loc) { + this.id = id; + this.init = init; + this.loc = loc; +}); + +def('ArrayExpression', function (elements, loc) { + this.elements = elements; + this.loc = loc; +}); + +def('ObjectExpression', function (properties, loc) { + this.properties = properties; + this.loc = loc; +}); + +// Function declaration node +var funIni = function (ident, params, body, isGen, isExp, loc) { + this.id = ident; + this.params = params; + this.body = body; + this.generator = isGen; + this.expression = isExp; + this.loc = loc; +}; + +def('FunctionDeclaration', funIni); + +def('FunctionExpression', funIni); + +// return statement node +def('ReturnStatement', function (argument, loc) { + this.argument = argument; + this.loc = loc; +}); + +def('TryStatement', function (block, handler, finalizer, loc) { + this.block = block; + this.handler = handler; + this.finalizer = finalizer; + this.loc = loc; +}); + +def('CatchClause', function (param, guard, body, loc) { + this.param = param; + this.guard = guard; + this.body = body; + this.loc = loc; +}); + +def('ThrowStatement', function (argument, loc) { + this.argument = argument; + this.loc = loc; +}); + +def('LabeledStatement', function (label, body, loc) { + this.label = label; + this.body = body; + this.loc = loc; +}); + +def('BreakStatement', function (label, loc) { + this.label = label; + this.loc = loc; +}); + +def('ContinueStatement', function (label, loc) { + this.label = label; + this.loc = loc; +}); + +def('SwitchStatement', function (discriminant, cases, lexical, loc) { + this.discriminant = discriminant; + this.cases = cases; + this.lexical = !!lexical; + this.loc = loc; +}); + +def('SwitchCase', function (test, consequent, loc) { + this.test = test; + this.consequent = consequent; + this.loc = loc; +}); + +def('WithStatement', function (object, body, loc) { + this.object = object; + this.body = body; + this.loc = loc; +}); + + +// operators +def('ConditionalExpression', function (test, consequent, alternate, loc) { + this.test = test; + this.alternate = alternate; + this.consequent = consequent; + this.loc = loc; +}); + +def('SequenceExpression', function (expressions, loc) { + this.expressions = expressions; + this.loc = loc; +}); + +def('BinaryExpression', function (op, left, right, loc) { + this.operator = op; + this.left = left; + this.right = right; + this.loc = loc; +}); + +def('AssignmentExpression', function (op, left, right, loc) { + this.operator = op; + this.left = left; + this.right = right; + this.loc = loc; +}); + +def('LogicalExpression', function (op, left, right, loc) { + this.operator = op; + this.left = left; + this.right = right; + this.loc = loc; +}); + +def('UnaryExpression', function (operator, argument, loc) { + this.operator = operator; + this.argument = argument; + this.loc = loc; +}); + + +def('UpdateExpression', function (operator, argument, prefix, loc) { + this.operator = operator; + this.argument = argument; + this.prefix = prefix; + this.loc = loc; +}); + +def('CallExpression', function (callee, args, loc) { + this.callee = callee; + this["arguments"] = args; + this.loc = loc; +}); + + +def('NewExpression', function (callee, args, loc) { + this.callee = callee; + this["arguments"] = args; + this.loc = loc; +}); + + +def('MemberExpression', function (object, property, computed, loc) { + this.object = object; + this.property = property; + this.computed = computed; + this.loc = loc; +}); + +// debugger node +def('DebuggerStatement', defaultIni); + +// empty node +def('Empty', defaultIni); + +// control structs + +def('WhileStatement', function (test, body, loc) { + this.test = test; + this.body = body; + this.loc = loc; +}); + +def('DoWhileStatement', function (body, test, loc) { + this.test = test; + this.body = body; + this.loc = loc; +}); + +def('ForStatement', function (init, test, update, body, loc) { + this.init = init; + this.test = test; + this.update = update; + this.body = body; + this.loc = loc; +}); + +def('ForInStatement', function (left, right, body, each, loc) { + this.left = left; + this.right = right; + this.body = body; + this.each = !!each; + this.loc = loc; +}); + +def('IfStatement', function (test, consequent, alternate, loc) { + this.test = test; + this.alternate = alternate; + this.consequent = consequent; + this.loc = loc; +}); + +return def; +} + diff --git a/lib/reflect.js b/lib/reflect.js new file mode 100644 index 0000000..5eca282 --- /dev/null +++ b/lib/reflect.js @@ -0,0 +1,105 @@ + +var parser = require("./parser").parser, + nodes = require("./nodes"); + +function JSParser (options) { + // Create a parser constructor and an instance + this.parser = new Parser(options||{}); +} + +JSParser.prototype = { + parse: function (source) { + return this.parser.parse(source); + } +}; + +var builder = {}; + +// Define AST nodes +nodes.defineNodes(builder); + +function Parser (options) { + this.yy.source = options.source||null; + this.yy.startLine = options.line || 1; + this.yy.noloc = options.loc === false; + this.yy.builder = options.builder||null; +} + +Parser.prototype = parser; + +// allow yy.NodeType calls in parser +for (var con in builder) { + if (builder.hasOwnProperty(con)) { + parser.yy[con] = function (name){ + return function (a,b,c,d,e,f,g,h) { + return builder[name](a,b,c,d,e,f,g,h); + } + }(con); + } +} + +// used named arguments to avoid arguments array +parser.yy.Node = function Node (type, a,b,c,d,e,f,g,h) { + var buildName = type[0].toLowerCase()+type.slice(1); + if (this.builder && this.builder[buildName]) { + return this.builder[buildName](a,b,c,d,e,f,g,h); + } else if (builder[buildName]) { + return builder[buildName](a,b,c,d,e,f,g,h); + } else { + throw 'no such node type: '+type; + } +}; + +parser.yy.locComb = function (start, end) { + start.last_line = end.last_line; + start.last_column = end.last_column; + return start; +}; + +parser.yy.loc = function (loc) { + if (this.noloc) return null; + if ("length" in loc) loc = this.locComb(loc[0],loc[1]); + + return { source: this.source + , start: { line: this.startLine+loc.first_line - 1 + , column: loc.first_column } + , end: { line: this.startLine+loc.last_line - 1 + , column: loc.last_column } + }; +}; + +// Handle parse errors and recover from ASI +parser.yy.parseError = function (err, hash) { + // don't print error for missing semicolon + if (!(hash.expected.indexOf("';'") >= 0 && (hash.token === 'CLOSEBRACE' || parser.yy.lineBreak || parser.yy.lastLineBreak || hash.token === 1))) { + throw new Error(err); + } +}; + +// used to check if last match was a line break (for ; insertion) +var realLex = parser.lexer.lex; +parser.lexer.lex = function () { + parser.yy.lastLineBreak = parser.yy.lineBreak; + parser.yy.lineBreak = false; + return realLex.call(parser.lexer); +}; + +parser.yy.escapeString = function (s) { + return s.replace(/\\\n/,'').replace(/\\([^xubfnvrt0\\])/g, '$1'); +}; + +var oldParse = parser.parse; +parser.parse = function (source) { + parser.yy.lineBreak = false; + parser.yy.inRegex = false; + parser.yy.ASI = false; + return oldParse.call(this,source); +}; + +exports.Reflect = { + parse: function (src, options) { + return new JSParser(options).parse(src); + } +}; + +exports.parse = exports.Reflect.parse; diff --git a/package.json b/package.json new file mode 100644 index 0000000..76e6c0e --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "reflect", + "version": "0.0.1", + "author": "Zach Carter", + "email": "zach@carter.name", + "keywords": [ + "parser", + "reflect", + "javascript" + ], + "githubName": "reflect.js", + "type": "zip", + "location": "http://github.com/zaach/reflect.js/zipball/master", + "main": "dist/reflect", + "engines": { + "node": ">=0.1.90" + }, + "directories": { + "lib": "lib", + "bin": "./bin" + }, + "modules": { + "reflect.js": "dist/reflect.js", + "parser.js": "dist/parser.js", + "nodes.js": "dist/nodes.js" + }, + "files": [ + "" + ] +} diff --git a/reflectjs.png b/reflectjs.png new file mode 100644 index 0000000..a5fa739 Binary files /dev/null and b/reflectjs.png differ diff --git a/test/match.js b/test/match.js new file mode 100644 index 0000000..f9373a2 --- /dev/null +++ b/test/match.js @@ -0,0 +1,183 @@ +/* -*- 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/ + */ + + +// The Worker constructor can take a relative URL, but different test runners +// run in different enough environments that it doesn't all just automatically +// work. For the shell, we use just a filename; for the browser, see browser.js. +var workerDir = ''; + +// explicitly turn on js185 +// XXX: The browser currently only supports up to version 1.8 +if (typeof version != 'undefined') +{ + version(185); +} + +String.prototype.quote = function () { return JSON.stringify(this); }; + +// 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; + } else throw e; + } + }, + + assert: function(act, message) { + try { + return this.match(act); + } + catch (e) { + console.log(act); + console.log(this.template); + if (e instanceof MatchError) + throw new Error((message || "failed match") + ": " + e.message); + else throw e; + } + }, + + toString: function() { return "[object Pattern]"; } + }; + + Pattern.ANY = new Pattern; + Pattern.ANY.template = Pattern.ANY; + + var quote = JSON.stringify; + + 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 === "function" || 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 }; + +})(); + +exports.Match = Match; diff --git a/test/reflect-parse.js b/test/reflect-parse.js new file mode 100644 index 0000000..00c7c05 --- /dev/null +++ b/test/reflect-parse.js @@ -0,0 +1,1643 @@ +/* -*- 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 Reflect = require('../dist/reflect').Reflect; +var Match = require('./match').Match; + +var Pattern = Match.Pattern; +var MatchError = Match.MatchError; + +var _ = Pattern.ANY; + + +function program (elts) { + return Pattern({ + "type": "Program", + "body": elts + }); +} +function exprStmt (expr) { + return Pattern({ + "type": "ExpressionStatement", + "expression": expr + }); +} +function throwStmt (expr) { + return Pattern({ + "type": "ThrowStatement", + "argument": expr + }); +} +function returnStmt (expr) { + return Pattern({ + "type": "ReturnStatement", + "argument": expr + }); +} +function yieldExpr (expr) { + return Pattern({ + "type": "YieldExpression", + "argument": expr + }); +} +function lit (val) { + return Pattern({ + "type": "Literal", + "value": val + }); +} +var thisExpr = Pattern({ + "type": "ThisExpression" + }); +function funDecl (id, params, body) { + return Pattern({ + "type": "FunctionDeclaration", + "id": id, + "params": params, + "body": body, + "generator": false + }); +} +function genFunDecl (id, params, body) { + return Pattern({ + "type": "FunctionDeclaration", + "id": id, + "params": params, + "body": body, + "generator": true + }); +} +function varDecl (decls) { + return Pattern({ + "type": "VariableDeclaration", + "declarations": decls, + "kind": "var" + }); +} +function letDecl (decls) { + return Pattern({ + "type": "VariableDeclaration", + "declarations": decls, + "kind": "let" + }); +} +function constDecl (decls) { + return Pattern({ + "type": "VariableDeclaration", + "declarations": decls, + "kind": "const" + }); +} +function ident (name) { + return Pattern({ + "type": "Identifier", + "name": name + }); +} +function dotExpr (obj, id) { + return Pattern({ + "type": "MemberExpression", + "computed": false, + "object": obj, + "property": id + }); +} +function memExpr (obj, id) { + return Pattern({ + "type": "MemberExpression", + "computed": true, + "object": obj, + "property": id + }); +} +function forStmt (init, test, update, body) { + return Pattern({ + "type": "ForStatement", + "init": init, + "test": test, + "update": update, + "body": body + }); +} +function forInStmt (lhs, rhs, body) { + return Pattern({ + "type": "ForInStatement", + "left": lhs, + "right": rhs, + "body": body, + "each": false + }); +} +function forEachInStmt (lhs, rhs, body) { + return Pattern({ + "type": "ForInStatement", + "left": lhs, + "right": rhs, + "body": body, + "each": true + }); +} +function breakStmt (lab) { + return Pattern({ + "type": "BreakStatement", + "label": lab + }); +} +function continueStmt (lab) { + return Pattern({ + "type": "ContinueStatement", + "label": lab + }); +} +function blockStmt (body) { + return Pattern({ + "type": "BlockStatement", + "body": body + }); +} +var emptyStmt = Pattern({ + "type": "EmptyStatement" + }); +function ifStmt (test, cons, alt) { + return Pattern({ + "type": "IfStatement", + "test": test, + "alternate": alt, + "consequent": cons + }); +} +function labStmt (lab, stmt) { + return Pattern({ + "type": "LabeledStatement", + "label": lab, + "body": stmt + }); +} +function withStmt (obj, stmt) { + return Pattern({ + "type": "WithStatement", + "object": obj, + "body": stmt + }); +} +function whileStmt (test, stmt) { + return Pattern({ + "type": "WhileStatement", + "test": test, + "body": stmt + }); +} +function doStmt (stmt, test) { + return Pattern({ + "type": "DoWhileStatement", + "test": test, + "body": stmt + }); +} +function switchStmt (disc, cases) { + return Pattern({ + "type": "SwitchStatement", + "discriminant": disc, + "cases": cases + }); +} +function caseClause (test, stmts) { + return Pattern({ + "type": "SwitchCase", + "test": test, + "consequent": stmts + }); +} +function defaultClause (stmts) { + return Pattern({ + "type": "SwitchCase", + "test": null, + "consequent": stmts + }); +} +function catchClause (id, guard, body) { + return Pattern({ + "type": "CatchClause", + "param": id, + "guard": guard, + "body": body + }); +} +function tryStmt (body, catches, fin) { + return Pattern({ + "type": "TryStatement", + "block": body, + "handler": catches, + "finalizer": fin + }); +} +function letStmt (head, body) { + return Pattern({ + "type": "LetStatement", + "head": head, + "body": body + }); +} +function funExpr (id, args, body, gen) { + return Pattern({ + "type": "FunctionExpression", + "id": id, + "params": args, + "body": body, + "generator": false + }); +} +function genFunExpr (id, args, body) { + return Pattern({ + "type": "FunctionExpression", + "id": id, + "params": args, + "body": body, + "generator": true + }); +} +function unExpr (op, arg) { + return Pattern({ + "type": "UnaryExpression", + "operator": op, + "argument": arg + }); +} +function binExpr (op, left, right) { + return Pattern({ + "type": "BinaryExpression", + "operator": op, + "left": left, + "right": right + }); +} +function aExpr (op, left, right) { + return Pattern({ + "type": "AssignmentExpression", + "operator": op, + "left": left, + "right": right + }); +} +function updExpr (op, arg, prefix) { + return Pattern({ + "type": "UpdateExpression", + "operator": op, + "argument": arg, + "prefix": prefix + }); +} +function logExpr (op, left, right) { + return Pattern({ + "type": "LogicalExpression", + "operator": op, + "left": left, + "right": right + }); +} +function condExpr (test, cons, alt) { + return Pattern({ + "type": "ConditionalExpression", + "test": test, + "consequent": cons, + "alternate": alt + }); +} +function seqExpr (exprs) { + return Pattern({ + "type": "SequenceExpression", + "expressions": exprs + }); +} +function newExpr (callee, args) { + return Pattern({ + "type": "NewExpression", + "callee": callee, + "arguments": args + }); +} +function callExpr (callee, args) { + return Pattern({ + "type": "CallExpression", + "callee": callee, + "arguments": args + }); +} +function arrExpr (elts) { + return Pattern({ + "type": "ArrayExpression", + "elements": elts + }); +} +function objExpr (elts) { + return Pattern({ + "type": "ObjectExpression", + "properties": elts + }); +} +function compExpr (body, blocks, filter) { + return Pattern({ + "type": "ComprehensionExpression", + "body": body, + "blocks": blocks, + "filter": filter + }); +} +function genExpr (body, blocks, filter) { + return Pattern({ + "type": "GeneratorExpression", + "body": body, + "blocks": blocks, + "filter": filter + }); +} +function graphExpr (idx, body) { + return Pattern({ + "type": "GraphExpression", + "index": idx, + "expression": body + }); +} +function letExpr (head, body) { + return Pattern({ + "type": "LetExpression", + "head": head, + "body": body + }); +} +function idxExpr (idx) { + return Pattern({ + "type": "GraphIndexExpression", + "index": idx + }); +} +function compBlock (left, right) { + return Pattern({ + "type": "ComprehensionBlock", + "left": left, + "right": right, + "each": false + }); +} +function compEachBlock (left, right) { + return Pattern({ + "type": "ComprehensionBlock", + "left": left, + "right": right, + "each": true + }); +} +function arrPatt (elts) { + return Pattern({ + "type": "ArrayPattern", + "elements": elts + }); +} +function objPatt (elts) { + return Pattern({ + "type": "ObjectPattern", + "properties": elts + }); +} +function localSrc (src) { + return "(function(){ " + src + " })"; +} +function localPatt (patt) { + return program([exprStmt(funExpr(null, [], blockStmt([patt])))]); +} +function blockSrc (src) { + return "(function(){ { " + src + " } })"; +} +function blockPatt (patt) { + return program([exprStmt(funExpr(null, [], blockStmt([blockStmt([patt])])))]); +} +var xmlAnyName = Pattern({ + "type": "XMLAnyName" + }); +function xmlQualId (left, right, computed) { + return Pattern({ + "type": "XMLQualifiedIdentifier", + "left": left, + "right": right, + "computed": computed + }); +} +function xmlFuncQualId (right, computed) { + return Pattern({ + "type": "XMLFunctionQualifiedIdentifier", + "right": right, + "computed": computed + }); +} +function xmlAttrSel (id, computed) { + return Pattern({ + "type": "XMLAttributeSelector", + "attribute": id, + "computed": !!computed + }); +} +function xmlFilter (left, right) { + return Pattern({ + "type": "XMLFilterExpression", + "left": left, + "right": right + }); +} +function xmlPointTag (contents) { + return Pattern({ + "type": "XMLPointTag", + "contents": contents + }); +} +function xmlStartTag (contents) { + return Pattern({ + "type": "XMLStartTag", + "contents": contents + }); +} +function xmlEndTag (contents) { + return Pattern({ + "type": "XMLEndTag", + "contents": contents + }); +} +function xmlEscape (expr) { + return Pattern({ + "type": "XMLEscape", + "expression": expr + }); +} +function xmlElt (contents) { + return Pattern({ + "type": "XMLElement", + "contents": contents + }); +} +function xmlAttr (value) { + return Pattern({ + "type": "XMLAttribute", + "value": value + }); +} +function xmlText (text) { + return Pattern({ + "type": "XMLText", + "text": text + }); +} +function xmlPI (target, contents) { + return Pattern({ + "type": "XMLProcessingInstruction", + "target": target, + "contents": contents + }); +} +function xmlDefNS (ns) { + return Pattern({ + "type": "XMLDefaultDeclaration", + "namespace": ns + }); +} +function xmlName (name) { + return Pattern({ + "type": "XMLName", + "contents": name + }); +} +function xmlComment (contents) { + return Pattern({ + "type": "XMLComment", + "contents": contents + }); +} +function xmlCdata (cdata) { + return Pattern({ + "type": "XMLCdata", + "contents": cdata + }); +} + +function assertBlockStmt(src, patt) { + blockPatt(patt).assert(Reflect.parse(blockSrc(src))); +} + +function assertBlockExpr(src, patt) { + assertBlockStmt(src, exprStmt(patt)); +} + +function assertBlockDecl(src, patt, builder) { + blockPatt(patt).assert(Reflect.parse(blockSrc(src), {builder: builder})); +} + +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, builder) { + program([patt]).assert(Reflect.parse(src, {builder: builder})); +} + +function assertGlobalExpr(src, patt, builder) { + program([exprStmt(patt)]).assert(Reflect.parse(src, {builder: builder})); + //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); +} + +function assertError(src, errorType) { + try { + Reflect.parse(src); + } catch (expected) { + if (expected instanceof errorType) { + return; + } + } + throw new Error("expected " + errorType.name + " for " + uneval(src)); +} + + +// general tests + +// NB: These are useful but for now jit-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))]))); + + +// Bug 591437: rebound args have their defs turned into uses +assertDecl("function f(a) { function a() { } }", + funDecl(ident("f"), [ident("a")], blockStmt([funDecl(ident("a"), [], blockStmt([]))]))); +assertDecl("function f(a,b,c) { function b() { } }", + funDecl(ident("f"), [ident("a"),ident("b"),ident("c")], blockStmt([funDecl(ident("b"), [], blockStmt([]))]))); +//assertDecl("function f(a,[x,y]) { function a() { } }", + //funDecl(ident("f"), + //[ident("a"), arrPatt([ident("x"), ident("y")])], + //blockStmt([funDecl(ident("a"), [], blockStmt([]))]))); + +// Bug 591450: this test currently crashes because of a bug in jsparse +// assertDecl("function f(a,[x,y],b,[w,z],c) { function b() { } }", +// funDecl(ident("f"), +// [ident("a"), arrPatt([ident("x"), ident("y")]), ident("b"), arrPatt([ident("w"), ident("z")]), ident("c")], +// blockStmt([funDecl(ident("b"), [], blockStmt([]))]))); + + +// 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("+", binExpr("+", binExpr("+", ident("w"), ident("x")), ident("y")), ident("z"))); +assertExpr("(x - y)", binExpr("-", ident("x"), ident("y"))); +assertExpr("(w - x - y - z)", binExpr("-", binExpr("-", binExpr("-", ident("w"), ident("x")), 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 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("||", logExpr("||", logExpr("||", ident("w"), ident("x")), 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),,lit(2),lit(3)])); +assertExpr("[1,,,2,3]", arrExpr([lit(1),,,lit(2),lit(3)])); +assertExpr("[1,,,2,,3]", arrExpr([lit(1),,,lit(2),,lit(3)])); +assertExpr("[1,,,2,,,3]", arrExpr([lit(1),,,lit(2),,,lit(3)])); +assertExpr("[,1,2,3]", arrExpr([,lit(1),lit(2),lit(3)])); +assertExpr("[,,1,2,3]", arrExpr([,,lit(1),lit(2),lit(3)])); +assertExpr("[,,,1,2,3]", arrExpr([,,,lit(1),lit(2),lit(3)])); +assertExpr("[,,,1,2,3,]", arrExpr([,,,lit(1),lit(2),lit(3)])); +assertExpr("[,,,1,2,3,,]", arrExpr([,,,lit(1),lit(2),lit(3),])); +assertExpr("[,,,1,2,3,,,]", arrExpr([,,,lit(1),lit(2),lit(3),,])); +assertExpr("[,,,,,]", arrExpr([,,,,])); +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([]))); + +// redeclarations (TOK_NAME nodes with lexdef) + +assertStmt("function f() { function g() { } function g() { } }", + funDecl(ident("f"), [], blockStmt([funDecl(ident("g"), [], blockStmt([])), + funDecl(ident("g"), [], blockStmt([]))]))); + +assertStmt("function f() { function g() { } function g() { return 42 } }", + funDecl(ident("f"), [], blockStmt([funDecl(ident("g"), [], blockStmt([])), + funDecl(ident("g"), [], blockStmt([returnStmt(lit(42))]))]))); + +assertStmt("function f() { var x = 42; var x = 43; }", + funDecl(ident("f"), [], blockStmt([varDecl([{ id: ident("x"), init: lit(42) }]), + varDecl([{ id: ident("x"), init: lit(43) }])]))); + + +//assertDecl("var {x:y} = foo;", varDecl([{ id: objPatt([{ key: ident("x"), value: ident("y") }]), + //init: ident("foo") }])); + +// 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" } ])); + +/* +// 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)); +assertError("for (const x in foo);", SyntaxError); +assertError("for (const {a:x,b:y,c:z} in foo);", SyntaxError); +assertError("for (const [x,y,z] in foo);", SyntaxError); +assertError("for each (const x in foo);", SyntaxError); +assertError("for each (const {a:x,b:y,c:z} in foo);", SyntaxError); +assertError("for each (const [x,y,z] in foo);", SyntaxError); + +// destructuring in for-in and for-each-in loop heads with initializers + +assertStmt("for (var {a:x,b:y,c:z} = 22 in foo);", forInStmt(varDecl([{ id: axbycz, init: lit(22) }]), ident("foo"), emptyStmt)); +assertStmt("for (var [x,y,z] = 22 in foo);", forInStmt(varDecl([{ id: xyz, init: lit(22) }]), ident("foo"), emptyStmt)); +assertStmt("for each (var {a:x,b:y,c:z} = 22 in foo);", forEachInStmt(varDecl([{ id: axbycz, init: lit(22) }]), ident("foo"), emptyStmt)); +assertStmt("for each (var [x,y,z] = 22 in foo);", forEachInStmt(varDecl([{ id: xyz, init: lit(22) }]), ident("foo"), emptyStmt)); +assertError("for (x = 22 in foo);", SyntaxError); +assertError("for ({a:x,b:y,c:z} = 22 in foo);", SyntaxError); +assertError("for ([x,y,z] = 22 in foo);", SyntaxError); +assertError("for (const x = 22 in foo);", SyntaxError); +assertError("for (const {a:x,b:y,c:z} = 22 in foo);", SyntaxError); +assertError("for (const [x,y,z] = 22 in foo);", SyntaxError); +assertError("for each (const x = 22 in foo);", SyntaxError); +assertError("for each (const {a:x,b:y,c:z} = 22 in foo);", SyntaxError); +assertError("for each (const [x,y,z] = 22 in foo);", SyntaxError); + +// 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)))]))); + + +// 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) }]))); + +// let expressions + +assertExpr("(let (x=1) x)", letExpr([{ id: ident("x"), init: lit(1) }], ident("x"))); +assertExpr("(let (x=1,y=2) y)", letExpr([{ id: ident("x"), init: lit(1) }, + { id: ident("y"), init: lit(2) }], + ident("y"))); +assertExpr("(let (x=1,y=2,z=3) z)", letExpr([{ id: ident("x"), init: lit(1) }, + { id: ident("y"), init: lit(2) }, + { id: ident("z"), init: lit(3) }], + ident("z"))); +assertExpr("(let (x) x)", letExpr([{ id: ident("x"), init: null }], ident("x"))); +assertExpr("(let (x,y) y)", letExpr([{ id: ident("x"), init: null }, + { id: ident("y"), init: null }], + ident("y"))); +assertExpr("(let (x,y,z) z)", letExpr([{ id: ident("x"), init: null }, + { id: ident("y"), init: null }, + { id: ident("z"), init: null }], + ident("z"))); +assertExpr("(let (x = 1, y = x) y)", letExpr([{ id: ident("x"), init: lit(1) }, + { id: ident("y"), init: ident("x") }], + ident("y"))); +assertError("(let (x = 1, x = 2) x)", TypeError); + +// let statements + +assertStmt("let (x=1) { }", letStmt([{ id: ident("x"), init: lit(1) }], blockStmt([]))); +assertStmt("let (x=1,y=2) { }", letStmt([{ id: ident("x"), init: lit(1) }, + { id: ident("y"), init: lit(2) }], + blockStmt([]))); +assertStmt("let (x=1,y=2,z=3) { }", letStmt([{ id: ident("x"), init: lit(1) }, + { id: ident("y"), init: lit(2) }, + { id: ident("z"), init: lit(3) }], + blockStmt([]))); +assertStmt("let (x) { }", letStmt([{ id: ident("x"), init: null }], blockStmt([]))); +assertStmt("let (x,y) { }", letStmt([{ id: ident("x"), init: null }, + { id: ident("y"), init: null }], + blockStmt([]))); +assertStmt("let (x,y,z) { }", letStmt([{ id: ident("x"), init: null }, + { id: ident("y"), init: null }, + { id: ident("z"), init: null }], + blockStmt([]))); +assertStmt("let (x = 1, y = x) { }", letStmt([{ id: ident("x"), init: lit(1) }, + { id: ident("y"), init: ident("x") }], + blockStmt([]))); +assertError("let (x = 1, x = 2) { }", TypeError); + + +// E4X + +assertExpr("x..tagName", binExpr("..", ident("x"), lit("tagName"))); +assertExpr("x.*", dotExpr(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("x.y::z", dotExpr(ident("x"), xmlQualId(ident("y"), ident("z"), false))); +assertExpr("x[y::z]", memExpr(ident("x"), xmlQualId(ident("y"), ident("z"), false))); +assertExpr("x[y::[z]]", memExpr(ident("x"), xmlQualId(ident("y"), ident("z"), true))); +assertExpr("function::x", xmlFuncQualId(ident("x"), false)); +assertExpr("function::[foo]", xmlFuncQualId(ident("foo"), true)); +assertExpr("@foo", xmlAttrSel(ident("foo"), false)); +assertExpr("@[foo]", xmlAttrSel(ident("foo"), true)); +assertExpr("x.@foo", dotExpr(ident("x"), xmlAttrSel(ident("foo"), false))); +assertExpr("x.@[foo]", dotExpr(ident("x"), xmlAttrSel(ident("foo"), true))); +assertExpr("x[@foo]", memExpr(ident("x"), xmlAttrSel(ident("foo"), false))); +assertExpr("x[@[foo]]", memExpr(ident("x"), xmlAttrSel(ident("foo"), true))); +assertExpr("x.(p)", xmlFilter(ident("x"), ident("p"))); +assertExpr("<{foo}/>", xmlPointTag([xmlEscape(ident("foo"))])); +assertExpr("<{foo}>", xmlElt([xmlStartTag([xmlEscape(ident("foo"))]), + xmlEndTag([xmlEscape(ident("foo"))])])); +assertExpr("<{foo} {attr}='attr'/>", xmlPointTag([xmlEscape(ident("foo")), + xmlEscape(ident("attr")), + xmlAttr("attr")])); +assertExpr("<{foo}>text", xmlElt([xmlStartTag([xmlEscape(ident("foo"))]), + xmlText("text"), + xmlEndTag([xmlEscape(ident("foo"))])])); +assertExpr("", xmlPI("xml", "")); +assertExpr("", xmlPI("xml", "version='1.0'")); +assertDecl("default xml namespace = 'js';", xmlDefNS(lit("js"))); +assertDecl("default xml namespace = foo;", xmlDefNS(ident("foo"))); + +// The parser turns these into TOK_UNARY nodes with pn_op == JSOP_SETXMLNAME. + +assertExpr("x::y = foo", aExpr("=", xmlQualId(ident("x"), ident("y"), false), ident("foo"))); +assertExpr("function::x = foo", aExpr("=", xmlFuncQualId(ident("x"), false), ident("foo"))); +assertExpr("@x = foo", aExpr("=", xmlAttrSel(ident("x")), ident("foo"))); +assertExpr("x::* = foo", aExpr("=", xmlQualId(ident("x"), ident("*"), false), ident("foo"))); +assertExpr("*::* = foo", aExpr("=", xmlQualId(xmlAnyName, ident("*"), false), ident("foo"))); +assertExpr("x.* = foo", aExpr("=", dotExpr(ident("x"), xmlAnyName), ident("foo"))); +assertExpr("x[*] = foo", aExpr("=", memExpr(ident("x"), xmlAnyName), ident("foo"))); + +assertExpr("x::y += foo", aExpr("+=", xmlQualId(ident("x"), ident("y"), false), ident("foo"))); +assertExpr("function::x += foo", aExpr("+=", xmlFuncQualId(ident("x"), false), ident("foo"))); +assertExpr("@x += foo", aExpr("+=", xmlAttrSel(ident("x")), ident("foo"))); +assertExpr("x::* += foo", aExpr("+=", xmlQualId(ident("x"), ident("*"), false), ident("foo"))); +assertExpr("*::* += foo", aExpr("+=", xmlQualId(xmlAnyName, ident("*"), false), ident("foo"))); +assertExpr("x.* += foo", aExpr("+=", dotExpr(ident("x"), xmlAnyName), ident("foo"))); +assertExpr("x[*] += foo", aExpr("+=", memExpr(ident("x"), xmlAnyName), ident("foo"))); + +assertExpr("x::y++", updExpr("++", xmlQualId(ident("x"), ident("y"), false), false)); +assertExpr("function::x++", updExpr("++", xmlFuncQualId(ident("x"), false), false)); +assertExpr("@x++", updExpr("++", xmlAttrSel(ident("x")), false)); +assertExpr("x::*++", updExpr("++", xmlQualId(ident("x"), ident("*"), false), false)); +assertExpr("*::*++", updExpr("++", xmlQualId(xmlAnyName, ident("*"), false), false)); +assertExpr("x.*++", updExpr("++", dotExpr(ident("x"), xmlAnyName), false)); +assertExpr("x[*]++", updExpr("++", memExpr(ident("x"), xmlAnyName), false)); + +assertExpr("++x::y", updExpr("++", xmlQualId(ident("x"), ident("y"), false), true)); +assertExpr("++function::x", updExpr("++", xmlFuncQualId(ident("x"), false), true)); +assertExpr("++@x", updExpr("++", xmlAttrSel(ident("x")), true)); +assertExpr("++x::*", updExpr("++", xmlQualId(ident("x"), ident("*"), false), true)); +assertExpr("++*::*", updExpr("++", xmlQualId(xmlAnyName, ident("*"), false), true)); +assertExpr("++x.*", updExpr("++", dotExpr(ident("x"), xmlAnyName), true)); +assertExpr("++x[*]", updExpr("++", memExpr(ident("x"), xmlAnyName), true)); + + +// The parser turns these into TOK_UNARY nodes with pn_op == JSOP_BINDXMLNAME. + +function singletonObjPatt(name, val) objPatt([{ key: ident(name), value: val }]) + +assertExpr("({a:x::y}) = foo", aExpr("=", singletonObjPatt("a", xmlQualId(ident("x"), ident("y"), false)), ident("foo"))); +assertExpr("({a:function::x}) = foo", aExpr("=", singletonObjPatt("a", xmlFuncQualId(ident("x"), false)), ident("foo"))); +assertExpr("({a:@x}) = foo", aExpr("=", singletonObjPatt("a", xmlAttrSel(ident("x"))), ident("foo"))); +assertExpr("({a:x::*}) = foo", aExpr("=", singletonObjPatt("a", xmlQualId(ident("x"), ident("*"), false)), ident("foo"))); +assertExpr("({a:*::*}) = foo", aExpr("=", singletonObjPatt("a", xmlQualId(xmlAnyName, ident("*"), false)), ident("foo"))); +assertExpr("({a:x.*}) = foo", aExpr("=", singletonObjPatt("a", dotExpr(ident("x"), xmlAnyName)), ident("foo"))); +assertExpr("({a:x[*]}) = foo", aExpr("=", singletonObjPatt("a", memExpr(ident("x"), xmlAnyName)), ident("foo"))); + +function emptyForInPatt(val, rhs) forInStmt(val, rhs, emptyStmt) + +assertStmt("for (x::y in foo);", emptyForInPatt(xmlQualId(ident("x"), ident("y"), false), ident("foo"))); +assertStmt("for (function::x in foo);", emptyForInPatt(xmlFuncQualId(ident("x"), false), ident("foo"))); +assertStmt("for (@x in foo);", emptyForInPatt(xmlAttrSel(ident("x")), ident("foo"))); +assertStmt("for (x::* in foo);", emptyForInPatt(xmlQualId(ident("x"), ident("*"), false), ident("foo"))); +assertStmt("for (*::* in foo);", emptyForInPatt(xmlQualId(xmlAnyName, ident("*"), false), ident("foo"))); +assertStmt("for (x.* in foo);", emptyForInPatt(dotExpr(ident("x"), xmlAnyName), ident("foo"))); +assertStmt("for (x[*] in foo);", emptyForInPatt(memExpr(ident("x"), xmlAnyName), ident("foo"))); + + +// I'm not quite sure why, but putting XML in the callee of a call expression is +// the only way I've found to be able to preserve TOK_XMLNAME, TOK_XMLSPACE, +// TOK_XMLCDATA, and TOK_XMLCOMMENT parse nodes. + +assertExpr("( )()", callExpr(xmlElt([xmlStartTag([xmlName("x")]), + xmlText(" "), + xmlEndTag([xmlName("x")])]), + [])); +assertExpr("( )()", callExpr(xmlElt([xmlStartTag([xmlName("x")]), + xmlText(" "), + xmlEndTag([xmlName("x")])]), + [])); +assertExpr("()()", callExpr(xmlElt([xmlStartTag([xmlName("x")]), + xmlCdata("hello, world"), + xmlEndTag([xmlName("x")])]), + [])); +assertExpr("()()", callExpr(xmlElt([xmlStartTag([xmlName("x")]), + xmlComment(" hello, world "), + xmlEndTag([xmlName("x")])]), + [])); + +*/ + +// Source location information + +var withoutFileOrLine = Reflect.parse("42"); +var withFile = Reflect.parse("42", {source:"foo.js"}); +var withFileAndLine = Reflect.parse("42", {source:"foo.js", line: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", {source:"foo.js"}); +var withFileAndLine2 = Reflect.parse("foo +\nbar", {source:"foo.js", line: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)", {source:"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); + + +// No source location +if(Reflect.parse("42", {loc:false}).loc !== null) throw 'not null'; +program([exprStmt(lit(42))]).assert(Reflect.parse("42", {loc:false})); + + +// Builder tests +Pattern("program").match(Reflect.parse("42", {builder:{program:function(){return "program"}}})); + +assertGlobalStmt("throw 42", 1, { throwStatement: function(){ return 1 }}); +assertGlobalStmt("for (;;);", 2, { forStatement: function(){ return 2 }}); +assertGlobalStmt("for (x in y);", 3, { forInStatement: function(){ return 3 }}); +assertGlobalStmt("{ }", 4, { blockStatement: function(){ return 4 }}); +assertGlobalStmt("foo: { }", 5, { labeledStatement: function(){ return 5 }}); +assertGlobalStmt("with (o) { }", 6, { withStatement: function(){ return 6 }}); +assertGlobalStmt("while (x) { }", 7, { whileStatement: function(){ return 7 }}); +assertGlobalStmt("do { } while(false);", 8, { doWhileStatement: function(){ return 8 }}); +assertGlobalStmt("switch (x) { }", 9, { switchStatement: function(){ return 9 }}); +assertGlobalStmt("try { } catch(e) { }", 10, { tryStatement: function(){ return 10 }}); +assertGlobalStmt(";", 11, { emptyStatement: function(){ return 11 }}); +assertGlobalStmt("debugger;", 12, { debuggerStatement: function(){ return 12 }}); +assertGlobalStmt("42;", 13, { expressionStatement: function(){ return 13 }}); +assertGlobalStmt("for (;;) break", forStmt(null, null, null, 14), { breakStatement: function(){ return 14 }}); +assertGlobalStmt("for (;;) continue", forStmt(null, null, null, 15), { continueStatement: function(){ return 15 }}); + +assertBlockDecl("var x", "var", { variableDeclaration: function(kind){ return kind }}); +//assertBlockDecl("let x", "let", { variableDeclaration: function(kind){ return kind }}); +assertBlockDecl("const x", "const", { variableDeclaration: function(kind){ return kind }}); +assertBlockDecl("function f() { }", "function", { functionDeclaration: function(){ return "function" }}); + +assertGlobalExpr("(x,y,z)", 1, { sequenceExpression: function(){ return 1 }}); +assertGlobalExpr("(x ? y : z)", 2, { conditionalExpression: function(){ return 2 }}); +assertGlobalExpr("x + y", 3, { binaryExpression: function(){ return 3 }}); +assertGlobalExpr("delete x", 4, { unaryExpression: function(){ return 4 }}); +assertGlobalExpr("x = y", 5, { assignmentExpression: function(){ return 5 }}); +assertGlobalExpr("x || y", 6, { logicalExpression: function(){ return 6 }}); +assertGlobalExpr("x++", 7, { updateExpression: function(){ return 7 }}); +assertGlobalExpr("new x", 8, { newExpression: function(){ return 8 }}); +assertGlobalExpr("x()", 9, { callExpression: function(){ return 9 }}); +assertGlobalExpr("x.y", 10, { memberExpression: function(){ return 10 }}); +assertGlobalExpr("(function(){ return { }})", 11, { functionExpression: function(){return 11 }}); +assertGlobalExpr("[1,2,3]", 12, { arrayExpression: function(){ return 12 }}); +assertGlobalExpr("({ x: y })", 13, { objectExpression: function(){ return 13 }}); +assertGlobalExpr("this", 14, { thisExpression: function(){ return 14 }}); +//assertGlobalExpr("#1={ }", 15, { graphExpression: function(){ return 15 }}); +//assertGlobalExpr("#1={ self: #1# }", graphExpr(1, objExpr([{ key: ident("self"), value: 16 }])), { graphIndexExpression: function(){ return 16 }}); +//assertGlobalExpr("[x for (x in y)]", 17, { comprehensionExpression: function(){ return 17 }}); +//assertGlobalExpr("(x for (x in y))", 18, { generatorExpression: function(){ return 18 }}); +//assertGlobalExpr("(function(){ return { yield 42 })", genFunExpr(null, [], blockStmt([exprStmt(19)])), { yieldExpression: function(){ return 19 }}); +//assertGlobalExpr("(let (x) x)", 20, { letExpression: function(){ return 20 }}); + +assertGlobalStmt("switch (x) { case y: }", switchStmt(ident("x"), [1]), { switchCase: function(){ return 1 }}); +assertGlobalStmt("try { } catch (e) { }", tryStmt(blockStmt([]), 2, null), { catchClause: function(){ return 2 }}); +//assertGlobalStmt("try { } catch (e if e instanceof A) { } catch (e if e instanceof B) { }", + //tryStmt(blockStmt([]), [2, 2], null), + //{ catchClause: function(){ return 2 }}); +//assertGlobalExpr("[x for (y in z) for (x in y)]", compExpr(ident("x"), [3, 3], null), { comprehensionBlock: function(){ return 3 }}); + +//assertGlobalExpr("({ x: y } = z)", aExpr("=", 1, ident("z")), { objectPattern: function(){ return 1 }}); +//assertGlobalExpr("({ x: y } = z)", aExpr("=", objPatt([2]), ident("z")), { propertyPattern: function(){ return 2 }}); +//assertGlobalExpr("[ x ] = y", aExpr("=", 3, ident("y")), { arrayPattern: function(){ return 3 }}); + +//assertGlobalExpr("({a:x::y}) = foo", aExpr("=", singletonObjPatt("a", 1), ident("foo")), { xmlQualifiedIdentifier: function(){ return 1 }}); +//assertGlobalExpr("({a:function::x}) = foo", aExpr("=", singletonObjPatt("a", 2), ident("foo")), { xmlFunctionQualifiedIdentifier: function(){ return 2 }}); +//assertGlobalExpr("({a:@x}) = foo", aExpr("=", singletonObjPatt("a", 3), ident("foo")), { xmlAttributeSelector: function(){ return 3 }}); +//assertGlobalExpr("({a:x.*}) = foo", aExpr("=", singletonObjPatt("a", dotExpr(ident("x"), 4)), ident("foo")), { xmlAnyName: function(){ return 4 }}); + +//assertGlobalExpr("( )()", callExpr(xmlElt([5, xmlText(" "), xmlEndTag([xmlName("x")])]), []), { xmlStartTag: function(){ return 5 }}); +//assertGlobalExpr("( )()", callExpr(xmlElt([xmlStartTag([6]), xmlText(" "), xmlEndTag([6])]), []), { xmlName: function(){ return 6 }}); +//assertGlobalExpr("( )()", callExpr(xmlElt([xmlStartTag([xmlName("x")]), 7, xmlEndTag([xmlName("x")])]), []), { xmlText: function(){ return 7 }}); +//assertGlobalExpr("( )()", callExpr(xmlElt([xmlStartTag([xmlName("x")]), xmlText(" "), 8]), []), { xmlEndTag: function(){ return 8 }}); +//assertGlobalExpr("()()", callExpr(xmlElt([xmlStartTag([xmlName("x")]), 9, xmlEndTag([xmlName("x")])]), []), { xmlCdata: function(){ return 9 }}); +//assertGlobalExpr("()()", callExpr(xmlElt([xmlStartTag([xmlName("x")]), 10, xmlEndTag([xmlName("x")])]), []), { xmlComment: function(){ return 10 }}); + + +// Ensure that exceptions thrown by builder methods propagate. +var thrown = false; +try { + Reflect.parse("42", { builder: { program: function() { throw "expected" } } }); +} catch (e) { + if (e === "expected") + thrown = true; +} +if (!thrown) + throw new Error("builder exception not propagated"); + + +// A simple proof-of-concept that the builder API can be used to generate other +// formats, such as JsonMLAst: +// +// http://code.google.com/p/es-lab/wiki/JsonMLASTFormat +// +// It's incomplete (e.g., it doesn't convert source-location information and +// doesn't use all the direct-eval rules), but I think it proves the point. + +var JsonMLAst = (function() { +function reject() { + throw new SyntaxError("node type not supported"); +} + +function isDirectEval(expr) { + // an approximation to the actual rules. you get the idea + return (expr[0] === "IdExpr" && expr[1].name === "eval"); +} + +function functionNode(type) { + return function(id, args, body, isGenerator, isExpression) { + if (isExpression) + body = ["ReturnStmt", {}, body]; + + if (!id) + id = ["Empty"]; + + // Patch up the argument node types: s/IdExpr/IdPatt/g + for (var i = 0; i < args.length; i++) { + args[i][0] = "IdPatt"; + } + + args.unshift("ParamDecl", {}); + + return [type, {}, id, args, body]; + } +} + +return { + program: function(stmts) { + stmts.unshift("Program", {}); + return stmts; + }, + identifier: function(name) { + return ["IdExpr", { name: name }]; + }, + literal: function(val) { + return ["LiteralExpr", { value: val }]; + }, + expressionStatement: function(expr) {return expr}, + conditionalExpression: function(test, cons, alt) { + return ["ConditionalExpr", {}, test, cons, alt]; + }, + unaryExpression: function(op, expr) { + return ["UnaryExpr", {op: op}, expr]; + }, + binaryExpression: function(op, left, right) { + return ["BinaryExpr", {op: op}, left, right]; + }, + property: function(kind, key, val) { + return [kind === "init" + ? "DataProp" + : kind === "get" + ? "GetterProp" + : "SetterProp", + {name: key[1].name}, val]; + }, + functionDeclaration: functionNode("FunctionDecl"), + variableDeclaration: function(kind, elts) { + if (kind === "let" || kind === "const") + throw new SyntaxError("let and const not supported"); + elts.unshift("VarDecl", {}); + return elts; + }, + variableDeclarator: function(id, init) { + id[0] = "IdPatt"; + if (!init) + return id; + return ["InitPatt", {}, id, init]; + }, + sequenceExpression: function(exprs) { + var length = exprs.length; + var result = ["BinaryExpr", {op:","}, exprs[exprs.length - 2], exprs[exprs.length - 1]]; + for (var i = exprs.length - 3; i >= 0; i--) { + result = ["BinaryExpr", {op:","}, exprs[i], result]; + } + return result; + }, + assignmentExpression: function(op, lhs, expr) { + return ["AssignExpr", {op: op}, lhs, expr]; + }, + logicalExpression: function(op, left, right) { + return [op === "&&" ? "LogicalAndExpr" : "LogicalOrExpr", {}, left, right]; + }, + updateExpression: function(expr, op, isPrefix) { + return ["CountExpr", {isPrefix:isPrefix, op:op}, expr]; + }, + newExpression: function(callee, args) { + args.unshift("NewExpr", {}, callee); + return args; + }, + callExpression: function(callee, args) { + args.unshift(isDirectEval(callee) ? "EvalExpr" : "CallExpr", {}, callee); + return args; + }, + memberExpression: function(isComputed, expr, member) { + return ["MemberExpr", {}, expr, isComputed ? member : ["LiteralExpr", {type: "string", value: member[1].name}]]; + }, + functionExpression: functionNode("FunctionExpr"), + arrayExpression: function(elts) { + for (var i = 0; i < elts.length; i++) { + if (!elts[i]) + elts[i] = ["Empty"]; + } + elts.unshift("ArrayExpr", {}); + return elts; + }, + objectExpression: function(props) { + props.unshift("ObjectExpr", {}); + return props; + }, + thisExpression: function() { + return ["ThisExpr", {}]; + }, + + graphExpression: reject, + graphIndexExpression: reject, + comprehensionExpression: reject, + generatorExpression: reject, + yieldExpression: reject, + letExpression: reject, + + emptyStatement: function() {return ["EmptyStmt", {}] }, + blockStatement: function(stmts) { + stmts.unshift("BlockStmt", {}); + return stmts; + }, + labeledStatement: function(lab, stmt) { + return ["LabelledStmt", {label: lab}, stmt]; + }, + ifStatement: function(test, cons, alt) { + return ["IfStmt", {}, test, cons, alt || ["EmptyStmt", {}]]; + }, + switchStatement: function(test, clauses, isLexical) { + clauses.unshift("SwitchStmt", {}, test); + return clauses; + }, + whileStatement: function(expr, stmt) { + return ["WhileStmt", {}, expr, stmt]; + }, + doWhileStatement: function(stmt, expr) { + return ["DoWhileStmt", {}, stmt, expr]; + }, + forStatement: function(init, test, update, body) { + return ["ForStmt", {}, init || ["Empty"], test || ["Empty"], update || ["Empty"], body]; + }, + forInStatement: function(lhs, rhs, body) { + return ["ForInStmt", {}, lhs, rhs, body]; + }, + breakStatement: function(lab) { + return lab ? ["BreakStmt", {}, lab] : ["BreakStmt", {}]; + }, + continueStatement: function(lab) { + return lab ? ["ContinueStmt", {}, lab] : ["ContinueStmt", {}]; + }, + withStatement: function(expr, stmt) { + return ["WithStmt", {}, expr, stmt]; + }, + returnStatement: function(expr) { + return expr ? ["ReturnStmt", {}, expr] : ["ReturnStmt", {}]; + }, + tryStatement: function(body, catches, fin) { + if (catches.length > 1) + throw new SyntaxError("multiple catch clauses not supported"); + var node = ["TryStmt", body, catches[0] || ["Empty"]]; + if (fin) + node.push(fin); + return node; + }, + throwStatement: function(expr) { + return ["ThrowStmt", {}, expr]; + }, + debuggerStatement: function(){ return ["DebuggerStmt", {}] }, + letStatement: reject, + switchCase: function(expr, stmts) { + if (expr) + stmts.unshift("SwitchCase", {}, expr); + else + stmts.unshift("DefaultCase", {}); + return stmts; + }, + catchClause: function(param, guard, body) { + if (guard) + throw new SyntaxError("catch guards not supported"); + param[0] = "IdPatt"; + return ["CatchClause", {}, param, body]; + }, + comprehensionBlock: reject, + + arrayPattern: reject, + objectPattern: reject, + propertyPattern: reject, + + xmlAnyName: reject, + xmlAttributeSelector: reject, + xmlEscape: reject, + xmlFilterExpression: reject, + xmlDefaultDeclaration: reject, + xmlQualifiedIdentifier: reject, + xmlFunctionQualifiedIdentifier: reject, + xmlElement: reject, + xmlText: reject, + xmlList: reject, + xmlStartTag: reject, + xmlEndTag: reject, + xmlPointTag: reject, + xmlName: reject, + xmlAttribute: reject, + xmlCdata: reject, + xmlComment: reject, + xmlProcessingInstruction: reject +}; +})(); + +Pattern(["Program", {}, + ["BinaryExpr", {op: "+"}, + ["LiteralExpr", {value: 2}], + ["BinaryExpr", {op: "*"}, + ["UnaryExpr", {op: "-"}, ["IdExpr", {name: "x"}]], + ["IdExpr", {name: "y"}]]]]).match(Reflect.parse("2 + (-x * y)", {loc: false, builder: JsonMLAst})); + +