blob: 74f0e3e4eebcf78e8e0fba6ee554dc0f50aa25f5 [file] [log] [blame]
// |reftest| skip-if(!xulRuntime.shell)
// 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.
function test() {
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: expr => 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", {}];
},
templateLiteral: function(elts) {
for (var i = 0; i < elts.length; i++) {
if (!elts[i])
elts[i] = ["Empty"];
}
elts.unshift("TemplateLit", {});
return elts;
},
graphExpression: reject,
graphIndexExpression: reject,
comprehensionExpression: reject,
generatorExpression: reject,
yieldExpression: reject,
emptyStatement: () => ["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: () => ["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,
};
})();
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}));
}
runtest(test);