filbert
Version:
Python parser, Mozilla AST output
1,033 lines (902 loc) • 36.6 kB
JavaScript
// Filbert: Loose parser
//
// This module provides an alternative parser (`parse_dammit`) that
// exposes that same interface as `parse`, but will try to parse
// anything as Python, repairing syntax errors the best it can.
// There are circumstances in which it will raise an error and give
// up, but they are very rare. The resulting AST will be a mostly
// valid JavaScript AST (as per the [Mozilla parser API][api], except
// that:
//
// - Return outside functions is allowed
//
// - Bogus Identifier nodes with a name of `"✖"` are inserted whenever
// the parser got too confused to return anything meaningful.
//
// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
//
// The expected use for this is to *first* try `filbert.parse`, and only
// if that fails switch to `parse_dammit`. The loose parser might
// parse badly indented code incorrectly, so **don't** use it as
// your default parser.
//
// Quite a lot of filbert.js is duplicated here. The alternative was to
// add a *lot* of extra cruft to that file, making it less readable
// and slower. Copying and editing the code allowed invasive changes and
// simplifications without creating a complicated tangle.
(function(root, mod) {
if (typeof exports == "object" && typeof module == "object") return mod(exports, require("./filbert")); // CommonJS
if (typeof define == "function" && define.amd) return define(["exports", "./filbert_loose"], mod); // AMD
mod(root.filbert_loose || (root.filbert_loose = {}), root.filbert); // Plain browser env
})(this, function(exports, filbert) {
"use strict";
var tt = filbert.tokTypes;
var scope = filbert.scope;
var indentHist = filbert.indentHist;
var options, input, inputLen, fetchToken, nc;
exports.parse_dammit = function(inpt, opts) {
input = String(inpt); inputLen = input.length;
setOptions(opts);
if (!options.tabSize) options.tabSize = 4;
fetchToken = filbert.tokenize(inpt, options);
ahead.length = 0;
newAstIdCount = 0;
scope.init();
nc = filbert.getNodeCreator(startNode, startNodeFrom, finishNode, unpackTuple);
next();
return parseTopLevel();
};
function setOptions(opts) {
options = opts || {};
for (var opt in filbert.defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt))
options[opt] = filbert.defaultOptions[opt];
sourceFile = options.sourceFile || null;
}
var lastEnd, token = {start: 0, end: 0}, ahead = [];
var lastEndLoc, sourceFile;
var newAstIdCount = 0;
function next() {
lastEnd = token.end;
if (options.locations) lastEndLoc = token.endLoc;
if (ahead.length) token = ahead.shift();
else token = readToken();
}
function readToken() {
for (;;) {
try {
return fetchToken();
} catch(e) {
if (!(e instanceof SyntaxError)) throw e;
// Try to skip some text, based on the error message, and then continue
var msg = e.message, pos = e.raisedAt, replace = true;
if (/unterminated/i.test(msg)) {
pos = lineEnd(e.pos);
if (/string/.test(msg)) {
replace = {start: e.pos, end: pos, type: tt.string, value: input.slice(e.pos + 1, pos)};
} else if (/regular expr/i.test(msg)) {
var re = input.slice(e.pos, pos);
try { re = new RegExp(re); } catch(e) {}
replace = {start: e.pos, end: pos, type: tt.regexp, value: re};
} else {
replace = false;
}
} else if (/invalid (unicode|regexp|number)|expecting unicode|octal literal|is reserved|directly after number/i.test(msg)) {
while (pos < input.length && !isSpace(input.charCodeAt(pos)) && !isNewline(input.charCodeAt(pos))) ++pos;
} else if (/character escape|expected hexadecimal/i.test(msg)) {
while (pos < input.length) {
var ch = input.charCodeAt(pos++);
if (ch === 34 || ch === 39 || isNewline(ch)) break;
}
} else if (/unexpected character/i.test(msg)) {
pos++;
replace = false;
} else if (/regular expression/i.test(msg)) {
replace = true;
} else {
throw e;
}
resetTo(pos);
if (replace === true) replace = {start: pos, end: pos, type: tt.name, value: "✖"};
if (replace) {
if (options.locations) {
replace.startLoc = filbert.getLineInfo(input, replace.start);
replace.endLoc = filbert.getLineInfo(input, replace.end);
}
return replace;
}
}
}
}
function resetTo(pos) {
var ch = input.charAt(pos - 1);
var reAllowed = !ch || /[\[\{\(,;:?\/*=+\-~!|&%^<>]/.test(ch) ||
/[enwfd]/.test(ch) && /\b(keywords|case|else|return|throw|new|in|(instance|type)of|delete|void)$/.test(input.slice(pos - 10, pos));
fetchToken.jumpTo(pos, reAllowed);
}
function copyToken(token) {
var copy = {start: token.start, end: token.end, type: token.type, value: token.value};
if (options.locations) {
copy.startLoc = token.startLoc;
copy.endLoc = token.endLoc;
}
return copy;
}
function lookAhead(n) {
// Copy token objects, because fetchToken will overwrite the one
// it returns, and in this case we still need it
if (!ahead.length)
token = copyToken(token);
while (n > ahead.length)
ahead.push(copyToken(readToken()));
return ahead[n-1];
}
var newline = /[\n\r\u2028\u2029]/;
var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
function isNewline(ch) {
return ch === 10 || ch === 13 || ch === 8232 || ch === 8329;
}
function isSpace(ch) {
return ch === 9 || ch === 11 || ch === 12 ||
ch === 32 || // ' '
ch === 35 || // '#'
ch === 160 || // '\xa0'
ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch));
}
function lineEnd(pos) {
while (pos < input.length && !isNewline(input.charCodeAt(pos))) ++pos;
return pos;
}
function skipLine() {
fetchToken.jumpTo(lineEnd(token.start), false);
}
function Node(start) {
this.type = null;
}
Node.prototype = filbert.Node.prototype;
function SourceLocation(start) {
this.start = start || token.startLoc || {line: 1, column: 0};
this.end = null;
if (sourceFile !== null) this.source = sourceFile;
}
function startNode() {
var node = new Node(token.start);
if (options.locations)
node.loc = new SourceLocation();
if (options.directSourceFile)
node.sourceFile = options.directSourceFile;
if (options.ranges)
node.range = [token.start, 0];
return node;
}
function startNodeFrom(other) {
var node = new Node(other.start);
if (options.locations)
node.loc = new SourceLocation(other.loc.start);
if (options.ranges)
node.range = [other.range[0], 0];
return node;
}
function finishNode(node, type) {
node.type = type;
if (options.locations)
node.loc.end = lastEndLoc;
if (options.ranges)
node.range[1] = lastEnd;
return node;
}
function getDummyLoc() {
if (options.locations) {
var loc = new SourceLocation();
loc.end = loc.start;
return loc;
}
}
var dummyCount = 0
function dummyIdent() {
var dummy = new Node(token.start);
dummy.type = "Identifier";
dummy.end = token.start;
dummy.name = "dummy" + dummyCount++;
dummy.loc = getDummyLoc();
return dummy;
}
function isDummy(node) { return node.name && node.name.indexOf("dummy") === 0; }
function eat(type) {
if (token.type === type) {
next();
return true;
}
}
function expect(type) {
if (eat(type)) return true;
if (lookAhead(1).type == type) {
next(); next();
return true;
}
if (lookAhead(2).type == type) {
next(); next(); next();
return true;
}
}
function checkLVal(expr) {
if (expr.type === "Identifier" || expr.type === "MemberExpression") return expr;
return dummyIdent();
}
// Get args for a new tuple expression
function getTupleArgs(expr) {
if (expr.callee && expr.callee.object && expr.callee.object.object &&
expr.callee.object.object.name === options.runtimeParamName &&
expr.callee.property && expr.callee.property.name === "tuple")
return expr.arguments;
return null;
}
// Unpack an lvalue tuple into indivual variable assignments
// 'arg0, arg1 = right' becomes:
// var tmp = right
// arg0 = tmp[0]
// arg1 = tmp[1]
// ...
function unpackTuple(tupleArgs, right) {
var varStmts = [];
// var tmp = right
var tmpId = nc.createNodeSpan(right, right, "Identifier", { name: "__filbertTmp" + newAstIdCount++ });
var tmpDecl = nc.createVarDeclFromId(right, tmpId, right);
varStmts.push(tmpDecl);
// argN = tmp[N]
if (tupleArgs && tupleArgs.length > 0) {
for (var i = 0; i < tupleArgs.length; i++) {
var lval = tupleArgs[i];
var subTupleArgs = getTupleArgs(lval);
if (subTupleArgs) {
var subLit = nc.createNodeSpan(right, right, "Literal", { value: i });
var subRight = nc.createNodeSpan(right, right, "MemberExpression", { object: tmpId, property: subLit, computed: true });
var subStmts = unpackTuple(subTupleArgs, subRight);
for (var j = 0; j < subStmts.length; j++) varStmts.push(subStmts[j]);
} else {
checkLVal(lval);
var indexId = nc.createNodeSpan(right, right, "Literal", { value: i });
var init = nc.createNodeSpan(right, right, "MemberExpression", { object: tmpId, property: indexId, computed: true });
if (lval.type === "Identifier" && !scope.exists(lval.name)) {
scope.addVar(lval.name);
var varDecl = nc.createVarDeclFromId(lval, lval, init);
varStmts.push(varDecl);
}
else {
var node = startNodeFrom(lval);
node.left = lval;
node.operator = "=";
node.right = init;
finishNode(node, "AssignmentExpression");
varStmts.push(nc.createNodeFrom(node, "ExpressionStatement", { expression: node }));
}
}
}
}
return varStmts;
}
// ### Statement parsing
function parseTopLevel() {
var node = startNode();
node.body = [];
while (token.type !== tt.eof) {
var stmt = parseStatement();
if (stmt) node.body.push(stmt);
}
return finishNode(node, "Program");
}
function parseStatement() {
var starttype = token.type, node = startNode();
switch (starttype) {
case tt._break:
next();
return finishNode(node, "BreakStatement");
case tt._continue:
next();
return finishNode(node, "ContinueStatement");
case tt._class:
next();
return parseClass(node);
case tt._def:
next();
return parseFunction(node);
case tt._for:
next();
return parseFor(node);
case tt._from: // Skipping from and import statements for now
skipLine();
next();
return parseStatement();
case tt._if: case tt._elif:
next();
if (token.type === tt.parenL) node.test = parseParenExpression();
else node.test = parseExpression();
expect(tt.colon);
node.consequent = parseSuite();
if (token.type === tt._elif)
node.alternate = parseStatement();
else
node.alternate = eat(tt._else) && eat(tt.colon) ? parseSuite() : null;
return finishNode(node, "IfStatement");
case tt._import: // Skipping from and import statements for now
skipLine();
next();
return parseStatement();
case tt.newline:
// TODO: parseStatement() should probably eat it's own newline
next();
return null;
case tt._pass:
next();
return finishNode(node, "EmptyStatement");
case tt._return:
next();
if (token.type === tt.newline || token.type === tt.eof) node.argument = null;
else { node.argument = parseExpression(); }
return finishNode(node, "ReturnStatement");
case tt._while:
next();
if (token.type === tt.parenL) node.test = parseParenExpression();
else node.test = parseExpression();
expect(tt.colon);
node.body = parseSuite();
return finishNode(node, "WhileStatement");
case tt.semi:
next();
return finishNode(node, "EmptyStatement");
case tt.indent:
// Unexpected indent, let's ignore it
indentHist.undoIndent();
next();
return parseStatement();
default:
var expr = parseExpression();
if (isDummy(expr)) {
next();
if (token.type === tt.eof) return finishNode(node, "EmptyStatement");
return parseStatement();
} else if (expr.type === "VariableDeclaration" || expr.type === "BlockStatement") {
return expr;
} else {
node.expression = expr;
return finishNode(node, "ExpressionStatement");
}
}
}
function parseSuite() {
var node = startNode();
node.body = [];
if (eat(tt.newline)) {
eat(tt.indent);
while (!eat(tt.dedent) && token.type !== tt.eof) {
var stmt = parseStatement();
if (stmt) node.body.push(stmt);
}
} else {
node.body.push(parseStatement());
next();
}
return finishNode(node, "BlockStatement");
}
function parseFor(node) {
var init = parseExpression(false, true);
var tupleArgs = getTupleArgs(init);
if (!tupleArgs) checkLVal(init);
expect(tt._in);
var right = parseExpression();
expect(tt.colon);
var body = parseSuite();
finishNode(node, "BlockStatement");
return nc.createFor(node, init, tupleArgs, right, body);
}
// ### Expression parsing
function parseExpression(noComma, noIn) {
return parseMaybeAssign(noIn);
}
function parseParenExpression() {
expect(tt.parenL);
var val = parseExpression();
expect(tt.parenR);
return val;
}
function parseMaybeAssign(noIn) {
var left = parseMaybeTuple(noIn);
if (token.type.isAssign) {
var tupleArgs = getTupleArgs(left);
if (tupleArgs) {
next();
var right = parseMaybeTuple(noIn);
var blockNode = startNodeFrom(left);
blockNode.body = unpackTuple(tupleArgs, right);
return finishNode(blockNode, "BlockStatement");
}
if (scope.isClass()) {
var thisExpr = nc.createNodeFrom(left, "ThisExpression");
left = nc.createNodeFrom(left, "MemberExpression", { object: thisExpr, property: left });
}
var node = startNodeFrom(left);
node.operator = token.value;
node.left = checkLVal(left);
next();
node.right = parseMaybeTuple(noIn);
if (node.operator === '+=' || node.operator === '*=') {
var right = nc.createNodeSpan(node.right, node.right, "CallExpression");
right.callee = nc.createNodeOpsCallee(right, node.operator === '+=' ? "add" : "multiply");
right.arguments = [left, node.right];
node.right = right;
node.operator = '=';
}
if (left.type === "Identifier" && !scope.exists(left.name)) {
scope.addVar(left.name);
return nc.createVarDeclFromId(node.left, node.left, node.right);
}
return finishNode(node, "AssignmentExpression");
}
return left;
}
function parseMaybeTuple(noIn) {
var expr = parseExprOps(noIn);
if (token.type === tt.comma) {
return parseTuple(noIn, expr);
}
return expr;
}
function parseExprOps(noIn) {
return parseExprOp(parseMaybeUnary(noIn), -1, noIn);
}
function parseExprOp(left, minPrec, noIn) {
var node, exprNode, right, op = token.type, val = token.value;
var prec = op === tt._not ? tt._in.prec : op.prec;
if (op === tt.exponentiation && prec >= minPrec) {
node = startNodeFrom(left);
next();
right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
exprNode = nc.createNodeMemberCall(node, "Math", "pow", [left, right]);
return parseExprOp(exprNode, minPrec, noIn);
} else if (prec != null && (!noIn || op !== tt._in)) {
if (prec > minPrec) {
next();
node = startNodeFrom(left);
if (op === tt.floorDiv) {
right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
finishNode(node);
var binExpr = nc.createNodeSpan(node, node, "BinaryExpression", { left: left, operator: '/', right: right });
exprNode = nc.createNodeMemberCall(node, "Math", "floor", [binExpr]);
} else if (op === tt._in || op === tt._not) {
if (op === tt._in || eat(tt._in)) {
right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
finishNode(node);
var notLit = nc.createNodeSpan(node, node, "Literal", { value: op === tt._not });
exprNode = nc.createNodeRuntimeCall(node, 'ops', 'in', [left, right, notLit]);
} else exprNode = dummyIdent();
} else if (op === tt.plusMin && val === '+' || op === tt.multiplyModulo && val === '*') {
node.arguments = [left];
node.arguments.push(parseExprOp(parseMaybeUnary(noIn), prec, noIn));
finishNode(node, "CallExpression");
node.callee = nc.createNodeOpsCallee(node, op === tt.plusMin ? "add" : "multiply");
exprNode = node;
} else {
if (op === tt._is) {
if (eat(tt._not)) node.operator = "!==";
else node.operator = "===";
} else node.operator = op.rep != null ? op.rep : val;
// Accept '===' as '=='
if (input[token.start - 1] === '=' && input[token.start - 2] === '=') next();
node.left = left;
node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
exprNode = finishNode(node, (op === tt._or || op === tt._and) ? "LogicalExpression" : "BinaryExpression");
}
return parseExprOp(exprNode, minPrec, noIn);
}
}
return left;
}
function parseMaybeUnary(noIn) {
if (token.type.prefix || token.type === tt.plusMin) {
var prec = token.type === tt.plusMin ? tt.posNegNot.prec : token.type.prec;
var node = startNode();
node.operator = token.type.rep != null ? token.type.rep : token.value;
node.prefix = true;
next();
node.argument = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
return finishNode(node, "UnaryExpression");
}
return parseSubscripts(parseExprAtom(), false);
}
function parseSubscripts(base, noCalls) {
var node = startNodeFrom(base);
if (eat(tt.dot)) {
var id = parseIdent(true);
if (filbert.pythonRuntime.imports[base.name] && filbert.pythonRuntime.imports[base.name][id.name]) {
// Calling a Python import function
var runtimeId = nc.createNodeSpan(base, base, "Identifier", { name: options.runtimeParamName });
var importsId = nc.createNodeSpan(base, base, "Identifier", { name: "imports" });
var runtimeMember = nc.createNodeSpan(base, base, "MemberExpression", { object: runtimeId, property: importsId, computed: false });
node.object = nc.createNodeSpan(base, base, "MemberExpression", { object: runtimeMember, property: base, computed: false });
} else if (base.name && base.name === scope.getThisReplace()) {
node.object = nc.createNodeSpan(base, base, "ThisExpression");
} else node.object = base;
node.property = id;
node.computed = false;
return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
} else if (eat(tt.bracketL)) {
var expr, isSlice = false;
if (eat(tt.colon)) isSlice = true;
else expr = parseExpression();
if (!isSlice && eat(tt.colon)) isSlice = true;
if (isSlice) return parseSlice(node, base, expr, noCalls);
var subscriptCall = nc.createNodeSpan(expr, expr, "CallExpression");
subscriptCall.callee = nc.createNodeOpsCallee(expr, "subscriptIndex");
subscriptCall.arguments = [base, expr];
node.object = base;
node.property = subscriptCall;
node.computed = true;
expect(tt.bracketR);
return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
} else if (!noCalls && eat(tt.parenL)) {
if (scope.isUserFunction(base.name)) {
// Unpack parameters into JavaScript-friendly parameters, further processed at runtime
var createParamsCall = nc.createNodeRuntimeCall(node, 'utils', 'createParamsObj', parseParamsList());
node.arguments = [createParamsCall];
} else node.arguments = parseExprList(tt.parenR, false);
if (scope.isNewObj(base.name)) finishNode(node, "NewExpression");
else finishNode(node, "CallExpression");
if (filbert.pythonRuntime.functions[base.name]) {
// Calling a Python built-in function
var runtimeId = nc.createNodeSpan(base, base, "Identifier", { name: options.runtimeParamName });
var functionsId = nc.createNodeSpan(base, base, "Identifier", { name: "functions" });
var runtimeMember = nc.createNodeSpan(base, base, "MemberExpression", { object: runtimeId, property: functionsId, computed: false });
node.callee = nc.createNodeSpan(base, base, "MemberExpression", { object: runtimeMember, property: base, computed: false });
} else node.callee = base;
return parseSubscripts(node, noCalls);
}
return base;
}
function parseSlice(node, base, start, noCalls) {
var end, step;
if (!start) start = nc.createNodeFrom(node, "Literal", { value: null });
if (token.type === tt.bracketR || eat(tt.colon)) {
end = nc.createNodeFrom(node, "Literal", { value: null });
} else {
end = parseExpression();
if (token.type !== tt.bracketR) expect(tt.colon);
}
if (token.type === tt.bracketR) step = nc.createNodeFrom(node, "Literal", { value: null });
else step = parseExpression();
expect(tt.bracketR);
node.arguments = [start, end, step];
var sliceId = nc.createNodeFrom(base, "Identifier", { name: "_pySlice" });
var memberExpr = nc.createNodeSpan(base, base, "MemberExpression", { object: base, property: sliceId, computed: false });
node.callee = memberExpr;
return parseSubscripts(finishNode(node, "CallExpression"), noCalls);
}
function parseExprAtom() {
switch (token.type) {
case tt._dict:
next();
return parseDict(tt.parenR);
case tt.name:
return parseIdent();
case tt.num: case tt.string: case tt.regexp:
var node = startNode();
node.value = token.value;
node.raw = input.slice(token.start, token.end);
next();
return finishNode(node, "Literal");
case tt._None: case tt._True: case tt._False:
var node = startNode();
node.value = token.type.atomValue;
node.raw = token.type.keyword;
next();
return finishNode(node, "Literal");
case tt.parenL:
var tokStartLoc1 = token.startLoc, tokStart1 = token.start;
next();
if (token.type === tt.parenR) {
var node = parseTuple(true);
eat(tt.parenR);
return node;
}
var val = parseMaybeTuple(true);
if (options.locations) {
val.loc.start = tokStartLoc1;
val.loc.end = token.endLoc;
}
if (options.ranges)
val.range = [tokStart1, token.end];
expect(tt.parenR);
return val;
case tt.bracketL:
return parseList();
case tt.braceL:
return parseDict(tt.braceR);
default:
return dummyIdent();
}
}
// Parse list
// Custom list object is used to simulate native Python list
// E.g. Python '[]' becomes JavaScript 'new __pythonRuntime.objects.list();'
// If list comprehension, build something like this:
//(function() {
// var _list = [];
// ...
// _list.push(expr);
// return _list;
//}());
function parseList() {
var node = startNode();
node.arguments = [];
next();
if (!eat(tt.bracketR)) {
var expr = parseExprOps(false);
if (token.type === tt._for || token.type === tt._if) {
// List comprehension
var tmpVarSuffix = newAstIdCount++;
expr = nc.createListCompPush(expr, tmpVarSuffix);
var body = parseCompIter(expr, true);
finishNode(node);
return nc.createListCompIife(node, body, tmpVarSuffix);
} else if (eat(tt.comma)) {
node.arguments = [expr].concat(parseExprList(tt.bracketR, true, false));
}
else {
expect(tt.bracketR);
node.arguments = [expr];
}
}
finishNode(node, "NewExpression");
var runtimeId = nc.createNodeSpan(node, node, "Identifier", { name: options.runtimeParamName });
var objectsId = nc.createNodeSpan(node, node, "Identifier", { name: "objects" });
var runtimeMember = nc.createNodeSpan(node, node, "MemberExpression", { object: runtimeId, property: objectsId, computed: false });
var listId = nc.createNodeSpan(node, node, "Identifier", { name: "list" });
node.callee = nc.createNodeSpan(node, node, "MemberExpression", { object: runtimeMember, property: listId, computed: false });
return node;
}
// Parse a comp_iter from Python language grammar
// 'expr' is the body to be used after unrolling the ifs and fors
function parseCompIter(expr, first) {
if (first && token.type !== tt._for) return dummyIdent();
if (eat(tt.bracketR)) return expr;
var node = startNode();
if (eat(tt._for)) {
var init = parseExpression(false, true);
var tupleArgs = getTupleArgs(init);
if (!tupleArgs) checkLVal(init);
expect(tt._in);
var right = parseExpression();
var body = parseCompIter(expr, false);
var block = nc.createNodeSpan(body, body, "BlockStatement", { body: [body] });
finishNode(node, "BlockStatement");
return nc.createFor(node, init, tupleArgs, right, block);
} else if (eat(tt._if)) {
if (token.type === tt.parenL) node.test = parseParenExpression();
else node.test = parseExpression();
node.consequent = parseCompIter(expr, false);
return finishNode(node, "IfStatement");
} else return dummyIdent();
}
// Parse class
function parseClass(ctorNode) {
// Container for class constructor and prototype functions
var container = startNodeFrom(ctorNode);
container.body = [];
// Parse class signature
ctorNode.id = parseIdent();
ctorNode.params = [];
var classParams = [];
if (eat(tt.parenL)) {
var first = true;
while (!eat(tt.parenR) && token.type !== tt.eof) {
if (!first) expect(tt.comma); else first = false;
classParams.push(parseIdent());
}
}
expect(tt.colon);
// Start new namespace for class body
scope.startClass(ctorNode.id.name);
// Save a reference for source ranges
var classBodyRefNode = finishNode(startNode());
// Parse class body
var classBlock = parseSuite();
// Generate additional AST to implement class
var classStmt = nc.createClass(container, ctorNode, classParams, classBodyRefNode, classBlock);
scope.end();
return classStmt;
}
// Parse dictionary
// Custom dict object used to simulate native Python dict
// E.g. "{'k1':'v1', 'k2':'v2'}" becomes "new __pythonRuntime.objects.dict(['k1', 'v1'], ['k2', 'v2']);"
function parseDict(tokClose) {
var node = startNode(), first = true, key, value;
node.arguments = [];
next();
while (!eat(tokClose) && !eat(tt.newline) && token.type !== tt.eof) {
if (!first) {
expect(tt.comma);
} else first = false;
if (tokClose === tt.braceR) {
key = parsePropertyName();
expect(tt.colon);
value = parseExprOps(false);
} else if (tokClose === tt.parenR) {
var keyId = parseIdent(true);
key = startNodeFrom(keyId);
key.value = keyId.name;
finishNode(key, "Literal");
expect(tt.eq);
value = parseExprOps(false);
}
node.arguments.push(nc.createNodeSpan(key, value, "ArrayExpression", { elements: [key, value] }));
}
finishNode(node, "NewExpression");
var runtimeId = nc.createNodeSpan(node, node, "Identifier", { name: options.runtimeParamName });
var objectsId = nc.createNodeSpan(node, node, "Identifier", { name: "objects" });
var runtimeMember = nc.createNodeSpan(node, node, "MemberExpression", { object: runtimeId, property: objectsId, computed: false });
var listId = nc.createNodeSpan(node, node, "Identifier", { name: "dict" });
node.callee = nc.createNodeSpan(node, node, "MemberExpression", { object: runtimeMember, property: listId, computed: false });
return node;
}
function parsePropertyName() {
if (token.type === tt.num || token.type === tt.string) return parseExprAtom();
if (token.type === tt.name || token.type.keyword) return parseIdent();
}
function parseIdent() {
var node = startNode();
node.name = token.type === tt.name ? token.value : token.type.keyword;
if (!node.name) node = dummyIdent();
next();
return finishNode(node, "Identifier");
}
function parseFunction(node) {
var suffix = newAstIdCount++;
node.id = parseIdent();
node.params = [];
// Parse parameters
var formals = []; // In order, maybe with default value
var argsId = null; // *args
var kwargsId = null; // **kwargs
var first = true;
expect(tt.parenL);
while (!eat(tt.parenR) && token.type !== tt.eof) {
if (!first) expect(tt.comma); else first = false;
if (token.value === '*') {
next(); argsId = parseIdent();
} else if (token.value === '**') {
next(); kwargsId = parseIdent();
} else {
var paramId = parseIdent();
if (eat(tt.eq))
formals.push({ id: paramId, expr: parseExprOps(false) });
else
formals.push({ id: paramId, expr: null });
}
}
expect(tt.colon);
scope.startFn(node.id.name);
// If class method, remove class instance var from params and save for 'this' replacement
if (scope.isParentClass()) {
var selfId = formals.shift();
scope.setThisReplace(selfId.id.name);
}
var body = parseSuite();
node.body = nc.createNodeSpan(body, body, "BlockStatement", { body: [] });
// Add runtime parameter processing
if (formals.length > 0 || argsId || kwargsId) {
node.body.body.push(nc.createNodeParamsCheck(node.id, suffix));
node.body.body.push(nc.createVarDeclFromId(node.id,
nc.createNodeSpan(node.id, node.id, "Identifier", { name: '__formalsIndex' + suffix }),
nc.createNodeSpan(node.id, node.id, "Literal", { value: 0 })));
node.body.body.push(nc.createVarDeclFromId(node.id,
nc.createNodeSpan(node.id, node.id, "Identifier", { name: '__args' + suffix }),
nc.createNodeSpan(node.id, node.id, "Identifier", { name: 'arguments' })));
}
if (formals.length > 0) {
node.body.body.push(nc.createNodeGetParamFn(node.id, suffix));
for (var i = 0; i < formals.length; i++) {
var __getParamCall = nc.createNodeSpan(formals[i].id, formals[i].id, "CallExpression", {
callee: nc.createNodeSpan(formals[i].id, formals[i].id, "Identifier", { name: '__getParam' + suffix }),
arguments: [nc.createNodeSpan(formals[i].id, formals[i].id, "Literal", { value: formals[i].id.name })]
});
if (formals[i].expr) __getParamCall.arguments.push(formals[i].expr);
node.body.body.push(nc.createVarDeclFromId(formals[i].id, formals[i].id, __getParamCall));
}
}
var refNode = argsId || kwargsId;
if (refNode) {
if (argsId) {
var argsAssign = nc.createVarDeclFromId(argsId, argsId, nc.createNodeSpan(argsId, argsId, "ArrayExpression", { elements: [] }));
node.body.body.push(argsAssign);
}
if (kwargsId) {
var kwargsAssign = nc.createVarDeclFromId(kwargsId, kwargsId, nc.createNodeSpan(kwargsId, kwargsId, "ObjectExpression", { properties: [] }));
node.body.body.push(kwargsAssign);
}
var argsIf = nc.createNodeSpan(refNode, refNode, "IfStatement", {
test: nc.createNodeSpan(refNode, refNode, "Identifier", { name: '__params' + suffix }),
consequent: nc.createNodeSpan(refNode, refNode, "BlockStatement", { body: [] })
})
if (argsId) {
argsIf.consequent.body.push(nc.createNodeArgsWhileConsequent(argsId, suffix));
argsIf.alternate = nc.createNodeArgsAlternate(argsId);
}
if (kwargsId) {
argsIf.consequent.body.push(nc.createNodeSpan(kwargsId, kwargsId, "ExpressionStatement", {
expression: nc.createNodeSpan(kwargsId, kwargsId, "AssignmentExpression", {
operator: '=', left: kwargsId, right: nc.createNodeMembIds(kwargsId, '__params' + suffix, 'keywords'),
})
}));
}
node.body.body.push(argsIf);
}
node.body.body.push(nc.createNodeFnBodyIife(body));
// If class method, replace with prototype function literals
var retNode;
if (scope.isParentClass()) {
finishNode(node);
var classId = nc.createNodeSpan(node, node, "Identifier", { name: scope.getParentClassName() });
var prototypeId = nc.createNodeSpan(node, node, "Identifier", { name: "prototype" });
var functionId = node.id;
var prototypeMember = nc.createNodeSpan(node, node, "MemberExpression", { object: classId, property: prototypeId, computed: false });
var functionMember = nc.createNodeSpan(node, node, "MemberExpression", { object: prototypeMember, property: functionId, computed: false });
var functionExpr = nc.createNodeSpan(node, node, "FunctionExpression", { body: node.body, params: node.params });
var assignExpr = nc.createNodeSpan(node, node, "AssignmentExpression", { left: functionMember, operator: "=", right: functionExpr });
retNode = nc.createNodeSpan(node, node, "ExpressionStatement", { expression: assignExpr });
} else retNode = finishNode(node, "FunctionDeclaration");
scope.end();
return retNode;
}
function parseExprList(close) {
var elts = [];
while (!eat(close) && !eat(tt.newline) && token.type !== tt.eof) {
var elt = parseExprOps(false);
if (isDummy(elt)) {
next();
} else {
elts.push(elt);
}
while (eat(tt.comma)) {}
}
return elts;
}
function parseParamsList() {
var elts = [], first = true;
while (!eat(tt.parenR) && !eat(tt.newline) && token.type !== tt.eof) {
if (!first) expect(tt.comma);
else first = false;
var expr = parseExprOps(false);
if (eat(tt.eq)) {
var right = parseExprOps(false);
var kwId = nc.createNodeSpan(expr, right, "Identifier", { name: "__kwp" });
var kwLit = nc.createNodeSpan(expr, right, "Literal", { value: true });
var left = nc.createNodeSpan(expr, right, "ObjectExpression", { properties: [] });
left.properties.push({ type: "Property", key: expr, value: right, kind: "init" });
left.properties.push({ type: "Property", key: kwId, value: kwLit, kind: "init" });
expr = left;
}
elts.push(expr);
}
return elts;
}
function parseTuple(noIn, expr) {
var node = expr ? startNodeFrom(expr) : startNode();
node.arguments = expr ? [expr] : [];
// Tuple with single element has special trailing comma: t = 'hi',
// Look ahead and eat comma in this scenario
if (token.type === tt.comma) {
var pos = token.start + 1;
while (isSpace(input.charCodeAt(pos))) ++pos;
if (pos >= inputLen || input[pos] === ';' || input[pos] === ')' || isNewline(input.charCodeAt(pos)))
eat(tt.comma);
}
while (eat(tt.comma)) {
node.arguments.push(parseExprOps(noIn));
}
finishNode(node, "NewExpression");
var runtimeId = nc.createNodeSpan(node, node, "Identifier", { name: options.runtimeParamName });
var objectsId = nc.createNodeSpan(node, node, "Identifier", { name: "objects" });
var runtimeMember = nc.createNodeSpan(node, node, "MemberExpression", { object: runtimeId, property: objectsId, computed: false });
var listId = nc.createNodeSpan(node, node, "Identifier", { name: "tuple" });
node.callee = nc.createNodeSpan(node, node, "MemberExpression", { object: runtimeMember, property: listId, computed: false });
return node;
}
});