diff --git a/chapter-lambda-1/interpreter.coffee b/chapter-lambda-1/interpreter.coffee index cffd91f..6bb8944 100644 --- a/chapter-lambda-1/interpreter.coffee +++ b/chapter-lambda-1/interpreter.coffee @@ -1,8 +1,6 @@ {listToString, listToVector, pairp, cons, car, cdr, caar, cddr, cdar, cadr, caadr, cadar, caddr, nilp, nil, setcdr, metacadr} = require "cons-lists/lists" -readline = require "readline" -{inspect} = require "util" -{Symbol} = require '../chapter1/reader_types' +{Node} = require "../chapter1/reader_types" class LispInterpreterError extends Error name: 'LispInterpreterError' @@ -59,8 +57,8 @@ extend = (env, variables, values) -> else if (nilp variables) if (nilp values) then env else throw new LispInterpreterError "Too many values" else - if (variables instanceof Symbol) - (cons (cons variables.v, values), env) + if (variables.type == 'symbol') + (cons (cons variables, values), env) else nil @@ -119,13 +117,12 @@ update = (id, env, value, callback) -> # TODO: Reengineer this with a call to normalize astSymbolsToLispSymbols = (node) -> - nvalu = (node) -> cadr node return nil if nilp node - throw (new LispInterpreterError "Not a list of variable names") if not ((car node) is 'list') - handler = (node) -> - return nil if nilp node - cons (nvalu car node).v, (handler cdr node) - handler(nvalu node) + throw (new LispInterpreterError "Not a list of variable names") if not node.type == 'list' + handler = (cell) -> + return nil if nilp cell + cons (car cell).value, (handler cdr cell) + handler node.value # Takes an AST node and evaluates it and its contents. A node may be # ("list" (... contents ...)) or ("number" 42) or ("symbol" x), etc. @@ -133,39 +130,40 @@ astSymbolsToLispSymbols = (node) -> cadddr = metacadr('cadddr') metadata_evaluation = - listp: (node) -> (car node) == 'list' - symbolp: (node) -> (car node) == 'symbol' - numberp: (node) -> (car node) == 'number' - stringp: (node) -> (car node) == 'string' - nvalu: (node) -> cadr node - mksymbols: astSymbolsToLispSymbols + listp: (node) -> node.type == 'list' + symbolp: (node) -> node.type == 'symbol' + numberp: (node) -> node.type == 'number' + stringp: (node) -> node.type == 'string' + commentp: (node) -> node.type == 'comment' + nvalu: (node) -> node.value + mksymbols: (list) -> astSymbolsToLispSymbols(list) straight_evaluation = - listp: (node) -> node.__type == 'list' - symbolp: (node) -> node instanceof Symbol - commentp: (node) -> node instanceof Comment - numberp: (node) -> typeof node == 'number' - stringp: (node) -> typeof node == 'string' - boolp: (node) -> typeof node == 'boolean' - nullp: (node) -> node == null - vectorp: (node) -> (not straight_evaluation.listp node) and toString.call(node) == '[object Array]' - recordp: (node) -> (not node._prototype?) and toSTring.call(node) == '[object Object]' - objectp: (node) -> (node._prototype?) and toString.call(node) == '[object Object]' - nilp: (node) -> nilp(node) - nvalu: (node) -> node - mksymbols: (node) -> node + listp: (cell) -> cell.__type == 'list' + symbolp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] not in ["\"", ";"] + commentp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] == ";" + numberp: (cell) -> typeof cell == 'number' + stringp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] == "\"" + boolp: (cell) -> typeof cell == 'boolean' + nullp: (cell) -> cell == null + vectorp: (cell) -> (not straight_evaluation.listp cell) and toString.call(cell) == '[object Array]' + recordp: (cell) -> (not cell._prototype?) and toSTring.call(cell) == '[object Object]' + objectp: (cell) -> (cell._prototype?) and toString.call(cell) == '[object Object]' + nilp: (cell) -> nilp(cell) + nvalu: (cell) -> cell + mksymbols: (cell) -> cell makeEvaluator = (ix = straight_evaluation, ty="straight") -> (exp, env, callback) -> if ix.symbolp exp - return callback lookup (ix.nvalu exp).v, env + return callback lookup (ix.nvalu exp), env else if ([ix.numberp, ix.stringp].filter (i) -> i(exp)).length > 0 return callback ix.nvalu exp else if ix.listp exp body = ix.nvalu exp head = car body if ix.symbolp head - return switch (ix.nvalu head).v + return switch (ix.nvalu head) when "quote" then callback cdr body when "if" evaluate (cadr body), env, (res) -> @@ -174,7 +172,7 @@ makeEvaluator = (ix = straight_evaluation, ty="straight") -> when "begin" then eprogn (cdr body), env, callback when "set!" evaluate (caddr body), env, (newvalue) -> - update (ix.nvalu cadr body).v, env, newvalue, callback + update (ix.nvalu cadr body), env, newvalue, callback when "lambda" make_function (ix.mksymbols cadr body), (cddr body), env, callback else @@ -192,6 +190,6 @@ nodeEval = makeEvaluator(metadata_evaluation, "node") lispEval = makeEvaluator(straight_evaluation, "lisp") evaluate = (exp, env, cb) -> - (if exp? and exp.__node then nodeEval else lispEval)(exp, env, cb) + (if exp? and (exp instanceof Node) then nodeEval else lispEval)(exp, env, cb) module.exports = (c, cb) -> evaluate c, env_global, cb diff --git a/chapter1/astAccessors.coffee b/chapter1/astAccessors.coffee index d1e007b..147b29b 100644 --- a/chapter1/astAccessors.coffee +++ b/chapter1/astAccessors.coffee @@ -1,11 +1,10 @@ {car, cdr, listp} = require 'cons-lists/lists' +{Node, Symbol} = require "./reader_types" -symbol = (form) -> (car form) - -module.exports = - astObject: (form) -> typeof (car form) == "string" - aSymbol: symbol - aValue: (form) -> (car cdr form) - isAList: (form) -> (symbol form) == 'list' - isARecord: (form) -> (symbol form) == 'record' - isAVector: (form) -> (symbol form) == 'vector' +module.exports = ops = + astObject: (form) -> form instanceof Node + aValue: (form) -> form.value + aSymbol: (form) -> form.value + isAList: (form) -> ops.astObject(form) and form.type == 'list' + isARecord: (form) -> ops.astObject(form) and form.type == 'record' + isAVector: (form) -> ops.astObject(form) and form.type == 'vector' diff --git a/chapter1/astToList.coffee b/chapter1/astToList.coffee index bfdd540..5ed2a64 100644 --- a/chapter1/astToList.coffee +++ b/chapter1/astToList.coffee @@ -1,7 +1,6 @@ {car, cdr, cons, listp, nilp, nil, list, pairp, listToString} = require 'cons-lists/lists' -{aSymbol, aValue, astObject} = require './astAccessors' -{Symbol} = require './reader_types' +{astObject} = require './astAccessors' # RICH_AST -> LISP_AST @@ -26,7 +25,7 @@ normalizeForm = (form) -> 'record': (atom) -> listToRecord1(atom) # Basic native types. Meh. - 'symbol': (a) -> a.v + 'symbol': id 'number': id 'string': (atom) -> atom 'nil': (atom) -> nil @@ -37,7 +36,7 @@ normalizeForm = (form) -> 'null': (atom) -> null 'undefined': (atom) -> undefined - methods[(car form)](car cdr form) + methods[form.type](form.value) normalizeForms = (forms) -> diff --git a/chapter1/interpreter.coffee b/chapter1/interpreter.coffee index c36d225..435fab5 100644 --- a/chapter1/interpreter.coffee +++ b/chapter1/interpreter.coffee @@ -1,8 +1,7 @@ {listToString, listToVector, pairp, cons, car, cdr, caar, cddr, cdar, cadr, caadr, cadar, caddr, nilp, nil, setcdr, metacadr} = require "cons-lists/lists" readline = require "readline" -{inspect} = require "util" -{Symbol} = require "./reader_types" +{Node} = require "./reader_types" class LispInterpreterError extends Error name: 'LispInterpreterError' @@ -56,8 +55,8 @@ extend = (env, variables, values) -> else if (nilp variables) if (nilp values) then env else throw new LispInterpreterError "Too many values" else - if (variables instanceof Symbol) - (cons (cons variables.v, values), env) + if (variables.type == 'symbol') + (cons (cons variables, values), env) else nil @@ -109,15 +108,15 @@ update = (id, env, value) -> # TODO: Reengineer this with a call to normalize -astSymbolsToLispSymbols = (node) -> - nvalu = (node) -> cadr node - return nil if nilp node - throw (new LispInterpreterError "Not a list of variable names") if not ((car node) is 'list') - handler = (node) -> - return nil if nilp node - cons (nvalu car node).v, (handler cdr node) - handler(nvalu node) +tap = (i) -> console.log(i) ; i +astSymbolsToLispSymbols = (node) -> + return nil if nilp node + throw (new LispInterpreterError "Not a list of variable names") if not node.type == 'list' + handler = (cell) -> + return nil if nilp cell + cons (car cell).value, (handler cdr cell) + handler node.value cadddr = metacadr('cadddr') @@ -127,42 +126,42 @@ cadddr = metacadr('cadddr') # favor of normalizeForm (s?) and Symbol extraction metadata_evaluation = - listp: (node) -> (car node) == 'list' - symbolp: (node) -> (car node) == 'symbol' - numberp: (node) -> (car node) == 'number' - stringp: (node) -> (car node) == 'string' - nvalu: (node) -> cadr node - mksymbols: (node) -> astSymbolsToLispSymbols(node) + listp: (node) -> node.type == 'list' + symbolp: (node) -> node.type == 'symbol' + numberp: (node) -> node.type == 'number' + stringp: (node) -> node.type == 'string' + commentp: (node) -> node.type == 'comment' + nvalu: (node) -> node.value + mksymbols: (list) -> astSymbolsToLispSymbols(list) straight_evaluation = - listp: (node) -> node.__type == 'list' - symbolp: (node) -> node instanceof Symbol - commentp: (node) -> node instanceof Comment - numberp: (node) -> typeof node == 'number' - stringp: (node) -> typeof node == 'string' - boolp: (node) -> typeof node == 'boolean' - nullp: (node) -> node == null - vectorp: (node) -> (not straight_evaluation.listp node) and toString.call(node) == '[object Array]' - recordp: (node) -> (not node._prototype?) and toSTring.call(node) == '[object Object]' - objectp: (node) -> (node._prototype?) and toString.call(node) == '[object Object]' - nilp: (node) -> nilp(node) - nvalu: (node) -> node - mksymbols: (node) -> node + listp: (cell) -> cell.__type == 'list' + symbolp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] not in ["\"", ";"] + commentp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] == ";" + numberp: (cell) -> typeof cell == 'number' + stringp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] == "\"" + boolp: (cell) -> typeof cell == 'boolean' + nullp: (cell) -> cell == null + vectorp: (cell) -> (not straight_evaluation.listp cell) and toString.call(cell) == '[object Array]' + recordp: (cell) -> (not cell._prototype?) and toSTring.call(cell) == '[object Object]' + objectp: (cell) -> (cell._prototype?) and toString.call(cell) == '[object Object]' + nilp: (cell) -> nilp(cell) + nvalu: (cell) -> cell + mksymbols: (cell) -> cell makeEvaluator = (ix = straight_evaluation, ty="straight") -> (exp, env) -> # Takes an AST node and evaluates it and its contents. A node may be # ("list" (... contents ...)) or ("number" 42) or ("symbol" x), etc. - if ix.symbolp(exp) - return lookup (ix.nvalu exp).v, env + return lookup (ix.nvalu exp), env else if ([ix.numberp, ix.stringp].filter (i) -> i(exp)).length > 0 return ix.nvalu exp else if ix.listp(exp) body = ix.nvalu exp head = car body if ix.symbolp(head) - switch (ix.nvalu head).v + switch (ix.nvalu head) when "quote" then cdr body when "if" unless (evaluate (cadr body), env) == the_false_value @@ -170,18 +169,18 @@ makeEvaluator = (ix = straight_evaluation, ty="straight") -> else evaluate (cadddr body), env when "begin" then eprogn (cdr body), env - when "set!" then update (ix.nvalu cadr body).v, env, (evaluate (caddr body), env) + when "set!" then update (ix.nvalu cadr body), env, (evaluate (caddr body), env) when "lambda" then make_function (ix.mksymbols cadr body), (cddr body), env else invoke (evaluate (car body), env), (evlis (cdr body), env) else invoke (evaluate (car body), env), (evlis (cdr body), env) else - throw new LispInterpreterError "Can't handle a #{type}" + throw new LispInterpreterError "Can't handle a #{exp.type}" nodeEval = makeEvaluator(metadata_evaluation, "node") lispEval = makeEvaluator(straight_evaluation, "lisp") evaluate = (exp, env) -> - (if exp? and exp.__node then nodeEval else lispEval)(exp, env) + (if exp? and (exp instanceof Node) then nodeEval else lispEval)(exp, env) module.exports = (c) -> evaluate c, env_global diff --git a/chapter1/reader.coffee b/chapter1/reader.coffee index 8b2bf24..d5f9721 100644 --- a/chapter1/reader.coffee +++ b/chapter1/reader.coffee @@ -1,7 +1,6 @@ {car, cdr, cons, nil, nilp, pairp, vectorToList, list} = require 'cons-lists/lists' {inspect} = require "util" -{Symbol, Comment} = require "./reader_types" - +{Node, Comment} = require "./reader_types" NEWLINES = ["\n", "\r", "\x0B", "\x0C"] WHITESPACE = [" ", "\t"].concat(NEWLINES) @@ -29,19 +28,13 @@ class Source done: -> @index > @max -mkNode = (obj) -> Object.defineProperty obj, '__node', {value: true} - # IO -> IO skipWS = (inStream) -> while inStream.peek() in WHITESPACE then inStream.next() -# (type, value, line, column) -> (node {type, value, line, column)} -mkObj = (type, value, line, column) -> - mkNode list type, value, line, column - # msg -> (IO -> Node => Error) handleError = (message) -> - (line, column) -> mkObj('error', message, line, column) + (line, column) -> new Node('error', message, line, column) # IO -> Node => Comment readComment = (inStream) -> @@ -50,7 +43,7 @@ readComment = (inStream) -> inStream.next()).join("") if not inStream.done() inStream.next() - mkObj 'comment', (new Comment r), line, column + new Node 'comment', (new Comment r), line, column # IO -> (Node => Literal => String) | Error readString = (inStream) -> @@ -63,7 +56,7 @@ readString = (inStream) -> if inStream.done() return handleError("end of file seen before end of string.")(line, column) inStream.next() - mkObj 'string', (string.join ''), line, column + new Node 'string', (string.join ''), line, column # (String) -> (Node => Literal => Number) | Nothing readMaybeNumber = (symbol) -> @@ -89,8 +82,8 @@ readSymbol = (inStream, tableKeys) -> inStream.next()).join '' number = readMaybeNumber symbol if number? - return mkObj 'number', number, line, column - mkObj 'symbol', (new Symbol symbol), line, column + return new Node 'number', number, line, column + new Node 'symbol', symbol, line, column # (Delim, TypeName) -> IO -> (IO, node) | Error @@ -102,7 +95,7 @@ makeReadPair = (delim, type) -> [line, column] = inStream.position() if inStream.peek() == delim inStream.next() - return mkObj(type, nil, line, column) + return new Node type, nil, line, column # IO -> (IO, Node) | Error dotted = false @@ -114,13 +107,13 @@ makeReadPair = (delim, type) -> return cons obj, nil if inStream.done() then return handleError("Unexpected end of input")(line, column) if dotted then return handleError("More than one symbol after dot") - return obj if (car obj) == 'error' - if (car obj) == 'symbol' and (car cdr obj).v == '.' + return obj if obj.type == 'error' + if obj.type == 'symbol' and obj.value == '.' dotted = true return readEachPair inStream cons obj, readEachPair inStream - ret = mkObj type, readEachPair(inStream), line, column + ret = new Node type, readEachPair(inStream), line, column inStream.next() ret @@ -132,8 +125,8 @@ prefixReader = (type) -> inStream.next() [line1, column1] = inStream.position() obj = read inStream, true, null, true - return obj if (car obj) == 'error' - mkObj "list", cons((mkObj("symbol", type, line1, column1)), cons(obj)), line, column + return obj if obj.type == 'error' + new Node "list", cons((new Node("symbol", type, line1, column1)), cons(obj)), line, column # I really wanted to make anything more complex than a list (like an # object or a vector) something handled by a read macro. Maybe in a @@ -180,7 +173,7 @@ read = (inStream, eofErrorP = false, eofError = EOF, recursiveP = false, inReadM while true form = matcher inStream, c - skip = (not nilp form) and (car form == 'comment') and not keepComments + skip = (not nilp form) and (form.type == 'comment') and not keepComments break if (not skip and not nilp form) or inStream.done() c = inStream.peek() null @@ -199,11 +192,13 @@ readForms = (inStream) -> readEach = (inStream) -> obj = read inStream, true, null, false return nil if (nilp obj) - return obj if (car obj) == 'error' + return obj if obj.type == 'error' cons obj, readEach inStream obj = readEach inStream - if (car obj) == 'error' then obj else mkObj "list", obj, line, column + if obj.type == 'error' then obj else new Node "list", obj, line, column exports.read = read exports.readForms = readForms +exports.Node = Node +exports.Symbol = Symbol diff --git a/chapter1/reader_types.coffee b/chapter1/reader_types.coffee index 16f6696..a995bde 100644 --- a/chapter1/reader_types.coffee +++ b/chapter1/reader_types.coffee @@ -1,6 +1,8 @@ -exports.Symbol = class - constructor: (@v) -> +exports.Node = class + constructor: (@type, @value, @line, @column) -> exports.Comment = class constructor: (@text) -> - + + + diff --git a/chapter3/interpreter.coffee b/chapter3/interpreter.coffee index 4758f94..747b4d7 100644 --- a/chapter3/interpreter.coffee +++ b/chapter3/interpreter.coffee @@ -2,10 +2,7 @@ cadr, caadr, cadar, caddr, nilp, nil, setcdr, metacadr, setcar} = require "cons-lists/lists" {normalizeForm} = require "../chapter1/astToList" -readline = require "readline" -{Symbol} = require '../chapter1/reader_types' -{inspect} = require "util" -minspect = (obj) -> inspect obj, false, null, true +{Node} = require '../chapter1/reader_types' class LispInterpreterError extends Error name: 'LispInterpreterError' @@ -13,6 +10,9 @@ class LispInterpreterError extends Error the_false_value = (cons "false", "boolean") +# Base class that represents a value. Base class representing a LiSP +# value, a primitive, or a function + class Value # Represents the base class of a continuation. Calls to invoke resume @@ -302,7 +302,6 @@ class Primitive extends Value invoke: (args, env, kont) -> @nativ.apply null, [args, env, kont] - env_init = new NullEnv() definitial = (name, value = nil) -> @@ -372,52 +371,52 @@ definitial "list", new Primitive "list", (values, env, kont) -> # Only called in rich node mode... astSymbolsToLispSymbols = (node) -> - nvalu = (node) -> cadr node return nil if nilp node - throw (new LispInterpreterError "Not a list of variable names") if not ((car node) is 'list') - handler = (node) -> - return nil if nilp node - cons (nvalu car node).v, (handler cdr node) - handler(nvalu node) + throw (new LispInterpreterError "Not a list of variable names") if not node.type == 'list' + handler = (cell) -> + return nil if nilp cell + cons (car cell).value, (handler cdr cell) + handler node.value metadata_evaluation = - listp: (node) -> (car node) == 'list' - symbolp: (node) -> (car node) == 'symbol' - numberp: (node) -> (car node) == 'number' - stringp: (node) -> (car node) == 'string' - nvalu: (node) -> cadr node - mksymbols: astSymbolsToLispSymbols + listp: (node) -> node.type == 'list' + symbolp: (node) -> node.type == 'symbol' + numberp: (node) -> node.type == 'number' + stringp: (node) -> node.type == 'string' + commentp: (node) -> node.type == 'comment' + nvalu: (node) -> node.value + mksymbols: (list) -> astSymbolsToLispSymbols(list) straight_evaluation = - listp: (node) -> node.__type == 'list' - symbolp: (node) -> node instanceof Symbol - commentp: (node) -> node instanceof Comment - numberp: (node) -> typeof node == 'number' - stringp: (node) -> typeof node == 'string' - boolp: (node) -> typeof node == 'boolean' - nullp: (node) -> node == null - vectorp: (node) -> (not straight_evaluation.listp node) and toString.call(node) == '[object Array]' - recordp: (node) -> (not node._prototype?) and toSTring.call(node) == '[object Object]' - objectp: (node) -> (node._prototype?) and toString.call(node) == '[object Object]' - nilp: (node) -> nilp(node) - nvalu: (node) -> node - mksymbols: (node) -> node + listp: (cell) -> cell.__type == 'list' + symbolp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] not in ["\"", ";"] + commentp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] == ";" + numberp: (cell) -> typeof cell == 'number' + stringp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] == "\"" + boolp: (cell) -> typeof cell == 'boolean' + nullp: (cell) -> cell == null + vectorp: (cell) -> (not straight_evaluation.listp cell) and toString.call(cell) == '[object Array]' + recordp: (cell) -> (not cell._prototype?) and toSTring.call(cell) == '[object Object]' + objectp: (cell) -> (cell._prototype?) and toString.call(cell) == '[object Object]' + nilp: (cell) -> nilp(cell) + nvalu: (cell) -> cell + mksymbols: (cell) -> cell makeEvaluator = (ix = straight_evaluation, ty="straight") -> (exp, env, kont) -> if ix.symbolp exp - return evaluateVariable (ix.nvalu exp).v, env, kont + return evaluateVariable (ix.nvalu exp), env, kont else if ([ix.numberp, ix.stringp].filter (i) -> i(exp)).length > 0 return kont.resume ix.nvalu exp else if ix.listp exp body = ix.nvalu exp head = car body if ix.symbolp head - switch (ix.nvalu head).v + switch (ix.nvalu head) when "quote" then evaluateQuote (cdr body), env, kont when "if" then evaluateIf (cdr body), env, kont when "begin" then evaluateBegin (cdr body), env, kont - when "set!" then evaluateSet (ix.nvalu cadr body).v, (caddr body), env, kont + when "set!" then evaluateSet (ix.nvalu cadr body), (caddr body), env, kont when "lambda" then evaluateLambda (ix.mksymbols cadr body), (cddr body), env, kont when "block" then evaluateBlock (ix.nvalu cadr body), (cddr body), env, kont when "return-from" then evaluateReturnFrom (ix.nvalu cadr body), (caddr body), env, kont @@ -434,7 +433,7 @@ nodeEval = makeEvaluator(metadata_evaluation, "node") lispEval = makeEvaluator(straight_evaluation, "lisp") evaluate = (exp, env, kont) -> - (if exp? and exp.__node then nodeEval else lispEval)(exp, env, kont) + (if exp? and (exp instanceof Node) then nodeEval else lispEval)(exp, env, kont) interpreter = (ast, kont) -> evaluate ast, env_init, new BottomCont null, kont diff --git a/test/test_reader.coffee b/test/test_reader.coffee index 3e93da7..6112466 100644 --- a/test/test_reader.coffee +++ b/test/test_reader.coffee @@ -24,6 +24,7 @@ describe "Core reader functions", -> ['[]', []] ['{}', {}] ['[1 2 3]', [1, 2, 3]] + # ['(1 2 3', 'error'] ['{foo "bar"}', {foo: "bar"}] ]