diff --git a/cexps.js b/cexps.js index 17b484a..b7726d0 100644 --- a/cexps.js +++ b/cexps.js @@ -40,7 +40,7 @@ function offset(i, v, w, next_cexp) { offset.prototype = cexp; function app(k, vs) { - this.k =k; + this.k = k; this.vs = vs; return this; } diff --git a/closure_conversion.js b/closure_conversion.js index c102cc2..eeb59c0 100644 --- a/closure_conversion.js +++ b/closure_conversion.js @@ -8,8 +8,8 @@ * in the current environment. * A free variable is simply those that are not in the list of formal parameters. * We start with the global environment and traverse the AST. Every time a new function is entered - * the current environment gets extended with the formal parameters of the function. - * When a let is encountered the current environment also gets extended. + * the current environment gets $.extended with the formal parameters of the function. + * When a let is encountered the current environment also gets $.extended. * The algorithm continues for any further function definitions in that branch * otherwise it just stops for that particular branch and continues with the rest of the AST * @@ -24,33 +24,11 @@ var env = require("./environments.js"); var errors = require("./errors.js"); var parser = require("./parse.js"); var pprint = require("./pprint.js"); -var tool = require("./tools.js"); +var $ = require("./tools.js"); -/*function convert(stx, cur_types, cur_exprs) { - switch (stx.exprType) { - case "If": - case "Definition": - case "Name": - case "Application": - case "Function": - case "Let": - default: - return stx; - } -}*/ - -function fvs(stx) { - /*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 notEmpty = $.compose($.not, $.eq([])); +function fvs_helper(stx) { switch (stx.exprType) { case "Integer": return []; @@ -62,38 +40,62 @@ function fvs(stx) { return []; case "Nil": return []; - case "List": - return []; case "Bool": return []; - case "FunctionDefinition": - return []; case "Let": - return stx.pairs.map(fvs); + return []; case "Unary": - return fvs(stx.val); + return $.flatten([stx.op.ident, fvs_helper(stx.val)]); case "Definition": - return [fvs(stx.val)]; + return $.flatten(fvs_helper(stx.val)); case "Application": - var vs = fvs(stx.p); - var f_fvs = fvs(stx.func); - return [].concat.apply([], [vs, f_fvs]); + var vs = $.flatten(fvs_helper(stx.p)); + var f_fvs = $.flatten(fvs_helper(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 [cond_fvs, then_fvs, else_fvs]; + var cond_fvs = fvs_helper(stx.condition); + var then_fvs = fvs_helper(stx.thenexp); + var else_fvs = fvs_helper(stx.elseexp); + return $.flatten([cond_fvs, then_fvs, else_fvs]); } else { - return [fvs(stx.condition)] + [fvs(stx.thenexp)]; + return $.flatten([fvs_helper(stx.condition), fvs_helper(stx.thenexp)]); } case "Name": return stx.ident; } } -var ast = parser.parse("(^ wat (a+(ar*b*c^twerp+\"sdfdsfsdfsdfsdf\")*rt))")[0]; -console.log(pprint.pprint(ast)); -console.log(tool.unique(fvs(ast))); -//console.log(ast); +function fvs(stx) { + 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; + switch (stx.exprType) { + case "Let": + var bound_vars = stx.pairs.map( + function (stx) { + return stx.ident.ident; + }); + var let_fvs = stx.pairs.map(fvs_helper); + var body_fvs = fvs_helper(stx.body); + variables = $.flatten(let_fvs); + $.extend(variables, $.flatten(body_fvs)); + } + return $.difference($.unique($.flatten(variables)), bound_vars); + +} + +//var ast = parser.parse("let { c = trtr a = let {tttt = (rtertret^yyyy) } let { dfsdf = let { asdsd = 3434 } gdfgdfg } (45+(asdddy*uyuy)) q = ((lambda x y -> (x+y)) 4 ui) } (^ wat (a+(ar*b*c^twerp+\"sdfdsfsdfsdfsdf\")*rt))")[0]; +//var ast = parser.parse("let { a = let { b = let {dsdfgf = sddd } fdgfg } gggggg } t")[0]; +//console.log(pprint.pprint(ast)); +var ast = parser.parse("let { a = 12 b = (a + t) } (a + b * d)")[0]; +console.log(fvs(ast)); diff --git a/desugar.js b/desugar.js index 2a1d19e..daef9eb 100644 --- a/desugar.js +++ b/desugar.js @@ -32,7 +32,7 @@ function desugarLet(stx) { } function desugar(stx) { - switch (stx.exprType) { + switch (stx.exprType) { case "If": if (stx.elseexp) return new typ.If(desugar(stx.condition), desugar(stx.thenexp), desugar(stx.elseexp)); diff --git a/environments.js b/environments.js index 291a494..51ee39e 100644 --- a/environments.js +++ b/environments.js @@ -11,10 +11,15 @@ var rep = require("./representation.js"); function extend(env, values) { - for (var i = 0; i < values.length; i++) { - env[values[i][0].val] = values[i][1]; + var new_env = {}; + var env_keys = Object.keys(env); + for (var i = 0; i < env_keys.length; i++) { + new_env[env_keys[i]] = env[env_keys[i]]; } - return env; + for (i = 0; i < values.length; i++) { + new_env[values[i][0].val] = values[i][1]; + } + return new_env; } // creates a new environment initialized with the pairs in values @@ -22,7 +27,7 @@ function makeEnv(name, values) { var env = {}; env.name = name; for (var i = 0; i < values.length; i++) { - var name = values[i][0].val; + name = values[i][0].val; var val = values[i][1]; env[name] = val; } diff --git a/errors.js b/errors.js index 05a1dab..b7855f0 100644 --- a/errors.js +++ b/errors.js @@ -26,6 +26,14 @@ function JTypeError(linenum, charnum, token, message) { return this; } +function JInternalError(message) { + this.errormessage = message; + console.log(message); + return this; +} + module.exports = {JSyntaxError : JSyntaxError, - JTypeError : JTypeError}; + JTypeError : JTypeError, + JInternalError : JInternalError + }; diff --git a/evaluate.js b/evaluate.js new file mode 100644 index 0000000..c87286e --- /dev/null +++ b/evaluate.js @@ -0,0 +1,18 @@ +var parse = require("./parse.js").parse; + +function isAtom(x) { + return stx.exprType != "List"; +} + +function evaluate(exp, env) { + if (isAtom(exp)) { + switch (exp.exprType) { + case "Function": + return evaluate(invoke(exp, env), env); + case "Name": + return lookup(expr.val); + default: + return expr.val; + } + } +} diff --git a/parse.js b/parse.js index 903ce75..2f52e4f 100755 --- a/parse.js +++ b/parse.js @@ -21,7 +21,7 @@ function snd(ts) { /*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") + throw error.JSyntaxError(0,0,"unexpected end of source"); } var nextT = fst(tokens)[0]; if (checks.some(function (x) { @@ -71,7 +71,7 @@ function parseMany(parse, exprType, valid, tokens, charnum, linenum) { if (!exprType(fst(results).exprType)) break; if (fst(tokens)) - current = fst(tokens)[0] + current = fst(tokens)[0]; else throw error.JSyntaxError(charnum, linenum, "Unexpected end of source"); if (tokens.length <= 1) @@ -115,15 +115,16 @@ function parseBetween(exprType, between, tokens, charnum, linenum) { } function parseList(tokens) { + var xs; if (fst(tokens)[0] === "right_square") { - var xs = []; + xs = []; } else if (fst(tokens)[0] === "comma") { tokens.pop(); - var xs = []; + xs = []; } else { - var xs = parseBetween(function (x) { return true; }, "comma", tokens, fst(tokens)[3], fst(tokens)[2]); + 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], @@ -137,16 +138,17 @@ function parseList(tokens) { function parseDefFunction(tokens) { var fname = parse(tokens); + var parameters; 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") { - var parameters = []; + parameters = []; } else { - var parameters = parseMany(parse, + parameters = parseMany(parse, validName, validFormPar, tokens, @@ -209,6 +211,7 @@ function parseLetForm(tokens, linenum, charnum) { function parseLetFunction(tokens, linenum, charnum) { var fname = parse(tokens); + var parameters; if (fname.exprType != "Name") { throw error.JSyntaxError(fst(tokens)[3], @@ -216,10 +219,10 @@ function parseLetFunction(tokens, linenum, charnum) { "Expected an identifier in function definition"); } if (fst(tokens)[0] === "right_paren") { - var parameters = []; + parameters = []; } else { - var parameters = parseMany(parse, + parameters = parseMany(parse, validName, validFormPar, tokens, @@ -235,7 +238,7 @@ function parseLetFunction(tokens, linenum, charnum) { if (fst(tokens)[0] !== "arrow") { throw error.JSyntaxError(fst(tokens)[3], fst(tokens)[2], - "Function parameters in let/def form must be followed by ->") + "Function parameters in let/def form must be followed by ->"); } tokens.pop(); var body = parse(tokens); @@ -435,7 +438,7 @@ function parseLambda(tokens) { fst(tokens)[2], "arrow must follow parameters in lambda, not "+fst(tokens)[0]); } - tokens.pop() + tokens.pop(); var body = parse(tokens); return new typ.FuncT(parameters, body); } @@ -448,8 +451,10 @@ var validOperator = makeChecker(["identifier"]); /* Parses function application (either infix or prefix) */ function computeApp(tokens, charnum, linenum) { var lhs = parse(tokens); + var next; + var result; if (fst(tokens)) - var next = fst(tokens); + next = fst(tokens); else { throw error.JSyntaxError(linenum, charnum, @@ -457,7 +462,7 @@ function computeApp(tokens, charnum, linenum) { } if (typ.OPInfo[next[1]]) { /* it's an infix expression */ - var result = parseInfix(tokens, 1, lhs, linenum, charnum); + result = parseInfix(tokens, 1, lhs, linenum, charnum); if (!fst(tokens) || fst(tokens)[0] !== "right_paren") { throw error.JSyntaxError(linenum, charnum, @@ -495,7 +500,7 @@ function computeApp(tokens, charnum, linenum) { */ function parseInfix(tokens, minPrec, lhs, linenum, charnum) { if (!lhs) { - var lhs = parse(tokens); + lhs = parse(tokens); } while (true) { var cur = fst(tokens); @@ -524,8 +529,9 @@ function parseInfix(tokens, minPrec, lhs, linenum, charnum) { function parse(tokens) { var charnum = fst(tokens)[2]; var linenum = fst(tokens)[3]; + var toktype; if (fst(tokens)) { - var toktype = fst(tokens)[0]; + toktype = fst(tokens)[0]; } else { process.exit(code=1); @@ -570,9 +576,9 @@ function parse(tokens) { } } -var istr = fs.readFileSync('/dev/stdin').toString(); +//var istr = fs.readFileSync('/dev/stdin').toString(); function parseFull(tokenized) { - var ast = new Array(); + var ast = []; try { while (tokenized.length > 0) { var parsed = desugarer.desugar(parse(tokenized)); @@ -584,7 +590,14 @@ function parseFull(tokenized) { process.exit(1); } } -console.log(parseFull(tokenizer.tokenize(istr)).map(pprint.pprint).join("\n")); + +module.exports = { parse : function(str) { + return parseFull(tokenizer.tokenize(str)); + } + }; + + +//console.log(parseFull(tokenizer.tokenize(istr)).map(pprint.pprint).join("\n")); //console.log(tokenizer.tokenize(istr)); //console.log(parseFull(tokenizer.tokenize(istr))); diff --git a/representation.js b/representation.js index 09d456f..ccc708c 100644 --- a/representation.js +++ b/representation.js @@ -168,13 +168,6 @@ function TypeOp(name, params, body) { return this; } -function TypeBinding(name, type) { - this.name = name; - this.type = type; - return this; -} - - //convenience function to construct binary operators //assumes that the identifier refers to the name of a primitive //operation @@ -195,11 +188,22 @@ function makeApp(name, parameters) { } +function makeGensym() { + var n = 0; + return function() { + var x = "G"+n; + n = n + 1; + return x; + }; +} + +var gensym = makeGensym(); + OPInfo = {"+" : [3, "Left"], - "-" : [3, "Left"], - "*" : [4, "Left"], - "/" : [4, "Left"], - "^" : [5, "Right"], + "-" : [3, "Left"], + "*" : [4, "Left"], + "/" : [4, "Left"], + "^" : [5, "Right"], "++" : [3, "Left"], "==" : [2, "Left"], ">" : [2, "Left"], @@ -234,5 +238,8 @@ module.exports = DefFunc : DefFunc, UnaryOp : UnaryOp, Nil : Nil, - LetExp : LetExp - } + LetExp : LetExp, + gensym : gensym, + TypeVar : TypeVar, + TypeOp : TypeOp + }; diff --git a/tokenize.js b/tokenize.js index 5cdd1cc..0db5e72 100755 --- a/tokenize.js +++ b/tokenize.js @@ -136,70 +136,83 @@ function tokenize(tokstream, matchop) { var tokens = []; var charnum = 1; var linenum = 1; + var i, result, lambda, num; while (tokstream) { switch (tokstream[0].charCodeAt()) { + /* falls through */ 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; 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: // '"' - var result = tokenizeStr(tokstream, charnum, linenum); + result = tokenizeStr(tokstream, charnum, linenum); var str = result[1]; - var i = result[0]; + i = result[0]; tokens.push(str); tokstream = tokstream.substr(i); break; + /* falls through */ case 45: // '-' - var lambda = peek(tokstream, "arrow", "->"); + lambda = peek(tokstream, "arrow", "->"); if (lambda) { tokens.push(lambda); tokstream = tokstream.substr(2); @@ -212,24 +225,27 @@ function tokenize(tokstream, matchop) { break; } + /* falls through */ case 46: // '.' if (isDigit(tokstream[1])) { - var result = tokenizeNum(tokstream, charnum, linenum); - var num = result[1]; - var i = result[0]; - if (num[1] !== NaN) + 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' - var result = tokenizeT(tokstream); + result = tokenizeT(tokstream); if (result) { tokens.push(result); tokstream = tokstream.substr(4); // 4 = length of either token break; } - + /* falls through */ case 105: // 'i' var ifexp = peek(tokstream, "ifexp", "if"); if (ifexp) { @@ -244,6 +260,7 @@ function tokenize(tokstream, matchop) { break; } + /* falls through */ case 100: // 'd' var defop = peek(tokstream, "defop", "defop"); if (defop) { @@ -257,22 +274,25 @@ function tokenize(tokstream, matchop) { tokstream = tokstream.substr(3); break; } + /* falls through */ case 101: // e - var result = peek(tokstream, "elsexp", "else"); + result = peek(tokstream, "elsexp", "else"); if (result) { tokens.push(result); tokstream = tokstream.substr(4); break; } + /* falls through */ case 102: // f - var result = peek(tokstream, "falselit", "false"); + result = peek(tokstream, "falselit", "false"); if (result) { tokens.push(result); tokstream = tokstream.substr(5); break; } + /* falls through */ case 108: // l - var lambda = peek(tokstream, "lambda", "lambda"); + lambda = peek(tokstream, "lambda", "lambda"); if (lambda) { tokens.push(lambda); tokstream = tokstream.substr(6); @@ -285,13 +305,15 @@ function tokenize(tokstream, matchop) { break; } + /* falls through */ default: if (isDigit(tokstream[0])) { - var result = tokenizeNum(tokstream, charnum, linenum); - var num = result[1]; - var i = result[0]; - if (num[1] !== NaN) + result = tokenizeNum(tokstream, charnum, linenum); + num = result[1]; + i = result[0]; + if (!isNaN(num[1])) { tokens.push(num); + } tokstream = tokstream.substr(i); break; } @@ -303,12 +325,12 @@ function tokenize(tokstream, matchop) { tokens.push(["identifier", op, charnum, linenum]); } else { - var result = tokenizeIdent(tokstream, matchop, charnum, linenum); - result.map(function(x) { + result = tokenizeIdent(tokstream, matchop, charnum, linenum); + for(var index = 0; index < result.length; index++) { charnum++; - tokens.push(x[1]); - tokstream = tokstream.substr(x[0]); - }); + tokens.push(result[index][1]); + tokstream = tokstream.substr(result[index][0]); + } } } } @@ -328,9 +350,9 @@ function tokenizeHelp(input, matchop) { function tokenizeFull(input) { var matchop = tools.opMatch(operators); - var initialPass = tokenizeHelp(input, matchop).reverse();; + var initialPass = tokenizeHelp(input, matchop).reverse(); for (var i = 0; i < initialPass.length; i++) { - if (initialPass.slice(i, i+7).map(function(x) { return x[0]; }) === + if (initialPass.slice(i, i+7).map(tools.fst) === ["defop", "integer", "identifier", "Left", "Right", "left_paren", "identifier", "identifier", "identifier", "right_paren"]) diff --git a/tools.js b/tools.js index 0b4db5c..9d682a0 100644 --- a/tools.js +++ b/tools.js @@ -85,6 +85,10 @@ function eq(a) { }; } +function fst(xs) { + return xs[0]; +} + function equal(a) { return function(b) { return a === b; @@ -103,11 +107,11 @@ function groupBy(eq, xs) { } function groupOps(ops) { - return groupBy(eq, ops.sort()); + return groupBy(function (x) { return function(y) { return x === y; };}, ops.sort()); } function unique(ops) { - return groupOps(ops).map(function(x) { return x[0]; }); + return groupOps(ops).map(fst); } function find(f, haystack) { @@ -126,13 +130,34 @@ function dict(pairs) { return o; } +function flatten(xs) { + if (!(xs instanceof Array)) + return xs; + if (xs.every(function (x) { return !(x instanceof Array); })) + return xs; + return [].concat.apply([], xs); +} + +function extend(xs, ys) { + xs.push.apply(xs, ys); + return xs; +} + +function difference(xs, ys) { + var difflist = groupOps(extend(xs, ys)); + return difflist.filter(function (group) { + if (group.length > 1) { + return false; + } + return true; + }).map(fst); +} /* * Problem: * >> > >>^ <- longest one must be matched * regex? */ - RegExp.escape= function(s) { return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); }; @@ -151,6 +176,8 @@ function operatorMatch(ops) { return false; }; } + + /* var print = console.log; @@ -168,4 +195,9 @@ module.exports = {compose : compose, groupOps : groupOps, opMatch : operatorMatch, dict: dict, - unique : unique}; + unique : unique, + fst : fst, + eq: eq, + extend : extend, + flatten : flatten, + difference : difference} diff --git a/typecheck.js b/typecheck.js index e69de29..7c198c6 100644 --- a/typecheck.js +++ b/typecheck.js @@ -0,0 +1,19 @@ +/* + * 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"); + +var TypeOp = rep.TypeOp; +var TypeVar = rep.TypeVar; + +