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:
parent
9f9aec25f4
commit
6bd1b4d27f
|
@ -4,4 +4,5 @@
|
||||||
*.orig
|
*.orig
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
node_modules/*
|
node_modules/*
|
||||||
|
lib/lisp_parser.js
|
||||||
tmp/
|
tmp/
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
|
lookup = require './lookup'
|
||||||
|
|
||||||
lispeval = (element, scope) ->
|
lispeval = (element, scope) ->
|
||||||
switch element.type
|
switch element.type
|
||||||
when 'boolean' then element.value == '#t'
|
when 'boolean' then element.value == '#t'
|
||||||
when 'number' then parseInt(element.value, 10)
|
when 'number' then parseInt(element.value, 10)
|
||||||
when 'symbol'
|
when 'symbol'
|
||||||
scope.lookup(element.value)
|
lookup(scope, element.value)
|
||||||
when 'list'
|
when 'list'
|
||||||
proc = lispeval(element.value[0], scope)
|
proc = lispeval(element.value[0], scope)
|
||||||
args = element.value.slice(1)
|
args = element.value.slice(1)
|
||||||
proc.apply(args, scope)
|
proc args, scope
|
||||||
|
|
||||||
else throw new Error ("Unrecognized type in parse")
|
else throw new Error ("Unrecognized type in parse")
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
lispeval = require './eval'
|
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)
|
|
||||||
else
|
|
||||||
inner = @scope.fork()
|
|
||||||
@params.forEach((name, i) -> inner.set(name, args[i]))
|
|
||||||
@body.map((e) -> lispeval(e, inner)).pop()
|
|
||||||
|
|
||||||
class Syntax extends Proc
|
|
||||||
apply: (cells, scope) ->
|
|
||||||
return @body(cells, scope)
|
|
||||||
|
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
Proc: Proc
|
create_expression_evaluator: (defining_scope, params, body) ->
|
||||||
Syntax: Syntax
|
if body instanceof Function
|
||||||
|
(cells, scope) ->
|
||||||
|
args = cells.map (i) -> lispeval i, scope
|
||||||
|
body.apply null, args
|
||||||
|
else
|
||||||
|
(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()
|
||||||
|
|
||||||
|
create_special_form_evaluator: (defining_scope, params, body) ->
|
||||||
|
(cells, scope) -> body(cells, scope)
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
fs = require 'fs'
|
fs = require 'fs'
|
||||||
{parse} = require './lisp_parser'
|
{parse} = require './lisp_parser'
|
||||||
lisp = require './parser'
|
lisp = require './parser'
|
||||||
Scope = require './scope'
|
{scope} = require './scope'
|
||||||
{inspect} = require 'util'
|
{inspect} = require 'util'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
run: (pathname) ->
|
run: (pathname) ->
|
||||||
text = fs.readFileSync(pathname, 'utf8')
|
text = fs.readFileSync(pathname, 'utf8')
|
||||||
ast = parse(text)
|
ast = parse(text)
|
||||||
return lisp(ast, new Scope.Toplevel())
|
return lisp(ast, scope)
|
||||||
|
|
||||||
|
|
|
@ -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))
|
|
@ -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)
|
||||||
|
|
|
@ -1,50 +1,28 @@
|
||||||
parser = require './parser'
|
parser = require './parser'
|
||||||
lispeval = require './eval'
|
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
|
'define': create_special_form_evaluator scope, [], (list, scope) ->
|
||||||
constructor: (@parent) ->
|
current = (car scope)
|
||||||
@_symbols = {}
|
current[list[0].value] = lispeval(list[1], scope)
|
||||||
|
|
||||||
lookup: (name) ->
|
'lambda': create_special_form_evaluator scope, [], (list, scope) ->
|
||||||
if @_symbols[name]?
|
|
||||||
return @_symbols[name]
|
|
||||||
|
|
||||||
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
|
params = list[0].value.map (n) -> return n.value
|
||||||
new Proc(scope, params, list.slice(1))
|
create_expression_evaluator(scope, params, list.slice(1))
|
||||||
|
|
||||||
@syntax 'if', (list, scope) ->
|
'if': create_special_form_evaluator scope, [], (list, scope) ->
|
||||||
lispeval(list[if lispeval(list[0], scope) then 1 else 2], 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
|
||||||
|
|
Loading…
Reference in New Issue