24 changed files with 3047 additions and 1 deletions
Binary file not shown.
Binary file not shown.
@ -0,0 +1,135 @@ |
|||||
|
/* |
||||
|
* Defines the data structures associated with Continuation Expressions (cexps) |
||||
|
* The object here is to have an intermediate representation that allows a code generator backend |
||||
|
* to easily create sequential code. In other words we are taking an alegraic/applicative language |
||||
|
* and translating it to something more similar to the "one word at a time" architecture of the von-Neumann |
||||
|
* architecture |
||||
|
*/ |
||||
|
|
||||
|
var cexp = { |
||||
|
type : "cexp" |
||||
|
}; |
||||
|
|
||||
|
function record(values_accesspaths, |
||||
|
w, |
||||
|
next_cexp) { |
||||
|
this.values_accesspaths = values_accesspaths; |
||||
|
this.w = w; |
||||
|
this.next_cexp = next_cexp; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
record.prototype = cexp; |
||||
|
|
||||
|
function select(i, v, w, next_cexp) { |
||||
|
this.i = i; |
||||
|
this.v = v; |
||||
|
this.w = w; |
||||
|
this.next_cexp = next_cexp; |
||||
|
return this; |
||||
|
} |
||||
|
select.prototype = cexp; |
||||
|
|
||||
|
function offset(i, v, w, next_cexp) { |
||||
|
this.i = i; |
||||
|
this.v = v; |
||||
|
this.w = w; |
||||
|
this.next_cexp = next_cexp; |
||||
|
return this; |
||||
|
} |
||||
|
offset.prototype = cexp; |
||||
|
|
||||
|
function app(k, vs) { |
||||
|
this.k = k; |
||||
|
this.vs = vs; |
||||
|
return this; |
||||
|
} |
||||
|
app.prototype = cexp; |
||||
|
|
||||
|
function fix(fs, next_cexp) { |
||||
|
this.fs = fs; |
||||
|
this.next_cexp = next_cexp; |
||||
|
return this; |
||||
|
} |
||||
|
fix.prototype = cexp; |
||||
|
|
||||
|
function switchl(v, cexps) { |
||||
|
this.v = v; |
||||
|
this.cexps = cexps; |
||||
|
return this; |
||||
|
} |
||||
|
switchl.prototype = cexp; |
||||
|
|
||||
|
function primop(op, vals, vars, next_cexp) { |
||||
|
this.op = op; |
||||
|
this.vals = vals; |
||||
|
this.vars = vars; |
||||
|
this.next_cexp = next_cexp; |
||||
|
return this; |
||||
|
} |
||||
|
primop.prototype = cexp; |
||||
|
|
||||
|
function accessPath(offp, selp) { |
||||
|
this.offp = offp; |
||||
|
this.selp = selp; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
var primoptype = { |
||||
|
type : "primop", |
||||
|
equal : function(pOp) { |
||||
|
return this.name === pOp.name; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function Primop(name) { |
||||
|
function ptype() { |
||||
|
this.name = name; |
||||
|
return this; |
||||
|
} |
||||
|
ptype.prototype = primoptype; |
||||
|
return new ptype(); |
||||
|
} |
||||
|
|
||||
|
var times = Primop("+"); |
||||
|
var plus = Primop("+"); |
||||
|
var div = Primop("div"); |
||||
|
var tilde = Primop("~"); |
||||
|
var ieql = Primop("ieql"); |
||||
|
var ineq = Primop("ineq"); |
||||
|
var lessthan = Primop("<"); |
||||
|
var lessoreq = Primop("<="); |
||||
|
var greatthan = Primop(">"); |
||||
|
var greatoreq = Primop(">="); |
||||
|
var bang = Primop("!"); |
||||
|
var subscript = Primop("subscript"); |
||||
|
var ordof = Primop("ordof"); |
||||
|
var assign = Primop(":="); |
||||
|
var unboxedassign = Primop("unboxedassign"); |
||||
|
var update = Primop("update"); |
||||
|
var unboxedupdate = Primop("unboxedupdate"); |
||||
|
var store = Primop("store"); |
||||
|
var makeref = Primop("makeref"); |
||||
|
var makerefunboxed = Primop("makerefunboxed"); |
||||
|
var alength = Primop("alength"); |
||||
|
var slength = Primop("slength"); |
||||
|
var gethdlr = Primop("gethdlr"); |
||||
|
var sethdlr = Primop("sethdlr"); |
||||
|
var boxed = Primop("boxed"); |
||||
|
var fadd = Primop("fadd"); |
||||
|
var fsub = Primop("fsub"); |
||||
|
var fdiv = Primop("fdiv"); |
||||
|
var fmul = Primop("fmul"); |
||||
|
var feql = Primop("feql"); |
||||
|
var fneq = Primop("fneq"); |
||||
|
var fge = Primop("fge"); |
||||
|
var fgt = Primop("fgt"); |
||||
|
var fle = Primop("fle"); |
||||
|
var flt = Primop("flt"); |
||||
|
var rshift = Primop("rshift"); |
||||
|
var lshift = Primop("lshift"); |
||||
|
var orb = Primop("orb"); |
||||
|
var andb = Primop("andb"); |
||||
|
var xorb = Primop("xorb"); |
||||
|
var notb = Primop("notb"); |
||||
|
|
@ -0,0 +1,158 @@ |
|||||
|
/* Takes an AST and converts all of the functions into "closures" |
||||
|
* A closure is a triple of: |
||||
|
* the bound variables in a function or let |
||||
|
* the free variables in a function or let |
||||
|
* a function body or let body and bound values (if it is an escaping closure only) |
||||
|
* The closure has the property that all of the free variables of the function or let |
||||
|
* are in the environment, or an exception is raised because the variable is not bound |
||||
|
* in the current environment. |
||||
|
* A free variable is simply those that are not in the list of formal parameters or bound variables if it is a let |
||||
|
* |
||||
|
* Therefore in order to call a closure one must first extract the actual function and then |
||||
|
* call the function with the environment associated with it. |
||||
|
* For the purposes of type checking it does not matter how the function gets called, the environment |
||||
|
* is only used for looking up the types of names. Formal parameters are given type variables. |
||||
|
* |
||||
|
* The first phase of closure conversion is not really closure conversion exactly. |
||||
|
* All it does is find out the free variables in scope and tie those up into a data structure with their types later. |
||||
|
* The second phase will be done to the CPS language and closures will actually lambda-lifted out potentially. |
||||
|
*/ |
||||
|
|
||||
|
var rep = require("./representation.js"); |
||||
|
var errors = require("./errors.js"); |
||||
|
var parser = require("./parse.js"); |
||||
|
var $ = require("./tools.js"); |
||||
|
var _ = require("underscore"); |
||||
|
|
||||
|
var notEmpty = _.compose($.not, _.partial(_.equal, [])); |
||||
|
|
||||
|
function fvs(stx) { |
||||
|
switch (stx.exprType) { |
||||
|
case "Integer": |
||||
|
return []; |
||||
|
case "Float": |
||||
|
return []; |
||||
|
case "String": |
||||
|
return []; |
||||
|
case "Function": |
||||
|
return []; |
||||
|
case "Nil": |
||||
|
return []; |
||||
|
case "Bool": |
||||
|
return []; |
||||
|
case "Let": |
||||
|
return []; |
||||
|
case "Unary": |
||||
|
return _.flatten([stx.op.ident, fvs(stx.val)]); |
||||
|
case "Definition": |
||||
|
return _.flatten(fvs(stx.val)); |
||||
|
case "Application": |
||||
|
var vs = _.flatten(fvs(stx.p)); |
||||
|
var f_fvs = _.flatten(fvs(stx.func)); |
||||
|
return _.flatten([vs, f_fvs]); |
||||
|
case "If": |
||||
|
if (stx.elseexp) { |
||||
|
var cond_fvs = fvs(stx.condition); |
||||
|
var then_fvs = fvs(stx.thenexp); |
||||
|
var else_fvs = fvs(stx.elseexp); |
||||
|
return _.flatten([cond_fvs, then_fvs, else_fvs]); |
||||
|
} |
||||
|
else { |
||||
|
return _.flatten([fvs(stx.condition), fvs(stx.thenexp)]); |
||||
|
} |
||||
|
break; |
||||
|
case "Name": |
||||
|
return [stx.ident]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function annotate_fvs(stx) { |
||||
|
/* Takes a stx object that is either |
||||
|
* a lambda |
||||
|
* a let |
||||
|
* and returns a closure wrapped around that stx object |
||||
|
*/ |
||||
|
if (stx.exprType !== "Function" && |
||||
|
stx.exprType !== "Let") { |
||||
|
throw errors.JInternalError( |
||||
|
["Tried to calculate the free variables of", |
||||
|
"something that was not a function or let.\n", |
||||
|
"That something was a: " + stx.exprType +"\n"].reduce( |
||||
|
function (a,b) { |
||||
|
return a+" "+b; |
||||
|
}, "")); |
||||
|
} |
||||
|
var variables, free_variables, bound_vars, stx_type; |
||||
|
|
||||
|
switch (stx.exprType) { |
||||
|
case "Let": |
||||
|
bound_vars = stx.pairs.map( |
||||
|
function (stx) { |
||||
|
return stx.ident.ident; |
||||
|
}); |
||||
|
var let_fvs = stx.pairs.map(fvs); |
||||
|
var body_fvs = fvs(stx.body); |
||||
|
variables = _.flatten(let_fvs); |
||||
|
$.extend(variables, _.flatten(body_fvs)); |
||||
|
break; |
||||
|
case "Function": |
||||
|
bound_vars = [stx.p.ident,]; |
||||
|
variables = fvs(stx.body); |
||||
|
break; |
||||
|
} |
||||
|
free_variables = _.difference(_.uniq(variables), bound_vars); |
||||
|
return new rep.Closure(bound_vars, free_variables, stx, []); |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
* This traverse the tree and gathers up all of the free variables of various functions/let bindings |
||||
|
*/ |
||||
|
function annotate_fvs_all(stx) { |
||||
|
var closure; |
||||
|
switch (stx.exprType) { |
||||
|
case "Let": |
||||
|
closure = annotate_fvs(stx); |
||||
|
closure.body.pairs = closure.body.pairs.map(annotate_fvs_all); |
||||
|
closure.body = annotate_fvs_all(closure.body.body); |
||||
|
return closure; |
||||
|
case "Function": |
||||
|
closure = annotate_fvs(stx); |
||||
|
closure.body.body = annotate_fvs_all(closure.body.body); |
||||
|
return closure; |
||||
|
case "Unary": |
||||
|
stx.val = annotate_fvs_all(stx.val); |
||||
|
return stx; |
||||
|
case "Application": |
||||
|
stx.func = annotate_fvs_all(stx.func); |
||||
|
stx.p = annotate_fvs_all(stx.p); |
||||
|
return stx; |
||||
|
case "If": |
||||
|
if (stx.elseexp) { |
||||
|
stx.condition = annotate_fvs_all(stx.condition); |
||||
|
stx.thenexp = annotate_fvs_all(stx.thenexp); |
||||
|
stx.elseexp = annotate_fvs_all(stx.elseexp); |
||||
|
return stx; |
||||
|
} |
||||
|
else { |
||||
|
stx.condition = annotate_fvs_all(stx.condition); |
||||
|
stx.thenexp = annotate_fvs_all(stx.thenexp); |
||||
|
return stx; |
||||
|
} |
||||
|
break; |
||||
|
case "Definition": |
||||
|
stx.val = annotate_fvs_all(stx.val); |
||||
|
return stx; |
||||
|
default: |
||||
|
return stx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function test(src) { |
||||
|
var ast = parser.parse(src); |
||||
|
} |
||||
|
|
||||
|
//console.log(test("if something then if a then if b then c else d else rtrrt else some_other_thing"));
|
||||
|
module.exports = { |
||||
|
annotate_fvs : annotate_fvs_all |
||||
|
}; |
@ -0,0 +1,5 @@ |
|||||
|
#! /usr/bin/node
|
||||
|
|
||||
|
var typ = require("representation.js"); |
||||
|
|
||||
|
|
@ -0,0 +1,141 @@ |
|||||
|
/* |
||||
|
* This module takes a parse tree in a surface format |
||||
|
* and transforms it into the "core" language which is |
||||
|
* much simpler and easier to type-check, optimize, and evaluate |
||||
|
*/ |
||||
|
|
||||
|
var typ = require("./representation.js"); |
||||
|
var errors = require("./errors.js"); |
||||
|
var _ = require("underscore"); |
||||
|
|
||||
|
// Lists get desugared to nested function calls
|
||||
|
// i.e. (cons (cons (cons ...)))
|
||||
|
function desugarList(lst) { |
||||
|
if (lst.xs.length <= 0) { |
||||
|
return new typ.Nil(); |
||||
|
} |
||||
|
else { |
||||
|
var x = desugar(lst.xs[0]); |
||||
|
var rest = lst.xs.slice(1); |
||||
|
return new typ.App(new typ.App(new typ.Name("(:)"), x), desugarList(new typ.ListT(rest))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function desugarDefFunc(def) { |
||||
|
return new typ.Def(def.ident, |
||||
|
curryFunc(def.params, |
||||
|
def.body)); |
||||
|
} |
||||
|
|
||||
|
function curryFunc(ps, body) { |
||||
|
var result; |
||||
|
if (_.isEmpty(ps)) { |
||||
|
return desugar(body); |
||||
|
} |
||||
|
else { |
||||
|
result = new typ.FuncT(desugar(_.first(ps)), |
||||
|
curryFunc(_.rest(ps), body)); |
||||
|
result.charnum = ps.charnum; |
||||
|
result.linenum = ps.linenum; |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function desugarLet(stx) { |
||||
|
var values = stx.pairs.map(desugar); |
||||
|
return new typ.LetExp(values, desugar(stx.body)); |
||||
|
} |
||||
|
|
||||
|
function sugarTypeDecl(stx) { |
||||
|
var type; |
||||
|
var expression; |
||||
|
type = stx.p; |
||||
|
expression = desugar(stx.func.p); |
||||
|
expression.linenum = stx.linenum; |
||||
|
expression.charnum = stx.charnum; |
||||
|
return new typ.TypeDecl(expression, type); |
||||
|
} |
||||
|
|
||||
|
function desugarDefType(stx, typeEnv) { |
||||
|
var result; |
||||
|
var rhs = desugar(stx.rhs); |
||||
|
var name = stx.lhs.name; |
||||
|
typeEnv[name] = rhs; |
||||
|
|
||||
|
result = new typ.DefType(stx.lhs, desugar(stx.rhs)); |
||||
|
result.linenum = stx.linenum; |
||||
|
result.charnum = stx.charnum; |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function desugar(stx, typeEnv) { |
||||
|
var typeExpTest; |
||||
|
|
||||
|
switch (stx.exprType) { |
||||
|
case "If": |
||||
|
if (stx.elseexp) { |
||||
|
return new typ.If(desugar(stx.condition, typeEnv), desugar(stx.thenexp, typeEnv), desugar(stx.elseexp, typeEnv)); |
||||
|
} |
||||
|
return new typ.If(desugar(stx.condition, typeEnv), desugar(stx.thenexp, typeEnv)); |
||||
|
case "FunctionDefinition": |
||||
|
return desugarDefFunc(stx); |
||||
|
case "Definition": |
||||
|
return new typ.Def(stx.ident, desugar(stx.val, typeEnv)); |
||||
|
case "TypeDefinition": |
||||
|
return desugarDefType(stx, typeEnv); |
||||
|
case "Name": |
||||
|
return stx; |
||||
|
case "Application": |
||||
|
if ((stx.func.func !== undefined ) && |
||||
|
(stx.func.func.ident === "::")) { |
||||
|
/* It's a type declaration probably (will be verified later) |
||||
|
* In this case we actually *add* syntax here to differentiate type declarations |
||||
|
* from normal function application |
||||
|
*/ |
||||
|
typeExpTest = typ.isTypeExpr(stx.p); |
||||
|
|
||||
|
if (typeExpTest.failed !== undefined && |
||||
|
typeExpTest.failed) { |
||||
|
throw errors.JInternalError( |
||||
|
"Type declaration error near line " + stx.linenum + " at character #"+stx.charnum + |
||||
|
"\n"+typeExpTest.stx.exprType+" (" + typeExpTest.stx.val + ") found where a type operator or type application was expected"); |
||||
|
} |
||||
|
return sugarTypeDecl(stx); |
||||
|
} |
||||
|
|
||||
|
if ((stx.func.ident === "-" || |
||||
|
stx.func.ident === "+") && |
||||
|
stx.p) { |
||||
|
return new typ.UnaryOp(desugar(stx.func, typeEnv), desugar(stx.p, typeEnv)); |
||||
|
} |
||||
|
if (stx.p) { |
||||
|
return new typ.App(desugar(stx.func, typeEnv), desugar(stx.p, typeEnv)); |
||||
|
} |
||||
|
return new typ.App(stx.func); |
||||
|
case "Function": |
||||
|
return curryFunc(stx.p, stx.body); |
||||
|
case "List": |
||||
|
return desugarList(stx); |
||||
|
case "Bool": |
||||
|
return stx; |
||||
|
case "String": |
||||
|
return stx; |
||||
|
case "Float": |
||||
|
return stx; |
||||
|
case "Integer": |
||||
|
return stx; |
||||
|
case "Let": |
||||
|
return desugarLet(stx); |
||||
|
default: |
||||
|
return stx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { desugar : desugar }; |
||||
|
//var test = typ.ListT([1,2,3]);
|
||||
|
|
||||
|
//console.log(desugarList(test));
|
||||
|
|
||||
|
|
@ -0,0 +1,33 @@ |
|||||
|
/* |
||||
|
* An environment is just an object that maps identifiers to JLambda expressions |
||||
|
* with a few built-in (a standard Prelude environment) |
||||
|
*/ |
||||
|
|
||||
|
var errors = require("./errors.js"); |
||||
|
var rep = require("./representation.js"); |
||||
|
|
||||
|
// creates a new environment initialized with the pairs in values
|
||||
|
function makeEnv(name, values) { |
||||
|
var env = {}; |
||||
|
env.name = name; |
||||
|
env.bindings = {}; |
||||
|
for (var i = 0; i < values.length; i++) { |
||||
|
name = values[i][0]; |
||||
|
var val = values[i][1]; |
||||
|
env.bindings[name] = val; |
||||
|
} |
||||
|
return env; |
||||
|
} |
||||
|
|
||||
|
function lookup(name, env) { |
||||
|
var value = env.bindings[name]; |
||||
|
if (!value) { |
||||
|
throw errors.JUnboundError(name, env.name); |
||||
|
} |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
lookup : lookup, |
||||
|
makeEnv : makeEnv |
||||
|
}; |
@ -0,0 +1,38 @@ |
|||||
|
/* |
||||
|
* This file defines common error objects |
||||
|
* for reporting on syntax errors, type errors, |
||||
|
* and perhaps runtime exceptions although I have |
||||
|
* not thought about how that will work much |
||||
|
*/ |
||||
|
|
||||
|
function JSyntaxError(linenum, charnum, message) { |
||||
|
this.linenum = linenum; |
||||
|
this.charnum = charnum; |
||||
|
this.errormessage = message; |
||||
|
this.stxerror = function() { |
||||
|
console.log("Syntax Error\n", |
||||
|
"Line #", this.linenum,"\n", |
||||
|
"Near character #", this.charnum, "\n", |
||||
|
this.errormessage); |
||||
|
}; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
function JTypeError(linenum, charnum, token, message) { |
||||
|
this.linenum = linenum; |
||||
|
this.charnum = charnum; |
||||
|
this.errormessage = message; |
||||
|
this.token = token; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
function JInternalError(message) { |
||||
|
this.errormessage = message; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
module.exports = |
||||
|
{JSyntaxError : JSyntaxError, |
||||
|
JTypeError : JTypeError, |
||||
|
JInternalError : JInternalError |
||||
|
}; |
@ -0,0 +1,94 @@ |
|||||
|
def foo# 3 |
||||
|
|
||||
|
deftype Foo (A -> B) |
||||
|
|
||||
|
;; here is a comment |
||||
|
; here is another comment |
||||
|
|
||||
|
deftype (Foo a b) |
||||
|
(a -> b) |
||||
|
|
||||
|
(qat :: A -> b) |
||||
|
def tdeftype (lambda a b c -> (a + b)) |
||||
|
|
||||
|
def (add a b) |
||||
|
(a + b) |
||||
|
|
||||
|
def wat [[1,2,3], [4,5,6]] |
||||
|
|
||||
|
def (catstrs strs) |
||||
|
(foldr f |
||||
|
(head strs) |
||||
|
(tail strs)) |
||||
|
|
||||
|
def strs ["aa", "bb"] |
||||
|
|
||||
|
def (mymap f xs) |
||||
|
if ((length xs) == 0) |
||||
|
then |
||||
|
xs |
||||
|
else |
||||
|
((f (head xs)) |
||||
|
: (mymap f (tail xs))) |
||||
|
|
||||
|
def empty [] |
||||
|
|
||||
|
def getFile |
||||
|
(readFile "./parse.js") |
||||
|
|
||||
|
;;def fileLines |
||||
|
;; (getFile >>= |
||||
|
;; ((mapM_ putStrLn) . lines)) |
||||
|
|
||||
|
def (testUnary n) |
||||
|
((-n) + n) |
||||
|
|
||||
|
def (foo bar) |
||||
|
let { |
||||
|
lol = [1, |
||||
|
(lambda qwerty blah -> |
||||
|
[qerty, blah, |
||||
|
(lambda whatever -> whatever)])] |
||||
|
} |
||||
|
if bar |
||||
|
then [lol,(- 1.2),"lulz",lol] |
||||
|
else if something |
||||
|
then [,] |
||||
|
else |
||||
|
somethingelse |
||||
|
|
||||
|
def (splitHelp acc xs ys) |
||||
|
if (null xs) |
||||
|
then ((reverse acc), ys) |
||||
|
else if (null (tail xs)) |
||||
|
then ((reverse acc), ys) |
||||
|
else |
||||
|
(splitHelp ((head ys) : acc) |
||||
|
(tail (tail xs)) |
||||
|
(tail ys)) |
||||
|
|
||||
|
def (splitxs xs) |
||||
|
(splitHelp [] xs xs) |
||||
|
|
||||
|
def r def |
||||
|
{ |
||||
|
a = 4 |
||||
|
} |
||||
|
a |
||||
|
|
||||
|
def main |
||||
|
let { |
||||
|
(f a) -> a |
||||
|
unary = (print (testUnary 6)) |
||||
|
splitted = def { |
||||
|
xs = (fst (splitxs [12,3,4,56])) |
||||
|
} (xs ++ [0,9]) |
||||
|
} |
||||
|
if False |
||||
|
then superduper |
||||
|
else |
||||
|
(unary + |
||||
|
fileLines + |
||||
|
(print splitted)) |
||||
|
|
||||
|
def blah (3 / 4) |
@ -0,0 +1,21 @@ |
|||||
|
def (fib n) |
||||
|
if (n == 0) |
||||
|
then 0 |
||||
|
else if (n == 1) |
||||
|
then 1 |
||||
|
else |
||||
|
((fib (n-1)) + |
||||
|
(fib (n-2))) |
||||
|
|
||||
|
def (fact n) |
||||
|
if (n == 0) |
||||
|
then 1 |
||||
|
else |
||||
|
((fact (n-1)) * n) |
||||
|
|
||||
|
def ns [1,2,3,4,5] |
||||
|
|
||||
|
def main |
||||
|
((print (fib 19)) >> |
||||
|
(print (ns >>= (return . fact)))) |
||||
|
|
@ -0,0 +1,159 @@ |
|||||
|
/* Takes an AST and converts all of the functions into "closures" |
||||
|
* A closure is a triple of: |
||||
|
* the bound variables in a function or let |
||||
|
* the free variables in a function or let |
||||
|
* a function body or let body and bound values (if it is an escaping closure only) |
||||
|
* The closure has the property that all of the free variables of the function or let |
||||
|
* are in the environment, or an exception is raised because the variable is not bound |
||||
|
* in the current environment. |
||||
|
* A free variable is simply those that are not in the list of formal parameters or bound variables if it is a let |
||||
|
* |
||||
|
* Therefore in order to call a closure one must first extract the actual function and then |
||||
|
* call the function with the environment associated with it. |
||||
|
* For the purposes of type checking it does not matter how the function gets called, the environment |
||||
|
* is only used for looking up the types of names. Formal parameters are given type variables. |
||||
|
* |
||||
|
* The first phase of closure conversion is not really closure conversion exactly. |
||||
|
* All it does is find out the free variables in scope and tie those up into a data structure with their types later. |
||||
|
* The second phase will be done to the CPS language and closures will actually lambda-lifted out potentially. |
||||
|
*/ |
||||
|
|
||||
|
var rep = require("./representation.js"); |
||||
|
var errors = require("./errors.js"); |
||||
|
var parser = require("./parse.js"); |
||||
|
var $ = require("./tools.js"); |
||||
|
var _ = require("underscore"); |
||||
|
|
||||
|
var notEmpty = _.compose($.not, _.partial(_.equal, [])); |
||||
|
|
||||
|
function fvs(stx) { |
||||
|
switch (stx.exprType) { |
||||
|
case "Integer": |
||||
|
return []; |
||||
|
case "Float": |
||||
|
return []; |
||||
|
case "String": |
||||
|
return []; |
||||
|
case "Function": |
||||
|
return []; |
||||
|
case "Nil": |
||||
|
return []; |
||||
|
case "Bool": |
||||
|
return []; |
||||
|
case "Let": |
||||
|
return []; |
||||
|
case "Unary": |
||||
|
return _.flatten([stx.op.ident, fvs(stx.val)]); |
||||
|
case "Definition": |
||||
|
return _.flatten(fvs(stx.val)); |
||||
|
case "Application": |
||||
|
var vs = _.flatten(fvs(stx.p)); |
||||
|
var f_fvs = _.flatten(fvs(stx.func)); |
||||
|
return _.flatten([vs, f_fvs]); |
||||
|
case "If": |
||||
|
if (stx.elseexp) { |
||||
|
var cond_fvs = fvs(stx.condition); |
||||
|
var then_fvs = fvs(stx.thenexp); |
||||
|
var else_fvs = fvs(stx.elseexp); |
||||
|
return _.flatten([cond_fvs, then_fvs, else_fvs]); |
||||
|
} |
||||
|
else { |
||||
|
return _.flatten([fvs(stx.condition), fvs(stx.thenexp)]); |
||||
|
} |
||||
|
break; |
||||
|
case "Name": |
||||
|
return [stx.ident]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function annotate_fvs(stx) { |
||||
|
/* Takes a stx object that is either |
||||
|
* a lambda |
||||
|
* a let |
||||
|
* and returns a closure wrapped around that stx object |
||||
|
*/ |
||||
|
if (stx.exprType !== "Function" && |
||||
|
stx.exprType !== "Let") { |
||||
|
throw errors.JInternalError( |
||||
|
["Tried to calculate the free variables of", |
||||
|
"something that was not a function or let.\n", |
||||
|
"That something was a: " + stx.exprType +"\n"].reduce( |
||||
|
function (a,b) { |
||||
|
return a+" "+b; |
||||
|
}, "")); |
||||
|
} |
||||
|
var variables, free_variables, bound_vars, stx_type; |
||||
|
|
||||
|
switch (stx.exprType) { |
||||
|
case "Let": |
||||
|
bound_vars = stx.pairs.map( |
||||
|
function (stx) { |
||||
|
return stx.ident.ident; |
||||
|
}); |
||||
|
var let_fvs = stx.pairs.map(fvs); |
||||
|
var body_fvs = fvs(stx.body); |
||||
|
variables = _.flatten(let_fvs); |
||||
|
$.extend(variables, _.flatten(body_fvs)); |
||||
|
break; |
||||
|
case "Function": |
||||
|
bound_vars = [stx.p.ident,]; |
||||
|
variables = fvs(stx.body); |
||||
|
break; |
||||
|
} |
||||
|
free_variables = _.difference(_.uniq(variables), bound_vars); |
||||
|
return new rep.Closure(bound_vars, free_variables, stx, []); |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
* This traverse the tree and gathers up all of the free variables of various functions/let bindings |
||||
|
*/ |
||||
|
function annotate_fvs_all(stx) { |
||||
|
var closure; |
||||
|
switch (stx.exprType) { |
||||
|
case "Let": |
||||
|
closure = annotate_fvs(stx); |
||||
|
closure.body.pairs = closure.body.pairs.map(annotate_fvs_all); |
||||
|
closure.body = annotate_fvs_all(closure.body.body); |
||||
|
return closure; |
||||
|
case "Function": |
||||
|
closure = annotate_fvs(stx); |
||||
|
closure.body.body = annotate_fvs_all(closure.body.body); |
||||
|
return closure; |
||||
|
case "Unary": |
||||
|
stx.val = annotate_fvs_all(stx.val); |
||||
|
return stx; |
||||
|
case "Application": |
||||
|
stx.func = annotate_fvs_all(stx.func); |
||||
|
stx.p = annotate_fvs_all(stx.p); |
||||
|
return stx; |
||||
|
case "If": |
||||
|
if (stx.elseexp) { |
||||
|
stx.condition = annotate_fvs_all(stx.condition); |
||||
|
stx.thenexp = annotate_fvs_all(stx.thenexp); |
||||
|
stx.elseexp = annotate_fvs_all(stx.elseexp); |
||||
|
return stx; |
||||
|
} |
||||
|
else { |
||||
|
stx.condition = annotate_fvs_all(stx.condition); |
||||
|
stx.thenexp = annotate_fvs_all(stx.thenexp); |
||||
|
return stx; |
||||
|
} |
||||
|
break; |
||||
|
case "Definition": |
||||
|
stx.val = annotate_fvs_all(stx.val); |
||||
|
return stx; |
||||
|
default: |
||||
|
return stx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function test(src) { |
||||
|
var ast = parser.parse(src); |
||||
|
} |
||||
|
|
||||
|
//console.log(test("if something then if a then if b then c else d else rtrrt else some_other_thing"));
|
||||
|
module.export = { |
||||
|
test : test, |
||||
|
annotate_fvs: annotate_fvs_all |
||||
|
}; |
@ -0,0 +1,844 @@ |
|||||
|
#! /usr/bin/node
|
||||
|
var fs = require("fs"); |
||||
|
var typ = require("./representation.js"); |
||||
|
var $ = require("./tools.js"); |
||||
|
var _ = require("underscore"); |
||||
|
var tokenizer = require("./tokenize.js"); |
||||
|
var desugarer = require("./desugar.js"); |
||||
|
var pprint = require("./pprint.js"); |
||||
|
var error = require("./errors.js"); |
||||
|
var closure = require("./closures.js"); |
||||
|
var print = console.log; |
||||
|
|
||||
|
function sourcePos(tokens, linenum, charnum) { |
||||
|
if (!tokens || tokens.length === 0) { |
||||
|
return { linenum : linenum, |
||||
|
charnum : charnum |
||||
|
}; |
||||
|
} |
||||
|
return { |
||||
|
linenum : fst(tokens)[3], |
||||
|
charnum : fst(tokens)[2] |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function addSrcPos(stx, tokens, linenum, charnum) { |
||||
|
var pos = sourcePos(tokens, linenum, charnum); |
||||
|
stx.linenum = pos.linenum; |
||||
|
stx.charnum = pos.charnum; |
||||
|
return stx; |
||||
|
} |
||||
|
|
||||
|
/* Gets the first token from the right side of the stack */ |
||||
|
function fst(ts) { |
||||
|
return ts[ts.length-1]; |
||||
|
} |
||||
|
|
||||
|
/* Gets the second token from the right side of the stack */ |
||||
|
function snd(ts) { |
||||
|
return ts[ts.length-2]; |
||||
|
} |
||||
|
|
||||
|
/*Checks if the next token is not followed by any of ``checks'' */ |
||||
|
function notFollowedBy(tokens, checks, linenum, charnum) { |
||||
|
if (!fst(tokens)) { |
||||
|
throw error.JSyntaxError(0,0,"unexpected end of source"); |
||||
|
} |
||||
|
var nextT = fst(tokens)[0]; |
||||
|
if (checks.some(function (x) { |
||||
|
return x === nextT; |
||||
|
})) |
||||
|
return false; |
||||
|
else |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/* returns a function that takes a parameter and |
||||
|
checks if it is in the array ``props''*/ |
||||
|
function makeChecker(props) { |
||||
|
return function(x) { |
||||
|
return x && props.some(function (y) {return y(x);}); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function tokTypeCheck(name) { |
||||
|
return function(tok) { |
||||
|
return tok[0] === name; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function formTypeCheck(stxtype) { |
||||
|
return function(stx) { |
||||
|
return stx.exprType === stxtype; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/*Tries to parse until the prediction ``valid'' fails or the wrong type is parsed |
||||
|
Collects the results into an array and returns it*/ |
||||
|
function parseMany(parse, exprType, valid, tokens, charnum, linenum) { |
||||
|
if (!fst(tokens)) { |
||||
|
throw error.JSyntaxError(charnum, |
||||
|
linenum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
var current = fst(tokens)[0]; |
||||
|
var results = []; |
||||
|
var parsed; |
||||
|
|
||||
|
if (valid(fst(tokens))) { |
||||
|
parsed = parse(tokens); |
||||
|
} |
||||
|
else { |
||||
|
throw error.JSyntaxError(fst(tokens)[3], |
||||
|
fst(tokens)[2], |
||||
|
"Unexpected token: ``"+fst(tokens)[0]+"''"); |
||||
|
} |
||||
|
results.push(parsed); |
||||
|
|
||||
|
//make sure there are at least 2 tokens to parse
|
||||
|
if (tokens.length > 1 && fst(tokens) && valid(fst(tokens))) { |
||||
|
while (valid(snd(tokens))) { |
||||
|
if (!(valid(fst(tokens)))) { |
||||
|
break; |
||||
|
} |
||||
|
results.push(parse(tokens)); |
||||
|
if (!exprType(fst(results).exprType)) { |
||||
|
break; |
||||
|
} |
||||
|
if (fst(tokens)) { |
||||
|
current = fst(tokens)[0]; |
||||
|
} |
||||
|
else { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
if (tokens.length <= 1) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
//do the same validity check as before and in the loop
|
||||
|
if (!fst(tokens)) { |
||||
|
throw error.JSyntaxError(fst(tokens)[3], |
||||
|
fst(tokens)[2], |
||||
|
"unexpected end of source"); |
||||
|
} |
||||
|
if (valid(fst(tokens))) { |
||||
|
results.push(parse(tokens)); |
||||
|
} |
||||
|
return results; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* Tries to parse exprType separated by the token between |
||||
|
* e.g. <identifier>,<identifier>,... |
||||
|
*/ |
||||
|
function parseBetween(exprType, between, tokens, charnum, linenum) { |
||||
|
var first = parse(tokens); |
||||
|
var items; |
||||
|
var parsed; |
||||
|
|
||||
|
if (!exprType(first)) { |
||||
|
throw error.JSyntaxError(fst(tokens)[2], fst(tokens)[3], "Unexpected token: ``"+fst(tokens)[0]+"''"); |
||||
|
} |
||||
|
|
||||
|
items = [first]; |
||||
|
|
||||
|
if (tokens.length > 1 && fst(tokens)[0] === between) { |
||||
|
while (fst(tokens)[0] === between) { |
||||
|
tokens.pop(); |
||||
|
parsed = parse(tokens); |
||||
|
if (!fst(tokens)) |
||||
|
throw error.JSyntaxError(fst(tokens)[3], |
||||
|
fst(tokens)[2], |
||||
|
"Missing terminator: "+between); |
||||
|
items.push(parsed); |
||||
|
} |
||||
|
return items; |
||||
|
} |
||||
|
return items; |
||||
|
} |
||||
|
|
||||
|
function parseList(tokens, linenum, charnum) { |
||||
|
var xs; |
||||
|
var result; |
||||
|
|
||||
|
if (fst(tokens)[0] === "right_square") { |
||||
|
xs = []; |
||||
|
} |
||||
|
else if (fst(tokens)[0] === "comma") { |
||||
|
tokens.pop(); |
||||
|
xs = []; |
||||
|
} |
||||
|
else { |
||||
|
xs = parseBetween(function (x) { |
||||
|
return true; |
||||
|
}, "comma", tokens, fst(tokens)[3], fst(tokens)[2]); |
||||
|
} |
||||
|
if (!fst(tokens) || fst(tokens)[0] !== "right_square") { |
||||
|
throw error.JSyntaxError(fst(tokens)[3], |
||||
|
fst(tokens)[2], |
||||
|
"list must be terminated by ]"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
result = addSrcPos(new typ.ListT(xs), tokens, linenum, charnum); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function parseDefFunction(tokens, linenum, charnum) { |
||||
|
var fname = parse(tokens); |
||||
|
var parameters; |
||||
|
var result; |
||||
|
var body; |
||||
|
|
||||
|
if (fname.exprType !== "Name") { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"Expected an identifier in function definition"); |
||||
|
} |
||||
|
if (fst(tokens)[0] === "right_paren") { |
||||
|
parameters = []; |
||||
|
} |
||||
|
else { |
||||
|
parameters = parseMany(parse, |
||||
|
validName, |
||||
|
validFormPar, |
||||
|
tokens, |
||||
|
charnum, |
||||
|
linenum); |
||||
|
} |
||||
|
if (!tokens || (fst(tokens)[0]) !== "right_paren") { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"Formal parameters must be followed by )"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
body = parse(tokens); |
||||
|
result = addSrcPos(new typ.DefFunc(fname, parameters, body), tokens, body.linenum, body.charnum); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
validLet = makeChecker(["Definition", "FunctionDefinition"].map(formTypeCheck)); |
||||
|
letEnd = _.compose($.not, makeChecker(["right_brace"].map(tokTypeCheck))); |
||||
|
|
||||
|
function parseLetForm(tokens, linenum, charnum) { |
||||
|
var result; |
||||
|
var pairs; |
||||
|
var body; |
||||
|
|
||||
|
if (!fst(tokens)) { |
||||
|
error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
pairs = parseMany(parseLetItem, |
||||
|
validLet, |
||||
|
letEnd, |
||||
|
tokens, |
||||
|
charnum, |
||||
|
linenum); |
||||
|
if (fst(tokens) && fst(tokens)[0] !== "right_brace") { |
||||
|
throw error.JSyntaxError(fst(tokens)[2], |
||||
|
fst(tokens)[3], |
||||
|
"let/def form must have a closing }"); |
||||
|
} |
||||
|
if (!fst(tokens)) { |
||||
|
throw error.JSyntaxError(_.last(pairs).linenum, |
||||
|
_.last(pairs).charnum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
if (tokens.length <= 0) { |
||||
|
throw error.JSyntaxError(_.last(pairs).linenum, |
||||
|
_.last(pairs).charnum, |
||||
|
"let/def form must have a body"); |
||||
|
} |
||||
|
body = parse(tokens); |
||||
|
if (body.exprType === "Definition" || |
||||
|
body.exprType === "FunctionDefinition") { |
||||
|
throw error.JSyntaxError(body.linenum, |
||||
|
body.charnum, |
||||
|
"Body of a let/def expression cannot be a definition"); |
||||
|
} |
||||
|
result = addSrcPos(new typ.LetExp(pairs, body), tokens, body.linenum, body.charnum); |
||||
|
return result; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
function parseLetFunction(tokens, linenum, charnum) { |
||||
|
var fname = parse(tokens); |
||||
|
var parameters; |
||||
|
var result; |
||||
|
var body; |
||||
|
|
||||
|
if (fname.exprType != "Name") { |
||||
|
throw error.JSyntaxError(fst(tokens)[3], |
||||
|
fst(tokens)[2], |
||||
|
"Expected an identifier in function definition"); |
||||
|
} |
||||
|
if (fst(tokens)[0] === "right_paren") { |
||||
|
parameters = []; |
||||
|
} |
||||
|
else { |
||||
|
parameters = parseMany(parse, |
||||
|
validName, |
||||
|
validFormPar, |
||||
|
tokens, |
||||
|
charnum, |
||||
|
linenum); |
||||
|
} |
||||
|
if ((fst(tokens)[0]) !== "right_paren") { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"Formal parameters must be followed by )"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
if (fst(tokens)[1] !== "->") { |
||||
|
throw error.JSyntaxError(fst(tokens)[3], |
||||
|
fst(tokens)[2], |
||||
|
"Function parameters in let/def form must be followed by ->"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
body = parse(tokens); |
||||
|
result = addSrcPos(new typ.DefFunc(fname, parameters, body), tokens, body.linenum, body.charnum); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
function parseLetBinding(tokens, linenum, charnum) { |
||||
|
var name = parse(tokens); |
||||
|
var result; |
||||
|
var bound; |
||||
|
|
||||
|
if (name.exprType != "Name") { |
||||
|
throw error.JSyntaxError(name.linenum, |
||||
|
name.charnum, |
||||
|
"Expected an identifier in let/def binding"); |
||||
|
} |
||||
|
if (!fst(tokens) || fst(tokens)[1] !== "=") { |
||||
|
throw error.JSyntaxError(name.linenum, |
||||
|
name.charnum, |
||||
|
"An identifier in a let/def binding must be followed by ``=''"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
if (!notFollowedBy(tokens, |
||||
|
["comma", "arrow", "right_brace", "right_square"], |
||||
|
name.linenum, |
||||
|
name.charnum)) { |
||||
|
throw error.JSyntaxError(name.linenum, |
||||
|
name.charnum, |
||||
|
"The binding of " + identifier.val + " must not be followed by " + fst(tokens)[0]); |
||||
|
} |
||||
|
bound = parse(tokens); |
||||
|
if (bound.exprType === "Definition" || |
||||
|
bound.exprType === "FunctionDefinition") { |
||||
|
throw error.JSyntaxError(bound.linenum, |
||||
|
bound.charnum, |
||||
|
"A definition cannot be the value of a binding"); |
||||
|
} |
||||
|
result = addSrcPos(new typ.Def(name, bound), tokens, bound.linenum, bound.charnum); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
function parseLetItem(tokens) { |
||||
|
var linenum = fst(tokens)[3]; |
||||
|
var charnum = fst(tokens)[2]; |
||||
|
|
||||
|
if (fst(tokens) && fst(tokens)[0] === "left_paren") { |
||||
|
tokens.pop(); |
||||
|
return parseLetFunction(tokens, |
||||
|
linenum, |
||||
|
charnum); |
||||
|
} |
||||
|
else { |
||||
|
return parseLetBinding(tokens, |
||||
|
linenum, |
||||
|
charnum); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function parseDataType(tokens, linenum, charnum) { |
||||
|
var typeName = parse(tokens, linenum, charnum); |
||||
|
var typeParams; |
||||
|
var typeBody; |
||||
|
var result; |
||||
|
|
||||
|
if (typeName.exprType !== "TypeOperator") { |
||||
|
throw error.JSyntaxError(typeName.linenum, |
||||
|
typeName.charnum, |
||||
|
"Expected a type operator in data type definition"); |
||||
|
} |
||||
|
if (fst(tokens)[0] !== "right_paren") { |
||||
|
parameters = parseMany(parse, |
||||
|
validName, |
||||
|
validFormPar, |
||||
|
tokens, |
||||
|
charnum, |
||||
|
linenum); |
||||
|
} |
||||
|
else { |
||||
|
parameters = []; |
||||
|
} |
||||
|
if (!tokens || (fst(tokens)[0]) !== "right_paren") { |
||||
|
throw error.JSyntaxError(_.last(parameters).linenum, |
||||
|
_.last(parameters).charnum, |
||||
|
"Data type parameters must be followed by )"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
typeBody = parse(tokens); |
||||
|
result = addSrcPos(new typ.DataType(typeName, parameters, typeBody), tokens, typeBody.linenum, typeBody.charnum); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
function parseDefType(tokens, linenum, charnum) { |
||||
|
var result; |
||||
|
var rhs; |
||||
|
var lhs; |
||||
|
|
||||
|
if (tokens.length < 2) { |
||||
|
/* Minimal number of tokens required is 2 |
||||
|
* because it could be 'deftype Foo Blah' |
||||
|
*/ |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
if (fst(tokens)[0] === "left_paren") { |
||||
|
/* It's an actual data type definition |
||||
|
* i.e. not just an alias |
||||
|
*/ |
||||
|
tokens.pop(); |
||||
|
return parseDataType(tokens, linenum, charnum); |
||||
|
} |
||||
|
|
||||
|
if (notFollowedBy(tokens, ["constructor"], linenum, charnum)) { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"deftype must be followed by a single constructor" + |
||||
|
" if it is not a data type definition with type variables"); |
||||
|
} |
||||
|
else { |
||||
|
lhs = parse(tokens, linenum, charnum); |
||||
|
if (!tokens) { |
||||
|
throw error.JSyntaxError(lhs.linenum, |
||||
|
lhs.charnum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
if (lhs.exprType !== "TypeOperator") { |
||||
|
throw error.JSyntaxError(lhs.linenum, |
||||
|
lhs.charnum, |
||||
|
"left-hand side of type alias was not a type operator"); |
||||
|
} |
||||
|
rhs = parse(tokens, linenum, charnum); |
||||
|
|
||||
|
if (rhs.exprType !== "Application" && |
||||
|
rhs.exprType !== "TypeOperator") { |
||||
|
throw error.JSyntaxError(rhs.linenum, |
||||
|
rhs.charnum, |
||||
|
"was expecting an application or type operator on the right-hand side of a type alias"); |
||||
|
} |
||||
|
result = addSrcPos(new typ.DefType(lhs, rhs), tokens, rhs.linenum, rhs.charnum); |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
function parseDef(tokens, linenum, charnum) { |
||||
|
var result; |
||||
|
var identifier; |
||||
|
var bound; |
||||
|
|
||||
|
if (tokens.length < 2) { |
||||
|
/* Minimal number of tokens required is 2 |
||||
|
* because it could be 'def foo blah' |
||||
|
*/ |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
if (fst(tokens)[0] === "left_paren") { |
||||
|
/* It's a function definition */ |
||||
|
tokens.pop(); |
||||
|
return parseDefFunction(tokens, linenum, charnum); |
||||
|
} |
||||
|
|
||||
|
if (fst(tokens)[0] === "left_brace") { |
||||
|
/* It's a let/def form */ |
||||
|
tokens.pop(); |
||||
|
return parseLetForm(tokens, |
||||
|
fst(tokens)[3], |
||||
|
fst(tokens)[2]); |
||||
|
} |
||||
|
|
||||
|
if (notFollowedBy(tokens, ["identifier"], linenum, charnum)) { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"def must be followed by identifier, not "+fst(tokens)[0]); |
||||
|
} |
||||
|
else { |
||||
|
identifier = parse(tokens); |
||||
|
if (!fst(tokens)) |
||||
|
throw error.JSyntaxError(identifier.linenum, |
||||
|
identifier.charnum, |
||||
|
"Unexpected end of source"); |
||||
|
if (!notFollowedBy(tokens, |
||||
|
["comma", "arrow", "right_brace", "right_square"], |
||||
|
identifier.linenum, |
||||
|
identifier.charnum)) { |
||||
|
throw error.JSyntaxError(identifier.linenum, |
||||
|
identifier.charnum, |
||||
|
"def " + identifier.val + " must not be followed by " + fst(tokens)[0]); |
||||
|
} |
||||
|
bound = parse(tokens); |
||||
|
if (bound.exprType === "Definition" || |
||||
|
bound.exprType === "FunctionDefinition") { |
||||
|
throw error.JSyntaxError(bound.linenum, |
||||
|
bound.charnum, |
||||
|
"A definition cannot be the value of a binding"); |
||||
|
} |
||||
|
result = addSrcPos(new typ.Def(identifier, bound), tokens, bound.linenum, bound.charnum); |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function parseDefOp(tokens, linenum, charnum) { |
||||
|
var result; |
||||
|
var names; |
||||
|
var pattern; |
||||
|
|
||||
|
if (fst(tokens)[0] !== "integer" || |
||||
|
fst(tokens)[1] < 1) { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"defop must be followed by integer precedence >= 1"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
|
||||
|
if (fst(tokens)[1] !== "Left" && fst(tokens)[1] !== "Right") { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"defop must be followed by precedence and then either Left or Right"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
if (fst(tokens)[0] !== "left_paren") { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"defop arguments must start with ("); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
if (!(tokens.slice(tokens.length-3, |
||||
|
tokens.length).every(function(x) { |
||||
|
return x[0] === "identifier"; |
||||
|
}))) { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"defop must be surrounded by exactly 3 identifiers"); |
||||
|
} |
||||
|
pattern = tokens.slice(tokens.length-3, |
||||
|
tokens.length); |
||||
|
tokens.pop(); tokens.pop(); tokens.pop(); |
||||
|
if (fst(tokens)[0] !== "right_paren") { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"defop pattern must be terminated with )"); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
names = [new typ.Name(pattern[1][1]), |
||||
|
new typ.Name(pattern[0][1]), |
||||
|
new typ.Name(pattern[2][1])]; |
||||
|
names.map(function(name) { |
||||
|
name.linenum = linenum; |
||||
|
name.charnum = charnum; |
||||
|
return name; |
||||
|
}); |
||||
|
|
||||
|
result = addSrcPos(new typ.DefFunc(names[0], |
||||
|
names.slice(1,3), |
||||
|
parse(tokens)), |
||||
|
tokens, |
||||
|
_.last(names).linenum, |
||||
|
_.last(names).charnum); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
function parseIf(tokens, linenum, charnum) { |
||||
|
var result; |
||||
|
var ifC; |
||||
|
var thenC; |
||||
|
var elseC; |
||||
|
|
||||
|
if (!notFollowedBy(tokens, |
||||
|
["def","comma","lambda"], |
||||
|
linenum, |
||||
|
charnum)) { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"``if'' cannot be followed by "+fst(tokens)[0]) ; |
||||
|
} |
||||
|
else { |
||||
|
ifC = parse(tokens); |
||||
|
if (!fst(tokens) || fst(tokens)[0] !== "thenexp") { |
||||
|
throw error.JSyntaxError(ifC.linenum, |
||||
|
ifC.charnum, |
||||
|
"if ``exp'' must be folowed by ``then'' exp, not "+snd(tokens)[0]); |
||||
|
} |
||||
|
else { |
||||
|
tokens.pop(); |
||||
|
thenC = parse(tokens); |
||||
|
|
||||
|
if (fst(tokens) && fst(tokens)[0] === "elsexp") { |
||||
|
tokens.pop(); |
||||
|
if (_.size(tokens) < 1) { |
||||
|
throw error.JSyntaxError(thenC.linenum, |
||||
|
thenC.charnum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
else { |
||||
|
elseC = parse(tokens); |
||||
|
result = addSrcPos(new typ.If(ifC, thenC, elseC), tokens, elseC.linenum, elseC.charnum); |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
throw error.JSyntaxError(thenC.linenum, |
||||
|
thenC.charnum, |
||||
|
"If expression must include an else variant"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var validName = makeChecker(["Name"].map(formTypeCheck)); |
||||
|
|
||||
|
function validFormPar(tok) { |
||||
|
return tok[0] === "identifier" && |
||||
|
tok[1] !== "->"; |
||||
|
} |
||||
|
|
||||
|
function parseLambda(tokens, linenum, charnum) { |
||||
|
var result; |
||||
|
var parameters = parseMany(parse, |
||||
|
validName, |
||||
|
validFormPar, |
||||
|
tokens, |
||||
|
charnum, |
||||
|
linenum); |
||||
|
if (fst(tokens)[1] !== "->") { |
||||
|
throw error.JSyntaxError(_.last(parameters).linenum, |
||||
|
_.last(parameters).charnum, |
||||
|
"arrow must follow parameters in lambda, not "+fst(tokens)[0]); |
||||
|
} |
||||
|
tokens.pop(); |
||||
|
var body = parse(tokens); |
||||
|
result = addSrcPos(new typ.FuncT(parameters, body), tokens, body.linenum, body.charnum); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
var invalidArguments = ["def", "comma", "right_paren", "right_square", "right_brace", "left_brace", "right_brace"].map(tokTypeCheck); |
||||
|
var validArgument = _.compose($.not, makeChecker(invalidArguments)); |
||||
|
var validArgTypes = _.compose($.not, makeChecker(["Definition"].map(formTypeCheck))); |
||||
|
|
||||
|
/* Parses function application (either infix or prefix) */ |
||||
|
function computeApp(tokens, charnum, linenum) { |
||||
|
var lhs = parse(tokens); |
||||
|
var next; |
||||
|
var result; |
||||
|
var parameters; |
||||
|
|
||||
|
if (fst(tokens)) { |
||||
|
next = fst(tokens); |
||||
|
} |
||||
|
else { |
||||
|
throw error.JSyntaxError(lhs.linenum, |
||||
|
lhs.charnum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
if (typ.OPInfo[next[1]]) { |
||||
|
/* it's an infix expression */ |
||||
|
result = parseInfix(tokens, 1, lhs, lhs.linenum, lhs.charnum); |
||||
|
if (!fst(tokens) || fst(tokens)[0] !== "right_paren") { |
||||
|
throw error.JSyntaxError(lhs.linenum, |
||||
|
lhs.charnum, |
||||
|
"Mismatched parentheses or missing parenthesis on right-hand side"); |
||||
|
} |
||||
|
else { |
||||
|
tokens.pop(); |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
/* it's a prefix application */ |
||||
|
if (fst(tokens)[0] !== "right_paren") { |
||||
|
parameters = parseMany(parse, |
||||
|
validArgTypes, |
||||
|
validArgument, |
||||
|
tokens, |
||||
|
fst(tokens)[2], |
||||
|
fst(tokens)[3]); |
||||
|
} |
||||
|
else { |
||||
|
parameters = []; |
||||
|
} |
||||
|
if ((!fst(tokens)) || fst(tokens)[0] !== "right_paren") { |
||||
|
throw error.JSyntaxError(fst(tokens)[3], |
||||
|
fst(tokens)[2], |
||||
|
"Mismatched parentheses or missing parenthesis on right-hand side"); |
||||
|
} |
||||
|
else { |
||||
|
tokens.pop(); |
||||
|
return addSrcPos(typ.makeApp(lhs, parameters), tokens, linenum, charnum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/*Parses infix expressions by precedence climbing |
||||
|
* console.log(stx); |
||||
|
See this for more info and an implementation in python |
||||
|
http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing/
|
||||
|
*/ |
||||
|
function parseInfix(tokens, minPrec, lhs, linenum, charnum) { |
||||
|
if (!lhs) { |
||||
|
lhs = parse(tokens); |
||||
|
} |
||||
|
while (true) { |
||||
|
var cur = fst(tokens); |
||||
|
if (!cur) { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"Unexpected end of source"); |
||||
|
} |
||||
|
var opinfo = typ.OPInfo[cur[1]]; |
||||
|
|
||||
|
if (!opinfo || opinfo[0] < minPrec) |
||||
|
break; |
||||
|
|
||||
|
var op = addSrcPos(new typ.Name(cur[1]), tokens, linenum, charnum); |
||||
|
var prec = opinfo[0]; |
||||
|
var assoc = opinfo[1]; |
||||
|
var nextMinPrec = assoc === "Left" ? prec + 1 : prec; |
||||
|
tokens.pop(); |
||||
|
/*remove the operator token*/ |
||||
|
var rhs = parseInfix(tokens, nextMinPrec); |
||||
|
lhs = addSrcPos(typ.makeApp(op, [lhs, rhs]), tokens, rhs.linenum, rhs.charnum); |
||||
|
} |
||||
|
return lhs; |
||||
|
} |
||||
|
|
||||
|
function parse(tokens) { |
||||
|
var charnum = fst(tokens)[2]; |
||||
|
var linenum = fst(tokens)[3]; |
||||
|
var toktype; |
||||
|
var result; |
||||
|
if (fst(tokens)) { |
||||
|
toktype = fst(tokens)[0]; |
||||
|
} |
||||
|
else { |
||||
|
process.exit(code=1); |
||||
|
} |
||||
|
var token = fst(tokens)[1]; |
||||
|
tokens.pop(); |
||||
|
if (toktype === "stringlit") { |
||||
|
result = addSrcPos(new typ.StrT(token), tokens, linenum, charnum); |
||||
|
return result; |
||||
|
} |
||||
|
else if (toktype === "left_square") { |
||||
|
return parseList(tokens, linenum, charnum); |
||||
|
} |
||||
|
else if (toktype === "lambda") { |
||||
|
return parseLambda(tokens, linenum, charnum); |
||||
|
} |
||||
|
else if (toktype === "integer") { |
||||
|
result = addSrcPos(new typ.IntT(token), tokens, linenum, charnum); |
||||
|
return result; |
||||
|
} |
||||
|
else if (toktype === "float") { |
||||
|
result = addSrcPos(new typ.FloatT(token), tokens, linenum, charnum); |
||||
|
return result; |
||||
|
} |
||||
|
else if (toktype === "identifier") { |
||||
|
result = addSrcPos(new typ.Name(token), tokens, linenum, charnum); |
||||
|
return result; |
||||
|
} |
||||
|
else if (toktype === "constructor") { |
||||
|
result = addSrcPos(new typ.TypeOp(token), tokens, linenum, charnum); |
||||
|
return result; |
||||
|
} |
||||
|
else if (toktype === "truelit" || toktype === "falselit") { |
||||
|
result = addSrcPos(new typ.BoolT(token), tokens, linenum, charnum); |
||||
|
return result; |
||||
|
} |
||||
|
else if (toktype === "def" || |
||||
|
toktype === "let") { |
||||
|
return parseDef(tokens, linenum, charnum); |
||||
|
} |
||||
|
else if (toktype === "deftype") { |
||||
|
return parseDefType(tokens, linenum, charnum); |
||||
|
} |
||||
|
else if (toktype === "defop") { |
||||
|
return parseDefOp(tokens, linenum, charnum); |
||||
|
} |
||||
|
else if (toktype === "ifexp") { |
||||
|
return parseIf(tokens, linenum, charnum); |
||||
|
} |
||||
|
else if (toktype === "left_paren") { |
||||
|
if (fst(tokens)[0] === "lambda") { |
||||
|
tokens.pop(); |
||||
|
var parsed = parseLambda(tokens, linenum, charnum); |
||||
|
tokens.pop(); |
||||
|
return parsed; |
||||
|
} |
||||
|
else |
||||
|
return computeApp(tokens, linenum, charnum); |
||||
|
} |
||||
|
else { |
||||
|
throw error.JSyntaxError(linenum, |
||||
|
charnum, |
||||
|
"Unexpected token: ``" + toktype+"''"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function parseFull(tokenized) { |
||||
|
var ast = []; |
||||
|
var typeBindings = {}; |
||||
|
var current; |
||||
|
try { |
||||
|
while (tokenized.length > 0) { |
||||
|
current = closure.annotate_fvs(desugarer.desugar(parse(tokenized), typeBindings)); |
||||
|
ast.push(current); |
||||
|
} |
||||
|
return { |
||||
|
"ast" : ast, |
||||
|
"types" : typeBindings |
||||
|
}; |
||||
|
} catch (e) { |
||||
|
if (e.stxerror !== undefined) { |
||||
|
e.stxerror(); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
else { |
||||
|
console.log(e.errormessage); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { parse : function(str) { |
||||
|
return parseFull(tokenizer.tokenize(str)); |
||||
|
}, |
||||
|
tokenize : tokenizer.tokenize, |
||||
|
parseFull : parseFull, |
||||
|
}; |
||||
|
//var istr = fs.readFileSync('/dev/stdin').toString();
|
||||
|
//var testParse = parseFull(tokenizer.tokenize(istr));
|
||||
|
//console.log(testParse[1]);
|
||||
|
//console.log(testParse[0].map(pprint.pprint));
|
@ -0,0 +1,102 @@ |
|||||
|
function pprintName(ident) { |
||||
|
return pprint(ident.val); |
||||
|
} |
||||
|
|
||||
|
function pprintFunc(func) { |
||||
|
if (func.p.exprType === "Name") |
||||
|
return "(lambda " + pprint(func.p) + " -> " + pprint(func.body) + ")"; |
||||
|
else |
||||
|
return "(lambda " + func.p.map(pprint).join(" ") + " -> " + pprint(func.body) + ")"; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
function pprintApp(app) { |
||||
|
if (!app.p || app.p === undefined) |
||||
|
return pprint(app.func); |
||||
|
return "((" + pprint(app.func) + ") " + pprint(app.p) + ")"; |
||||
|
} |
||||
|
|
||||
|
function pprintDef(def) { |
||||
|
return pprint(def.ident) + " = " + pprint(def.val); |
||||
|
} |
||||
|
|
||||
|
function pprintIf(ifexp) { |
||||
|
return ("(if " + pprint(ifexp.condition) + |
||||
|
" then " + pprint(ifexp.thenexp) + |
||||
|
" else " + pprint(ifexp.elseexp) + ")"); |
||||
|
} |
||||
|
|
||||
|
function pprintDefType(stx) { |
||||
|
return pprint(stx.lhs) + " = " + pprint(stx.rhs); |
||||
|
} |
||||
|
|
||||
|
function pprintTypeFunc(stx) { |
||||
|
return "(" + stx.name.name + " " + stx.params.map(pprint).join(" ") + ") = " + pprint(stx.type); |
||||
|
} |
||||
|
|
||||
|
function pprint(expr) { |
||||
|
if (expr.exprType === "Name") { |
||||
|
return expr.val; |
||||
|
} |
||||
|
else if (expr.exprType === "Bool") { |
||||
|
if (expr.val) { |
||||
|
return "True"; |
||||
|
} |
||||
|
else { |
||||
|
return "False"; |
||||
|
} |
||||
|
} |
||||
|
else if (expr.exprType === "Integer") { |
||||
|
return "("+expr.val+")"; |
||||
|
} |
||||
|
else if (expr.exprType === "Float") { |
||||
|
return "("+expr.val+")"; |
||||
|
} |
||||
|
else if (expr.exprType === "String") { |
||||
|
return '"'+expr.val+'"'; |
||||
|
} |
||||
|
else if (expr.exprType === "Name") { |
||||
|
return expr.val; |
||||
|
} |
||||
|
else if (expr.exprType === "Application") { |
||||
|
return pprintApp(expr); |
||||
|
} |
||||
|
else if (expr.exprType === "Definition") { |
||||
|
return pprintDef(expr); |
||||
|
} |
||||
|
else if (expr.exprType === "TypeDefinition") { |
||||
|
return pprintDefType(expr); |
||||
|
} |
||||
|
else if (expr.exprType === "TypeFuncDefinition") { |
||||
|
return pprintTypeFunc(expr); |
||||
|
} |
||||
|
else if (expr.exprType === "If") { |
||||
|
return pprintIf(expr); |
||||
|
} |
||||
|
else if (expr.exprType === "Function") { |
||||
|
return pprintFunc(expr); |
||||
|
} |
||||
|
else if (expr.exprType === "Nil") { |
||||
|
return "[]"; |
||||
|
} |
||||
|
else if (expr.exprType === "Unary") { |
||||
|
return "("+expr.op.ident+" "+pprint(expr.val)+")"; |
||||
|
} |
||||
|
else if (expr.exprType === "Let") { |
||||
|
return "let {" + expr.pairs.map( |
||||
|
function (v) { |
||||
|
return pprint(v); |
||||
|
}).join(" ; ") + "} in " + pprint(expr.body); |
||||
|
} |
||||
|
else if (expr.exprType === "TypeOperator") { |
||||
|
return "("+expr.val+")"; |
||||
|
} |
||||
|
else if (expr.exprType === "TypeVar") { |
||||
|
return "("+expr.name+")"; |
||||
|
} |
||||
|
else if (expr.exprType === "TypeDeclaration") { |
||||
|
return "( " + pprint(expr.expression) + " :: " + pprint(expr.type) + " )"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = {pprint : pprint}; |
@ -0,0 +1,118 @@ |
|||||
|
;; This file declares the various types used by intrinsic/prelude definitions |
||||
|
;; It is sort of special in that it doesn't care whether there are any associated definitions |
||||
|
;; just that there are type definitions for that particular binding's name |
||||
|
|
||||
|
|
||||
|
;; Type definitions |
||||
|
deftype String (Vector Char) |
||||
|
|
||||
|
deftype (Int) Intrinsic |
||||
|
|
||||
|
deftype (Float) Intrinsic |
||||
|
|
||||
|
deftype (Char) Intrinsic |
||||
|
|
||||
|
deftype (Byte) Intrinsic |
||||
|
|
||||
|
deftype (Void) Intrinsic |
||||
|
|
||||
|
deftype (IO a) Intrinsic |
||||
|
|
||||
|
deftype (Vector a) Intrinsic |
||||
|
|
||||
|
deftype (List a) |
||||
|
(Empty | |
||||
|
(Cons a (List a))) |
||||
|
|
||||
|
deftype (Bottom) |
||||
|
Undefined |
||||
|
|
||||
|
deftype (Maybe a) |
||||
|
(Nothing | |
||||
|
(Just a)) |
||||
|
|
||||
|
deftype (Either a b) |
||||
|
((Left a) | |
||||
|
(Right b)) |
||||
|
|
||||
|
;; List functions |
||||
|
|
||||
|
(: :: (a -> (List a) -> (List a))) |
||||
|
|
||||
|
(map :: ((a -> b) -> (List a) -> (List b))) |
||||
|
|
||||
|
(head :: ((List a) -> a)) |
||||
|
|
||||
|
(tail :: ((List a) -> (List a))) |
||||
|
|
||||
|
(!! :: (Int -> (List a) -> a)) |
||||
|
|
||||
|
(take :: (Int -> (List a) -> (Maybe (List a)))) |
||||
|
|
||||
|
(drop :: (Int -> (List a) -> (Maybe (List a)))) |
||||
|
|
||||
|
;; Optional functions |
||||
|
|
||||
|
(maybe :: (b -> (a -> b) -> (Maybe a) -> b)) |
||||
|
|
||||
|
(either :: ((b -> c) -> (b -> c) -> (Either a b) -> c)) |
||||
|
|
||||
|
;; I/O functions |
||||
|
|
||||
|
(print :: (String -> (IO Void))) |
||||
|
|
||||
|
;; Operator definitions |
||||
|
|
||||
|
defop 1 Left (a + b) |
||||
|
(add a b) |
||||
|
|
||||
|
defop 1 Left (a - b) |
||||
|
(minus a b) |
||||
|
|
||||
|
defop 2 Left (a * b) |
||||
|
(mul a b) |
||||
|
|
||||
|
defop 2 Left (a / b) |
||||
|
(div a b) |
||||
|
|
||||
|
defop 2 Right (a ^ b) |
||||
|
(pow a b) |
||||
|
|
||||
|
defop 3 Left (a ++ b) |
||||
|
(listConcat a b) |
||||
|
|
||||
|
defop 3 Left (a == b) |
||||
|
(eq a b) |
||||
|
|
||||
|
defop 3 Left (a > b) |
||||
|
(gt a b) |
||||
|
|
||||
|
defop 3 Left (a >= b) |
||||
|
(gte a b) |
||||
|
|
||||
|
defop 3 Left (a < b) |
||||
|
(lt a b) |
||||
|
|
||||
|
defop 3 Left (a <= b) |
||||
|
(lte a b) |
||||
|
|
||||
|
defop 3 Left (a && b) |
||||
|
(and a b) |
||||
|
|
||||
|
defop 3 Left (a || b) |
||||
|
(or a b) |
||||
|
|
||||
|
defop 4 Left (x : xs) |
||||
|
(cons x xs) |
||||
|
|
||||
|
defop 5 Left (f $ x) |
||||
|
(fapply f x) |
||||
|
|
||||
|
defop 5 Left (f . g) |
||||
|
(compose f g) |
||||
|
|
||||
|
defop 3 Left (a | b) |
||||
|
(bitwiseOr a b) |
||||
|
|
||||
|
defop 3 Left (a & b) |
||||
|
(bitwiseAnd a b) |
@ -0,0 +1,404 @@ |
|||||
|
var errors = require("./errors.js"); |
||||
|
var _ = require("underscore"); |
||||
|
|
||||
|
var Expression = { |
||||
|
display : |
||||
|
function() { |
||||
|
return this.exprType + " " + this.val; |
||||
|
}, |
||||
|
type : |
||||
|
function () { |
||||
|
return this.exprType; |
||||
|
}, |
||||
|
linenum : 0, |
||||
|
charnum : 0 |
||||
|
}; |
||||
|
|
||||
|
var TypeExpression = { |
||||
|
unify : |
||||
|
function (t) { |
||||
|
if (this.expr === t.expr) { |
||||
|
return t.expr; |
||||
|
} |
||||
|
else { |
||||
|
console.log("Could not unify " + this.expr + " with " + t.expr); |
||||
|
} |
||||
|
}, |
||||
|
isTypeExpr : true, |
||||
|
linenum : 0, |
||||
|
charnum : 0 |
||||
|
}; |
||||
|
|
||||
|
function isTypeExpr(x) { |
||||
|
return x.isTypeExpr !== undefined; |
||||
|
} |
||||
|
|
||||
|
function isIrregularTypeOp(x) { |
||||
|
return (x === "->"); |
||||
|
} |
||||
|
|
||||
|
function flattenTypeDecl(stx) { |
||||
|
if (isTypeExpr(stx)) { |
||||
|
return true; |
||||
|
} |
||||
|
if (stx.exprType === "Application") { |
||||
|
/* it might be a type application so recursively check it */ |
||||
|
if (stx.p !== undefined) { |
||||
|
return _.flatten([flattenTypeDecl(stx.p), flattenTypeDecl(stx.func)]); |
||||
|
} |
||||
|
else { |
||||
|
return _.flatten([flattenTypeDecl(stx.func)]); |
||||
|
} |
||||
|
} |
||||
|
if (stx.exprType === "Name") { |
||||
|
/* |
||||
|
* Either it is a type operator |
||||
|
* or we assume it is a type variable |
||||
|
* since it was not capitalized |
||||
|
*/ |
||||
|
return true; |
||||
|
} |
||||
|
return { |
||||
|
failed : true, |
||||
|
stx : stx |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function isTypeExprRec(stx) { |
||||
|
var flattened = flattenTypeDecl(stx); |
||||
|
for(var i = 0; i < flattened.length; i++) { |
||||
|
if (flattened[i].failed !== undefined && |
||||
|
flattened[i].failed) { |
||||
|
return flattened[i]; |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
function App(func, p) { |
||||
|
this.func = func; |
||||
|
this.exprType = "Application"; |
||||
|
if (p) |
||||
|
this.p = p; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
function Closure(bound_vars, free_vars, body, env) { |
||||
|
this.bound_vars = bound_vars; |
||||
|
this.free_vars = free_vars; |
||||
|
this.body = body; |
||||
|
this.env = env; |
||||
|
this.exprType = "Closure"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
function LetExp(pairs, body) { |
||||
|
if (!pairs.every(function(x) { |
||||
|
return (x.exprType === "Definition" || |
||||
|
x.exprType === "FunctionDefinition"); |
||||
|
})) { |
||||
|
throw errors.JInternalError( |
||||
|
"let can only be used to bind things to names or functions" |
||||
|
); |
||||
|
} |
||||
|
this.exprType = "Let"; |
||||
|
this.val = [pairs, body]; |
||||
|
this.pairs = pairs; |
||||
|
this.body = body; |
||||
|
return this; |
||||
|
} |
||||
|
LetExp.prototype = Expression; |
||||
|
|
||||
|
function UnaryOp(op, v) { |
||||
|
this.exprType = "Unary"; |
||||
|
this.val = v; |
||||
|
this.op = op; |
||||
|
return this; |
||||
|
} |
||||
|
UnaryOp.prototype = Expression; |
||||
|
|
||||
|
function IntT(v) { |
||||
|
this.exprType = "Integer"; |
||||
|
this.val = parseInt(v, 10); |
||||
|
return this; |
||||
|
} |
||||
|
IntT.prototype = Expression; |
||||
|
|
||||
|
function FloatT(v) { |
||||
|
this.exprType = "Float"; |
||||
|
this.val = parseFloat(v, 10); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
FloatT.prototype = Expression; |
||||
|
|
||||
|
function StrT(v) { |
||||
|
this.exprType = "String"; |
||||
|
this.val = v; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
StrT.prototype = Expression; |
||||
|
|
||||
|
function BoolT(b) { |
||||
|
if (b === "true") { |
||||
|
this.val = true; |
||||
|
} |
||||
|
else { |
||||
|
this.val = false; |
||||
|
} |
||||
|
this.exprType = "Bool"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
BoolT.prototype = Expression; |
||||
|
|
||||
|
function ListT(xs) { |
||||
|
this.xs = xs; |
||||
|
this.val = xs; |
||||
|
this.exprType = "List"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
ListT.prototype = Expression; |
||||
|
|
||||
|
function Nil() { |
||||
|
this.exprType = "Nil"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
Nil.prototype = Expression; |
||||
|
|
||||
|
|
||||
|
function FuncT(p, body) { |
||||
|
this.p = p; |
||||
|
this.body = body; |
||||
|
this.val = [p, body]; |
||||
|
this.exprType = "Function"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
FuncT.prototype = Expression; |
||||
|
|
||||
|
//Wrapper for function objects
|
||||
|
function OpT(operator) { |
||||
|
this.op = operator; |
||||
|
this.val = this.op; |
||||
|
this.exprType = "Function"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
OpT.prototype = Expression; |
||||
|
|
||||
|
// Applications separate from other types
|
||||
|
function App(func, p) { |
||||
|
this.func = func; |
||||
|
this.exprType = "Application"; |
||||
|
if (p) |
||||
|
this.p = p; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
App.prototype = Expression; |
||||
|
|
||||
|
// Names are not types
|
||||
|
function Name(identifier) { |
||||
|
this.ident = identifier; |
||||
|
this.val = this.ident; |
||||
|
this.exprType = "Name"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
Name.prototype = Expression; |
||||
|
|
||||
|
function Def(ident, exp) { |
||||
|
this.ident = ident; |
||||
|
this.val = exp; |
||||
|
this.exprType = "Definition"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
Def.prototype = Expression; |
||||
|
|
||||
|
function DefFunc(ident, params, body) { |
||||
|
this.ident = ident; |
||||
|
this.val = this.ident; |
||||
|
this.params = params; |
||||
|
this.body = body; |
||||
|
this.exprType = "FunctionDefinition"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
DefFunc.prototype = Expression; |
||||
|
|
||||
|
function If(condition, thenexp, elseexp) { |
||||
|
this.condition = condition; |
||||
|
this.thenexp = thenexp; |
||||
|
this.elseexp = elseexp; |
||||
|
this.exprType = "If"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
If.prototype = Expression; |
||||
|
|
||||
|
function TypeVar(name) { |
||||
|
this.exprtype = "TypeVar"; |
||||
|
this.name = name; |
||||
|
this.exprType = "TypeVar"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
TypeVar.prototype = TypeExpression; |
||||
|
|
||||
|
function TypeOp(name) { |
||||
|
this.name = name; |
||||
|
this.val = name; |
||||
|
this.exprType = "TypeOperator"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
TypeOp.prototype = TypeExpression; |
||||
|
|
||||
|
function isTypeExpr(expr) { |
||||
|
if (!expr.exprType) { |
||||
|
throw errors.JInternalError(expr); |
||||
|
} |
||||
|
return ((expr.exprType === "TypeOperator") || |
||||
|
(expr.exprType === "TypeVar") || |
||||
|
(expr.exprType === "TypeDeclaration")); |
||||
|
} |
||||
|
|
||||
|
function TypeDecl(expression, type) { |
||||
|
if (isTypeExprRec(expression) && |
||||
|
expression.exprType !== "Name") { |
||||
|
throw errors.JSyntaxError( |
||||
|
expression.linenum, |
||||
|
expression.charnum, |
||||
|
"Left-hand-side of type declaration must not be in the type language" |
||||
|
); |
||||
|
} |
||||
|
if (isTypeExprRec(type).failed) { |
||||
|
throw errors.JInternalError( |
||||
|
"Right-hand-side of type declaration must be a type expression" |
||||
|
); |
||||
|
} |
||||
|
this.expression = expression; |
||||
|
this.type = type; |
||||
|
this.exprType = "TypeDeclaration"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
TypeDecl.prototype = TypeExpression; |
||||
|
|
||||
|
function DefType(lhs, rhs) { |
||||
|
/* Both rhs and lhs are expected |
||||
|
* to be fully desugared already |
||||
|
*/ |
||||
|
if (isTypeExprRec(rhs).failed || |
||||
|
!isTypeExpr(lhs)) { |
||||
|
throw errors.JSyntaxError( |
||||
|
rhs.linenum, |
||||
|
rhs.charnum, |
||||
|
"Illegal type definition, both sides must be valid type expressions"); |
||||
|
} |
||||
|
this.rhs = rhs; |
||||
|
this.lhs = lhs; |
||||
|
this.exprType = "TypeDefinition"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
DefType.prototype = Expression; |
||||
|
|
||||
|
function checkName(exp) { |
||||
|
if (exp.exprType !== "Name") { |
||||
|
throw errors.JSyntaxError( |
||||
|
exp.linenum, |
||||
|
exp.charnum, |
||||
|
"Expected a type variable (an identifier starting with a lowercase character), got " + exp.val); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function DataType(name, params, type) { |
||||
|
/* Params is a list of type variables |
||||
|
* type is a type expression |
||||
|
*/ |
||||
|
if (name.exprType !== "TypeOperator") { |
||||
|
throw errors.JSyntaxError( |
||||
|
name.linenum, |
||||
|
name.charnum, |
||||
|
"First element in a data type definition must be its name " + |
||||
|
"which is a type operator"); |
||||
|
} |
||||
|
_.each(params, checkName); |
||||
|
if (isTypeExprRec(type).failed) { |
||||
|
throw errors.JSyntaxError( |
||||
|
type.linenum, |
||||
|
type.charnum, |
||||
|
"Body of a type definition must be a valid type expression"); |
||||
|
} |
||||
|
this.name = name; |
||||
|
this.params = params; |
||||
|
this.type = type; |
||||
|
this.exprType = "TypeFuncDefinition"; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* Applies the function ``name'' to the list of parameters */ |
||||
|
function makeApp(name, parameters) { |
||||
|
if (parameters) { |
||||
|
return parameters.slice(1).reduce(function(f, ident) { |
||||
|
return new App(f, ident); |
||||
|
}, new App(name, parameters[0])); |
||||
|
} |
||||
|
else { |
||||
|
return new App(name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function makeGensym() { |
||||
|
var n = 0; |
||||
|
return function() { |
||||
|
var x = "G"+n; |
||||
|
n = n + 1; |
||||
|
return x; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
var gensym = makeGensym(); |
||||
|
|
||||
|
OPInfo = { |
||||
|
"::" : [2, "Left"], |
||||
|
"," : [1, "Left"], |
||||
|
"->" : [1, "Right"] |
||||
|
}; |
||||
|
|
||||
|
module.exports = |
||||
|
{ |
||||
|
IntT : IntT, |
||||
|
FloatT : FloatT, |
||||
|
StrT : StrT, |
||||
|
BoolT : BoolT, |
||||
|
ListT : ListT, |
||||
|
FuncT : FuncT, |
||||
|
App : App, |
||||
|
Name : Name, |
||||
|
Def : Def, |
||||
|
OpT : OpT, |
||||
|
OPInfo : OPInfo, |
||||
|
makeApp : makeApp, |
||||
|
If : If, |
||||
|
DefFunc : DefFunc, |
||||
|
UnaryOp : UnaryOp, |
||||
|
Nil : Nil, |
||||
|
LetExp : LetExp, |
||||
|
gensym : gensym, |
||||
|
TypeVar : TypeVar, |
||||
|
TypeOp : TypeOp, |
||||
|
TypeDecl : TypeDecl, |
||||
|
Closure : Closure, |
||||
|
isTypeExpr : isTypeExprRec, |
||||
|
DefType : DefType, |
||||
|
DataType : DataType |
||||
|
}; |
Binary file not shown.
@ -1,9 +1,12 @@ |
|||||
var express = require('express'); |
var express = require('express'); |
||||
var router = express.Router(); |
var router = express.Router(); |
||||
|
var vm = require("../vm.js"); |
||||
|
|
||||
/* GET home page. */ |
/* GET home page. */ |
||||
router.get('/', function(req, res, next) { |
router.get('/', function(req, res, next) { |
||||
res.render('index', { title: 'Express' }); |
var query_params = req.query; |
||||
|
var evaluated = vm.evaluate(query_params.source); |
||||
|
res.render('index', { title: 'Express', output: JSON.stringify(evaluated)}); |
||||
}); |
}); |
||||
|
|
||||
module.exports = router; |
module.exports = router; |
||||
|
@ -0,0 +1,149 @@ |
|||||
|
#! /usr/bin/node |
||||
|
|
||||
|
var parser = require("./parse.js"); |
||||
|
var cexps = require("./cexps.js"); |
||||
|
var closures = require("./closure_conversion.js"); |
||||
|
var desugar = require("./desugar.js"); |
||||
|
var environments = require("./environments.js"); |
||||
|
var errors = require("./errors.js"); |
||||
|
var tokens = require("./tokenize.js"); |
||||
|
var tools = require("./tools.js"); |
||||
|
var typecheck = require("./typecheck.js"); |
||||
|
var representation = require("./representation.js"); |
||||
|
var _ = require("underscore"); |
||||
|
|
||||
|
var qc = require("quickcheck"); |
||||
|
var assert = require("assert"); |
||||
|
|
||||
|
|
||||
|
/* my own generators */ |
||||
|
|
||||
|
function arbChars(n, max, min) { |
||||
|
return function () { |
||||
|
return _.invoke(_.times(_.random(1, n), |
||||
|
_.partial(arbChar, max, min)), |
||||
|
"call"); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function arbChar(max, min) { |
||||
|
return function() { |
||||
|
return String.fromCharCode(_.random(max, min)); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function arbCharRanges(ranges, max) { |
||||
|
return function() { |
||||
|
return _.flatten( |
||||
|
_.shuffle( |
||||
|
_.map(ranges, |
||||
|
function(bound) { |
||||
|
return _.sample(arbChars(max, bound[0], bound[1])(), |
||||
|
bound[1] - bound[0]); |
||||
|
}))).join(""); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
var arbName = arbCharRanges([[33, 33], |
||||
|
[35, 39], |
||||
|
[42,43], |
||||
|
[45, 122], |
||||
|
[124, 126]], |
||||
|
200); |
||||
|
|
||||
|
var arbCapital = arbChar(65, 90); |
||||
|
|
||||
|
function arbArray(gen) { |
||||
|
return qc.arbArray(gen); |
||||
|
} |
||||
|
|
||||
|
function arbStrings() { |
||||
|
return qc.arbArray(qc.arbString); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function arbPair(gen) { |
||||
|
return function() { |
||||
|
return [gen(), gen()]; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function arbArrayofPairs() { |
||||
|
return arbArray(function() { |
||||
|
return arbArray(arbPair(qc.arbString)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function arbPairs() { |
||||
|
return arbArray(arbPair(qc.arbString)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* Tests for misc tools */ |
||||
|
function emptyProp(xs) { |
||||
|
return (tools.empty(xs) === tools.empty(xs) && |
||||
|
((tools.empty(xs) === true) || |
||||
|
(tools.empty(xs) === false))); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function dictProp(pairs) { |
||||
|
var dict = tools.dict(pairs); |
||||
|
var result = _.map(pairs, |
||||
|
function(pair) { |
||||
|
if ((_.size(pair) < 2) || |
||||
|
(_.size(pair[0]) < 1) || |
||||
|
(_.size(pair[1]) < 1)) { |
||||
|
return true; |
||||
|
} |
||||
|
return dict[pair[0]] === pair[1]; |
||||
|
}); |
||||
|
if (_.every(result, _.identity)) { |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
function opMatchProp(strings) { |
||||
|
var match = tools.opMatch(strings); |
||||
|
var result = _.every(_.map(strings, |
||||
|
function (str) { |
||||
|
if (str.replace(/ /g,'').length < 1) { |
||||
|
return true; |
||||
|
} |
||||
|
var res = match(str); |
||||
|
if (res !== false) { |
||||
|
console.log(str); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
}), |
||||
|
_.identity); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
function extendProp(pair) { |
||||
|
if (pair.length < 2) { |
||||
|
// empty lists or lists with one item are undefined
|
||||
|
// so just return true because extend can't handle them
|
||||
|
return true; |
||||
|
} |
||||
|
var x = _.first(pair); |
||||
|
var y = _.first(_.rest(pair)); |
||||
|
var extended = tools.extend(x,y); |
||||
|
return x.length + y.length === extended.length; |
||||
|
} |
||||
|
|
||||
|
/* Tokenizer tests */ |
||||
|
|
||||
|
|
||||
|
function toolsTests() { |
||||
|
assert.equal(true, tools.empty([])); |
||||
|
assert.equal(true, qc.forAll(dictProp, arbArrayofPairs)); |
||||
|
assert.equal(true, qc.forAll(extendProp, arbPairs)); |
||||
|
//assert.equal(true, qc.forAll(opMatchProp, arbStrings));
|
||||
|
} |
||||
|
|
||||
|
console.log(arbName()); |
||||
|
//toolsTests();
|
@ -0,0 +1,432 @@ |
|||||
|
#! /usr/bin/node |
||||
|
|
||||
|
var fs = require("fs"); |
||||
|
var rep = require("./representation.js"); |
||||
|
var $ = require("./tools.js"); |
||||
|
var error = require("./errors.js"); |
||||
|
var operators = Object.keys(rep.OPInfo); |
||||
|
var _ = require("underscore"); |
||||
|
|
||||
|
function isDigit(c) { |
||||
|
if (!c) |
||||
|
return false; |
||||
|
var code = c.charCodeAt(); |
||||
|
if (isNaN(code)) { |
||||
|
return false; |
||||
|
} |
||||
|
return (47 < code) && (code < 58) |
||||
|
} |
||||
|
|
||||
|
function isWhitespace(c) { |
||||
|
if (!c) |
||||
|
return true; |
||||
|
|
||||
|
var code = c.charCodeAt(); |
||||
|
if (isNaN(code)) { |
||||
|
return true; |
||||
|
} |
||||
|
return (code === 9 || |
||||
|
code === 32 || |
||||
|
code === 10 || |
||||
|
code === 13 || |
||||
|
code === 11); |
||||
|
} |
||||
|
|
||||
|
function isIdentifier(c) { |
||||
|
var code = c.charCodeAt(); |
||||
|
return (!isNaN(code) && |
||||
|
code !== 41 && |
||||
|
code !== 40 && |
||||
|
code !== 125 && |
||||
|
code !== 123 && |
||||
|
code !== 93 && |
||||
|
code !== 91 && |
||||
|
code !== 44 && |
||||
|
code !== 34 && |
||||
|
code > 32); |
||||
|
} |
||||
|
|
||||
|
function isUpper(c) { |
||||
|
var code = c.charCodeAt(); |
||||
|
return (!isNaN(code) && |
||||
|
(code >= 65) && |
||||
|
(code <= 90)); |
||||
|
} |
||||
|
|
||||
|
function tokenizeNum(tokstream, charnum, linenum) { |
||||
|
var number = []; |
||||
|
var code = tokstream[0].charCodeAt(); |
||||
|
var isFloat = false; |
||||
|
var n = 0; |
||||
|
// + -
|
||||
|
// might want to remove this since it probably won't ever get run?
|
||||
|
if (code === 43 || code === 45) { // + or -
|
||||
|
number.push(tokstream[0]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
n++; |
||||
|
} |
||||
|
else if (code === 46) { // .
|
||||
|
tokstream = tokstream.substr(1); |
||||
|
n++; |
||||
|
charnum++; |
||||
|
number.push('0'); |
||||
|
number.push('.'); |
||||
|
isFloat = true; |
||||
|
} |
||||
|
|
||||
|
while (isDigit(tokstream[0]) && tokstream.length !== 0) { |
||||
|
number.push(tokstream[0]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
charnum++; |
||||
|
n++; |
||||
|
} |
||||
|
if (tokstream[0] === '.' && isDigit(tokstream[1])) { |
||||
|
number.push('.'); |
||||
|
number.push(tokstream[1]); |
||||
|
tokstream = tokstream.substr(2); |
||||
|
charnum++; charnum++; |
||||
|
n++; n++; |
||||
|
while (isDigit(tokstream[0]) && tokstream.length !== 0) { |
||||
|
number.push(tokstream[0]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
n++; |
||||
|
charnum++; |
||||
|
} |
||||
|
return [n, ["float", parseFloat(number.join(''), 10), charnum, linenum]]; |
||||
|
} |
||||
|
if (!isFloat) |
||||
|
return [n, ["integer", parseInt(number.join(''), 10), charnum, linenum]]; |
||||
|
else |
||||
|
return [n, ["float", parseFloat(number.join(''), 10), charnum, linenum]]; |
||||
|
} |
||||
|
|
||||
|
/* Split up the tokenized identifier if an operator appears in it |
||||
|
* prefer longer identifiers that start with the same character(s) as shorter ones |
||||
|
* e.g. ++ over + |
||||
|
* Everything after the operator goes back on to the token stream |
||||
|
*/ |
||||
|
|
||||
|
function tokenizeIdent(tokstream, |
||||
|
matchop, |
||||
|
charnum, |
||||
|
linenum) { |
||||
|
var identifier = []; |
||||
|
var n = 0; |
||||
|
while ((!isWhitespace(tokstream[0])) && isIdentifier(tokstream[0]) && !matchop(tokstream)) { |
||||
|
identifier.push(tokstream[0]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
n++; |
||||
|
charnum++; |
||||
|
} |
||||
|
identifier = identifier.join(''); |
||||
|
|
||||
|
return [[n, ["identifier", identifier, charnum, linenum]]]; |
||||
|
} |
||||
|
|
||||
|
function tokenizeCtor(tokstream, |
||||
|
matchop, |
||||
|
charnum, |
||||
|
linenum) { |
||||
|
var ident = tokenizeIdent(tokstream, |
||||
|
matchop, |
||||
|
charnum, |
||||
|
linenum); |
||||
|
ident[0][1][0] = "constructor"; |
||||
|
return ident; |
||||
|
} |
||||
|
|
||||
|
function tokenizeStr(tokstream, charnum, linenum) { |
||||
|
var stringlit = []; |
||||
|
var n = 1; |
||||
|
var new_charnum = charnum; |
||||
|
tokstream = tokstream.substr(1); |
||||
|
while (tokstream[0].charCodeAt() !== 34) { |
||||
|
stringlit.push(tokstream[0]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
n++; |
||||
|
new_charnum++; |
||||
|
if (tokstream.length < 1) { |
||||
|
throw error.JSyntaxError(linenum, charnum, "Error: missing quotation mark"); |
||||
|
} |
||||
|
} |
||||
|
n++; |
||||
|
return [n, ["stringlit", stringlit.join(''), new_charnum, linenum]]; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
function tokenizeT(tokstream, charnum, linenum) { |
||||
|
if (tokstream.length < 4) |
||||
|
return false; |
||||
|
var next4 = tokstream.substr(0,4); |
||||
|
if (next4 === "then") |
||||
|
return ["thenexp", "then"]; |
||||
|
else if (next4 === "true") |
||||
|
return ["truelit", "true"]; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
function peek(tokstream, toktype, word, charnum, linenum) { |
||||
|
var n = word.length; |
||||
|
if (tokstream.length < n) |
||||
|
return false; |
||||
|
var nextN = tokstream.substr(0, n); |
||||
|
if (nextN == word) { |
||||
|
return [toktype, word]; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function tokenize(tokstream, matchop) { |
||||
|
var tokens = []; |
||||
|
var charnum = 1; |
||||
|
var linenum = 1; |
||||
|
var i, result, lambda, num, comment; |
||||
|
|
||||
|
while (tokstream) { |
||||
|
switch (tokstream[0].charCodeAt()) { |
||||
|
/* falls through */ |
||||
|
case 59: // ;
|
||||
|
while (tokstream[0].charCodeAt() !== 10) { |
||||
|
tokstream = tokstream.substr(1); |
||||
|
} |
||||
|
break; |
||||
|
case 9: // '\t'
|
||||
|
charnum++; |
||||
|
tokens.push(["whitespace", '\t', charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 32: // ' '
|
||||
|
charnum++; |
||||
|
tokens.push(["whitespace", ' ', charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 10: // '\n'
|
||||
|
linenum++; |
||||
|
charnum = 1; /* Reset the character number for each line to 1 */ |
||||
|
tokens.push(["whitespace", '\n', charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 44: // ','
|
||||
|
charnum++; |
||||
|
tokens.push(["comma", ",", charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 40: // '('
|
||||
|
charnum++; |
||||
|
tokens.push(["left_paren", '(', charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
|
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 41: // ')'
|
||||
|
charnum++; |
||||
|
tokens.push(["right_paren", ')', charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 123: // '{'
|
||||
|
charnum++; |
||||
|
tokens.push(["left_brace", '{', charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 125: // '}'
|
||||
|
charnum++; |
||||
|
tokens.push(["right_brace", '}', charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 91: // '['
|
||||
|
charnum++; |
||||
|
tokens.push(["left_square", '[', charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 93: // ']'
|
||||
|
charnum++; |
||||
|
tokens.push(["right_square", ']', charnum, linenum]); |
||||
|
tokstream = tokstream.substr(1); |
||||
|
break; |
||||
|
/* falls through */ |
||||
|
case 34: // '"'
|
||||
|
result = tokenizeStr(tokstream, charnum, linenum); |
||||
|
var str = result[1]; |
||||
|
i = result[0]; |
||||
|
tokens.push(str); |
||||
|
tokstream = tokstream.substr(i); |
||||
|
break; |
||||
|
|
||||
|
/* falls through */ |
||||
|
case 46: // '.'
|
||||
|
if (isDigit(tokstream[1])) { |
||||
|
result = tokenizeNum(tokstream, charnum, linenum); |
||||
|
num = result[1]; |
||||
|
i = result[0]; |
||||
|
if (!isNaN(num[1])) { |
||||
|
tokens.push(num); |
||||
|
} |
||||
|
tokstream = tokstream.substr(i); |
||||
|
break; |
||||
|
} |
||||
|
/* falls through */ |
||||
|
case 116: // 't'
|
||||
|
result = tokenizeT(tokstream); |
||||
|
if (result) { |
||||
|
tokens.push($.extend(result, [charnum, linenum])); |
||||
|
tokstream = tokstream.substr(4); // 4 = length of either token
|
||||
|
break; |
||||
|
} |
||||
|
/* falls through */ |
||||
|
case 105: // 'i'
|
||||
|
var ifexp = peek(tokstream, "ifexp", "if"); |
||||
|
if (ifexp) { |
||||
|
tokens.push($.extend(ifexp, [charnum, linenum])); |
||||
|
tokstream = tokstream.substr(2); |
||||
|
break; |
||||
|
} |
||||
|
var inkeyword = peek(tokstream, "in", "in "); |
||||
|
if (inkeyword) { |
||||
|
tokens.push($.extend(inkeyword, [charnum, linenum])); |
||||
|
tokstream = tokstream.substr(3); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
/* falls through */ |
||||
|
case 100: // 'd'
|
||||
|
var defop = peek(tokstream, "defop", "defop"); |
||||
|
if (defop) { |
||||
|
tokens.push(["defop", "defop", charnum, linenum]); |
||||
|
tokstream = tokstream.substr(5); |
||||
|
break; |
||||
|
} |
||||
|
var deftype = peek(tokstream, "deftype", "deftype"); |
||||
|
if (deftype) { |
||||
|
tokens.push(["deftype", "deftype", charnum, linenum]); |
||||
|
tokstream = tokstream.substr(7); |
||||
|
break; |
||||
|
} |
||||
|
var def = peek(tokstream, "def", "def"); |
||||
|
if (def) { |
||||
|
tokens.push(["def", "def", charnum, linenum]); |
||||
|
tokstream = tokstream.substr(3); |
||||
|
break; |
||||
|
} |
||||
|
/* falls through */ |
||||
|
case 101: // e
|
||||
|
result = peek(tokstream, "elsexp", "else"); |
||||
|
if (result) { |
||||
|
tokens.push($.extend(result, [charnum, linenum])); |
||||
|
tokstream = tokstream.substr(4); |
||||
|
break; |
||||
|
} |
||||
|
/* falls through */ |
||||
|
case 102: // f
|
||||
|
result = peek(tokstream, "falselit", "false"); |
||||
|
if (result) { |
||||
|
tokens.push($.extend(result, [charnum, linenum])); |
||||
|
tokstream = tokstream.substr(5); |
||||
|
break; |
||||
|
} |
||||
|
/* falls through */ |
||||
|
case 108: // l
|
||||
|
lambda = peek(tokstream, "lambda", "lambda"); |
||||
|
if (lambda) { |
||||
|
tokens.push($.extend(lambda, [charnum, linenum])); |
||||
|
tokstream = tokstream.substr(6); |
||||
|
break; |
||||
|
} |
||||
|
var letexp = peek(tokstream, "let", "let"); |
||||
|
if (letexp) { |
||||
|
tokens.push($.extend(letexp, [charnum, linenum])); |
||||
|
tokstream = tokstream.substr(3); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
/* falls through */ |
||||
|
default: |
||||
|
if (isDigit(tokstream[0])) { |
||||
|
result = tokenizeNum(tokstream, charnum, linenum); |
||||
|
num = result[1]; |
||||
|
i = result[0]; |
||||
|
if (!isNaN(num[1])) { |
||||
|
tokens.push(num); |
||||
|
} |
||||
|
tokstream = tokstream.substr(i); |
||||
|
break; |
||||
|
} |
||||
|
var op = matchop(tokstream); |
||||
|
if (op) { |
||||
|
var l = op.length; |
||||
|
charnum = charnum + l; |
||||
|
tokstream = tokstream.substr(l); |
||||
|
tokens.push(["identifier", op, charnum, linenum]); |
||||
|
} |
||||
|
else { |
||||
|
if (isUpper(tokstream[0])) { |
||||
|
result = tokenizeCtor(tokstream, matchop, charnum, linenum); |
||||
|
} |
||||
|
else { |
||||
|
result = tokenizeIdent(tokstream, matchop, charnum, linenum); |
||||
|
} |
||||
|
for(var index = 0; index < result.length; index++) { |
||||
|
charnum++; |
||||
|
tokens.push(result[index][1]); |
||||
|
tokstream = tokstream.substr(result[index][0]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return tokens; |
||||
|
} |
||||
|
|
||||
|
function tokenizeHelp(input, matchop, strip_whitespace) { |
||||
|
try { |
||||
|
return tokenize(input, matchop).reverse().filter(function(x) { |
||||
|
if (strip_whitespace) { |
||||
|
return x[0] !== "whitespace"; |
||||
|
} |
||||
|
else { |
||||
|
return true; |
||||
|
} |
||||
|
}); |
||||
|
} catch (e) { |
||||
|
console.log(e.stxerror()); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var defop_pattern = ["defop", "integer", "constructor", |
||||
|
"left_paren", "identifier", |
||||
|
"identifier", "identifier", "right_paren"]; |
||||
|
|
||||
|
function checkPattern(x, i) { |
||||
|
return x === defop_pattern[i]; |
||||
|
} |
||||
|
|
||||
|
function tokenizeFull(input) { |
||||
|
var preludeSrc = fs.readFileSync("./prelude.jl"); |
||||
|
var matchop; |
||||
|
input = [preludeSrc, input].join(""); |
||||
|
var initialPass = tokenizeHelp(input, _.constant(false), true).reverse(); |
||||
|
|
||||
|
for (var i = 0; i < initialPass.length; i++) { |
||||
|
if (initialPass.slice(i, i+8). |
||||
|
map(_.first). |
||||
|
every(checkPattern)) { |
||||
|
rep.OPInfo[initialPass[i+5][1]] = |
||||
|
[parseInt(initialPass[i+1][1], 10), |
||||
|
initialPass[i+2][1]]; |
||||
|
} |
||||
|
} |
||||
|
operators = Object.keys(rep.OPInfo); |
||||
|
matchop = $.opMatch(operators); |
||||
|
return tokenizeHelp(input, matchop, true); |
||||
|
} |
||||
|
|
||||
|
module.exports = {tokenize : tokenizeFull, |
||||
|
isIdentifier : isIdentifier}; |
@ -0,0 +1,104 @@ |
|||||
|
var _ = require("underscore"); |
||||
|
|
||||
|
function empty(xs) { |
||||
|
return _.size(xs) < 1; |
||||
|
} |
||||
|
|
||||
|
function not(x) { |
||||
|
return !x; |
||||
|
} |
||||
|
|
||||
|
function min(a, b) { |
||||
|
if (a < b) { |
||||
|
return 1; |
||||
|
} |
||||
|
else if (a > b) { |
||||
|
return -1; |
||||
|
} |
||||
|
else { |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function max(a, b) { |
||||
|
if (a > b) { |
||||
|
return 1; |
||||
|
} |
||||
|
else if (a < b) { |
||||
|
return -1; |
||||
|
} |
||||
|
else { |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function groupOps(ops) { |
||||
|
return _.groupBy(ops.sort(), _.isEqual); |
||||
|
} |
||||
|
|
||||
|
function find(f, haystack) { |
||||
|
for(var i = 0; i < haystack.length; i++) { |
||||
|
if (f(haystack[i])) { |
||||
|
return i; |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
function dict(pairs) { |
||||
|
var o = {}; |
||||
|
pairs.map(function(p) { |
||||
|
o[p[0]] = p[1]; |
||||
|
}); |
||||
|
return o; |
||||
|
} |
||||
|
|
||||
|
function extend(xs, ys) { |
||||
|
var result = _.clone(xs); |
||||
|
result.push.apply(result, ys); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
RegExp.escape= function(s) { |
||||
|
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); |
||||
|
}; |
||||
|
|
||||
|
function operatorMatch(ops) { |
||||
|
ops = _.filter(ops, |
||||
|
function (op) { |
||||
|
return op.length > 0; |
||||
|
}); |
||||
|
ops.sort(min); |
||||
|
var rstring = ops.reduce( |
||||
|
function(acc, x) { |
||||
|
if (!x || x.length < 1) { |
||||
|
return ""; |
||||
|
} |
||||
|
return acc + "(" + RegExp.escape(x) + ")|"; |
||||
|
}, ""); |
||||
|
var reg = new RegExp(rstring); |
||||
|
return function(x) { |
||||
|
var matched = reg.exec(x); |
||||
|
if ((!(_.isNull(matched))) && matched[0]) { |
||||
|
return matched[0]; |
||||
|
} |
||||
|
else { |
||||
|
return false; |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function debugPrint(stx) { |
||||
|
console.log("%j\n", stx); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
module.exports = { |
||||
|
not : not, |
||||
|
groupOps : groupOps, |
||||
|
opMatch : operatorMatch, |
||||
|
dict: dict, |
||||
|
extend : extend, |
||||
|
empty : empty, |
||||
|
debugPrint : debugPrint |
||||
|
}; |
@ -0,0 +1,18 @@ |
|||||
|
/* |
||||
|
* Typecheck an AST with a given environment |
||||
|
* the environment maps variables to types |
||||
|
* a variable can either be bound or free |
||||
|
* when we say a variable is free that means that it is either |
||||
|
* unbound (which causes an exception to be raised immediately) |
||||
|
* or it is bound in the outer scope |
||||
|
* |
||||
|
* So the AST must first be converted to a form where each function body is tied |
||||
|
* to an environment mapping identifiers to types |
||||
|
*/ |
||||
|
|
||||
|
var rep = require("./representation.js"); |
||||
|
var env = require("./environments.js"); |
||||
|
|
||||
|
/* |
||||
|
* Map all bindings with explicit type annotations in the environment |
||||
|
*/ |
Binary file not shown.
@ -0,0 +1,87 @@ |
|||||
|
#! /usr/bin/node
|
||||
|
|
||||
|
var typ = require("./representation.js"); |
||||
|
var parse = require("./parse.js"); |
||||
|
var tokenizer = require("./tokenize.js"); |
||||
|
var pprint = require("./pprint.js"); |
||||
|
var env = require("./environments.js"); |
||||
|
var fs = require("fs"); |
||||
|
|
||||
|
var istr = fs.readFileSync('/dev/stdin').toString(); |
||||
|
var ast = parse.parseFull(tokenizer.tokenize(istr)); |
||||
|
|
||||
|
|
||||
|
var testenv = env.makeEnv("toplevel", |
||||
|
[ |
||||
|
["+", function(a) { return function(b) { return a + b; } }], |
||||
|
["*", function(a) { return function(b) { return a * b; } }], |
||||
|
["-", function(a) { return function(b) { return a - b; } }], |
||||
|
["a", 2], |
||||
|
["b", 3]]); |
||||
|
|
||||
|
console.log(JSON.stringify(evaluate(ast.ast[ast.ast.length-1], testenv))); |
||||
|
|
||||
|
function lookup(ident, env) { |
||||
|
var func = evaluate(env.bindings[ident], env); |
||||
|
return func; |
||||
|
} |
||||
|
|
||||
|
function evaluateString(input) { |
||||
|
var parsed = parse.parseFull(tokenizer.tokenize(input)).ast; |
||||
|
var evaluated = evaluateAll(parsed, testenv); |
||||
|
return evaluated; |
||||
|
} |
||||
|
|
||||
|
function apply(func, p) { |
||||
|
return func(evaluate(p)); |
||||
|
} |
||||
|
|
||||
|
function evaluateAll(ast, environment) { |
||||
|
var l = ast.length; |
||||
|
var evaled = []; |
||||
|
for (var i = 0; i < l; i++) { |
||||
|
// should look for closures?
|
||||
|
evaled.push(evaluate(ast[i], environment)); |
||||
|
} |
||||
|
return evaled; |
||||
|
} |
||||
|
|
||||
|
function evaluate(ast, environment) { |
||||
|
if (ast.exprType == "Application") { |
||||
|
var func = evaluate(ast.func, environment); |
||||
|
return apply( |
||||
|
func, |
||||
|
evaluate(ast.p, environment)); |
||||
|
} |
||||
|
else if (ast.exprType === "Unary") { |
||||
|
/* Unary function application */ |
||||
|
var func = evaluate(ast.op, environment); |
||||
|
return apply( |
||||
|
func, |
||||
|
evaluate(ast.val, environment)); |
||||
|
} |
||||
|
else if (ast.exprType === "Name") { |
||||
|
return lookup(ast.ident, environment); |
||||
|
} |
||||
|
else if (ast.exprType === "If") { |
||||
|
if (evaluate(ast.condition, environment)) { |
||||
|
return evaluate(ast.thenexp, environment); |
||||
|
} |
||||
|
else { |
||||
|
return evaluate(ast.elseexp, environment); |
||||
|
} |
||||
|
} |
||||
|
else if (ast.exprType === "Definition") { |
||||
|
return; /* XXX */ |
||||
|
} |
||||
|
else if (ast.exprType === "Integer") { |
||||
|
return ast.val; |
||||
|
} |
||||
|
else { |
||||
|
return ast; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
"evaluate" : evaluateString |
||||
|
}; |
Loading…
Reference in new issue