Vp.validate = function validate(src) { this._src = src; var module = esprima.parse("(" + src + ")", { raw: true, loc: true }).body[0].expression; var vars = this.match(module, "asm.js module declaration").when({ type: 'FunctionExpression', id: match.var('id', { type: 'Identifier' }), params: match.var('params', { length: match.range(0, 4) }), body: { loc: match.var('loc'), body: match.var('body') } }); return this.module(vars.params, vars.body.filter(nonEmpty), vars.loc); };
return match(loc, function(when) { when({ start: { line: match.var('sl'), column: match.var('sc') }, end: { line: match.var('el'), column: match.var('ec') } }, function(vars) { return " at " + vars.sl + ":" + vars.sc + "-" + vars.el + ":" + vars.ec; }); when(match.any, function() { return ""; }); });
match(todo.pop(), function(when) { when({ type: 'BinaryExpression', operator: match.some('+', '-'), left: match.var('left'), right: match.var('right') }, function(vars) { todo.push(vars.right, vars.left); }); when(match.var('operand'), function(vars) { result.push(vars.operand); }); });
Vp.table = function table(x, rhs, loc) { this.match(rhs, "function table").when({ type: 'ArrayExpression', elements: match.var('elements') }, function(vars) { var fs = elements.map(function(element) { return this.match(element, "function table entry").when(match.var('f', { type: 'Identifier' }), function(vars) { return vars.f; }, this); }, this); if (fs.length === 0) this.fail("empty function table", loc); if (!powerOf2(fs.length)) this.fail("function table length must be a power of 2, got " + fs.length, loc); var fts = fs.map(function(f) { var ft = this.lookup(f.name, f.loc); if (!(ft instanceof ty.Arrow)) this.fail("non-function " + f.name + " in function table", f.loc); return ft; }, this); var ft = fts[0]; for (var i = 1, n = fts.length; i < n; i++) { if (!ft.equals(fts[i])) this.fail("unexpected function type " + fs[i].name + " : " + fts[i] + " in function table", fs[i].loc); } return new ty.Table(ft, fs.length); }); };
Vp.paramType = function paramType(id, stmt) { return this.match(stmt, "parameter annotation").when({ type: 'ExpressionStatement', expression: { type: 'AssignmentExpression', left: { type: 'Identifier', name: id.name }, right: match.var('right') } }, function(vars) { return this.match(vars.right, "parameter annotation type", function(when) { when({ type: 'UnaryExpression', operator: '+', argument: { type: 'Identifier', name: id.name } }, function() { return ty.Double; }); when({ type: 'BinaryExpression', operator: '|', left: { type: 'Identifier', name: id.name }, right: { type: 'Literal', value: 0 } }, function() { return ty.Int; }); }); }); };
return this.match(e, "function call", function(when) { when({ type: 'CallExpression', callee: { type: 'Identifier', name: match.var('f'), loc: match.var('loc') }, arguments: match.var('args') }, function(vars) { var formalReturnType = this.checkArguments(vars.args.map(this.expression, this), this.lookup(vars.f, vars.loc), "function call", vars.args.map(function(arg) { return arg.loc; }), e.loc); this.checkSameType(formalReturnType, t, "function call", e.loc); }, this); when({ type: 'CallExpression', callee: { type: 'MemberExpression', object: { type: 'Identifier', name: match.var('f'), loc: match.var('loc') }, property: { type: 'BinaryExpression', operator: '&', left: match.var('index'), right: { type: 'Literal', value: match.var('n', match.number), raw: dotless, loc: match.var('nloc') } }, computed: true }, arguments: match.var('args') }, function(vars) { var t = this.lookup(vars.f, vars.loc); if (!(t instanceof ty.Table)) this.fail("expected function table, got " + vars.f, vars.loc); this.checkSubtype(this.expression(vars.index), ty.Intish, "function pointer", vars.index.loc); if (t.length !== vars.n + 1) this.fail("function table mask should be " + (t.length - 1) + ", got " + vars.n, vars.nloc); var formalReturnType = this.checkArguments(vars.args.map(this.expression, this), t.type, "function pointer call", vars.args.map(function(arg) { return arg.loc; }), e.loc); this.checkSameType(formalReturnType, t, "function call", e.loc); }, this); });
return this.match(prop, "export declaration", function(when) { when({ key: { type: 'Literal', value: match.var('external', match.string) }, value: { type: 'Identifier', name: match.var('internal'), loc: match.var('loc') }, kind: 'init' }, function(vars) { add(vars.internal, vars.external, vars.loc); }, this); when({ key: { type: 'Identifier', name: match.var('external') }, value: { type: 'Identifier', name: match.var('internal'), loc: match.var('loc') }, kind: 'init' }, function(vars) { add(vars.internal, vars.external, vars.loc); }, this); });
varKeys.forEach(function(key){ pattern[key] = match.var(key) })
return this.match(e, "expression", function(when) { when({ type: 'Literal', raw: hasDot }, function() { return ty.Double; }); when({ type: 'Literal', value: match.range(-0x80000000, 0xffffffff) }, function() { return ty.Fixnum; }); when({ type: 'Identifier' }, function() { return this.lookupValueType(e.name, e.loc); }, this); when({ type: 'AssignmentExpression', left: match.var('left', { type: match.some('Identifier', 'MemberExpression') }), right: match.var('right') }, function(vars) { var s = this.expression(vars.left); var t = this.expression(vars.right); this.checkSubtype(t, s, "assignment", e.loc); return t; }, this); when({ type: 'MemberExpression', object: { type: 'Identifier', name: match.var('x'), loc: match.var('loc') }, property: { type: 'Literal', value: match.range(0, 0x100000000), raw: dotless } }, function(vars) { var t = this.lookup(vars.x, vars.loc); if (!(t instanceof ty.View)) this.fail("expected view type, got " + t); return t.elementType; }, this); when({ type: 'MemberExpression', object: { type: 'Identifier', name: match.var('x'), loc: match.var('loc') }, property: { type: 'BinaryExpression', operator: '>>', left: match.var('e'), right: match.var('n', { type: 'Literal', value: match.var('shift', match.number), raw: dotless }) }, computed: true }, function(vars) { var t = this.lookup(vars.x, vars.loc); if (!(t instanceof ty.View)) this.fail("expected view type, got " + t, vars.loc); this.checkSubtype(this.expression(vars.e), ty.Intish, "heap address" , vars.e.loc); var expectedShift = log2(t.bytes); if (vars.shift !== expectedShift) this.fail("expected shift of " + expectedShift + " bits for view type " + t + ", got " + vars.shift, vars.n.loc); return t.elementType; }, this); when({ type: 'MemberExpression', object: { type: 'Identifier', name: match.var('x'), loc: match.var('loc') }, property: match.var('e'), computed: true }, function(vars) { var t = this.lookup(vars.x, vars.loc); if (!(t instanceof ty.View)) this.fail("expected view type, got " + t, vars.loc); if (t.bytes !== 1) this.fail("expected view type with element size 1, got " + t, vars.loc); if (t.elementType !== ty.Intish) this.fail("expected view type with intish elements, got " + t, vars.loc); this.checkSubtype(this.expression(vars.e), ty.Int, "heap address", vars.e.loc); return t.Intish; }, this); when({ type: 'ConditionalExpression', test: match.var('test'), consequent: match.var('cons'), alternate: match.var('alt') }, function(vars) { this.checkSubtype(this.expression(vars.test), ty.Int, "conditional test", vars.test.loc); var t1 = this.expression(vars.cons); var t2 = this.expression(vars.alt); if (t1 !== t2) this.fail("type mismatch between conditional branches", e.loc); if (t1 !== ty.Int && t1 !== ty.Double) this.fail("expected int or double in conditional branch, got " + t1, vars.cons.loc); return t1; }, this); when({ type: 'SequenceExpression', expressions: match.var('es') }, function(vars) { var last = vars.es.pop(); vars.es.forEach(function(e) { if (e.type === 'CallExpression') this.call(e, ty.Void); else this.expression(e); }, this); return this.expression(last); }, this); when({ type: 'UnaryExpression', operator: '~', argument: { type: 'UnaryExpression', operator: '~', argument: match.var('e') } }, function(vars) { this.checkSubtype(this.expression(vars.e), ty.Double, "double->signed coercion", e.loc); return ty.Signed; }, this); when({ type: 'UnaryExpression', operator: '+', argument: match.var('e', { type: 'CallExpression' }) }, function(vars) { this.call(vars.e, ty.Double); return ty.Double; }, this); when({ type: 'UnaryExpression', operator: match.var('op'), argument: match.var('arg') }, function(vars) { var t = tables.UNOPS.get(vars.op); if (!t) this.fail("unknown unary operator " + vars.op, e.loc); return this.checkArguments([this.expression(vars.arg)], t, "unary expression", [vars.op.loc], e.loc); }, this); when({ type: 'BinaryExpression', operator: '|', left: match.var('e', { type: 'CallExpression' }), right: { type: 'Literal', value: 0, raw: dotless } }, function(vars) { this.call(vars.e, ty.Signed); return ty.Signed; }, this); when({ type: 'BinaryExpression', operator: match.some('+', '-'), left: match.var('left'), right: match.var('right') }, function(vars) { var operands = flattenAdditive(vars.left, vars.right); var n = operands.length; var t = this.expression(operands[0]); if (t.subtype(ty.Double)) { for (var i = 1; i < n; i++) { var operand = operands[i]; this.checkSubtype(this.expression(operand), ty.Double, "additive operand", operand.loc); } return ty.Double; } else if (t.subtype(ty.Int)) { if (n > 0x100000) this.fail("too many additive operations without coercion: " + n + " > maximum 2^20", e.loc); for (var i = 1; i < n; i++) { var operand = operands[i]; this.checkSubtype(this.expression(operand), ty.Int, "additive operand", operand.loc); } return ty.Intish; } this.fail("expected type int or double, got " + t, operands[0].loc); }, this); when({ type: 'BinaryExpression', operator: match.var('op'), left: match.var('left'), right: match.var('right') }, function(vars) { var t = tables.BINOPS.get(vars.op); if (!t) this.fail("unknown binary operator " + vars.op, e.loc); return this.checkArguments([this.expression(vars.left), this.expression(vars.right)], t, "operator " + vars.op, [vars.left.loc, vars.right.loc], e.loc); }, this); });
return this.match(expr, "exports declaration", function(when) { when({ type: 'Identifier', name: match.var('f'), loc: match.var('loc') }, function(vars) { var t = this.lookup(vars.f, vars.loc); if (!(t instanceof ty.Arrow)) this.fail("expected exported function, got definition of type " + t, vars.loc); return { type: 'single', export: { name: vars.f, type: t } }; }, this); when({ type: 'ObjectExpression', properties: match.var('props') }, function(vars) { var table = dict(); var self = this; function add(internal, external, loc) { var t = self.lookup(internal, loc); if (!(t instanceof ty.Arrow)) self.fail("expected exported function, got definition of type " + t, loc); table.set(external, { name: internal, type: t }); } vars.props.forEach(function(prop) { return this.match(prop, "export declaration", function(when) { when({ key: { type: 'Literal', value: match.var('external', match.string) }, value: { type: 'Identifier', name: match.var('internal'), loc: match.var('loc') }, kind: 'init' }, function(vars) { add(vars.internal, vars.external, vars.loc); }, this); when({ key: { type: 'Identifier', name: match.var('external') }, value: { type: 'Identifier', name: match.var('internal'), loc: match.var('loc') }, kind: 'init' }, function(vars) { add(vars.internal, vars.external, vars.loc); }, this); }); }, this); return { type: 'multiple', exports: table }; }, this); });
var fs = elements.map(function(element) { return this.match(element, "function table entry").when(match.var('f', { type: 'Identifier' }), function(vars) { return vars.f; }, this); }, this);
return this.match(rhs, "global declaration", function(when) { when({ type: 'Literal', value: match.var('f', match.number), raw: match.var('src', hasDot) }, function(vars) { return { mutable: true, type: ty.Double }; }, this); when({ type: 'Literal', value: match.var('n', match.all(match.integer, match.range(-0x80000000, 0x100000000))), raw: match.var('src') }, function(vars) { return { mutable: true, type: ty.Int }; }, this); when({ type: 'MemberExpression', object: { type: 'MemberExpression', object: { type: 'Identifier', name: this._roots.stdlib }, property: { type: 'Identifier', name: 'Math' } }, property: { type: 'Identifier', name: match.var('x') } }, function(vars) { if (!tables.STDLIB_MATH_TYPES.has(vars.x)) this.fail("unknown library: Math." + vars.x, init.loc); return { mutable: false, type: tables.STDLIB_MATH_TYPES.get(vars.x) }; }, this); when({ type: 'MemberExpression', object: { type: 'Identifier', name: this._roots.stdlib }, property: match.var('x') }, function(vars) { if (!tables.STDLIB_TYPES.has(vars.x)) this.fail("unknown library: " + vars.x, init.loc); return { mutable: false, type: tables.STDLIB_TYPES.get(vars.x) }; }, this); when({ type: 'MemberExpression', object: { type: 'Identifier', name: this._roots.foreign }, property: match.var('x') }, function(vars) { return { mutable: false, type: ty.Function }; }, this); when({ type: 'BinaryExpression', operator: '|', left: { type: 'MemberExpression', object: { type: 'Identifier', name: this._roots.foreign }, property: match.var('x') }, right: { type: 'Literal', value: 0 } }, function(vars) { return { mutable: false, type: ty.Int }; }, this); when({ type: 'UnaryExpression', operator: '+', argument: { type: 'MemberExpression', object: { type: 'Identifier', name: this._roots.foreign }, property: match.var('x') } }, function(vars) { return { mutable: false, type: ty.Double }; }, this); when({ type: 'NewExpression', callee: { type: 'MemberExpression', object: { type: 'Identifier', name: this._roots.stdlib }, property: { type: 'Identifier', name: match.var('view'), loc: match.var('loc') } }, arguments: match.var('args', [{ type: 'Identifier', name: this._roots.heap }]) }, function(vars) { if (vars.args.length !== 1) this.fail("heap view constructor expects 1 argument, got " + vars.args.length, vars.args[1].loc); if (!tables.HEAP_VIEW_TYPES.has(vars.view)) this.fail("unknown typed array type: " + vars.view, vars.loc); return { mutable: false, type: tables.HEAP_VIEW_TYPES.get(vars.view) }; }, this); }, this);