hopper
Version:
An interpreter for the Grace programming language
232 lines (189 loc) • 7.6 kB
JavaScript
// The core module of the library, exposing an interpreter that takes Grace code
// and executes it. It also exposes the constructor for the underlying
// Interpreter object, which allows for the preservation of state between
// multiple executions.
;
var Task, interpreter, loader, parser, rt, util;
parser = require("./parser");
Task = require("./task");
util = require("./util");
function parseAndHandle(text, path) {
return parser.parse(text, path).then(null, function (error) {
if (error instanceof parser.ParseError) {
return rt.ParseFailure.raise(rt.string(error.message))
.then(null, function (packet) {
packet.object.stackTrace = [
rt.trace(null, null, {
"module": path,
"line": error.line,
"column": error.column
})
];
throw packet;
});
}
return rt.InternalError.raiseFromPrimitiveError(error);
});
}
function CheckResult(isSuccess, name, result, stackTrace) {
this.isSuccess = isSuccess;
this.name = name;
if (isSuccess) {
this.value = result;
} else {
this.message = result;
this.stackTrace = stackTrace || null;
}
}
CheckResult.prototype.toString = function () {
return this.name + (this.message ? ": " + this.message : "");
};
// new Interpreter(preludeGen : Task<Object> | Object = <sys>,
// moduleLoader : Function<Interpreter, Path, Callback<Object>> = <fs>)
// A new interpreter, with internal state preserved between executions. The
// prelude generator may either be a task to build the prelude object, or just
// the object itself.
function Interpreter(preludeGen, moduleLoader) {
var self = this;
if (moduleLoader === undefined && typeof preludeGen === "function") {
moduleLoader = preludeGen;
preludeGen = rt.prelude;
}
this.prelude = Task.resolve(preludeGen || rt.prelude);
moduleLoader = moduleLoader || loader.defaultLoader;
self.prelude.then(function (prelude) {
self.interpreter = new interpreter.Interpreter(prelude,
function (path, callback) {
moduleLoader.apply(null, [self, path, callback]);
});
});
}
function makeInterpret(method, optionalPath, parse, onSuccess, onFailure) {
return function (path, code, callback) {
var self = this;
if (optionalPath && typeof code !== "string") {
callback = code;
code = path;
path = null;
}
function next(ast) {
return self.prelude.then(function () {
delete self.interpreter.modulePath;
if (path !== null) {
self.interpreter.modulePath = path;
}
return self.interpreter[method](ast, path);
});
}
return (util.isArray(code) ? next(code) : parse(code, path).then(next))
.then(onSuccess || null, onFailure || null).callback(callback).stopify();
};
}
// interpret(path : Path = undefined,
// code : String, callback : Callback<Object>) -> Function<Boolean>
// Interpret Grace code with the existing state of this interpreter, returning
// the result of the final expression. Takes an optional module path that will
// be used to report problems. Returns a function that will attempt to stop
// the execution when called.
Interpreter.prototype.interpret =
makeInterpret("interpret", true, parseAndHandle);
// check(path : Path = undefined,
// code : String, callback : Callback<StaticError>) -> Function<Boolean>
// Parse and check the given code, returning an object with information about
// the problem if the code fails to parse or fails its check. Takes an
// optional module path that will be used to report problems. Returns a
// function that will attempt to stop the execution when called.
Interpreter.prototype.check =
makeInterpret("check", true, parser.parse, function (result) {
if (util.isArray(result)) {
return new CheckResult(true, "Success", result);
}
return result.message().then(function (message) {
return message.asPrimitiveString();
}).then(function (message) {
return new CheckResult(false,
"Checker Failure", message, result.object.stackTrace);
});
}, function (packet) {
if (packet instanceof parser.ParseError) {
return new CheckResult(false, "Parse Failure", packet.message, [packet]);
}
throw packet;
});
// module(path : Path, code : String,
// callback : Callback<Object>) -> Function<Boolean>
// Interpret Grace code as a module body and cache it based on the given path
// so a request for the same module does not occur again. Returns a function
// that will attempt to stop the execution when called.
Interpreter.prototype.module =
makeInterpret("module", false, parseAndHandle);
// load(path : Path, callback : Callback<Object>) -> Function<Boolean>
// Run the interpreter module loader on the given path. Returns a function
// that will attempt to stop the execution when called.
Interpreter.prototype.load = function (path, callback) {
var self = this;
return self.prelude.then(function () {
return self.interpreter.load(path);
}).callback(callback).stopify();
};
// enter(Callback<Object> = null)
// Enter into an object scope and stay in that state, passing the newly
// created self value to the given callback. This is useful for implementing
// an interactive mode.
Interpreter.prototype.enter = function (callback) {
var self = this;
self.prelude.then(function () {
return self.interpreter.enter();
}).callback(callback);
};
function buildAndApply(method, args) {
var built, len, required;
function Build() {
Interpreter.apply(this, util.slice(args, required, len));
}
Build.prototype = Interpreter.prototype;
required = typeof args[1] === "string" || util.isArray(args[1]) ? 2 : 1;
len = args.length - 1;
built = new Build();
return built[method].apply(built,
util.slice(args, 0, required).concat([args[len]]));
}
exports.CheckResult = CheckResult;
exports.Interpreter = Interpreter;
// interpret(path : Path = undefined, code : String, prelude : Object = <sys>,
// moduleLoader : Function<Interpreter, Path, Callback<Object>> = <fs>,
// callback : Callback<Object>)
// Interpret Grace code standalone.
exports.interpret = function () {
return buildAndApply("interpret", arguments);
};
// check(path : Path = undefined, code : String, prelude : Object = <sys>,
// moduleLoader : Function<Interpreter, Path, Callback<Object>> = <fs>,
// callback : Callback<Object>)
// Check Grace code standalone.
exports.check = function () {
return buildAndApply("check", arguments);
};
// module(path : Path, code : String, prelude : Object = <sys>,
// moduleLoader : Function<Interpreter, Path, Callback<Object>> = <fs>,
// callback : Callback<Object>)
// Interpret Grace code standalone as a module body and cache it based on the
// given path so a request for the same module does not occur again.
exports.module = function () {
return buildAndApply("module", arguments);
};
// load(path : Path, prelude : Object = <sys>,
// moduleLoader : Function<Interpreter, Path, Callback<Object>> = <fs>,
// callback : Callback<Object>)
// Run a new interpreter with a module loader on the given path.
exports.load = function () {
return buildAndApply("load", arguments);
};
rt = require("./runtime");
interpreter = require("./interpreter");
loader = require("./loader");
exports.Task = Task;
exports.runtime = rt;
exports.defaultLoader = loader.defaultLoader;
exports.prelude = rt.prelude;
util.extend(exports, parser);