Updated to make the internals more 'lispy.'

I've introduced a lisp-like singly-linked list handler, and removed all
of the object-oriented materials.  There are still *records*, but those
are different from scoped objects in the given context.  This has really
helped me understand what's going on with the difference between the dynamic
and lexical scopes, which has been one of my biggest headaches.
This commit is contained in:
Elf M. Sternberg 2015-03-16 10:17:51 -07:00
parent 9f9aec25f4
commit 6bd1b4d27f
7 changed files with 122 additions and 63 deletions

1
.gitignore vendored
View File

@ -4,4 +4,5 @@
*.orig
npm-debug.log
node_modules/*
lib/lisp_parser.js
tmp/

View File

@ -1,13 +1,15 @@
lookup = require './lookup'
lispeval = (element, scope) ->
switch element.type
when 'boolean' then element.value == '#t'
when 'number' then parseInt(element.value, 10)
when 'symbol'
scope.lookup(element.value)
lookup(scope, element.value)
when 'list'
proc = lispeval(element.value[0], scope)
args = element.value.slice(1)
proc.apply(args, scope)
proc args, scope
else throw new Error ("Unrecognized type in parse")

View File

@ -1,21 +1,20 @@
lispeval = require './eval'
{cons} = require './lists'
class Proc
constructor: (@scope, @params, @body) ->
apply: (cells, scope) ->
args = (cells.map (i) -> lispeval(i, scope))
if @body instanceof Function
@body.apply(this, args)
module.exports =
create_expression_evaluator: (defining_scope, params, body) ->
if body instanceof Function
(cells, scope) ->
args = cells.map (i) -> lispeval i, scope
body.apply null, args
else
inner = @scope.fork()
@params.forEach((name, i) -> inner.set(name, args[i]))
@body.map((e) -> lispeval(e, inner)).pop()
(cells, scope) ->
args = cells.map (i) -> lispeval i, scope
new_scope = {}
params.forEach (name, i) -> new_scope[name] = args[i]
inner = cons(new_scope, defining_scope)
body.map((i) -> lispeval i, inner).pop()
class Syntax extends Proc
apply: (cells, scope) ->
return @body(cells, scope)
create_special_form_evaluator: (defining_scope, params, body) ->
(cells, scope) -> body(cells, scope)
module.exports =
Proc: Proc
Syntax: Syntax

View File

@ -1,12 +1,12 @@
fs = require 'fs'
{parse} = require './lisp_parser'
lisp = require './parser'
Scope = require './scope'
{scope} = require './scope'
{inspect} = require 'util'
module.exports =
run: (pathname) ->
text = fs.readFileSync(pathname, 'utf8')
ast = parse(text)
return lisp(ast, new Scope.Toplevel())
return lisp(ast, scope)

71
lib/lists.coffee Normal file
View File

@ -0,0 +1,71 @@
vectorp = (a) -> toString.call(a) == '[object Array]'
listp = (a) -> vectorp(a) and a.__list == true
recordp = (a) -> Object.prototype.toString.call(a) == '[object Object]'
cons = (a, b = nil) ->
# Supporting an identity
l = if not (a?) then [] else if (nilp a) then b else [a, b]
l.__list = true
l
nil = cons()
car = (a) -> a[0]
cdr = (a) -> a[1]
nilp = (a) -> listp(a) and a.length == 0
vectorToList = (v, p) ->
p = if p? then p else 0
if p >= v.length then return nil
# Annoying, but since lists are represented as nested arrays, they
# 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]
cons(item, vectorToList(v, p + 1))
list = (v...) -> vectorToList v
listToVector = (l, v = []) ->
return v if nilp l
v.push if listp (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,
# because read() returns a rich version decorated with extra
# information.
listToString = (l) ->
return "" if nilp l
if listp (car l)
"(" + (listToString(car l)).replace(/\ *$/, "") + ") " + listToString(cdr l)
else
p = if typeof (car l) == 'string' then '"' else ''
p + (car l).toString() + p + ' ' + listToString(cdr l)
module.exports =
cons: cons
nil: nil
car: car
cdr: cdr
list: list
nilp: nilp
listp: listp
vectorp: vectorp
recordp: recordp
vectorToList: vectorToList
listToVector: listToVector
listToString: listToString
cadr: (l) -> car (cdr l)
cddr: (l) -> cdr (cdr l)
cdar: (l) -> cdr (car l)
caddr: (l) -> car (cdr (cdr l))
cdddr: (l) -> cdr (cdr (cdr l))
cadar: (l) -> car (cdr (car l))
cddar: (l) -> cdr (cdr (car l))
caadr: (l) -> car (car (cdr l))
cdadr: (l) -> cdr (car (cdr l))

8
lib/lookup.coffee Normal file
View File

@ -0,0 +1,8 @@
{nilp, car, cdr} = require './lists'
module.exports = lookup = (scopes, name) ->
throw new Error "Unknown variable '#{name}'" if nilp scopes
scope = car scopes
return scope[name] if scope[name]?
lookup((cdr scopes), name)

View File

@ -1,50 +1,28 @@
parser = require './parser'
lispeval = require './eval'
{cons, car, cdr, nilp} = require './lists'
{create_expression_evaluator, create_special_form_evaluator} = require './fn'
lookup = require './lookup'
{Proc, Syntax} = require './fn'
scope = cons
'+': create_expression_evaluator scope, [], (a, b) -> a + b
'-': create_expression_evaluator scope, [], (a, b) -> a - b
'*': create_expression_evaluator scope, [], (a, b) -> a * b
'/': create_expression_evaluator scope, [], (a, b) -> a / b
'==': create_expression_evaluator scope, [], (a, b) -> a == b
class Scope
constructor: (@parent) ->
@_symbols = {}
'define': create_special_form_evaluator scope, [], (list, scope) ->
current = (car scope)
current[list[0].value] = lispeval(list[1], scope)
lookup: (name) ->
if @_symbols[name]?
return @_symbols[name]
'lambda': create_special_form_evaluator scope, [], (list, scope) ->
params = list[0].value.map (n) -> return n.value
create_expression_evaluator(scope, params, list.slice(1))
'if': create_special_form_evaluator scope, [], (list, scope) ->
lispeval(list[if lispeval(list[0], scope) then 1 else 2], scope)
if @parent
return @parent.lookup(name)
throw new Error "Unknown variable '#{name}'"
define: (name, body) ->
@set name, (new Proc(this, [], body))
syntax: (name, body) ->
@set name, (new Syntax(this, [], body))
fork: -> new Scope(@)
set: (name, value) ->
@_symbols[name] = value
class Toplevel extends Scope
constructor: (@parent = null) ->
super
@define '+', (a, b) -> a + b
@define '-', (a, b) -> a - b
@define '*', (a, b) -> a * b
@define '==', (a, b) -> a == b
@syntax 'define', (list, scope) ->
scope.set(list[0].value, lispeval(list[1], scope))
@syntax 'lambda', (list, scope) ->
params = list[0].value.map (n) -> return n.value
new Proc(scope, params, list.slice(1))
@syntax 'if', (list, scope) ->
lispeval(list[if lispeval(list[0], scope) then 1 else 2], scope)
Scope.Toplevel = Toplevel
module.exports = Scope
module.exports =
lookup: lookup
scope: scope