diff --git a/Makefile b/Makefile index c290e79..1d4ed98 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,4 @@ -lib/lisp_parser.js: lib/lisp_parser.peg - node_modules/.bin/pegjs $< $@ - -tests: lib/lisp_parser.js +tests: node_modules/.bin/coffee bin/lisp test.scm diff --git a/lib/eval.coffee b/lib/eval.coffee index 35bdf14..a37a64c 100644 --- a/lib/eval.coffee +++ b/lib/eval.coffee @@ -1,17 +1,16 @@ lookup = require './lookup' -{car, cdr} = require './lists' +{car, cdr, cadr, caadr, cdadr} = require './lists' lispeval = (element, scope) -> - switch element.type - when 'number' then parseInt(element.value, 10) - when 'symbol' - lookup(scope, element.value) + switch (car element) + when 'number' then parseInt (cadr element), 10 + when 'symbol' then lookup scope, (cadr element) when 'list' - proc = lispeval((car element.value), scope) - args = (cdr element.value) + proc = lispeval (caadr element), scope + args = cdadr element proc args, scope - else throw new Error ("Unrecognized type in parse: #{element.type}") + else throw new Error ("Unrecognized type in parse: #{(car element)}") module.exports = lispeval diff --git a/lib/fn.coffee b/lib/fn.coffee index b98c7cf..4f51a49 100644 --- a/lib/fn.coffee +++ b/lib/fn.coffee @@ -4,19 +4,33 @@ lispeval = require './eval' module.exports = create_vm_expression_evaluator: (defining_scope, params, body) -> (cells, scope) -> - args = listToVector(cells).map (i) -> lispeval i, scope + args = (amap = (cells, accum) -> + return accum if nilp cells + amap((cdr cells), accum.concat(lispeval (car cells), scope)))(cells, []) body.apply null, args create_lisp_expression_evaluator: (defining_scope, params, body) -> (cells, scope) -> + + # Takes the current scope, which has been passed in during the + # execution phase, and evaluate the contents of the parameters + # in the context in which this call is made (i.e. when the + # function is *called*, rather than defined. + new_scope = (cmap = (cells, params, nscope) -> return nscope if (nilp cells) or (nilp params) nscope[(car params)] = lispeval (car cells), scope cmap((cdr cells), (cdr params), nscope))(cells, params, {}) - inner = cons(new_scope, defining_scope) + + # Execute and evaluate the body, creating an inner scope that + # consists of all the bound variables (the parameters) evaluated + # in the context of the function call, and all of free variables + # evaluated in the context of the defining scope. + + inner_scope = cons(new_scope, defining_scope) (nval = (body, memo) -> return memo if nilp body - nval((cdr body), lispeval((car body), inner)))(body) + nval((cdr body), lispeval((car body), inner_scope)))(body) create_special_form_evaluator: (defining_scope, params, body) -> (cells, scope) -> body(cells, scope) diff --git a/lib/lisp.coffee b/lib/lisp.coffee index 3123878..0d66588 100644 --- a/lib/lisp.coffee +++ b/lib/lisp.coffee @@ -2,16 +2,15 @@ fs = require 'fs' {readForms} = require './reader' lispeval = require './eval' scope = require './scope' -{car, cdr, nilp} = require './lists' +{car, cdr, nilp, cadr} = require './lists' module.exports = run: (pathname) -> text = fs.readFileSync(pathname, 'utf8') - root = scope ast = readForms(text) (nval = (body, memo) -> return memo if nilp body - nval((cdr body), lispeval((car body), root)))(ast.value) + nval((cdr body), lispeval((car body), scope)))(cadr ast) diff --git a/lib/lisp_parser.peg b/lib/lisp_parser.peg deleted file mode 100644 index 5893efc..0000000 --- a/lib/lisp_parser.peg +++ /dev/null @@ -1,34 +0,0 @@ -lisp - = cell* - -cell - = _* datum:datum _* - { return datum } - -datum - = list / boolean / number / symbol - -list - = "(" items:cell* ")" - { return { type: "list", value: items } } - -boolean - = b:("#t" / "#f") - { return { type: 'boolean', value: b } } - -delim - = paren / _ - -number - = b:( [0-9]+ ) - { return { type: 'number', value: b.join("") } } - -symbol - = b:(!delim c:. { return c })+ - { return { type: 'symbol', value: b.join("") } } - -paren - = "(" / ")" - -_ - = w:[ \t\n\r]+ diff --git a/lib/lists.coffee b/lib/lists.coffee index fa5b879..1354ade 100644 --- a/lib/lists.coffee +++ b/lib/lists.coffee @@ -1,6 +1,8 @@ vectorp = (a) -> toString.call(a) == '[object Array]' -listp = (a) -> vectorp(a) and a.__list == true +pairp = (a) -> vectorp(a) and a.__list == true + +listp = (a) -> (pairp a) and (pairp cdr a) recordp = (a) -> Object.prototype.toString.call(a) == '[object Object]' @@ -16,7 +18,7 @@ car = (a) -> a[0] cdr = (a) -> a[1] -nilp = (a) -> listp(a) and a.length == 0 +nilp = (a) -> pairp(a) and a.length == 0 vectorToList = (v, p) -> p = if p? then p else 0 @@ -25,14 +27,17 @@ vectorToList = (v, p) -> # have to be intercepted first. The use of duck-typing here is # frustrating, but I suppose the eventual runtime will be doing # something like this anyway for base types. - item = if listp(v[p]) then v[p] else if vectorp(v[p]) then vectorToList(v[p]) else v[p] + item = if pairp(v[p]) then v[p] else if vectorp(v[p]) then vectorToList(v[p]) else v[p] cons(item, vectorToList(v, p + 1)) -list = (v...) -> vectorToList v +list = (v...) -> + ln = v.length; + (nl = (a) -> + cons(v[a], if (a < ln) then (nl(a + 1)) else nil))(0) listToVector = (l, v = []) -> return v if nilp l - v.push if listp (car l) then listToVector(car l) else (car l) + v.push if pairp (car l) then listToVector(car l) else (car l) listToVector (cdr l), v # This is the simplified version. It can't be used stock with reader, @@ -41,7 +46,7 @@ listToVector = (l, v = []) -> listToString = (l) -> return "" if nilp l - if listp (car l) + if pairp (car l) "(" + (listToString(car l)).replace(/\ *$/, "") + ") " + listToString(cdr l) else p = if typeof (car l) == 'string' then '"' else '' @@ -54,7 +59,7 @@ module.exports = cdr: cdr list: list nilp: nilp - listp: listp + pairp: pairp vectorp: vectorp recordp: recordp vectorToList: vectorToList diff --git a/lib/reader.coffee b/lib/reader.coffee index 7fc816b..0f82347 100644 --- a/lib/reader.coffee +++ b/lib/reader.coffee @@ -1,4 +1,4 @@ -{car, cdr, cons, nil, nilp, listp} = require './lists' +{car, cdr, cons, nil, nilp, pairp, vectorToList} = require './lists' NEWLINES = ["\n", "\r", "\x0B", "\x0C"] WHITESPACE = [" ", "\t"].concat(NEWLINES) @@ -32,10 +32,7 @@ skipWS = (inStream) -> # (type, value, line, column) -> (node {type, value, line, column)} makeObj = (type, value, line, column) -> - 'type': type - 'value': value - 'line': line - 'column': column + vectorToList([type, value, line, column]) # msg -> (IO -> Node => Error) handleError = (message) -> diff --git a/lib/reduce.coffee b/lib/reduce.coffee index 3ed59e6..cd35aa7 100644 --- a/lib/reduce.coffee +++ b/lib/reduce.coffee @@ -1,4 +1,4 @@ -{car, cdr, cons, listp, nilp, nil, list, listToString} = require './lists' +{car, cdr, cons, pairp, nilp, nil, list, listToString} = require './lists' reduce = (lst, iteratee, memo, context) -> count = 0 diff --git a/lib/scope.coffee b/lib/scope.coffee index ed185dc..b8ba0e7 100644 --- a/lib/scope.coffee +++ b/lib/scope.coffee @@ -1,5 +1,5 @@ lispeval = require './eval' -{cons, car, cdr, nilp, nil} = require './lists' +{cons, car, cdr, nilp, nil, cadar, cadr, caddr} = require './lists' {create_lisp_expression_evaluator, create_vm_expression_evaluator, create_special_form_evaluator} = require './fn' scope = cons @@ -13,20 +13,19 @@ scope = cons 'define': create_special_form_evaluator scope, [], (nodes, scope) -> current = (car scope) - current[(car nodes).value] = lispeval((car cdr nodes), scope) + current[(cadar nodes)] = lispeval((cadr nodes), scope) 'lambda': create_special_form_evaluator scope, [], (nodes, scope) -> - param_nodes = (car nodes).value + param_nodes = cadar nodes reducer = (l) -> - if (nilp l) then nil else cons((car l).value, reducer(cdr l)) + if (nilp l) then nil else cons (cadar l), reducer(cdr l) param_names = reducer(param_nodes) - - create_lisp_expression_evaluator(scope, param_names, (cdr nodes)) + create_lisp_expression_evaluator scope, param_names, (cdr nodes) 'if': create_special_form_evaluator scope, [], (nodes, scope) -> - if lispeval((car nodes), scope) - lispeval((car cdr nodes), scope) + if lispeval (car nodes), scope + lispeval (cadr nodes), scope else - lispeval((car cdr cdr nodes), scope) + lispeval (caddr nodes), scope module.exports = scope