diff --git a/.gitignore b/.gitignore deleted file mode 100644 index da23d0d..0000000 --- a/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directory -# Deployed apps should consider commenting this line out: -# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git -node_modules diff --git a/README.md b/README.md index 57ad6bb..69e5928 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -JLambda -======= - JLambda is a functional language in the spirit of languages such as Scheme, SML, or Clean. It aims to have a very flexible syntax and a clean and easy to understand type system. Another goal is to generate very efficient JavaScript @@ -14,18 +11,3 @@ JLambda also aims to support concurrency which will be built on a continuation-passing style intermediate language. I have not figured out how scheduling threads will work, or whether I will provide any programmer directed way of scheduling (i.e. yield). - -Installation ------------- - - git clone git@github.com:nisstyre56/JLambda.git - cd JLambda - npm install - -Usage ------ - -Since the language is currently under heavy construction, the parser is the -entry point for now: - - cat example.jl | ./parse.js diff --git a/closures.js b/closures.js new file mode 100644 index 0000000..c261113 --- /dev/null +++ b/closures.js @@ -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 +}; diff --git a/cps.js b/cps.js new file mode 100644 index 0000000..afb69f5 --- /dev/null +++ b/cps.js @@ -0,0 +1,5 @@ +#! /usr/bin/node + +var typ = require("representation.js"); + + diff --git a/environments.js b/environments.js index d88426e..dedd90c 100644 --- a/environments.js +++ b/environments.js @@ -27,23 +27,25 @@ function extend(env, values) { function makeEnv(name, values) { var env = {}; env.name = name; + env.bindings = {}; for (var i = 0; i < values.length; i++) { - name = values[i][0].val; + name = values[i][0]; var val = values[i][1]; - env[name] = val; + env.bindings[name] = val; } return env; } function lookup(name, env) { - var value = env[name]; + var value = env.bindings[name]; if (!value) { - throw errors.UnboundError(name, env.name); + throw errors.JUnboundError(name, env.name); } return value; } module.exports = { lookup : lookup, - extend : extend + extend : extend, + makeEnv : makeEnv }; diff --git a/package.json b/package.json deleted file mode 100644 index f3044c2..0000000 --- a/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "JLambda", - "version": "0.0.0", - "description": "yet another static functional language implemented in JS", - "scripts": { - "test": "./test.js" - }, - "repository": { - "type": "git", - "url": "https://github.com/nisstyre56/JLambda.git" - }, - "keywords": [ - "static", - "functional", - "language" - ], - "author": "nisstyre56", - "license": "ΩF:∅", - "bugs": { - "url": "https://github.com/nisstyre56/JLambda/issues" - }, - "homepage": "https://github.com/nisstyre56/JLambda", - "dependencies": { - "underscore": "^1.6.0" - } -} diff --git a/parse.js b/parse.js index 08b6052..9d9bbc8 100755 --- a/parse.js +++ b/parse.js @@ -7,7 +7,7 @@ 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) { @@ -808,9 +808,11 @@ function parse(tokens) { function parseFull(tokenized) { var ast = []; var typeBindings = {}; + var current; try { while (tokenized.length > 0) { - ast.push(desugarer.desugar(parse(tokenized), typeBindings)); + current = closure.annotate_fvs(desugarer.desugar(parse(tokenized), typeBindings)); + ast.push(current); } return [ast, typeBindings]; } catch (e) { @@ -828,9 +830,10 @@ function parseFull(tokenized) { module.exports = { parse : function(str) { return parseFull(tokenizer.tokenize(str)); }, - tokenize : tokenizer.tokenize + 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)); +//var istr = fs.readFileSync('/dev/stdin').toString(); +//var testParse = parseFull(tokenizer.tokenize(istr)); +//console.log(testParse[1]); +//console.log(testParse[0].map(pprint.pprint)); diff --git a/vm.js b/vm.js new file mode 100755 index 0000000..e4ace80 --- /dev/null +++ b/vm.js @@ -0,0 +1,66 @@ +#! /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 istr = fs.readFileSync('/dev/stdin').toString(); +//var istr = "if true then (+ 6 (a+a*b)) else 1"; +var istr = "def (f a) (a + b)" +var ast = parse.parseFull(tokenizer.tokenize(istr)); + +function apply(func, p) { + return func(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") { + return apply(evaluate(ast.func, environment), evaluate(ast.p, environment)); + } + else if (ast.exprType === "Unary") { /* Unary function application */ + return apply(evaluate(ast.op, environment), evaluate(ast.val, environment)); + } + else if (ast.exprType === "Name") { + //console.log(env.lookup(ast.ident, environment)); + return env.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") { + console.log(ast); + } + else { + return ast.val; + } +} +var testenv = env.makeEnv("toplevel", + [ + ["+", function(a) { return function(b) { return a + b; } }], + ["*", function(a) { return function(b) { return a * b; } }], + ["a", 2], + ["b", 3]]); + +var all = evaluate(ast[0][ast[0].length - 1], testenv); +console.log(all); +//console.log("%j", testenv); +//console.log("%j", ast[0][ast[0].length - 1]); +//console.log("%j", ast[0][ast[0].length - 1]);