Skip to content

Instantly share code, notes, and snippets.

@fand
Last active December 29, 2015 17:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fand/7704086 to your computer and use it in GitHub Desktop.
Save fand/7704086 to your computer and use it in GitHub Desktop.
((coffeescriptで) 書く (Lisp) インタプリタ)
#!usr/bin/env coffee
# coding: utf-8
##########################
# Utilities
##########################
type = do ->
classToType = {}
for name in 'Boolean Number String Function Array Date RegExp Undefined Null'.split(' ')
classToType['[object ' + name + ']'] = name.toLowerCase()
(obj) ->
strType = Object::toString.call(obj)
classToType[strType] or 'object'
is_list = (l) -> Array.isArray(l) and l.length > 1
is_symbol = (s) -> type(s) == 'string'
zip = (keys, values) ->
dic = {}
lim = Math.min(keys.length, values.length) - 1
for i in [0..lim]
dic[keys[i]] = values[i]
return dic
##########################
# Environments
##########################
class Env
constructor: (params = [], args = [], outer = null) ->
this.update( zip(params, args) or {} )
this.outer = outer
find: (key) ->
if this[key]? then this else this.outer.find(key)
update: (dic) ->
this[k] = v for k,v of dic
global_env = new Env
global_env.update(
'+': ((x,y) -> x+y),
'-': ((x,y) -> x-y),
'*': ((x,y) -> x*y),
'/': ((x,y) -> x/y),
'not': ((x) -> !x),
'>': ((x,y) -> x>y),
'<': ((x,y) -> x<y),
'>=': ((x,y) -> x>=y),
'<=': ((x,y) -> x<=y),
'=': ((x,y) -> x==y),
'equal?': ((x,y) -> x==y),
'eq?': ((x,y) -> x==y),
'length': ((x) -> x.length),
'cons': ((x,y) -> y.unshift x),
'car': ((x) -> x[0]),
'cdr': ((x) -> x.slice(1)),
'append': ((x,y) -> x.concat y),
'list': ((args...) -> args),
'list?': ((x) -> is_list(x)),
'null?': ((x) -> x?),
'symbol?': ((x) -> is_symbol(x))
)
##########################
# Interpreters
##########################
evaluate = (x, env = global_env) ->
if is_symbol(x) # Variable
env.find(x)[x]
else if not is_list(x) # Constant literal
x[0] or x
else if x[0] == 'quote' # Quote
x[1]
else if x[0] == 'if' # if
[_, test, t, f] = x
evaluate((if evaluate(test, env) then t else f), env)
else if x[0] == 'set!' # set!
[_, v, exp] = x
env.find(v)[v] = evaluate(exp, env)
else if x[0] == 'define' # define
[_, v, exp] = x
env[v] = evaluate(exp, env)
else if x[0] == 'lambda' # lambda
[_, vars, exp] = x
(args...) ->
evaluate(exp, new Env(vars, args, env))
else if x[0] == 'begin' # begin
for exp in x.slice(1)
val = evaluate(exp, env)
val
else
exps = (evaluate(exp, env) for exp in x)
proc = exps.shift()
proc(exps...)
parse = (str) -> read_from(tokenize(str))
tokenize = (str) ->
str.replace(/([()])/g, ' $1 ').split(' ').filter((c) -> c != '')
read_from = (tokens) ->
if tokens.length == 0
console.error('invalid token!')
process.exit()
token = tokens.shift()
switch token
when '('
L = []
while tokens[0] != ')'
L.push(read_from(tokens))
tokens.shift()
L
when ')'
console.error("unexpected ')' !")
process.exit()
else
atom(token)
atom = (token) -> if isNaN +token then token else +token
to_string = (exp) ->
if is_list(exp)
'(' + exp.map((e)->to_string(e)).join(' ') + ')'
else
exp.toString()
repl = (line)->
val = evaluate(parse(line))
val if val?
exports.repl = repl
##########################
# Console
##########################
lines = []
reader = require('readline').createInterface(
input: process.stdin,
output: process.stdout
)
reader.on('close', () -> console.log('bye^^'))
reader.on('line', (line) -> console.log(repl(line)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment