Revision d5c974ac9ce897791737b6f4e0cee5de2254e6f6 authored by Andreas Stuhlmueller on 31 January 2017, 02:56:40 UTC, committed by Andreas Stuhlmueller on 31 January 2017, 02:56:40 UTC
1 parent 22b8f88
main.js
'use strict';
var fs = require('fs');
var types = require('ast-types');
var build = types.builders;
var esprima = require('esprima');
var escodegen = require('escodegen');
var assert = require('assert');
var _ = require('lodash');
var ad = require('./transforms/ad').ad;
var cps = require('./transforms/cps').cps;
var addFilename = require('./transforms/addFilename').addFilename;
var optimize = require('./transforms/optimize').optimize;
var naming = require('./transforms/naming').naming;
var store = require('./transforms/store').store;
var stack = require('./transforms/stack');
var varargs = require('./transforms/varargs').varargs;
var trampoline = require('./transforms/trampoline').trampoline;
var freevars = require('./transforms/freevars').freevars;
var caching = require('./transforms/caching');
var thunkify = require('./syntax').thunkify;
var util = require('./util');
var errors = require('./errors/errors');
var params = require('./params/params');
// Container for coroutine object and shared top-level
// functions (sample, factor, exit)
var env = {};
// Make header functions globally available:
function requireHeader(path) { requireHeaderWrapper(require(path)); }
function requireHeaderWrapper(wrapper) { makePropertiesGlobal(wrapper(env)); }
function makePropertiesGlobal(obj) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
global[prop] = obj[prop];
}
}
}
// Explicitly call require here to ensure that browserify notices that the
// header should be bundled.
requireHeaderWrapper(require('./header'));
function concatPrograms(programs) {
assert.ok(_.isArray(programs));
var concat = function(p0, p1) {
return build.program(p0.body.concat(p1.body));
};
var emptyProgram = esprima.parse('');
return programs.reduce(concat, emptyProgram);
}
function parse(code, filename) {
var ast = esprima.parse(code, {loc: true});
return addFilename(ast, filename);
}
function parseAll(bundles) {
return bundles.map(function(bundle) {
var ast = parse(bundle.code, bundle.filename);
return _.assign({ ast: ast }, bundle);
});
}
function headerPackage() {
// Create a pseudo package from the header.
var dists = fs.readFileSync(__dirname + '/dists.wppl', 'utf8');
var header = fs.readFileSync(__dirname + '/header.wppl', 'utf8');
return { wppl: [
{ code: dists, filename: 'dists.wppl' },
{ code: header, filename: 'header.wppl' }
]};
}
function unpack(packages) {
// Flatten an array of packages into an array of code bundles. A
// bundle contains wppl source code and its filename.
//
// Package :: { wppl: [{ code: ..., filename: ... }] }
// Bundle :: { code: String, filename: String }
//
return _.chain(packages).map(function(pkg) {
return pkg.wppl.map(function(wppl) {
return { code: wppl.code, filename: wppl.filename };
});
}).flatten().value();
}
function parsePackageCode(packages, verbose) {
// Takes an array of packages and turns them into an array of parsed
// bundles. i.e. Each bundle (as returned by unpack) is augmented
// with an ast.
// The contents of the header are included at this stage.
function _parsePackageCode() {
var allPackages = [headerPackage()].concat(packages);
return _.flow([
unpack,
parseAll
])(allPackages);
}
return util.timeif(verbose, 'parsePackageCode', _parsePackageCode);
}
function applyCaching(asts) {
return asts.map(function(ast) {
return caching.hasNoCachingDirective(ast) ? ast : caching.transform(ast);
});
}
function copyAst(ast) {
var ret = _.isArray(ast) ? [] : {};
_.each(ast, function(val, key) {
ret[key] = _.isObject(val) ? copyAst(val) : val;
});
return ret;
}
function generateCodeAndSourceMap(code, filename, bundles, ast) {
var codeAndMap = escodegen.generate(ast, {
sourceMap: true,
sourceMapWithCode: true
});
var sourceMap = JSON.parse(codeAndMap.map);
// Embed the original source in the source map for later use in
// error handling.
sourceMap.sourcesContent = sourceMap.sources.map(function(fn) {
return (fn === filename) ? code : _.find(bundles, {filename: fn}).code;
});
return {code: codeAndMap.code, sourceMap: sourceMap};
}
function compile(code, options) {
options = util.mergeDefaults(options, {
verbose: false,
filename: 'webppl:program'
});
var bundles = options.bundles || parsePackageCode([], options.verbose);
var addressMap;
var saveAddressMap = function(obj) {
addressMap = obj.map;
return obj.ast;
};
var transforms = options.transforms || [
ad,
thunkify,
naming,
saveAddressMap,
varargs,
cps,
store,
optimize,
options.debug ? stack.transform : _.identity,
trampoline,
stack.wrapProgram
];
function _compile() {
var programAst = parse(code, options.filename);
var asts = _.map(bundles, 'ast').map(copyAst).concat(programAst);
assert.strictEqual(bundles[0].filename, 'dists.wppl');
assert.strictEqual(bundles[1].filename, 'header.wppl');
var doCaching = _.some(asts.slice(2), caching.transformRequired);
if (options.verbose && doCaching) {
console.log('Caching transform will be applied.');
}
var generateCodeAndAssets = function(ast) {
var obj = generateCodeAndSourceMap(code, options.filename, bundles, ast);
obj.addressMap = addressMap;
return obj;
};
return _.flow([
doCaching ? applyCaching : _.identity,
concatPrograms,
doCaching ? freevars : _.identity,
_.flow(transforms),
generateCodeAndAssets
])(asts);
};
return util.timeif(options.verbose, 'compile', _compile);
}
function wrapWithHandler(f, handler) {
return function(x, y) {
try {
return f(x, y);
} catch (e) {
handler(e);
}
};
}
function wrapRunner(baseRunner, handlers) {
var wrappedRunner = handlers.reduce(wrapWithHandler, baseRunner);
var runner = function(t) { return wrappedRunner(t, runner); };
return runner;
}
function prepare(codeAndAssets, k, options) {
options = util.mergeDefaults(options, {
errorHandlers: [],
initialStore: {}
});
var currentAddress = {value: undefined};
var defaultHandler = function(error) {
errors.extendError(error, codeAndAssets, currentAddress);
// Trigger clean-up. When using the mongo store, the process won't
// exit until the connection is closed.
cleanup(_.identity);
throw error;
};
var allErrorHandlers = [defaultHandler].concat(options.errorHandlers);
// Wrap base runner with all error handlers.
var baseRunner = options.baseRunner || util.trampolineRunners[util.runningInBrowser() ? 'web' : 'cli']();
var runner = wrapRunner(baseRunner, allErrorHandlers);
// We store the trampoline runner so that header functions that call
// external asynchronous functions can resume execution in callbacks.
global.resumeTrampoline = runner;
// Before the program finishes, we tell the param store to finish up
// gracefully (e.g., shutting down a connection to a remote store).
var cleanup = params.stop;
var finish = function(s, x) {
return cleanup(function() {
return k(s, x);
});
};
var run = function() {
// We reset env since a previous call to run may have raised an
// exception and left an inference coroutine installed.
env.reset();
// We initialize the parameter store (e.g., connecting to a remote
// store, retrieving params).
params.init(function() {
var wpplFn = eval.call(global, codeAndAssets.code)(currentAddress)(runner);
var initialAddress = '';
return wpplFn(options.initialStore, finish, initialAddress);
});
util.resetWarnings();
};
return { run: run };
}
function run(code, k, options) {
options = options || {};
var codeAndAssets = compile(code, options);
util.timeif(options.verbose, 'run', prepare(codeAndAssets, k, options).run);
}
// Make webppl eval available within webppl
// runner is one of 'cli','web'
global.webpplEval = function(s, k, a, code, runnerName) {
if (runnerName === undefined) {
runnerName = util.runningInBrowser() ? 'web' : 'cli'
}
// On error, throw out the stack. We don't support recovering the
// stack from here.
var handler = function(error) {
throw 'webpplEval error:\n' + error;
};
var baseRunner = util.trampolineRunners[runnerName]();
var runner = wrapRunner(baseRunner, [handler]);
var compiledCode = compile(code, {filename: 'webppl:eval'}).code;
return eval.call(global, compiledCode)({})(runner)(s, k, a);
};
module.exports = {
requireHeader: requireHeader,
requireHeaderWrapper: requireHeaderWrapper,
parsePackageCode: parsePackageCode,
prepare: prepare,
run: run,
compile: compile
};
Computing file changes ...