move
Version:
A programming language
396 lines (354 loc) • 12.6 kB
JavaScript
var parser = require("./parser");
var processor = require("./process");
// AST modifier which adds implicit returns to the last statement of a function
exports.add_implicit_returns = function add_implicit_returns(ast) {
var w = processor.ast_walker(),
MAP = processor.MAP,
wrapStatement;
wrapStatement = function wrapStatement(statement) {
if (!statement) return statement;
var type = statement[0], i;
if (type === 'stat') {
// a statement e.g. "5 * 7" or "prefix + name.toString()"
statement[0] = 'return';
} else if (type === 'defun') {
// an anonymous function definition
statement[0] = 'function';
return ['return', statement];
} else if (type === 'if') {
for (i=2; i<statement.length; ++i)
statement[i] = wrapStatement(statement[i]);
} else if (type === 'block') {
var parts = statement[1];
if (parts && parts.length !== 0)
parts[parts.length-1] = wrapStatement(parts[parts.length-1]);
} //else console.log('last stmt', statement);
return statement;
};
var _lambda = function _lambda(name, args, body, kwargs) {
if (body.length) {
// here, body[body.length-1] == ['if', [...]]
body[body.length-1] = wrapStatement(body[body.length-1]);
}
return [ this[0], name, args.slice(), MAP(body, w.walk), kwargs ];
};
var ret = w.with_walkers({
"defun": _lambda,
"function": _lambda
}, function(){
return w.walk(ast);
});
return ret;
}
// AST modifier which declares first-time assignments as vars
exports.first_time_var_delc = function first_time_var_delc(ast, globals) {
var w = processor.ast_walker(), MAP = processor.MAP, Scope = processor.Scope;
var current_scope = null, slice = parser.slice;
if (globals) {
current_scope = new Scope();
current_scope.names = parser.array_to_hash(globals);
}
function with_new_scope(cont) {
current_scope = new Scope(current_scope);
current_scope.vars = [];
var ret = current_scope.body = cont();
// declare variables at top of function
if (current_scope.vars.length) {
var subs = (ret[0] === 'toplevel') ? ret[1] : ret;
var vars = ['var', MAP(current_scope.vars, function (v) { return [v] })];
subs.splice(0, 0, vars);
}
//ret.scope = current_scope;
current_scope = current_scope.parent;
return ret;
}
function define(name) {
return current_scope.define(name);
}
function _lambda(name, args, body, kwargs) {
return [ this[0], define(name), args, with_new_scope(function(){
MAP(args, define);
return MAP(body, w.walk);
}), kwargs];
}
return with_new_scope(function(){
var ret = w.with_walkers({
"defun": _lambda,
"function": _lambda,
/*"var": function(defs) {
var subs = ['assign', true];
MAP(defs, function(d){
define(d[0]);
current_scope.vars.push(d[0]);
if (d.length > 1) {
subs.push(['name', d[0]]);
subs.push(d[1]);
}
})
return subs.length > 2 ? ['stat', subs] : null;
},*/
"var": function(defs) {
return [ this[0], MAP(defs, function(def){
define(def[0]);
//current_scope.vars.push(def[0]);
var a = [ def[0] ];
if (def.length > 1)
a[1] = w.walk(def[1]);
return a;
}) ];
},
"import": function(defs) {
MAP(defs, function(d){
define(d[0]);
current_scope.vars.push(d[0]);
});
},
"export": function(defs) {
MAP(defs, function(d){
define(d[0]);
current_scope.vars.push(d[0]);
});
},
"const": function(defs) {
MAP(defs, function(d){ define(d[0]) });
},
"assign": function(op, lvalue, rvalue) {
if (lvalue[0] === 'name') {
var name = lvalue[1];
var value = current_scope.has(name);
if (!value) {
current_scope.vars.push(name);
define(name);
}
}
return [ this[0], op, w.walk(lvalue), w.walk(rvalue) ];
},
"for-in": function(vvar, key, hash, block) {
if (vvar) {
return [ this[0], w.walk(vvar), w.walk(key), w.walk(hash),
w.walk(block) ];
} else {
if (!current_scope.has(key)) {
current_scope.vars.push(key);
define(key);
}
return [ this[0], vvar, key, w.walk(hash), w.walk(block) ];
}
},
}, function(){
return w.walk(ast);
});
return ret;
});
}
// Matches a dot-capable name (which does not need to use subscript notation)
var DOT_NAME_RE = /^[_\$a-z][_\$\w\d]*$/i;
var nameOfAssignmentValue = function nameOfAssignmentValue(value) {
var name;
if (value[0] === 'name') {
name = value[1];
} else if (value[0] === 'dot') {
name = value[value.length-1];
} else if (value[0] === 'sub') {
value = value[value.length-1];
if (value[0] === 'string') {
name = value[1];
if (parser.KEYWORDS[name] || parser.RESERVED_WORDS[name] || !DOT_NAME_RE.test(name)) {
name = undefined;
}
} else {
name = value; // a symbol -- up to the caller to decide to use or not
}
}
return name;
}
// AST modifier which give a anonymous function expression name from assignment
exports.named_lambda_assignments = function named_lambda_assignments(ast) {
var w = processor.ast_walker(), MAP = processor.MAP;
var ret = w.with_walkers({
"export": function (defs) {
return [ this[0], MAP(defs, function(def){
var a = [ def[0] ];
if (def.length > 1) {
// Assign name to anonymous, exported function
if (def[1] && def[1][0] === 'function' && !def[1][1])
def[1][1] = def[0];
a[1] = w.walk(def[1]);
}
return a;
}) ];
},
"assign": function(op, lvalue, rvalue) {
//if (rvalue[0] === 'function' && rvalue[1])
//console.log('rvalue', rvalue)
if (Array.isArray(rvalue) && rvalue[0] === 'function' && !rvalue[1]) {
var name = nameOfAssignmentValue(lvalue);
if (typeof name === 'string')
rvalue[1] = name;
}
return [ this[0], op, w.walk(lvalue), w.walk(rvalue) ];
}
}, function(){
return w.walk(ast);
});
return ret;
}
// AST modifier which converts type-coercing comparison operators to regular,
// strict operators (i.e. "==" -> "===" and "!=" -> "!==")
exports.enforce_strict_eq_ops = function enforce_strict_eq_ops(ast) {
var w = processor.ast_walker(), MAP = processor.MAP;
return w.with_walkers({
"binary": function(op, left, right) {
if (op === '==') op = '===';
else if (op === '!=') op = '!==';
return [ this[0], op, w.walk(left), w.walk(right) ];
}
}, function(){ return w.walk(ast); });
}
// AST modifier which mutates function declarations to accept keyword arguments
exports.enable_keyword_arguments =
function enable_keyword_arguments(ast, options) {
var w = processor.ast_walker(), MAP = processor.MAP;
if (typeof options !== 'object') options = {};
var kwTestRValue = options.raw ? 'true' : '_MoveKWArgsT';
return w.with_walkers({
"function": function (name, args, body, kwargs) {
// Add test and assigments for keyword arguments.
//
// Given the following Move code:
//
// x = ^(a=1, b=2, c=3) {
// // ...
// }
//
// Applying our transformations, the code becomes:
//
// x = ^(a, b, c) {
// typeof a === "object" && a.__kw === true && (c = a.c, b = a.b, a = a.a);
// if (c === undefined) c = 3;
// if (b === undefined) b = 2;
// if (a === undefined) a = 1;
// // ...
// }
//
if (args.length) {
var i, argName, defaultValue, kwAssign = ['seq'];
var firstArgName = args[0], statements = [null];
// typeof arg1 === "object" && arg1.__kw === true
var kwHeaderTestStmt =
['binary', '&&',
['binary', '!==',
['name', firstArgName],
['name', 'null']],
['binary', '&&',
['binary', '===',
['unary-prefix', 'typeof', ['name', firstArgName]],
['string', 'object']],
['binary', '===',
['dot', ['name', firstArgName], '__kw'],
['name', kwTestRValue]]
]
];
kwAssign.push(['assign', true,
['dot', ['name', 'arguments'], 'keywords'],
['name', firstArgName]]);
for (i=args.length; --i !== -1;) {
argName = args[i];
defaultValue = kwargs && kwargs[argName];
// (arg3 = arg1.arg3, ...
kwAssign.push(['assign', true,
['name', argName],
['dot', ['name', firstArgName], argName]]);
if (defaultValue) {
// if (arg3 === undefined) arg3 = default3, ...
statements.push(['if',
['binary','===', ['name', argName], ['name', 'undefined']],
['stat', ['assign', true, ['name', argName], defaultValue]]
]);
}
}
// <kwHeaderTestStmt> && (arg3 = arg1.arg3, arg2 = arg1.arg2, arg1 = arg1.arg1)
statements[0] = ['stat', ['binary', '&&', kwHeaderTestStmt, kwAssign]];
//console.log(require('util').inspect(statements, 0, 10));
body = statements.concat(body);
}
// Walk function body...
return [this[0], name, args, MAP(body, w.walk), kwargs];
},
"call": function (expr, args) {
if (args.length === 1 && args[0][0] === 'object') {
var kwarg, kwargs = args[0][1], kwargsIndexes = [];
for (var i=kwargs.length; --i !== -1;) {
kwarg = kwargs[i];
if (kwarg[0] === '__kw' && kwarg[1] &&
kwarg[1][1] === '__Move_KWArgs__') {
kwargsIndexes.push(i);
}
}
if (kwargsIndexes.length) {
// In order to get rid of duplicates
while (kwargsIndexes.length > 1)
delete kwargs[kwargsIndexes.pop()];
kwargs[kwargsIndexes.pop()] = ['__kw', ['name', kwTestRValue]];
}
}
return [ this[0], w.walk(expr), MAP(args, w.walk) ];
}
}, function(){ return w.walk(ast); });
}
var disarmKeywordArguments = function disarmKeywordArguments(objExpr, options) {
var pairs, pair, i;
if (objExpr && objExpr[0] === 'object') {
pairs = objExpr[1];
for (i=0; i<pairs.length; i++) {
pair = pairs[i];
if (pair[0] === '__kw' && pair[1][0] === 'name' &&
( (options.raw && pair[1][1] === 'true') ||
(!options.raw && pair[1][1] === '_MoveKWArgsT') ) ) {
pairs.splice(i, 1);
return true;
}
}
}
return false;
};
// Enabled the use of "class" -> "Move.runtime.__class"
exports.enable_class_rt_function = function enable_class_rt_function(ast, options) {
var w = processor.ast_walker(), MAP = processor.MAP;
var lastAssignmentName;
return w.with_walkers({
"name": function(name) {
if (name === 'class')
name = '__class';
return [ this[0], name ];
},
"assign": function(op, lvalue, rvalue) {
var name = nameOfAssignmentValue(lvalue);
// Save a reference to the closest (but not further) assignment target
lastAssignmentName = (typeof name === 'string') ? name : undefined;
r = [ this[0], op, w.walk(lvalue), w.walk(rvalue) ];
lastAssignmentName = undefined;
return r;
},
"call": function(expr, args) {
var name, arg, i, keyValuePairs, pair;
if (expr[0] === 'name' && expr[1] === 'class' && lastAssignmentName) {
name = lastAssignmentName || '__UntitledClass'
// Prepend arguments with "T = ^{ __class.create T, arguments }"
args.unshift([ 'assign',
true,
[ 'name', name ],
[ 'function', name,
[],
[ [ 'return',
[ 'call',
[ 'dot', [ 'name', '__class' ], 'create' ],
[ [ 'name', name ], [ 'name', 'arguments' ] ] ] ] ]
] ]);
// disarm keyword arguments (only if first argument is an object)
disarmKeywordArguments(args[1], options);
}
return [ this[0], w.walk(expr), MAP(args, w.walk) ];
}
}, function(){ return w.walk(ast); });
}