node-mirror
Version:
node.js + CodeMirror = Great IDE and Admin Tool
1,513 lines (1,308 loc) • 315 kB
JavaScript
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),(f.asm||(f.asm={})).js=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var dict = require('dict');
function Env(v) {
if (!(this instanceof Env))
return new Env(v);
this._v = v;
this._dict = dict();
}
Env.prototype.lookup = function lookup(x) {
return this._dict.get(x) || null;
};
Env.prototype.bind = function bind(x, t, loc) {
if (x === 'arguments' || x === 'eval')
this._v.fail("illegal binding: '" + x + "'", loc);
if (this._dict.has(x))
this._v.fail("duplicate binding for " + x, loc);
this._dict.set(x, t);
};
module.exports = Env;
},{"dict":14}],2:[function(require,module,exports){
var match = require('pattern-match');
function ValidationError(message, stack) {
Error.call(this, message);
this.stack = stack;
}
ValidationError.prototype = Object.create(Error.prototype);
function spaces(n) {
var result = "";
for (var i = 0; i < n; i++)
result += " ";
return result;
}
ValidationError.prototype.context = function() {
if (!this.src || !this.loc)
return null;
var lines = this.src.split(/\r|\n|\r\n/);
var start = this.loc.start;
var line = lines[start.line - 1];
return line + "\n" + spaces(start.column) + "^";
};
ValidationError.prototype.toString = function() {
return this.context() + "\n\n" + Error.prototype.toString.call(this);
};
function fail(message, src, loc) {
message = message + locToString(loc);
// FIXME: V8-specific; make this stack-trace logic more robust
var stack = (new Error).stack
.replace(/[^n]*\n/, "ValidationError: " + message + "\n")
.replace(/\n[^\n]*\n/, "\n");
var e = new ValidationError(message, stack);
e.src = src;
e.loc = loc;
throw e;
}
// (Loc) -> str
function locToString(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 "";
});
});
}
fail.locToString = locToString;
fail.ValidationError = ValidationError;
module.exports = fail;
},{"pattern-match":16}],3:[function(require,module,exports){
var ty = require('./types');
function Report(globals, exports) {
this._globals = globals;
this._exports = exports;
}
Report.prototype.getFunction = function(f) {
var global = this._globals.lookup(f);
if (!global || !(global.type instanceof ty.Arrow))
return null;
return global.type;
};
Report.prototype.isSingleExport = function() {
return this._exports.type === 'single';
};
// ( this.isSingleExport => () -> string)
// (!this.isSingleExport => (string) -> string)
Report.prototype.getExport = function(f) {
return this._exports.type === 'single'
? this._exports.export.name
: this._exports.exports.lookup(f).name;
};
module.exports = Report;
},{"./types":5}],4:[function(require,module,exports){
var dict = require('dict');
var ty = require('./types');
exports.ROOT_NAMES = ['stdlib', 'foreign', 'heap'];
exports.HEAP_VIEW_TYPES = dict({
'Int8Array': ty.View(1, ty.Intish),
'Uint8Array': ty.View(1, ty.Intish),
'Int16Array': ty.View(2, ty.Intish),
'Uint16Array': ty.View(2, ty.Intish),
'Int32Array': ty.View(4, ty.Intish),
'Uint32Array': ty.View(4, ty.Intish),
'Float32Array': ty.View(4, ty.Doublish),
'Float64Array': ty.View(8, ty.Doublish)
});
var DoublishToDouble = ty.Arrow([ty.Doublish], ty.Double);
exports.STDLIB_TYPES = dict({
'Infinity': ty.Double,
'NaN': ty.Double
});
exports.STDLIB_MATH_TYPES = dict({
'acos': DoublishToDouble,
'asin': DoublishToDouble,
'atan': DoublishToDouble,
'cos': DoublishToDouble,
'sin': DoublishToDouble,
'tan': DoublishToDouble,
'ceil': DoublishToDouble,
'floor': DoublishToDouble,
'exp': DoublishToDouble,
'log': DoublishToDouble,
'sqrt': DoublishToDouble,
'abs': ty.Overloaded([
ty.Arrow([ty.Signed], ty.Signed),
DoublishToDouble
]),
'atan2': ty.Arrow([ty.Doublish, ty.Doublish], ty.Double),
'pow': ty.Arrow([ty.Doublish, ty.Doublish], ty.Double),
'imul': ty.Arrow([ty.Int, ty.Int], ty.Signed),
'E': ty.Double,
'LN10': ty.Double,
'LN2': ty.Double,
'LOG2E': ty.Double,
'LOG10E': ty.Double,
'PI': ty.Double,
'SQRT1_2': ty.Double,
'SQRT2': ty.Double,
'min': ty.Overloaded([
ty.Arrow([ty.Int, ty.Int], ty.Signed),
ty.Arrow([ty.Double, ty.Double], ty.Double)
]),
'max': ty.Overloaded([
ty.Arrow([ty.Int, ty.Int], ty.Signed),
ty.Arrow([ty.Double, ty.Double], ty.Double)
])
});
var SignedBitwise = ty.Arrow([ty.Intish, ty.Intish], ty.Signed);
var RelOp = ty.Overloaded([
ty.Arrow([ty.Signed, ty.Signed], ty.Int),
ty.Arrow([ty.Unsigned, ty.Unsigned], ty.Int),
ty.Arrow([ty.Double, ty.Double], ty.Int)
]);
exports.BINOPS = dict({
'+': ty.Arrow([ty.Double, ty.Double], ty.Double),
'-': ty.Arrow([ty.Doublish, ty.Doublish], ty.Double),
'*': ty.Arrow([ty.Doublish, ty.Doublish], ty.Double),
'/': ty.Overloaded([
ty.Arrow([ty.Signed, ty.Signed], ty.Intish),
ty.Arrow([ty.Unsigned, ty.Unsigned], ty.Intish),
ty.Arrow([ty.Doublish, ty.Doublish], ty.Double)
]),
'%': ty.Overloaded([
ty.Arrow([ty.Signed, ty.Signed], ty.Intish),
ty.Arrow([ty.Unsigned, ty.Unsigned], ty.Intish),
ty.Arrow([ty.Doublish, ty.Doublish], ty.Double)
]),
'|': SignedBitwise,
'&': SignedBitwise,
'^': SignedBitwise,
'<<': SignedBitwise,
'>>': SignedBitwise,
'>>>': ty.Arrow([ty.Intish, ty.Intish], ty.Unsigned),
'<': RelOp,
'<=': RelOp,
'>': RelOp,
'>=': RelOp,
'==': RelOp,
'!=': RelOp
});
exports.UNOPS = dict({
'+': ty.Overloaded([
ty.Arrow([ty.Signed], ty.Double),
ty.Arrow([ty.Unsigned], ty.Double),
ty.Arrow([ty.Doublish], ty.Double)
]),
'-': ty.Overloaded([
ty.Arrow([ty.Int], ty.Intish),
ty.Arrow([ty.Doublish], ty.Double)
]),
'~': ty.Arrow([ty.Intish], ty.Signed),
'!': ty.Arrow([ty.Int], ty.Int)
});
},{"./types":5,"dict":14}],5:[function(require,module,exports){
function Type() {}
// =============================================================================
// Value Types
// =============================================================================
function ValueType(name, supertypes) {
this._name = name;
this._supertypes = supertypes;
};
ValueType.prototype = Object.create(Type.prototype);
ValueType.prototype.equals = function equals(other) {
return this === other;
};
ValueType.prototype.subtype = function subtype(other) {
return this.equals(other) ||
(this._supertypes && this._supertypes.some(function(sup) {
return sup.subtype(other);
}));
};
ValueType.prototype.toString = function toString() {
return this._name;
}
var Extern = new ValueType("extern", null);
var Intish = new ValueType("intish", null);
var Doublish = new ValueType("doublish", null);
var Unknown = new ValueType("unknown", [Intish, Doublish]);
var Int = new ValueType("int", [Intish]);
var Double = new ValueType("double", [Extern, Doublish]);
var Signed = new ValueType("signed", [Extern, Int]);
var Unsigned = new ValueType("unsigned", [Extern, Int]);
var Fixnum = new ValueType("fixnum", [Signed, Unsigned]);
var Void = new ValueType("void", null);
// =============================================================================
// Global Types
// =============================================================================
// ([ValueType], ValueType) -> Arrow
function Arrow(params, result) {
if (!(this instanceof Arrow))
return new Arrow(params, result);
this.params = params;
this.result = result;
}
Arrow.prototype = Object.create(Type.prototype);
Arrow.prototype.equals = function equals(other) {
return other instanceof Arrow &&
this.params.length === other.params.length &&
this.params.every(function(p, i) { return p.equals(other.params[i]) }) &&
this.result.equals(other.result);
};
Arrow.prototype.toString = function toString() {
return "(" + this.params.join(", ") + ") -> " + this.result;
};
// ([Arrow]) -> Overloaded
function Overloaded(alts) {
if (!(this instanceof Overloaded))
return new Overloaded(alts);
this.alts = alts;
}
Overloaded.prototype = Object.create(Type.prototype);
Overloaded.prototype.toString = function toString() {
return this.alts.join(" ^ ");
};
// (1|2|4|8, ValueType) -> View
function View(bytes, elementType) {
if (!(this instanceof View))
return new View(bytes, elementType);
this.bytes = bytes;
this.elementType = elementType;
}
View.prototype.toString = function toString() {
return "View<" + this.bytes + ", " + this.elementType + ">";
};
// (Arrow, integer) -> Table
function Table(type, length) {
if (!(this instanceof Table))
return new Table(type, length);
this.type = type;
this.length = length;
}
Table.prototype = Object.create(Type.prototype);
Table.prototype.toString = function toString() {
return "(" + this.type + ")[" + this.length + "]";
};
var Function = Object.create(Type.prototype);
var Module = Object.create(Type.prototype);
var ModuleParameter = Object.create(Type.prototype);
Function.toString = function() {
return "Function";
};
exports.ValueType = ValueType;
exports.Extern = Extern;
exports.Intish = Intish;
exports.Doublish = Doublish;
exports.Unknown = Unknown;
exports.Int = Int;
exports.Double = Double;
exports.Signed = Signed;
exports.Unsigned = Unsigned;
exports.Fixnum = Fixnum;
exports.Void = Void;
exports.Arrow = Arrow;
exports.Overloaded = Overloaded;
exports.View = View;
exports.Table = Table;
exports.Function = Function;
exports.Module = Module;
exports.ModuleParameter = ModuleParameter;
},{}],6:[function(require,module,exports){
var esprima = require('esprima');
var dict = require('dict');
var match = require('pattern-match');
var array = require('array-extended');
var env = require('./env');
var ty = require('./types');
var fail = require('./fail');
var tables = require('./tables');
var Report = require('./report');
// -----------------------------------------------------------------------------
// utilities
// -----------------------------------------------------------------------------
var log = Math.log;
var LOG2 = log(2);
// (number) -> number
function log2(x) {
return log(x) / LOG2;
}
// (number) -> boolean
function powerOf2(x) {
return (x & (x - 1)) === 0;
}
// (Statement) -> boolean
function nonEmpty(s) {
return s.type !== 'EmptyStatement';
}
// ([AST], [{ name: string, type: string }]) -> { string: AST, ... }
function split(nodes, filters) {
var result = {};
var nNodes = nodes.length, nFilters = filters.length;
var iNode = 0;
for (var iFilter = 0; iFilter < nFilters; iFilter++) {
var filter = filters[iFilter];
var next = [];
while (iNode < nNodes && nodes[iNode].type === filter.type) {
next.push(nodes[iNode]);
iNode++;
}
result[filter.name] = next;
}
return result;
}
// (string) -> boolean
function hasDot(s) {
return s.indexOf(".") !== -1;
}
// (string) -> boolean
function dotless(s) {
return !hasDot(s);
}
// (Expression, Expression) -> [Expression]
function flattenAdditive(left, right) {
var result = [];
// Since .pop is faster than .shift we'll pop tasks from the end.
var todo = [right, left];
while (todo.length > 0) {
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);
});
});
}
return result;
}
// -----------------------------------------------------------------------------
// main methods
// -----------------------------------------------------------------------------
function Validator() {
this._roots = {
stdlib: null,
foreign: null,
heap: null
};
this._globals = env(this);
this._locals = null;
this._src = null;
this._result = null;
}
var Vp = Validator.prototype;
// (AST, string[, Function]) -> any
// Delegates to pattern-match library, converting MatchErrors to validation errors.
Vp.match = function(node, desc, body) {
if (body) {
try {
return match(node, body, this);
} catch (e) {
if (e instanceof match.MatchError)
this.fail("invalid " + desc);
else
throw e;
}
}
var delayed = match(node);
var self = this;
return {
when: function(pattern, template) {
try {
return delayed.when(pattern, template, self);
} catch (e) {
if (e instanceof match.MatchError) {
var loc = e && e.actual ? e.actual.loc : null;
self.fail("invalid " + desc + fail.locToString(loc), loc);
} else {
throw e;
}
}
}
};
};
// (string) -> Report
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);
};
// (string, Loc) -/->
Vp.fail = function fail_(msg, loc) {
fail(msg, this._src, loc);
};
// ([Statement], Loc) -> {
// imports: [VariableDeclaration],
// functions: [FunctionDeclaration],
// tables: [VariableDeclaration],
// exports: Expression
// }
Vp.splitModule = function splitModule(body, loc) {
var sections = split(body, [
{ name: 'directive', type: 'ExpressionStatement' },
{ name: 'globals', type: 'VariableDeclaration' },
{ name: 'functions', type: 'FunctionDeclaration' },
{ name: 'tables', type: 'VariableDeclaration' },
{ name: 'exports', type: 'ReturnStatement' }
]);
if (sections.directive.length !== 1)
this.fail("expected single \"use asm\" directive, got " + sections.directive.length + " ExpressionStatement nodes", loc);
this.match(sections.directive, "\"use asm\" directive").when([{
type: 'ExpressionStatement',
expression: { type: 'Literal', value: "use asm" }
}]);
if (sections.exports.length !== 1)
this.fail("expected single exports declaration, got " + sections.exports.length + " ReturnStatement nodes", loc);
return {
globals: sections.globals,
functions: sections.functions,
tables: sections.tables,
exports: sections.exports[0].argument
};
};
// (Identifier, Statement) -> Type
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;
});
});
});
};
// (Statement) -> Type
Vp.returnType = function returnType(stmt) {
if (stmt.type !== 'ReturnStatement')
return ty.Void;
return this.match(stmt.argument, "return type annotation", function(when) {
when(null, function() {
return ty.Void;
});
when({
type: 'UnaryExpression',
operator: '+'
}, function() {
return ty.Double;
});
when({
type: 'BinaryExpression',
operator: '|',
right: { type: 'Literal', value: 0 }
}, function() {
return ty.Signed;
});
when({
type: 'Literal',
value: match.number,
raw: hasDot
}, function() {
return ty.Double;
});
when({
type: 'Literal',
value: match.all(match.number,
match.range(-0x80000000, 0x100000000)),
raw: dotless
}, function() {
return ty.Signed;
});
});
};
// (FunctionDeclaration) -> Type
Vp.functionType = function functionType(funDecl) {
var params = funDecl.params;
var n = params.length;
var body = funDecl.body.body.filter(nonEmpty);
if (body.length < n)
this.fail("not enough annotations for parameters to " + funDecl.id.name, funDecl.body.loc);
var paramTypes = array.zip(params, body.slice(0, n)).map(function(pair) {
return this.paramType(pair[0], pair[1]);
}, this);
var returnType = body.length > 0 ? this.returnType(body[body.length - 1]) : ty.Void;
return ty.Arrow(paramTypes, returnType);
};
// (string, Loc) -> Type
Vp.lookup = function lookup(x, loc) {
if (this._locals) {
var t = this._locals.lookup(x);
if (t)
return t;
}
var mt = this._globals.lookup(x);
if (!mt)
this.fail("unbound variable " + x, loc);
return mt.type;
};
// (string, Loc) -> ValueType
Vp.lookupValueType = function lookupValueType(x, loc) {
var t = this.lookup(x, loc);
if (!(t instanceof ty.ValueType))
this.fail("expected value type, got " + t, loc);
return t;
};
// (string, Expression, Loc) -> { mutable: boolean, type: Type }
Vp.global = function global(x, rhs, loc) {
if (!rhs)
this.fail("global variable missing initializer expression", loc);
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);
};
// (string, Expression, Loc) -> Table
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);
});
};
// (string, Expression) -> Type
Vp.local = function local(x, rhs) {
return this.match(rhs, "declaration of local " + x, function(when) {
when({
type: 'Literal',
value: match.number,
raw: hasDot
}, function(vars) {
return ty.Double;
}, this);
when({
type: 'Literal',
value: match.all(match.integer, match.range(-0x80000000, 0x100000000))
}, function(vars) {
return ty.Int;
}, this);
});
};
// (FunctionDeclaration) -> void
Vp.function = function function_(decl) {
var f = decl.id.name;
var ft = this._globals.lookup(f).type;
var params = decl.params.map(function(id) { return id.name; });
var paramTypes = ft.params;
var resultType = ft.result;
var body = decl.body.body.filter(nonEmpty);
try {
this._locals = env(this);
this._result = resultType;
// Bind the parameters.
params.forEach(function(x, i) {
this._locals.bind(x, paramTypes[i]);
}, this);
// Bind the locals.
var i = params.length, n = body.length;
while (i < n && body[i].type === 'VariableDeclaration') {
body[i].declarations.forEach(function(dtor) {
var x = dtor.id.name;
this._locals.bind(x, this.local(x, dtor.init));
}, this);
i++;
}
// Check the body.
this.statements(body.slice(i));
} finally {
this._locals = null;
this._result = null;
}
};
// (Expression) -> { type: 'single', export: { name: string, type: Arrow } }
// | { type: 'multiple', exports: dict<{ name: string, type: Arrow }> }
Vp.exports = function exports(expr) {
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);
});
};
// ([Identifier], [Statement], Loc) -> Report
Vp.module = function module(params, body, loc) {
var sections = this.splitModule(body, loc);
// Bind module parameters.
params.forEach(function(id, i) {
this._roots[tables.ROOT_NAMES[i]] = id.name;
this._globals.bind(id.name, { mutable: false, type: ty.ModuleParameter }, id.loc);
}, this);
// Bind and check globals.
sections.globals.forEach(function(varDecl) {
varDecl.declarations.forEach(function(decl) {
var x = decl.id.name;
var mt = this.global(x, decl.init, decl.loc);
this._globals.bind(x, mt, decl.id.loc);
}, this);
}, this);
// Bind function types.
sections.functions.forEach(function(funDecl) {
var id = funDecl.id, f = id.name;
var t = this.functionType(funDecl);
this._globals.bind(f, { mutable: false, type: t }, id.loc);
}, this);
// Bind and check function tables.
sections.tables.forEach(function(varDecl) {
varDecl.declarations.forEach(function(decl) {
var x = decl.id.name;
var t = this.table(x, decl.init, decl.loc);
this._globals.bind(x, { mutable: false, type: t }, decl.id.loc);
}, this);
}, this);
// Check functions.
sections.functions.forEach(this.function, this);
// Check exports.
var exports = this.exports(sections.exports);
return new Report(this._globals, exports);
};
// -----------------------------------------------------------------------------
// statements
// -----------------------------------------------------------------------------
// ([Statement]) -> void
Vp.statements = function statements(ss) {
ss.forEach(this.statement, this);
};
// (Statement) -> void
Vp.statement = function statement(s) {
switch (s.type) {
case 'BlockStatement':
return this.statements(s.body);
case 'ExpressionStatement':
return (s.expression.type === 'CallExpression')
? this.call(s.expression, ty.Void)
: this.expression(s.expression);
case 'IfStatement':
return this.ifStatement(s.test, s.consequent, s.alternate);
case 'ReturnStatement':
return this.returnStatement(s.argument, s.loc);
case 'WhileStatement':
return this.whileStatement(s.test, s.body);
case 'DoWhileStatement':
return this.doWhileStatement(s.body, s.test);
case 'ForStatement':
return this.forStatement(s.init, s.test, s.update, s.body, s.loc);
case 'BreakStatement':
case 'ContinueStatement':
case "EmptyStatement":
return;
case 'LabeledStatement':
return this.statement(s.body);
case 'SwitchStatement':
return this.switchStatement(s.discriminant, s.cases);
default:
this.fail("illegal " + s.type + " node", s.loc);
}
};
// (Type, Type, string, Loc) -> void
Vp.checkSubtype = function checkSubtype(actual, expected, msg, loc) {
if (!(actual instanceof ty.ValueType) || !actual.subtype(expected))
this.fail("expected " + expected + " in " + msg + ", got " + actual, loc);
};
Vp.checkSameType = function checkSameType(actual, expected, msg, loc) {
if (!(actual instanceof ty.ValueType) || !actual.equals(expected))
this.fail("expected " + expected + " in " + msg + ", got " + actual, loc);
};
// ([Type], Type, string, [Loc], Loc) -> Type
Vp.checkArguments = function checkArguments(ts, expected, msg, locs, loc) {
if (expected instanceof ty.Arrow) {
ts.forEach(function(t, i) {
this.checkSubtype(t, expected.params[i], "argument", locs[i]);
}, this);
return expected.result;
} else if (expected instanceof ty.Overloaded) {
var t;
expected.alts.some(function(alt) {
var ss = alt.params;
if (ss.length === ts.length && ss.every(function(s, i) { return ts[i].subtype(s) })) {
t = alt.result;
return true;
}
}, this);
if (!t)
this.fail(msg + ": argument types do not match any overloading", loc);
return t;
} else {
this.fail("expected function type, got " + expected, loc);
}
};
// (Expression, Statement, Statement | null) -> void
Vp.ifStatement = function ifStatement(test, cons, alt) {
this.checkSubtype(this.expression(test), ty.Int, "if test", test.loc);
this.statement(cons);
if (alt)
this.statement(alt);
};
// (Expression | null, Loc) -> void
Vp.returnStatement = function returnStatement(arg, loc) {
this.checkSubtype(this.optExpression(arg) || ty.Void, this._result, "return argument", loc);
};
// (Expression, Statement) -> void
Vp.whileStatement = function whileStatement(test, body, labels) {
this.checkSubtype(this.expression(test), ty.Int, "while loop condition", test.loc);
this.statement(body);
};
// (Statement, Expression) -> void
Vp.doWhileStatement = function doWhileStatement(body, test) {
this.statement(body);
this.checkSubtype(this.expression(test), ty.Int, "do-while loop condition", test.loc);
};
// (VariableDeclaration | Expression | null, Expression | null, Expression | null, Statement, Loc) -> void
Vp.forStatement = function forStatement(init, test, update, body, loc) {
if (init.type === 'VariableDeclaration')
this.fail("illegal variable declaration in for-head", init.loc);
this.optExpression(init);
this.checkSubtype(this.optExpression(test) || ty.Int, ty.Int, "for loop condition", loc);
this.optExpression(update);
this.statement(body);
};
// (Expression, [SwitchCase], labels) -> void
Vp.switchStatement = function switchStatement(disc, cases) {
var s = this.expression(disc);
cases.forEach(function(c) {
this.case(c, s);
}, this);
};
// (SwitchCase, Type) -> void
Vp.case = function case_(c, s) {
if (c.test)
this.checkSubtype(this.literal(c.test), s, "case clause expression", c.test.loc);
this.statements(c.consequent);
};
// -----------------------------------------------------------------------------
// expressions
// -----------------------------------------------------------------------------
// (Expression | null) -> ValueType | null
Vp.optExpression = function optExpression(expr) {
return expr ? this.expression(expr) : null;
};
// (Expression) -> ValueType
Vp.expression = function expression(e) {
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);
});
};
// -----------------------------------------------------------------------------
// call expressions
// -----------------------------------------------------------------------------
// (CallExpression, ValueType) -> void
Vp.call = function call(e, t) {
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);
});
};
// -----------------------------------------------------------------------------
// front end
// -----------------------------------------------------------------------------
// (string) -> Report
module.exports = function validate(src) {
return (new Validator).validate(src);
};
},{"./env":1,"./fail":2,"./report":3,"./tables":4,"./types":5,"array-extended":7,"dict":14,"esprima":15,"pattern-match":16}],7:[function(require,module,exports){
(function () {
"use strict";
var arraySlice = Array.prototype.slice;
function argsToArray(args, slice) {
slice = slice || 0;
return arraySlice.call(args, slice);
}
function defineArray(extended, is) {
var isString = is.isString,
isArray = Array.isArray || is.isArray,
isDate = is.isDate,
floor = Math.floor,
abs = Math.abs,
mathMax = Math.max,
mathMin = Math.min,
arrayProto = Array.prototype,
arrayIndexOf = arrayProto.indexOf,
arrayForEach = arrayProto.forEach,
arrayMap = arrayProto.map,
arrayReduce = arrayProto.reduce,
arrayReduceRight = arrayProto.reduceRight,
arrayFilter = arrayProto.filter,
arrayEvery = arrayProto.every,
arraySome = arrayProto.some;
function cross(num, cros) {
return reduceRight(cros, function (a, b) {
if (!isArray(b)) {
b = [b];
}
b.unshift(num);
a.unshift(b);
return a;
}, []);
}
function permute(num, cross, length) {
var ret = [];
for (var i = 0; i < cross.length; i++) {
ret.push([num].concat(rotate(cross, i)).slice(0, length));
}
return ret;
}
function intersection(a, b) {
var ret = [], aOne;
if (a && b && a.length && b.length) {
for (var i = 0, l = a.length; i < l; i++) {
aOne = a[i];
if (indexOf(b, aOne) !== -1) {
ret.push(aOne);
}
}
}
return ret;
}
var _sort = (function () {
var isAll = function (arr, test) {
return every(arr, test);
};
var defaultCmp = function (a, b) {
return a - b;
};
var dateSort = function (a, b) {
return a.getTime() - b.getTime();
};
return function _sort(arr, property) {
var ret = [];
if (isArray(arr)) {
ret = arr.slice();
if (property) {
if (typeof property === "function") {
ret.sort(property);
} else {
ret.sort(function (a, b) {
var aProp = a[property], bProp = b[property];
if (isString(aProp) && isString(bProp)) {
return aProp > bProp ? 1 : aProp < bProp ? -1 : 0;
} else if (isDate(aProp) && isDate(bProp)) {
return aProp.getTime() - bProp.getTime();
} else {
return aProp - bProp;
}
});
}
} else {
if (isAll(ret, isString)) {