[refactor] Knuckled under and made nodes a Javascript type

This commit is contained in:
Elf M. Sternberg 2015-07-28 16:51:01 -07:00
parent 746f92fcdb
commit 501ac5fe72
8 changed files with 135 additions and 143 deletions

View File

@ -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

View File

@ -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'

View File

@ -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) ->

View File

@ -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

View File

@ -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

View File

@ -1,6 +1,8 @@
exports.Symbol = class
constructor: (@v) ->
exports.Node = class
constructor: (@type, @value, @line, @column) ->
exports.Comment = class
constructor: (@text) ->

View File

@ -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

View File

@ -24,6 +24,7 @@ describe "Core reader functions", ->
['[]', []]
['{}', {}]
['[1 2 3]', [1, 2, 3]]
# ['(1 2 3', 'error']
['{foo "bar"}', {foo: "bar"}]
]