UNPKG

hopper

Version:

An interpreter for the Grace programming language

443 lines (364 loc) 12.7 kB
// Runtime definitions that are independent of an Interpreter instance. "use strict"; var Task, defs, util; Task = require("./task"); util = require("./util"); function trace(name, object, location) { return { "name": name, "object": object, "location": location || null }; } // gte(count : Number) -> GTE // Represents a minimum number of parameters. function gte(count) { return { "minimum": count }; } function part(generics, args) { args.generics = generics; return args; } function handleInternalError(error) { if (!defs.isGraceExceptionPacket(error)) { return defs.InternalError.raiseFromPrimitiveError(error); } throw error; } // method(name : String, // parameters : Count = gte(0), func : Function) -> Function // Create a single part method of a certain parameter count. // // method(name : String, // parts : [Count | (generics : Number, parameters : Count = gte(0))], // func : Function) -> Function // Create an anypart method where each part has a certain generic count and // parameter count. // // where Count = Number | GTE function method(name, partCounts, func) { var body, i, isGeneric, isMulti, partsLength, unnormalised; if (arguments.length < 3) { func = partCounts; partCounts = [gte(0)]; } if (!util.isArray(partCounts)) { partCounts = [partCounts]; } partsLength = partCounts.length; isMulti = partCounts.length > 1; isGeneric = false; for (i = 0; i < partsLength; i += 1) { unnormalised = partCounts[i]; if (util.isArray(unnormalised)) { if (unnormalised.length === 1) { unnormalised[1] = gte(0); } } else { partCounts[i] = [0, unnormalised]; } if (unnormalised[0] > 0) { isGeneric = true; } } body = function () { var argParts, argsLength, first, self; argsLength = arguments.length; argParts = util.slice(arguments); self = this; if (partCounts.length === 1) { first = argParts[0]; if (!(util.isArray(first) && util.owns(first, "generics"))) { argsLength = 1; argParts = [argParts]; } } // The next two errors can't be caused by the interpreter without an // incorrect method definition in JavaScript. if (argsLength < partsLength) { throw new TypeError('Not enough parts for method "' + name + '"'); } if (argsLength > partsLength) { throw new TypeError('Too many parts for method "' + name + '"'); } return Task.each(partCounts, argParts, function (partCount, argPart) { if (typeof partCount[1] === "number" && argPart.length < partCount[1] || argPart.length < partCount[1].minimum) { return defs.InvalidRequest .raiseNotEnoughArgumentsForMethod(defs.string(name)); } if (typeof partCount[1] === "number" && argPart.length > partCount[1]) { return defs.InvalidRequest .raiseTooManyArgumentsForMethod(defs.string(name)); } if (util.isArray(argPart.generics) && argPart.generics.length !== 0) { if (argPart.generics.length < partCount[0]) { return defs.InvalidRequest .raiseNotEnoughGenericArgumentsForMethod(defs.string(name)); } if (argPart.generics.length > partCount[0]) { return defs.InvalidRequest .raiseTooManyGenericArgumentsForMethod(defs.string(name)); } return Task.each(argPart.generics, function (generic) { return defs.Pattern.assert(generic); }).then(function () { return argPart.generics.concat(argPart); }); } if (isGeneric) { // No generics given in the request. Default to Unknown. return util.replicate(partCount[0], defs.Unknown).concat(argPart); } return argPart; }).then(function (args) { if (!isMulti) { args = args[0]; } return func.apply(self, args); }).then(function (value) { if (value === null || value === undefined) { return defs.InternalError.raise(defs .string("Method " + body + " returned an undefined value")); } return value; }, handleInternalError).then(null, function (packet) { packet.object.stackTrace.push(trace(name, self)); throw packet; }); }; body.isGraceMethod = true; body.identifier = name; body.isAsynchronous = true; body.parts = partCounts; body.toString = function () { return "«" + name + "»"; }; return body; } // inheritor(name : String, // parameters : Count = gte(0), func : Function) -> Function // Create a single part inheritor of a certain parameter count. // // inheritor(name : String, // parts : [Count | (generics : Number, parameters : Count = gte(0))], // func : Function) -> Function // Create an anypart inheritor where each part has a certain generic count and // parameter count. // // where Count = Number | GTE function inheritor(name, parts, func) { return method(name, [1].concat(parts), function (inheriting) { var args = util.slice(arguments, 1); if (!util.isArray(parts) || parts.length === 1) { args = args[0]; } return func.apply(this, [inheriting[0]].concat(args)); }); } // constructor(name : String, // parameters : Count = gte(0), func : Function) -> Function // Create a single part constructor of a certain parameter count. // // constructor(name : String, // parts : [Count | (generics : Number, parameters : Count = gte(0))], // func : Function) -> Function // Create an anypart constructor where each part has a certain generic count // and parameter count. // // where Count = Number | GTE function constructor(name, parts, func) { var body = method(name, parts, function () { return func.apply(this, [null].concat(util.slice(arguments))); }); body.inherit = inheritor(name, parts, func); return body; } function asPrimitive(object) { return Task.resolve(typeof object.asPrimitive === "function" ? object.asPrimitive() : object); } function fromPrimitive(value) { if (typeof value === "boolean") { return defs.bool(value); } if (typeof value === "number") { return defs.number(value); } if (typeof value === "string") { return defs.string(value); } if (typeof value === "function") { return defs.block(value); } if (util.isArray(value)) { return defs.list(value); } if (value === undefined || value === null) { return defs.done; } return value; } function lookup(receiver, pretty, fromSelf) { var func, l, name, object, orig, type; name = util.uglify(pretty); func = receiver[name]; if (!defs.isGraceObject(receiver) && (typeof func !== "function" || !func.isGraceMethod)) { if (typeof func === "function") { if (!func.isGraceMethod) { orig = func; func = method(pretty, function () { var self = this; return Task.each(util.slice(arguments), asPrimitive) .then(function (args) { return orig.apply(self, args); }).then(fromPrimitive); }); } } else if (pretty === "asString") { // Use the regular toString in place of asString. func = method("asString", 0, function () { return defs.string(this.toString()); }); } else if (pretty === "at()") { func = method("at()", 1, function (index) { var self = this; return defs.asString(index).then(function (primIndex) { return fromPrimitive(self[primIndex]); }); }); } else if (pretty === "at() put()") { func = method("at() put()", [1, 1], function (index, value) { var self = this; return defs.asString(index).then(function (primIndex) { return asPrimitive(value).then(function (primValue) { self[primIndex] = primValue; return defs.done; }); }); }); } else { l = name.length - 2; if (name.substring(l) === ":=") { name = name.substring(0, l); orig = receiver[name]; // Produce a setter. This provides a mechanism for overwriting functions // in the object, which means you could assign a Grace block and have it // appear as a method rather than an object. You could replicate this // behaviour in Grace anyway, and JavaScript objects are always going to // appear a little wonky in Grace, so it's considered acceptable. if (typeof orig !== "function" || !orig.isGraceMethod) { func = method(pretty, 1, function (value) { return asPrimitive(value).then(function (primValue) { receiver[name] = primValue; return defs.done; }); }); } } else { func = receiver[name]; if (func === undefined) { type = typeof receiver; if (type === "object" && util.isArray(receiver)) { type = "list"; } if (type !== "object") { object = defs[type === "boolean" ? "bool" : type](receiver); orig = object[name]; if (typeof orig === "function") { func = method(orig.identifer, orig.parts, function () { return orig.apply(object, arguments).then(fromPrimitive); }); } } func = func || defs.base[name]; } else if (func !== null) { if (typeof func !== "function") { // Produce a getter. We use name here because there must not be // parentheses on the method. func = method(name, 0, function () { return fromPrimitive(receiver[name]); }); } } } } } if (typeof func !== "function" || defs.isGraceObject(receiver) && func === Object.prototype[name] || typeof func === "function" && func.internal) { return defs.UnresolvedRequest .raiseForName_inObject([defs.string(pretty)], [receiver]); } if (!fromSelf && func.isConfidential) { return defs.UnresolvedRequest .raiseConfidentialForName_inObject([defs.string(pretty)], [receiver]); } return Task.resolve(func); } function call(receiver, meth, args) { try { return Task.resolve(meth.apply(receiver, args)) .then(null, handleInternalError); } catch (reason) { return Task.reject(handleInternalError(reason)); } } // Asynchronous method application that works for either synchronous or // asynchronous methods. function apply(receiver, meth, args) { if (typeof meth === "string") { return lookup(receiver, meth).then(function (foundMethod) { return apply(receiver, foundMethod, args); }); } if (args === undefined) { // The user may optionally pass no arguments, signifying a call to a // single-part method with no arguments. args = []; } else if (args.length === 1 && !util.owns(args[0], "generics")) { // If the call is to a single-part method with arguments but no generics, it // needs to be removed from the part array to avoid confusing it with a // single-argument array. Removing is equivalent to constructing a true // 'part' with the part function from above, but avoids having to create an // empty generic list. args = args[0]; } return call(receiver, meth, args); } // Asynchronous inherits method application that works for either synchronous or // asynchronous methods. The call throws if the method cannot be inherited from. function inherit(receiver, meth, inheriting, args) { if (typeof meth === "string") { return lookup(receiver, meth).then(function (foundMethod) { return inherit(receiver, foundMethod, inheriting, args); }); } if (typeof meth.inherit !== "function") { return defs.InvalidInherits.raiseForName(defs.string(meth.identifier)); } if (args === undefined) { // As above, but inherited methods are always multi-part due to the // invisible part that takes the inheriting object inserted at the start. args = [[]]; } args.unshift([inheriting]); return call(receiver, meth.inherit, args); } exports.lookup = lookup; exports.handleInternalError = handleInternalError; exports.apply = apply; exports.inherit = inherit; exports.part = part; exports.gte = gte; exports.trace = trace; exports.method = method; exports.inheritor = inheritor; exports.constructor = constructor; defs = require("./runtime/definitions"); util.extend(exports, defs); exports.primitives = require("./runtime/primitives"); exports.prelude = require("./runtime/prelude");