UNPKG

hopper

Version:

An interpreter for the Grace programming language

1,436 lines (1,161 loc) 40.9 kB
// The Grace interpreter. Exposes both the Interpreter constructor and the // helper function 'interpret' which executes on an anonymous Interpreter. // // Almost every function in the interpreter runs asynchronously, taking a // callback as its last argument that expects an error and a result as its // parameters. This asynchronous behaviour allows the interpreter to take // single-tick breaks on each method request, freeing up the event loop. // Standard JavaScript functions can be marked as asynchronous by attaching an // 'asynchronous' property with a truthy value to the function. All functions // built by the interpreter from Method nodes in the AST are asynchronous by // default, but a user will be able to disable this in the future with an // annotation (this functionality is necessary for interacting with standard // JavaScript). // // The asynchronous behaviour of the whole interpreter can be turned off // wholesale by passing false to the constructor or by setting the // 'asynchronous' property to false. The interface will still asynchronous, but // the 'asynchronous' property of functions will be ignored and the interpreter // will not take single-tick breaks. This can also be achieved with the // 'interpret' helper by simply not passing a callback. "use strict"; var Task, ast, path, rt, util; path = require("path"); Task = require("./task"); ast = require("./ast"); rt = require("./runtime"); util = require("./util"); // new Interpreter(prelude : Object, // moduleLoader : Function<Path, Callback<Object>>) // A new interpreter, with internal state preserved between executions. function Interpreter(prelude, moduleLoader) { util.makeCloneable(this, "scope"); this.modules = {}; this.load = Task.taskify(this, moduleLoader); this.scope = { "outer": null, "self": prelude }; } // Interprets a list of AST nodes asynchronously, passing the result of // interpreting the final node in the list (or done, if the list is empty). Interpreter.prototype.interpret = function (nodes) { function handleObject() { var context, isConfidential, method, name, outer; if (this.scope.object && (nodes.length === 0 || nodes[0].constructor !== ast.Inherits)) { delete this.scope.object; context = this.self(); for (name in context) { if (typeof context[name] === "function") { method = context[name]; isConfidential = method.isConfidential; while (util.owns(method, "super")) { method = method["super"]; if (method.isConfidential) { isConfidential = true; } else if (isConfidential) { name = rt.string(method.identifier); return rt.InvalidMethod.raiseConfidentialOverrideForName(name); } } } } if (context.asString === rt.base.asString) { outer = this.searchScope("self", true); method = this.searchScope("method", true); if (method !== null) { name = method.identifier; context.asString = rt.method("asString", 0, function () { return outer.asString().then(function (string) { return string.asPrimitiveString().then(function (pstring) { return rt.string("object(" + pstring + "." + name + ")"); }); }); }); } else if (this.modulePath !== undefined) { name = this.modulePath; context.asString = rt.method("asString", 0, function () { return rt.string("object(" + name + ")"); }); } } } return this.resolve(rt.done); } if (nodes.length === 0) { return handleObject.call(this); } return this.imports(nodes).then(function () { // Methods and variables are hoisted to the top of their scope. return this.each(nodes, function (node) { var constructor = node.constructor; if (constructor === ast.Method || constructor === ast.Class) { return this.evaluate(node); } if (constructor === ast.Def || constructor === ast.Var) { return this.putVariable(node, rt.pattern("Uninitialized", function () { // It's an error to assign to a hoisted var before its actual // location in code has been reached. return rt.UndefinedValue.raiseForName(rt.string(node.name.value)); })); } }); }).then(function () { return this.decls(nodes); }).then(function () { return this.annotations(nodes); }).then(handleObject).then(function () { return this.each(nodes, function (node) { // Imports, methods, and types have already been hoisted. Variables still // need their contents to be evaluated. if (node.constructor !== ast.Dialect && node.constructor !== ast.Import && node.constructor !== ast.Method && node.constructor !== ast.Class && node.constructor !== ast.TypeDeclaration) { return this.evaluate(node); } return rt.done; }); }).then(function (results) { return results.pop(); }); }; // Enter into an object scope and stay in that state, returning the newly // created self value. This is useful for an interactive mode. Interpreter.prototype.enter = function () { var object = rt.object(); this.push(object); return object; }; // Interpret a list of nodes as a module body and cache it based on a path so a // request for the same module does not occur again. Interpreter.prototype.module = function (nodes, key) { var interpreter, module; key = path.normalize(key); interpreter = this.clone(); interpreter.modulePath = key; module = rt.object(); module.asString = rt.method("asString", 0, function () { return rt.string(key); }); return interpreter.objectBody(nodes, module).bind(this).then(function () { this.modules[path.normalize(key)] = module; return module; }, rt.handleInternalError); }; Interpreter.prototype.evaluate = function (node) { var constructor = node.constructor; if (constructor === ast.Method) { return this.method(node); } if (constructor === ast.Class) { return this["class"](node); } if (constructor === ast.Def || constructor === ast.Var) { return this.variable(node, false); } if (constructor === ast.Return) { return this["return"](node); } if (constructor === ast.Inherits) { return this.inherits(node); } return this.expression(node); }; Interpreter.prototype.expression = function (node) { var constructor = node.constructor; if (constructor === ast.UnqualifiedRequest || constructor === ast.QualifiedRequest) { return this.request(node); } if (constructor === ast.Self) { if (this.scope.object) { return rt.IncompleteObject.raiseForSelf() .bind(this).then(null, function (packet) { return this.report(packet, "self", null, node); }); } return this.resolve(this.searchScope("self")); } if (constructor === ast.ObjectConstructor) { return this.object(node); } if (constructor === ast.Block) { return this.task(function () { return this.block(node); }); } if (constructor === ast.Type) { return this.type(node); } if (constructor === ast.BooleanLiteral) { return this.bool(node); } if (constructor === ast.NumberLiteral) { return this.number(node); } if (constructor === ast.StringLiteral) { return this.string(node); } return this.raise("Unrecognised node of type " + constructor.name); }; Interpreter.prototype.inheriting = function (node, inheriting) { var constructor = node.constructor; if (constructor === ast.UnqualifiedRequest || constructor === ast.QualifiedRequest) { return this.request(node, inheriting); } if (constructor === ast.BooleanLiteral) { return this.bool(node, inheriting); } return this.raise(rt.string("Unrecognised node of type " + constructor.name + " in inheritance")).bind(this); }; Interpreter.prototype.imports = function (nodes) { return this.each(nodes, function (node) { var constructor = node.constructor; if (constructor === ast.Dialect) { return this.dialect(node, nodes); } if (constructor === ast.Import) { return this["import"](node); } }); }; Interpreter.prototype.check = function (nodes) { var name; if (nodes.length > 0 && nodes[0].constructor === ast.Dialect) { name = nodes[0].path.value; return this.dialect(nodes[0], nodes).then(function () { return nodes; }, function (packet) { if (packet instanceof rt.CheckerFailure.object.Packet && packet.object.module === name) { return packet; } throw packet; }); } return Task.resolve(nodes); }; Interpreter.prototype.dialect = function (node, nodes) { var name = node.path.value; return this.load(name).bind(this).then(function (module) { return this.task(function () { if (typeof module.check === "function") { return module.check(rt.list(nodes)); } }).then(function () { this.scope.outer = { "outer": null, "self": module }; }, function (packet) { var object = packet.object; if (packet instanceof rt.CheckerFailure.object.Packet && object.module === undefined) { object.stackTrace = []; object.module = name; if (object.node) { return this.reportNode(packet, object.node); } } throw packet; }); }).then(null, function (packet) { return this.report(packet, 'dialect "' + node.path.value + '"', null, node); }); }; Interpreter.prototype["import"] = function (node) { return this.load(node.path.value).bind(this).then(function (module) { var name = node.identifier.value; if (name !== "_") { return this.put(name, this.newVar(name, module), node); } }, function (packet) { return this.report(packet, 'import "' + node.path.value + '"', null, node); }); }; Interpreter.prototype.annotations = function (nodes) { return this.each(nodes, function (node) { return this.task(function () { var scope = this.scope; function getDefinition(name) { return scope[name || node.name.value]; } if (node.constructor === ast.TypeDeclaration || node.constructor === ast.Def || node.constructor === ast.Class) { return this.annotate([getDefinition()], node.annotations, node.constructor === ast.Def ? "Def" : node.constructor === ast.Class ? "Class" : "Type"); } if (node.constructor === ast.Method) { return this .annotate([getDefinition(util.uglify(node.signature.name()))], node.annotations, "Method"); } if (node.constructor === ast.Var) { return this.annotate([ getDefinition(), getDefinition(node.name.value + ":=") ], node.annotations, "Var"); } }).then(null, function (packet) { return this.reportNode(packet, node); }); }); }; Interpreter.prototype.annotate = function (values, annotations, name) { return this.each(annotations, function (annNode) { return this.expression(annNode).then(function (annotation) { return this.assert(annotation, rt[name + "Annotator"]) .then(function () { return this.apply(annotation, "annotate" + name, [values]); }); }); }); }; Interpreter.prototype.object = function (node, inheriting) { return this.objectBody(node.body, inheriting).then(function (object) { // This is the only set of annotations that is evaluated at the point where // it appears in the code. All other annotations are hoisted. return this.annotate([object], node.annotations, "Object") .then(function () { return object; }); }).then(null, function (packet) { return this.report(packet, "object", null, node); }); }; Interpreter.prototype.objectBody = function (body, inheriting) { var object = inheriting || rt.object(); return this.scoped(object, function () { this.scope.object = true; return this.interpret(body); }).then(function () { return object; }); }; Interpreter.prototype.block = function (node) { var block, interpreter, parameter, parameters, patternNode, signature; parameters = node.parameters; signature = new ast.Signature([ new ast.SignaturePart(new ast.Identifier("apply", false, node), [], parameters) ], null, node); interpreter = this.clone(); block = rt.block([0, parameters.length], function () { var args = [util.slice(arguments)]; return interpreter.clone().scoped(function () { return this.parts(signature, args, node).then(function () { return this.interpret(node.body); }); }); }); if (parameters.length === 1) { parameter = parameters[0]; patternNode = parameter.pattern; if (patternNode !== null) { block.match = rt.method("match()", 1, function (object) { return interpreter.pattern(patternNode).then(function (pattern) { return pattern.match(object).then(function (match) { return match.andAlso(rt.block(0, function () { // Reimplement apply(), without testing the pattern. return interpreter.clone().scoped(function () { var name = parameter.name.value; return this.task(function () { if (name !== "_") { return this.put(name, this.newVar(name, object), parameter); } }).then(function () { return this.interpret(node.body).then(function (result) { return rt.success(result, block); }); }); }); })); }); }); }); } } return block; }; Interpreter.prototype.assert = function (value, pattern) { if (pattern !== rt.Unknown) { return this.apply(pattern, "assert()", [[value]]); } return this.resolve(null); }; Interpreter.prototype.decls = function (nodes) { return this.each(nodes, function (node) { var name; if (node.constructor === ast.TypeDeclaration) { name = node.name.value; return this.put(name, this.newType(name, node.generics.length), node) .then(function () { return node; }); } }).then(function (declNodes) { return this.each(declNodes, this.decl).then(function (decls) { return this.each(declNodes, decls, this.putDecl); }).then(function () { return this.each(declNodes, function (node) { if (this.scope[node.name.value].value.object.become) { return rt.InvalidType .raiseSelfDependencyForType(rt.string(node.name.value)) .bind(this).then(null, this.reportDecl(node)); } }); }); }); }; Interpreter.prototype.decl = function (node) { function evaluate() { if (node.value.constructor === ast.Type) { return this.type(node.value, node.name.value); } return this.expression(node.value); } return this.task(function () { if (node.generics.length !== 0) { // TODO Build a better semantics for recursive types. return this.scoped(function () { return this.each(node.generics, function (parameter) { var name = parameter.value; return this.put(name, this.newVar(name, rt.Unknown), node); }).then(evaluate); }); } return evaluate.call(this); }).then(null, this.reportDecl(node)); }; Interpreter.prototype.putDecl = function (node, pattern) { // TODO Should assert that the value is statically known, not just // that it is a pattern. return this.assert(pattern, rt.Pattern).then(function () { // We need to retain the references of the hoisted values, so we // need to copy the properties of the resulting expression into // the referenced value. var decl, proxy; // This is safe because types cannot be overridden. decl = this.scope[node.name.value]; proxy = decl.value; return proxy.object.become(pattern); }).then(null, this.reportDecl(node)); }; Interpreter.prototype.reportDecl = function (node) { return function (packet) { var trace; // Remove the report about the anonymous type when it appears directly in a // type alias declaration. if (typeof packet.object === "object") { trace = packet.object.stackTrace; if (trace.length > 0 && trace[0].name === "type") { trace.shift(); } } return this.report(packet, "type " + node.name.value, null, node); }; }; Interpreter.prototype.type = function (node, decl) { var i, j, l, name, names, nsignatures, tsignatures; function report(packet) { return this.report(packet, "type", null, node); } nsignatures = node.signatures; names = []; tsignatures = []; for (i = 0, l = nsignatures.length; i < l; i += 1) { name = node.nameOf(i); for (j = 0; j < i; j += 1) { if (names[j] === name) { decl = decl === undefined ? node : rt.string(decl); return rt.InvalidType .raiseDuplicateMethodName_inType([rt.string(name)], [decl]) .bind(this).then(null, report); } } names.push(name); tsignatures.push(this.typeSignature(nsignatures[i])); } return this.resolve(rt.type(tsignatures)); }; Interpreter.prototype.typeSignature = function (signature) { var generics, hasVarArg, i, l, parameters, part, parts; function getValue(node) { return node.value; } function getName(node) { if (node.isVarArg) { hasVarArg = true; return "*" + node.name.value; } return node.name.value; } parts = []; for (i = 0, l = signature.parts.length; i < l; i += 1) { hasVarArg = false; part = signature.parts[i]; generics = util.map(part.generics, getValue); parameters = util.map(part.parameters, getName); parts.push(rt.sigPart(part.name.value, hasVarArg, generics, parameters)); } return rt.signature(parts); }; Interpreter.prototype.bool = function (node, inheriting) { var method = rt[node.value ? "mtrue" : "mfalse"]; if (inheriting !== undefined) { return this.inherit(null, method, inheriting); } return method().bind(this); }; Interpreter.prototype.number = function (node) { return this.resolve(rt.number(node.value)); }; Interpreter.prototype.string = function (node) { return this.resolve(rt.string(node.value)); }; // Handles both synchronous and asynchronous requests. Interpreter.prototype.apply = function () { return rt.apply.apply(null, arguments).bind(this); }; // Handles both synchronous and asynchronous inherit requests. Interpreter.prototype.inherit = function () { return rt.inherit.apply(null, arguments).bind(this); }; Interpreter.prototype.request = function (node, inheriting) { var name, pretty; pretty = node.name(); name = util.uglify(pretty); return this.task(function () { if (node.constructor === ast.UnqualifiedRequest) { return this.unqualifiedRequest(node, name, pretty); } return this.qualifiedRequest(node, name, pretty); }).then(function (pair) { var method, receiver; receiver = pair[0]; method = pair[1]; return this.each(node.parts, function (part) { if (method.isVariable && part.generics.length > 0) { return rt.InvalidRequest.raiseGenericsForVariable(rt.string(name)); } return this.each(part.generics, function (param) { return this.expression(param); }).then(function (generics) { if (part.arguments.length > 0) { if (method.isVariable) { return rt.InvalidRequest.raiseArgumentsForVariable(rt.string(name)); } if (method.isStatic) { return rt.InvalidRequest.raiseArgumentsForType(rt.string(name)); } } return this.each(part.arguments, this.expression) .then(function (args) { args.generics = generics; return args; }); }); }).then(function (args) { return this.task(function () { if (inheriting !== undefined) { return this.inherit(receiver, method, inheriting, args); } return this.apply(receiver, method, args); }).then(null, rt.handleInternalError).then(null, function (packet) { if (node.constructor === ast.QualifiedRequest && node.receiver.constructor === ast.Super) { receiver = "super"; } packet.object.stackTrace.pop(); return this.report(packet, pretty, receiver, node); }); }); }); }; Interpreter.prototype.unqualifiedRequest = function (node, name, pretty) { return this.search(name).then(function (pair) { var l, rec, ref; if (pair === null) { // Produce a more specific error message for missing assignment. l = name.length - 2; if (name.substring(l) === ":=") { // The pretty name has a space in it that moves the index to split // at forward by one, but the increased length of the total string // means that 'l' is still the correct index. pretty = rt.string(pretty.substring(0, l)); return this.search(name.substring(0, l)).then(function (found) { if (found === null) { return rt.UnresolvedRequest .raiseForAssignToUnresolvedName(pretty); } return rt.UnresolvedRequest.raiseForAssignToName(pretty); }); } return rt.UnresolvedRequest.raiseForName(rt.string(pretty)); } rec = pair[0]; ref = pair[1]; if (rec !== null && this.scope.object && rec === this.searchScope("self") && !util.owns(ref, "value")) { return rt.IncompleteObject.raiseForName(rt.string(pretty)); } return pair; }).then(null, function (packet) { return this.report(packet, pretty, null, node); }); }; Interpreter.prototype.qualifiedRequest = function (node, name, pretty) { var context, method, rnode, sup; rnode = node.receiver; if (rnode.constructor === ast.Super) { sup = this.searchScope("super", false); context = this.searchScope("self"); return this.task(function () { if (sup !== null) { if (util.owns(sup, name)) { // This super is attempting to request the method above the one that // was defined when this scope was first entered. method = sup[name]["super"]; } else { // No method with that name had appeared in the object when the // inheritance at this level ocurred. Attempt to recover by pulling // the method directly out of self: if it appears there, then it // must have been defined further up the inheritance chain, so it's // safe to say it's a super method. method = context[name]; } } if (method === undefined) { // Either the method didn't appear on the object at all, or there was // no overridden method to request. return rt.UnresolvedSuperRequest .raiseForName_inObject([rt.string(pretty)], [context]); } return [context, method]; }).bind(this).then(null, function (packet) { return this.report(packet, pretty, "super", node); }); } if (rnode.constructor === ast.Outer) { method = this.searchScope(name, true); if (method === null) { return rt.UnresolvedRequest.raiseForName(rt.string(pretty)) .bind(this).then(null, function (packet) { return this.report(packet, pretty, "outer", node); }); } return [null, method]; } return this.expression(rnode).then(function (receiver) { return rt.lookup(receiver, pretty, rnode.constructor === ast.Self) .bind(this).then(function (foundMethod) { return [receiver, foundMethod]; }, function (packet) { return this.report(packet, pretty, receiver, node); }); }); }; Interpreter.prototype["class"] = function (node) { var object = rt.object(); return this.scoped(object, function () { return this.method(node); }).then(function () { var def, name, string; name = node.name.value; def = this.newVar(name, object, true); string = rt.string(name); object.asString = rt.method("asString", 0, function () { return string; }); return this.put(name, def, node); }); }; Interpreter.prototype.method = function (node) { var body, constructor, init, interpreter, last, method, pretty, signature; pretty = node.signature.name(); signature = node.signature; body = node.body; // Save the state of the surrounding scope at the point where the method // is defined. interpreter = this.clone(); function buildMethod(isInherits, func) { return function (inheriting) { var argParts, clone; argParts = util.slice(arguments, isInherits ? 1 : 0); if (signature.parts.length === 1) { argParts = [argParts]; } // Reclone the interpreter to get a unique scope for this execution. clone = interpreter.clone(); if (this !== null && this !== global && this !== clone.scope.self) { clone.scope.self = this; } return clone.scoped(function () { return new Task(this, function (resolve, reject) { this.parts(signature, argParts, node).then(function (pattern) { var exit, top; // Ensures that the postcondition of the method holds before // exiting the method. exit = function (value) { top["return"] = function () { return rt.InvalidReturn .raiseForCompletedMethod(rt.string(pretty)); }; this.assert(value, pattern).then(function () { resolve(value); }, function (packet) { return this.reportNode(packet, signature.pattern) .then(null, reject); }); return new Task(function () { return; }); }; top = this.scope; top["return"] = exit; top.method = method; return func.call(this, inheriting).bind(this).then(exit, reject); }, reject); }); }).bind(null); }; } return this.signature(signature, pretty).then(function (parts) { method = rt.method(pretty, parts, buildMethod(false, node.constructor === ast.Class ? function () { return this.objectBody(body).bind(null); } : function () { return this.interpret(body).bind(null); })); // Build inheritance mechanism. if (node.constructor === ast.Class) { method.inherit = rt.inheritor(pretty, parts, buildMethod(true, function (inheriting) { return this.objectBody(body, inheriting); })); } else if (body.length > 0) { last = body[body.length - 1]; constructor = last.constructor; if (constructor === ast.Return) { last = last.expression; if (last !== null) { constructor = last.constructor; } } if (constructor === ast.ObjectConstructor) { body = body.concat(); body.pop(); init = body; body = init.concat([last]); method.inherit = rt.inheritor(pretty, parts, buildMethod(true, function (inheriting) { return this.interpret(init).then(function () { return this.object(last, inheriting); }); })); } } // Put the resulting method in the local scope and run annotations. return this.put(pretty, method, node); }); }; // Process a method signature into a runtime parameter count list. Interpreter.prototype.signature = function (signature, pretty) { var hasVarArg, i, j, k, l, param, params, part, parts; function report(packet) { return this.report(packet, "method", null, part); } parts = []; for (i = 0, l = signature.parts.length; i < l; i += 1) { part = signature.parts[i]; params = part.parameters; hasVarArg = false; for (j = 0, k = params.length; j < k; j += 1) { param = params[j]; if (param.isVarArg) { if (hasVarArg) { return rt.InvalidMethod .raiseMultipleVariadicParametersForName(rt.string(pretty)) .bind(this).then(null, report); } hasVarArg = true; } } parts.push([ part.generics.length, hasVarArg ? rt.gte(params.length - 1) : params.length ]); } return this.resolve(parts); }; // Handle the joining of a method and a request by adding generics, evaluating // patterns, and adding parameters, then producing the return pattern. Interpreter.prototype.parts = function (msig, rsig, node) { return this.each(msig.parts, rsig, function (mpart, rpart) { return this.part(mpart, rpart, node); }).then(function () { return this.pattern(msig.pattern); }); }; // Handle the joining of individual parts of a method and a request. Interpreter.prototype.part = function (mpart, rpart, node) { var genLength = mpart.generics.length; // Add generics, and test if they are patterns. return this.generics(mpart.generics, rpart.slice(0, genLength), node) .then(function () { return this.parameters(mpart.parameters, rpart.slice(genLength), node); }); }; // Join a method's generic parameters with the values given by a request. Interpreter.prototype.generics = function (mgens, rgens, node) { return this.each(mgens, rgens, function (mgen, rgen) { if (mgen.value !== "_") { return this.put(mgen.value, this.newVar(mgen.value, rgen), node); } }); }; // Evaluate a method part's parameters and join them with part of a request. Interpreter.prototype.parameters = function (params, args, node) { return this.each(params, function (param, i) { var varArgSize = args.length - params.length + 1; if (param.isVarArg) { args.splice(i, 0, rt.list(args.splice(i, varArgSize))); } }).then(function () { return this.patterns(params).then(function (patterns) { return this.each(params, function (param) { return param.name.value; }).then(function (names) { return this.args(names, patterns, args, node); }); }); }); }; // Evaluate a method part's patterns in the scope of its generic arguments. Interpreter.prototype.patterns = function (parameters) { return this.each(parameters, function (parameter) { var name = parameter.name.value; return this.pattern(parameter.pattern).then(function (pattern) { return rt.named(name, parameter.isVarArg ? rt.listOf(pattern) : pattern); }); }); }; Interpreter.prototype.pattern = function (expression) { if (expression === null) { // No pattern given, default to Unknown. return this.resolve(rt.Unknown); } return this.expression(expression).then(function (pattern) { // Check that it's actually a pattern. return this.assert(pattern, rt.Pattern).then(function () { return pattern; }); }); }; // Join parameters and arguments together. Interpreter.prototype.args = function (names, patterns, args, node) { return this.each(names, patterns, args, function (name, pattern, arg) { return this.assert(arg, pattern).then(function () { if (name !== "_") { return this.put(name, this.newVar(name, arg), node); } }); }); }; Interpreter.prototype.variable = function (node) { return this.pattern(node.pattern).then(function (pattern) { var name, variable; name = node.name.value; variable = this.scope[name]; while (!variable.isVariable) { variable = variable["super"]; } variable.pattern = pattern; if (node.value !== null) { return this.expression(node.value).then(function (value) { return this.assert(value, pattern).then(function () { variable.value = value; }); }); } }).then(function () { return rt.done; }, function (packet) { return this.reportNode(packet, node); }); }; Interpreter.prototype.putVariable = function (node, pattern) { var name, variable; name = node.name.value; variable = this.newVar(name); return this.put(name, variable, node).then(function () { var self, setter; if (node.constructor === ast.Var) { self = this; variable.pattern = pattern; setter = rt.method(name + " :=", 1, function (value) { return self.assert(value, variable.pattern).then(function () { variable.value = value; return rt.done; }); }); setter.isConfidential = true; return this.put(name + " :=", setter, node); } }).then(function () { return variable; }); }; Interpreter.prototype["return"] = function (node) { var exprNode = node.expression; return this.task(function () { if (exprNode === null) { return rt.done; } return this.expression(exprNode); }).then(function (expression) { var exit = this.searchScope("return", false); if (exit === null) { return rt.InvalidReturn.raiseInsideOfObject(); } return exit.call(this, expression).bind(this); }).then(null, function (packet) { return this.report(packet, "return", null, node); }); }; Interpreter.prototype.inherits = function (node) { var context, sup; context = this.self(); sup = {}; util.forProperties(context, function (name, method) { while (method["super"] !== undefined) { method = method["super"]; } sup[name] = method; }); this.scope["super"] = sup; return this.inheriting(node.request, context).then(function (value) { delete this.scope.object; return value; }, function (packet) { return this.report(packet, "inherits " + node.request.name(), null, node); }); }; // Create a new variable accessor that stores the value it is accessing as a // property. Interpreter.prototype.newVar = function (name, value, isPublic) { var variable = rt.method(name, 0, function () { if (util.owns(variable, "value")) { return variable.value; } return rt.UndefinedValue.raiseForName(rt.string(name)); }); if (value !== undefined) { variable.value = value; } variable.isVariable = true; variable.isConfidential = !isPublic; variable.identifier = name; variable.modulePath = this.modulePath; return variable; }; // Create a new type accessor that stores the number of generics as a property. Interpreter.prototype.newType = function (name, generics) { var type, value; value = rt.proxy(name); type = rt.method(name, [[generics, 0]], function () { return rt.withGenerics .apply(null, [name, value].concat(util.slice(arguments))); }); type.value = value; type.isStatic = true; type.modulePath = this.modulePath; return type; }; // scoped(self : Object, action : () -> T) -> Task<T> // Push a new layer and a new self context on to the scope stack, execute an // action, and then pop it off. // // scoped(action : () -> T) -> Task<T> // Push a new layer on to the scope stack, execute an action, and then pop it // off. Interpreter.prototype.scoped = function (context, action) { if (typeof context === "function") { action = context; context = undefined; } this.push(context); return this.task(action).then(function (value) { this.pop(); return value; }, function (reason) { this.pop(); throw reason; }); }; Interpreter.prototype.each = function () { return Task.each.apply(Task, [this].concat(util.slice(arguments))); }; Interpreter.prototype.self = function () { if (util.owns(this.scope, "self")) { return this.scope.self; } return null; }; Interpreter.prototype.put = function (pretty, method, node) { var context, existing, name, sub, top; name = util.uglify(pretty); top = this.scope; // Because method creation happens bottom upwards, if an invalid override // occurs it can't be detected until the super method is evaluated. By saving // the node with the method, the lower, erroneous method can be reported // rather than the non-erroneous super method. method.node = node; return this.task(function () { if (util.owns(top, name)) { existing = top[name] && top[name].identifier || pretty; return rt.Redefinition.raiseForName(rt.string(existing)); } context = this.self(); if (context === null) { top[name] = method; } else if (context[name] !== undefined) { if (util.owns(context, name)) { sub = context[name]; } else { sub = method; method = context[name]; context[name] = sub; } if (method.isStatic) { node = sub.node; return rt.InvalidMethod.raiseStaticOverrideForName(rt.string(pretty)); } if (sub.isVariable) { node = sub.node; return rt.InvalidMethod .raiseOverridingVariableForName(rt.string(pretty)); } while (util.owns(sub, "super")) { sub = sub["super"]; } if (!rt.isSubMethod(sub.parts, method.parts)) { node = sub.node; return rt.InvalidMethod .raiseMismatchedParametersForName(rt.string(pretty)); } sub["super"] = method; } else { context[name] = method; } top[name] = method; }).bind(this).then(null, function (packet) { return this.reportNode(packet, node); }); }; Interpreter.prototype.push = function (context) { var frame = {}; if (context !== undefined) { frame.self = context; } frame.outer = this.scope; this.scope = frame; }; Interpreter.prototype.pop = function () { this.scope = this.scope.outer; }; // Search for a value with the given name on self or in scope. Interpreter.prototype.search = function (name) { var context, frame; function pair(method) { return [context, method]; } for (frame = this.scope; frame !== null; frame = frame.outer) { if (util.owns(frame, "self")) { context = frame.self; if (context[name] !== undefined) { return rt.lookup(context, name, true).bind(this).then(pair); } } if (util.owns(frame, name)) { return this.resolve([null, frame[name]]); } } return this.resolve(null); }; // Find definitions stored in scope without searching through self. Takes an // optional boolean where false indicates that the search should stop once // it encounters a self value, and true indicates that the search should begin // after the first self value. Interpreter.prototype.searchScope = function (name, passSelf) { var frame; for (frame = this.scope; frame !== null; frame = frame.outer) { if (!passSelf && util.owns(frame, name)) { return frame[name]; } if (util.owns(frame, "self")) { if (frame.outer === null && frame.self[name] !== undefined) { return frame.self[name]; } if (passSelf === false) { return null; } if (passSelf === true) { passSelf = undefined; } } } return null; }; // Resolve to a task with this Interperter as the context. Interpreter.prototype.resolve = function (value) { return Task.resolve(this, value); }; // Safely wrap an action as a task. Interpreter.prototype.task = function (action) { return this.resolve(null).then(function () { return action.call(this); }); }; Interpreter.prototype.raise = function (message) { return rt.InternalError.raise(rt.string(message)).bind(this); }; Interpreter.prototype.report = function (packet, name, object, node) { return this.task(function () { return rt.handleInternalError(packet); }).then(null, function (internalError) { internalError.object.stackTrace.push(rt.trace(name, object, { "module": this.modulePath || null, "line": node.location.line, "column": node.location.column })); throw internalError; }); }; Interpreter.prototype.reportNode = function (packet, node) { var type; if (node.constructor === ast.Def) { type = "def " + node.name.value; } else if (node.constructor === ast.Var) { type = "var " + node.name.value; } else if (node.constructor === ast.Method) { type = "method " + node.signature.name(); } else if (node.constructor === ast.Class) { type = "class " + node.name.value; } else if (node.constructor === ast.TypeDeclaration) { type = "type " + node.name.value; } else if (node.constructor === ast.Import) { type = 'import "..." as ' + node.identifier.value; } else { type = node.toString(); } return this.report(packet, type, null, node); }; exports.Interpreter = Interpreter;