Code archives/Miscellaneous/Max Yourself A Scheme In 48 Hours
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
| This is a translation of the Scheme interpreter implemented in Jonathan Tang's Write Yourself a Scheme in 48 Hours. This is the completed program; however it's really just provided for the sake of example - the point of the series is the tutorial and the exercises, not the interpreter itself. It's actually an astonishingly bad Scheme implementation, mostly because the extremely literal translation from Haskell into BlitzMax results in a laughably inefficient design (it also piggybacks on BlitzMax for objects and GC, like the original, which means it uses huge amounts of memory and inherits Max's circular reference bug). The goal of this project is to provide a "bridge" that might help Blitz programmers interested in learning about functional programming understand the relevant techniques. To that end, instead of a sensible implementation of Scheme, this is, with some exceptions, a literal translation of the Haskell (the main differences are that it uses TMeta for parsing, instead of anything similar to Parsec, and a lot of the lessons involving Haskell's type system and monads simply do not translate). The idea is to show the same functional techniques and very similar program structure, but operating in a BlitzMax context, in the hope that it will make it easier to then read the original series (or one of its many translations into a similar language, e.g. F#), and understand what the code is supposed to mean. The individual lessons and "staged" form of the code will be posted in the BlitzMax tutorials forum in the coming days. Example session: lisp>>> (load "stdlib.scm") -> #t lisp>>> (map (curry + 2) '(1 2 3 4)) -> (3 4 5 6) lisp>>> quit Pass source files as arguments, or pass nothing and get an interactive session. I recommend compiling the program and then running it from the terminal rather than the IDE - BlitzMax Input() seems to go a bit weird on backspaces/arrows, so making mistakes is a nuisance. Only a small subset of R5RS procedures are supported (there's no let, no syntax-rules, no call-cc, no vector operations). However the implementation does provide TCE and a really basic form of "dumb" macros. stdlib.scm (from the original tutorial): | |||||
' Max Yourself a Scheme in 48 Hours!
' (a reimplementation from the Haskell)
' single-file "Code Archives Edition"
' If you haven't read the tutorials yet, go back and look at them first!
SuperStrict
Import "TMeta.bmx" 'Get this here: http://www.blitzbasic.com/codearcs/codearcs.php?code=3113
Import "Functional.bmx" 'Get this here: http://www.blitzbasic.com/codearcs/codearcs.php?code=3090
Local env:SchemeEnv
If AppArgs.Length > 1
env = SchemeREPL.RunFiles(AppArgs[1..])
Else
env = SchemeREPL.RunREPL()
EndIf
' "Scheme.bmx":
'===============
Type SchemeREPL
Function Eval:LispVal(env:SchemeEnv, val:LispVal)
Local ret:LispVal, tc:LispDeferredTailCall
Repeat
ret = TailEval(env, val) ; tc = LispDeferredTailCall(ret)
If tc
env = tc.env ; val = tc.val ; ret = Null
EndIf
Until ret
Return ret
End Function
Function EvalMany:LispVal(env:SchemeEnv, vals:LispVal[])
Local ret:LispVal
For Local v:LispVal = EachIn vals
ret = Eval(env, v)
Next
Return ret
End Function
Function TailEval:LispVal(env:SchemeEnv, val:LispVal)
Global ev:TDelegate = TDelegate.Make(Eval), unVal:RefCell(_:Object) = RefCell.unVal, unCons:RefCell(l:Object, r:RefCell) = RefCell.unCons
Global unType:RefCell(_:Object, t:Object(_:Object)) = RefCell.unType, unMaybe:RefCell(_:RefCell) = RefCell.unMaybe
Global isAtom:TDelegate = TDelegate.Make(LispAtom.Is)
Local A:RefCell = RefCell.Make(), B:RefCell = New A, C:RefCell = New A', D:RefCell = New A
Select val
Case LispAtom(val)
Return SchemeEnv.GetVar(env, LispAtom(val).name)
Case LispList(val)
Select True
Case unCons(unVal("quote"), B).match(val)
Return LispVal(ConsList(B._).val)
Case unCons(unVal("if"), unCons(A, unCons(B, unCons(C, Null)))).match(val)
Local pred:LispVal = Eval(env, LispVal(A._))
If LispBool(pred) And (LispBool(pred).val = 0) ..
Then Return LispDeferredTailCall.Make(env, LispVal(C._)) ..
Else Return LispDeferredTailCall.Make(env, LispVal(B._))
Case unCons(unVal("set!"), unCons(unType(A, LispAtom.Is), unCons(B, Null))).match(val)
Return SchemeEnv.SetVar(env, LispAtom(A._).name, Eval(env, LispVal(B._)))
Case unCons(unVal("define"), unCons(unType(A, LispAtom.Is), unCons(B, Null))).match(val)
Return SchemeEnv.DefineVar(env, LispAtom(A._).name, Eval(env, LispVal(B._)))
Case unCons(unVal("define"), unCons(unType(A, LispDottedList.Is), B)).match(val)
Local nargs:LispDottedList = LispDottedList(A._), name:Object = nargs.vals.val
Local f:LispFunc = LispFunc.Make(nargs.vals.nx, nargs.last.ToString(), ConsList(B._), env)
Return SchemeEnv.DefineVar(env, name.ToString(), f)
Case unCons(unVal("define"), unCons(unType(A, LispList.Is), B)).match(val)
Local nargs:LispList = LispList(A._), name:Object = nargs.vals.val
Local f:LispFunc = LispFunc.Make(nargs.vals.nx, Null, ConsList(B._), env)
Return SchemeEnv.DefineVar(env, name.ToString(), f)
Case unCons(unVal("lambda"), unCons(unType(A, LispDottedList.Is), B)).match(val)
Local args:LispDottedList = LispDottedList(A._)
Return LispFunc.Make(args.vals, args.last.ToString(), ConsList(B._), env)
Case unCons(unVal("lambda"), unCons(unType(A, LispList.Is), B)).match(val)
Return LispFunc.Make(LispList(A._).vals, Null, ConsList(B._), env)
Case unCons(unVal("lambda"), unCons(unType(A, LispAtom.Is), B)).match(val)
Return LispFunc.Make(Null, B._.ToString(), ConsList(B._), env)
Case unCons(unVal("macro"), B).match(val)
Global lam:LispAtom = LispAtom.Make("lambda")
Local ll:LispList = LispList.FromCons(ConsList.Cons(lam, ConsList(B._)))
Return LispMacro.FromFunc(LispFunc(TailEval(env, ll)))
Case unCons(unVal("load"), unCons(unType(A, LispString.Is), Null)).match(val)
Local port:LispPort = LispPort.Make("READMODE", LispString(A._).val)
ConsList.Map(ev.curry(env), SchemeBuiltins._readAll(ConsList.Cons(port, Null)).vals)
port.stream.Close() ; port.stream = Null
Return LispBool._True
Case unCons(unVal("if"), RefCell.Any).match(val), ..
unCons(unVal("set!"), RefCell.Any).match(val), ..
unCons(unVal("load"), RefCell.Any).match(val), ..
unCons(unVal("macro"), RefCell.Any).match(val), ..
unCons(unVal("define"), RefCell.Any).match(val), ..
unCons(unVal("lambda"), RefCell.Any).match(val)
DebugStop
badSpecialForm val
Case unCons(A, B).match(val)
Local func:LispVal = Eval(env, LispVal(A._)), args:ConsList = ConsList(B._)
If Not LispMacro(func) Then args = ConsList.Map(ev.curry(env), args)
Return Apply(func, args)
Case unCons(A, Null).match(val)
Return Apply(Eval(env, LispVal(A._)), Null)
End Select
Function badSpecialForm(val:LispVal)
LispError.Raise "Eval: malformed '" + LispList(val).vals.val.ToString() + "' expression: " + val.ToString()
End Function
Case LispNum(val), LispString(val), LispBool(val), LispChar(val), LispVector(val)
Return val
Case LispDeferredTailCall(val)
LispError.Raise "Eval: deferred tail calls are not supposed to be used as values"
End Select
LispError.Raise "Eval: bad special form " + val.ToString() 'Getting here requires something to go wrong; all match branches return
End Function
Function Apply:LispVal(op:LispVal, args:ConsList)
Select op
Case LispPrimitiveFunc(op)
Return LispVal(LispPrimitiveFunc(op).f.call(args))
Case LispFunc(op)
Local f:LispFunc = LispFunc(op), lnth:Int(_:ConsList) = ConsList.Length, fLen:Int = lnth(f.args)
If (lnth(args) <> fLen And f.vararg = "") Or lnth(args) < fLen Then ..
LispError.ArgCount ConsList.Length(f.args), args
Local newEnv:SchemeEnv = SchemeEnv.Make(f.closure)
Global bind:TDelegate = TDelegate.Make(SchemeEnv.DefineVar), ev:TDelegate = TDelegate.Make(Eval)
If f.vararg
ConsList.ZipWith bind.curry(newEnv), f.args, ConsList.Take(args, fLen)
bind.call2(f.vararg, ConsList.Drop(args, fLen))
Else
ConsList.ZipWith bind.curry(newEnv), f.args, args
EndIf
If LispMacro(f)
Return LispVal(ConsList.Last(ConsList.Map(ev.curry(f.closure), ..
ConsList.Map(ev.curry(newEnv), f.body))))
Else
Local nonTail:ConsList = ConsList.Take(f.body, ConsList.Length(f.body) - 1)
ConsList.Map(ev.curry(newEnv), nonTail)
Return LispDeferredTailCall.Make(newEnv, LispVal(ConsList.Last(f.body)))
EndIf
Default ; LispError.Raise "Apply: cannot apply non-function '" + op.ToString() + "'"
End Select
End Function
Function Read:LispVal[](p:SchemeParser, src:String)
Try
Local tree:TParseNode = p.Parse(SchemeLexer.Get().ScanString(src))
Return p.ToLispVals(tree)
Catch e:ParseError
Local msg:String = e.ToString(), SRCH:String = "error trying to complete '"
msg = msg.Replace(SRCH + "(", SRCH + "list").Replace(SRCH + "#", SRCH + "vector")
msg = msg.Replace(SRCH + "'", SRCH + "quoted form").Replace(SRCH + "`", SRCH + "quasiquoted form")
LispError.Raise msg
Catch e:LexError
LispError.Raise e.ToString()
End Try
End Function
Function ReadOne:LispVal(p:SchemeParser, port:LispPort)
Local vals:LispVal[]
If port.cached
vals = port.cached
Else
Local s:String ; While Not Eof(port.stream)
s :+ port.stream.ReadLine() + "~n"
Wend
vals = SchemeREPL.Read(p, s)
EndIf
port.cached = vals[1..] ; Return vals[0]
End Function
Function Write:LispVal(p:LispPort, v:LispVal)
p.stream.WriteLine(v.ToString())
p.stream.Flush
Return LispBool._True
End Function
Function Show:Object(v:LispVal)
Print "-> " + (v.ToString()) ; Return Null
End Function
Function RunREPL:SchemeEnv(env:SchemeEnv = Null)
Global read:TDelegate = TDelegate.Make(Read), eval:TDelegate = TDelegate.Make(EvalMany), write:TDelegate = TDelegate.Make(Show)
Local q:SchemeParser = New SchemeParser
If env = Null Then env = SchemeEnv.MakeGlobal()
Local _main:TDelegate = write.compose(eval.curry(env).compose(read.curry(q)))
Repeat
Local in:String = Input("lisp>>> ")
If in = "quit"
Exit
ElseIf in <> ""
Try
_main.call in
Catch e:LispError
Print e.ToString()
End Try
EndIf
Forever
Return env
End Function
Function RunFiles:SchemeEnv(files:String[], env:SchemeEnv = Null)
Global read:TDelegate = TDelegate.Make(Read), eval:TDelegate = TDelegate.Make(Eval)
If env = Null Then env = SchemeEnv.MakeGlobal()
Local ev:TDelegate = eval.curry(env)
For Local file:String = EachIn files
Local port:LispPort = LispPort.Make("READMODE", file)
ConsList.Map(ev, SchemeBuiltins._readAll(ConsList.Cons(port, Null)).vals)
port.stream.Close() ; port.stream = Null
Next
Return env
End Function
End Type
Type SchemeEnv
Field _local:TMap, _closure:SchemeEnv
Method Copy:SchemeEnv()
Local c:SchemeEnv = Make()
c._local = _local.Copy() ; If _closure Then c._closure = _closure.Copy()
Return c
End Method
Function Make:SchemeEnv(closure:SchemeEnv = Null)
Local e:SchemeEnv = New SchemeEnv ; e._local = CreateMap() ; e._closure = closure ; Return e
End Function
Function MakeGlobal:SchemeEnv()
Return Make(SchemeBuiltins.prims.Copy())
End Function
Function IsBound:Int(env:SchemeEnv, name:String)
If env = Null Then Return 0
Return env._local.Contains(name) Or IsBound(env._closure, name)
End Function
Function GetVar:LispVal(env:SchemeEnv, name:String)
If env = Null Then LispError.Raise "cannot get undefined variable '" + name + "'"
Local val:Object = env._local.ValueForKey(name) ; If val = Null Then val = GetVar(env._closure, name)
Return LispVal(val)
End Function
Function SetVar:LispVal(env:SchemeEnv, name:String, val:LispVal)
If env = Null Then LispError.Raise "cannot set undefined variable '" + name + "'"
If Not env._local.Contains(name) Then SetVar env._closure, name, val Else env._local.Insert name, val
Return val
End Function
Function DefineVar:LispVal(env:SchemeEnv, name:String, val:LispVal)
env._local.Insert(name, val) ; Return val
End Function
Function BindVars:SchemeEnv(env:SchemeEnv, bindings:ConsList)
Global addBinding:TDelegate = TDelegate.Make(_)
Function _:SchemeEnv(env:SchemeEnv, binding:Object[])
DefineVar env, String(binding[0]), LispVal(binding[1]) ; Return env
End Function
Return SchemeEnv(ConsList.FoldL(addBinding.curry(env), env, bindings))
End Function
End Type
Type LispError
Field msg:String
Function Raise(msg:String)
Local e:LispError = New LispError ; e.msg = msg ; Throw e
End Function
Function ArgCount(expect:Int, got:ConsList)
Local temp:LispLIst = New LispList ; temp.vals = got 'For printing
Raise "wrong number of arguments: expected " + expect + ", received actual arguments " + temp.ToString()
End Function
Function TypeMismatch(expect:String, got:LispVal)
Raise "wrong argument type: expected value of type " + expect + ", received actual value " + got.ToString()
End Function
Method ToString:String()
Return "Scheme interpreter error: " + msg
End Method
End Type
' "SchemeParser.bmx":
'=====================
Type SchemeLexer
Function Get:TLexer()
Function R:TLexRule(r:String, a(l:TLexer), res:String = "", m:String = "")
Return TLexRule.Create(r, a, res, m)
End Function
Global Store(_:TLexer) = TLexAction.Store, Mode(_:TLexer) = TLexAction.Mode, Discard(_:TLexer) = TLexAction.Discard
Const SYM:String = "!$%&|*+-/:<=>?^_~~"
Global l:TLexer = TLexer.withRules([..
R("(\+|-)?[0-9]+", Store, "LispNum"),.. 'Simple int
R("#[bBoOdDxX][0-9a-fA-F]+", Store, "LispNum"),.. 'Specific-base int, binary/octal/decimal/hex (style: #xABC12)
R("(\+|-)?[0-9]*\.[0-9]+([eE]-?[0-9][0-9]*)?", Store, "LispNum"),.. 'Float, simple or scientific
R("(#t|#f)", Store, "LispBool"),.. 'Boolean
..
R("~q([^~q]|\\~q)*~q", Store, "LispString"),..
R("#\\([\(\)\[\],\.'`~q#@"+SYM+"]|([a-zA-Z]+))", Store, "LispChar"),.. 'Character constant
..
R(";[^\n]*\n", Discard),.. 'Line comment: ; B3D-style
..
R("\(", Store, "lparen"),.. 'Punctuation
R("\)", Store, "rparen"),..
R("'", Store, "quote"),..
R("`", Store, "backquote"),..
R(",", Store, "comma"),..
R(",@", Store, "splice"),..
R("\.", Store, "dot"),..
R("(#)", Store, "hash"),..
..
R("[a-z"+SYM+"][a-z0-9@"+SYM+"]*", Store, "LispAtom"),..
..
.. 'Obvious lex-time errors:
R("[^[:space:]]", TLexAction.Error, "unrecognised character"),.. 'Any other printable character
R("[0-9]+[a-z_]", TLexAction.Error, "invalid identifier/number")..
])
l.SetCaseSensitivity False
l.SetGuardMode True
Return l
End Function
End Type
Type SchemeParser Extends TMetaParser Final
Field grammar:TMap {..
Prog = "Expr* : @program"..
Expr = "%LispAtom | %LispNum | %LispBool | %LispChar | %LispString | List | Dotted | Vector | Quoted | QQuote | UnQuote | Splice"..
List = "%lparen Expr* %rparen : ~ @elems ~"..
Dotted = "%lparen ! Expr+ %dot Expr %rparen : ~ @elems ~ @last ~"..
Vector = "%hash ! %lparen Expr* %rparen : ~ ~ @elems ~"..
Quoted = "%quote ! Expr : ~ @expr"..
QQuote = "%backquote ! Expr : ~ @expr"..
UnQuote = "%comma Expr : ~ @expr"..
Splice = "%splice Expr : ~ @expr"..
}
Function ToLispVals:LispVal[](ptree:TParseNode)
If ptree.elem And (ptree.rule = "" Or ptree.rule = "Prog")
Local vals:LispVal[] = New LispVal[ptree.elem.Length]
For Local e:Int = 0 Until vals.Length
vals[e] = ToLispVal(ptree.elem[e])
Next
Return vals
Else
Return [ToLispVal(ptree)]
EndIf
End Function
Function ToLispVal:LispVal(ptree:TParseNode)
Select ptree.rule
Case "List"
If ptree.elem = Null And ptree.term = Null Then Return LispList.Nil '()
Local pEl:TParseNode[] = ptree.GetElem("elems").elem, vals:LispVal[] = New LispVal[pEl.Length]
If pEl = Null Then Return LispList.Make([ToLispVal(ptree.GetElem("elems"))]) 'Single-element
For Local e:Int = 0 Until pEl.Length
vals[e] = ToLispVal(pEl[e])
Next
Return LispList.Make(vals)
Case "Dotted"
Local pEl:TParseNode[] = ptree.GetElem("elems").elem, vals:LispVal[] = New LispVal[pEl.Length]
If pEl = Null
vals = [ToLispVal(ptree.GetElem("elems"))]
Else
For Local e:Int = 0 Until pEl.Length
vals[e] = ToLispVal(pEl[e])
Next
EndIf
Local last:LispVal = ToLispVal(ptree.GetElem("last"))
Return LispDottedList.Make(vals, last)
Case "Vector"
Local pEl:TParseNode[] = ptree.GetElem("elems").elem, el:LispVal[] = New LispVal[pEl.Length]
For Local e:Int = 0 Until el.Length
el[e] = ToLispVal(pEl[e])
Next
Return LispVector.Make(el)
Case "Quoted" ; Return wrap("quote", ToLispVal(ptree.GetElem("expr")))
Case "QQuote" ; Return wrap("quasiquote", ToLispVal(ptree.GetElem("expr")))
Case "UnQuote" ; Return wrap("unquote", ToLispVal(ptree.GetElem("expr")))
Case "Splice" ; Return wrap("unquote-splicing", ToLispVal(ptree.GetElem("expr")))
End Select
Function wrap:LispVal(cmd:String, qval:LispVal) Return LispList.Make([LispVal(LispAtom.Make(cmd)), qval]) End Function
Local term:TToken = ptree.term
If term = Null Then Return LispBool._False
Select term.tType
Case "LispAtom" ; Return LispAtom.Make(term.value)
Case "LispBool" ; Return LispBool.Make(term.value = "#t")
Case "LispString"
Local s:String = term.value
Return LispString.Make(s[1..s.Length - 1].Replace("\n", "~n").Replace("\~q", "~q"))
Case "LispChar"
Local ch:String = term.value[2..]
If ch.Length = 1 Then Return LispChar.Make(ch[0])
Select ch
Case "newline" ; LispChar.Make(10)
Case "space" ; LispChar.Make(32)
Case "tab" ; LispChar.Make(9)
Default LispChar.Make(" "[0])'Throw
End Select
Case "LispNum" ; Return LispNum.Make(Double(term.value))
End Select
End Function
End Type
' "SchemeTypes.bmx":
'====================
Type LispVal
Function Is:Object(o:Object) Abstract
End Type
Type LispAtom Extends LispVal
Field name:String
Function Make:LispAtom(n:String)
Local a:LispAtom = New LispAtom ; a.name = n.ToLower() ; Return a
End Function
Method ToString:String() Return name End Method
Method Compare:Int(with:Object) Return name.Compare(with) End Method
Function Is:Object(o:Object) Return LispAtom(o) End Function
End Type
Type LispList Extends LispVal
Field vals:ConsList
Global Nil:LispList = LispList.Make(Null)
Function Make:LispList(vals:LispVal[], _: LispVal = Null)
Local l:LispList = New LispList ; l.vals = ConsList.FromArray(vals) ; Return l
End Function
Function FromCons:LispList(c:ConsList, _:LispVal = Null)
If c = Null Then Return Nil
Local l:LispList = New LispList ; l.vals = c ; Return l
End Function
Method ToString:String()
If vals = Null Then Return "()"
Local show:TDelegate = TDelegate.Make(_show), join:TDelegate = TDelegate.Make(_join)
Local l2:ConsList = ConsList.Map(show, vals)
Return "(" + String(ConsList.FoldL(join, l2.val, l2.nx)) +")"
Function _show:String(o:Object) Return o.ToString() End Function
Function _join:String(l:String, r:String) Return l + " " + r End Function
End Method
Method SendMessage:Object(msg:Object, ctx:Object)
If msg = RefCell.GetCons Then Return vals Else Return Null
End Method
Function Is:Object(o:Object) Return LispList(o) End Function
End Type
Type LispDottedList Extends LispList
Field last:LispVal
Function Make:LispList(vals:LispVal[], last:LispVal)
If LispDottedList(last)
vals :+ LispVal[](ConsList.ToArray(LispDottedList(last).vals))
last = LispDottedList(last).last
ElseIf LispList(last)
Return LispList.Make(vals + LispVal[](ConsList.ToArray(LispList(last).vals)))
EndIf
Local l:LispDottedList = New LispDottedList ; l.vals = ConsList.FromArray(vals) ; l.last = last ; Return l
End Function
Function FromCons:LispList(c:ConsList, last:LispVal)
Local l:LispDottedList = New LispDottedList ; l.vals = c ; l.last = last ; Return l
End Function
Method ToString:String()
Local ret:String = Super.ToString()
Return ret[..ret.Length - 1] + " . " + last.ToString() + ")"
End Method
Function Is:Object(o:Object) Return LispDottedList(o) End Function
End Type
Type LispNum Extends LispVal
Field val:Double
Function Make:LispNum(v:Double)
Local n:LispNum = New LispNum ; n.val = v ; Return n
End Function
Method ToString:String()
If Double(Long(val)) = val Then Return String(Long(val)) Else Return String(val)
End Method
Function Is:Object(o:Object) Return LispNum(o) End Function
End Type
Type LispString Extends LispVal
Field val:String
Function Make:LispString(v:String)
Local s:LispString = New LispString ; s.val = v ; Return s
End Function
Method ToString:String()
Return "~q" + (val.Replace("~n", "\n").Replace("~q", "\~q")) + "~q"
End Method
Function Is:Object(o:Object) Return LispString(o) End Function
End Type
Type LispBool Extends LispVal
Field val:Int
Global _False:LispBool = LispBool.Make(0), _True:LispBool = LispBool.Make(1)
Function Make:LispBool(v:Int)
Local b:LispBool = New LispBool ; b.val = (v <> 0) ; Return b
End Function
Method ToString:String()
If val Then Return "#t" Else Return "#f"
End Method
Function Is:Object(o:Object) Return LispBool(o) End Function
End Type
Type LispChar Extends LispVal
Field val:Int
Function Make:LispChar(v:Int)
Local c:LispChar = New LispChar ; c.val = v ; Return c
End Function
Method ToString:String()
If val > 32
Return "#\" + Chr(val)
Else
If val = 32 Return "#\space" ElseIf val = 10 Then Return "#\newline" ElseIf val = 9 Then Return "#\tab"
EndIf
End Method
Function Is:Object(o:Object) Return LispChar(o) End Function
End Type
Type LispVector Extends LispVal
Field elems:LispVal[]
Function Make:LispVector(el:LispVal[])
Local v:LispVector = New LispVector ; v.elems = el ; Return v
End Function
Method ToString:String()
Local s:String = "#("
For Local v:LispVal = EachIn elems
s :+ v.ToString() + " "
Next
Return s[..s.Length - 1] + ")"
End Method
Function Is:Object(o:Object) Return LispVector(o) End Function
End Type
Type LispFunc Extends LispVal
Field args:ConsList, vararg:String, body:ConsList, closure:SchemeEnv
Function Make:LispFunc(args:ConsList, vararg:String, body:ConsList, closure:SchemeEnv)
Local f:LispFunc = New LispFunc
Function _:String(o:Object) Return o.ToString() End Function ; Global toS:TDelegate = TDelegate.Make(_)
f.args = ConsList.Map(toS, args)
f.vararg = vararg ; f.body = body ; f.closure = closure
Return f
End Function
Method ToString:String()
Local s:String = "(lambda ("
If args Then s :+ ConsList.FoldL1(TDelegate.Make(_), args).ToString() 'Could be a LispVal or a String
Function _:String(l:LispVal, r:LispVal)
Return l.ToString() + " " + r.ToString()
End Function
If vararg <> "" Then s :+ " . " + vararg
Return s + ") ...)"
End Method
Function Is:Object(o:Object) Return LispFunc(o) End Function
End Type
Type LispPrimitiveFunc Extends LispVal
Field f:TDelegate
Function Make:LispPrimitiveFunc(d:TDelegate)
Local f:LispPrimitiveFunc = New LispPrimitiveFunc ; f.f = d ; Return f
End Function
Method ToString:String() Return "<primitive>" End Method
Function Is:Object(o:Object) Return LispPrimitiveFunc(o) End Function
End Type
Type LispDeferredTailCall Extends LispVal
Field env:SchemeEnv, val:LispVal
Function Make:LispDeferredTailCall(env:SchemeEnv, val:LispVal)
Local tc:LispDeferredTailCall = New LispDeferredTailCall
tc.env = env ; tc.val = val ; Return tc
End Function
Function Is:Object(o:Object) Return LispDeferredTailCall(o) End Function
End Type
Type LispPort Extends LispVal
Field stream:TStream, cached:LispVal[]
Global StdIn:LispPort = LispPort.FromStream(StandardIOStream), StdOut:LispPort = LispPort.FromStream(StandardIOStream)
Function Make:LispPort(mode:String, path:String)
Local p:LispPort = New LispPort
Select mode
Case "READMODE" ; p.stream = ReadStream(path)
Case "WRITEMODE" ; p.stream = WriteStream(path)
End Select
If p.stream = Null Then LispError.Raise "Unable to open file '" + path + "'"
Return p
End Function
Function FromStream:LispPort(str:TStream)
Local p:LispPort = New LispPort
p.stream = str ; Return p
End Function
Method Delete()
If stream Then stream.Close()
End Method
Method ToString:String() Return "<IO port>" End Method
Function Is:Object(o:Object) Return LispPort(o) End Function
End Type
Type LispMacro Extends LispFunc
Function FromFunc:LispMacro(f:LispFunc)
Local m:LispMacro = New LispMacro
m.args = f.args ; m.vararg = f.vararg ; m.body = f.body ; m.closure = f.closure
Return m
End Function
Method ToString:String()
Return "(macro" + Super.ToString()[7..]
End Method
Function Is:Object(o:Object) Return LispMacro(o) End Function
End Type
' "SchemeBuiltins.bmx":
'=======================
Type SchemeBuiltins
Global unCons:RefCell(l:Object, r:RefCell) = RefCell.unCons, unVal:RefCell(_:Object) = RefCell.unVal, ..
unType:RefCell(_:Object, t:Object(_:Object)) = RefCell.unType, unMaybe:RefCell(_:RefCell) = RefCell.unMaybe
Function _add:LispNum(l:LispNum, r:LispNum) Return LispNum.Make(l.val + r.val) End Function
Function _sub:LispNum(l:LispNum, r:LispNum) Return LispNum.Make(l.val - r.val) End Function
Function _mul:LispNum(l:LispNum, r:LispNum) Return LispNum.Make(l.val * r.val) End Function
Function _div:LispNum(l:LispNum, r:LispNum) Return LispNum.Make(l.val / r.val) End Function
Function _mod:LispNum(l:LispNum, r:LispNum) Return LispNum.Make(l.val Mod r.val) End Function
Function _numEq:LispBool(l:LispNum, r:LispNum) Return LispBool.Make(l.val = r.val) End Function
Function _numLt:LispBool(l:LispNum, r:LispNum) Return LispBool.Make(l.val < r.val) End Function
Function _numGt:LispBool(l:LispNum, r:LispNum) Return LispBool.Make(l.val > r.val) End Function
Function _numNe:LispBool(l:LispNum, r:LispNum) Return LispBool.Make(l.val <> r.val) End Function
Function _numLe:LispBool(l:LispNum, r:LispNum) Return LispBool.Make(l.val <= r.val) End Function
Function _numGe:LispBool(l:LispNum, r:LispNum) Return LispBool.Make(l.val >= r.val) End Function
Function _boolAnd:LispBool(l:LispBool, r:LispBool) Return LispBool.Make(l.val And r.val) End Function
Function _boolOr:LispBool(l:LispBool, r:LispBool) Return LispBool.Make(l.val Or r.val) End Function
Function _strEq:LispBool(l:LispString, r:LispString) Return LispBool.Make(l.val = r.val) End Function
Function _strLt:LispBool(l:LispString, r:LispString) Return LispBool.Make(l.val.Compare(r.val) < 0) End Function
Function _strGt:LispBool(l:LispString, r:LispString) Return LispBool.Make(l.val.Compare(r.val) > 0) End Function
Function _strLe:LispBool(l:LispString, r:LispString) Return LispBool.Make(l.val.Compare(r.val) <= 0) End Function
Function _strGe:LispBool(l:LispString, r:LispString) Return LispBool.Make(l.val.Compare(r.val) >= 0) End Function
Function NumericOp:LispVal(op:TDelegate, a:ConsList)
Global isNum:TDelegate = TDelegate.Make(UnpackNum)
Select True
Case a = Null, a.nx = Null 'Fewer than 2 args
LispError.ArgCount 2, a
Default
Return LispVal(ConsList.FoldL1(op, ConsList.Map(isNum, a)))
End Select
End Function
Function BinaryOp:LispVal(unpack:TDelegate, op:TDelegate, args:ConsList)
If ConsList.Length(args) <> 2 Then LispError.ArgCount 2, args
Local l:LispVal = LispVal(unpack.call(args.val))
Local r:LispVal = LispVal(unpack.call(args.nx.val))
Return LispVal(op.call2(l, r))
End Function
Function UnpackNum:LispVal(v:LispVal)
If LispNum(v) Then Return v Else LispError.TypeMismatch "Number", v
End Function
Function UnpackStr:LispVal(v:LispVal)
If LispString(v) Then Return v Else LispError.TypeMismatch "String", v
End Function
Function UnpackBool:LispVal(v:LispVal)
If LispBool(v) Then Return v Else LispError.TypeMismatch "Boolean", v
End Function
Function Car:LispVal(a:ConsList)
If ConsList.Length(a) <> 1 Then LispError.ArgCount 1, a
Local val:LispVal = LispVal(a.val), RET:RefCell = RefCell.Make()
Select True
Case unType(unCons(RET, RefCell.Any), LispDottedList.Is).match(val), ..
unType(unCons(RET, RefCell.Any), LispList.Is).match(val)
Return LispVal(RET._)
Default ; LispError.TypeMismatch "List", val
End Select
End Function
Function Cdr:LispVal(a:ConsList)
If ConsList.Length(a) <> 1 Then LispError.ArgCount 1, a
Local val:LispVal = LispVal(a.val), RET:RefCell = RefCell.Make()
Select True
Case unType(unCons(RefCell.Any, RET), LispDottedList.Is).match(val)
Return LispDottedList.FromCons(ConsList(RET._), LispDottedList(val).last)
Case unType(unCons(RefCell.Any, Null), LispDottedList.Is).match(val)
Return LispDottedList(val).last
Case unType(unCons(RefCell.Any, unMaybe(RET)), LispList.Is).match(val)
Return LispList.FromCons(ConsList(RET._))
Default ; LispError.TypeMismatch "List", val
End Select
End Function
Function Cons:LispVal(args:ConsList)
If ConsList.Length(args) <> 2 Then LispError.ArgCount 2, args
Local l:LispVal = LispVal(args.val), r:LispVal = LispVal(args.nx.val)
Select r
Case Null
Return LispList.Make([l])
Case LispDottedList(r)
Local dl:LispDottedList = LispDottedList(r)
Return LispDottedList.FromCons(ConsList.Cons(l, dl.vals), dl.last)
Case LispList(r)
Return LispList.FromCons(ConsList.Cons(l, LispList(r).vals))
Default
Return LispDottedList.Make([l], r)
End Select
End Function
Function EqvP:LispVal(args:ConsList)
If ConsList.Length(args) <> 2 Then LispError.ArgCount 2, args
Local l:LispVal = LispVal(args.val), r:LispVal = LispVal(args.nx.val)
If l = r Then Return LispBool._True
Select True
Case LispBool(l) And LispBool(r) ; Return LispBool.Make(LispBool(l).val = LispBool(r).val)
Case LispNum(l) And LispNum(r) ; Return LispBool.Make(LispNum(l).val = LispNum(r).val)
Case LispString(l) And LispString(r) ; Return LispBool.Make(LispString(l).val = LispString(r).val)
Case LispAtom(l) And LispAtom(r) ; Return LispBool.Make(LispAtom(l).name = LispAtom(r).name)
Case LispList(l) And LispList(r)
Global eqP:TDelegate = TDelegate.Make(eqvPair), zipEq:TDelegate = TDelegate.Make(ConsList.ZipWith).curry(eqP)
Function eqvPair:LispVal(l:LispVal, r:LispVal)
Return EqvP(ConsList.Cons(l, ConsList.Cons(r, Null)))
End Function
If ConsList.Length(LispList(l).vals) <> ConsList.Length(LispList(r).vals) Then Return LispBool._False
Local ret:LispVal = LispVal(ConsList.FoldL(eqP, LispBool._True, ..
ConsList(zipEq.call2(LispList(l).vals, LispList(r).vals))))
If LispDottedList(l) And LispDottedList(r) Then ..
ret = eqvPair(ret, eqvPair(LispDottedList(l).last, LispDottedList(r).last))
Return ret
Case LispVector(l) And LispVector(r)
Local lv:LispVector = LispVector(l), rv:LispVector = LispVector(r), ret:Int = 1
If lv.elems.Length <> rv.elems.Length Then Return LispBool._False
For Local e:Int = 0 Until lv.elems.Length
ret = ret & LispBool(EqvP(ConsList.Cons(lv.elems[e], ConsList.Cons(rv.elems[e], Null)))).val
Next
Return LispBool.Make(ret)
Default ; Return LispBool._False
End Select
End Function
Function _apply:LispVal(a:ConsList)
If ConsList.Length(a) <> 2 Then LispError.ArgCount 2, a
Return SchemeREPL.Apply(LispVal(a.val), a.nx)
End Function
Function _makePort:LispVal(s:String, a:ConsList)
If ConsList.Length(a) <> 1 Then LispError.ArgCount 1, a
If Not LispString(a.val) Then LispError.TypeMismatch "String", LispVal(a.val)
Return LispPort.Make(s, LispString(a.val).val)
End Function
Function _closePort:LispVal(a:ConsList)
If ConsList.Length(a) <> 1 Then LispError.ArgCount 1, a
Local p:LispPort = LispPort(a.val) ; If Not p Then LispError.TypeMismatch "Port", LispVal(a.val)
If p.stream Then p.stream.Close() ; p.stream = Null
Return LispList.Nil
End Function
Function _read:LispVal(a:ConsList)
If ConsList.Length(a) > 1 Then LispError.ArgCount 1, a
Local p:LispPort = LispPort(a.val) ; If a And Not p Then LispError.TypeMismatch "Port", LispVal(a.val)
Return SchemeREPL.ReadOne(New SchemeParser, p)
End Function
Function _write:LispVal(a:ConsList)
Local ln:Int = ConsList.Length(a)
If ln > 2 Or ln < 1 Then LispError.ArgCount 2, a
Local p:LispPort = LispPort.StdOut
If ln = 2 Then p = LispPort(a.nx.val) ; If a And Not p Then LispError.TypeMismatch "Port", LispVal(a.val)
Return SchemeREPL.Write(p, LispVal(a.val))
End Function
Function _readContents:LispString(a:ConsList)
If ConsList.Length(a) <> 1 Then LispError.ArgCount 1, a
Local p:LispPort = LispPort(a.val) ; If Not p Then LispError.TypeMismatch "Port", LispVal(a.val)
Local s:String ; While Not Eof(p.stream)
s :+ p.stream.ReadLine() + "~n"
Wend
Return LispString.Make(s)
End Function
Function _readAll:LispList(a:ConsList)
Return LispList.Make(SchemeREPL.Read(New SchemeParser, _readContents(a).val))
End Function
Function _print:LispVal(a:ConsList)
If ConsList.Length(a) <> 1 Then LispError.ArgCount 1, a
Local s:String = a.val.ToString()
If LispString(a.val) Then Print s[1..s.Length - 1] Else Print s
Return LispList.Nil
End Function
Global prims:SchemeEnv = SchemeBuiltins._init()
Function _init:SchemeEnv()
Local prims:SchemeEnv = SchemeEnv.Make()
Function addPrim(env:SchemeEnv, name:String, f:TDelegate)
SchemeEnv.DefineVar env, name, LispPrimitiveFunc.Make(f)
End Function
Local numericBinop:TDelegate = TDelegate.Make(NumericOp)
addPrim prims, "+", numericBinop.curry(TDelegate.Make(_add))
addPrim prims, "-", numericBinop.curry(TDelegate.Make(_sub))
addPrim prims, "*", numericBinop.curry(TDelegate.Make(_mul))
addPrim prims, "/", numericBinop.curry(TDelegate.Make(_div))
addPrim prims, "mod", numericBinop.curry(TDelegate.Make(_mod))
Local binop:TDelegate = TDelegate.Make(SchemeBuiltins.BinaryOp)
Local numBoolBinop:TDelegate = binop.curry(TDelegate.Make(UnpackNum))
Local strBoolBinop:TDelegate = binop.curry(TDelegate.Make(UnpackStr))
Local boolBoolBinop:TDelegate = binop.curry(TDelegate.Make(UnpackBool))
addPrim prims, "=", numBoolBinop.curry(TDelegate.Make(_numEq))
addPrim prims, "<", numBoolBinop.curry(TDelegate.Make(_numLt))
addPrim prims, ">", numBoolBinop.curry(TDelegate.Make(_numGt))
addPrim prims, "/=", numBoolBinop.curry(TDelegate.Make(_numNe))
addPrim prims, "<=", numBoolBinop.curry(TDelegate.Make(_numLe))
addPrim prims, ">=", numBoolBinop.curry(TDelegate.Make(_numGe))
addPrim prims, "&&", boolBoolBinop.curry(TDelegate.Make(_boolAnd))
addPrim prims, "||", boolBoolBinop.curry(TDelegate.Make(_boolOr))
addPrim prims, "string=?", strBoolBinop.curry(TDelegate.Make(_strEq))
addPrim prims, "string<?", strBoolBinop.curry(TDelegate.Make(_strLt))
addPrim prims, "string>?", strBoolBinop.curry(TDelegate.Make(_strGt))
addPrim prims, "string<=?", strBoolBinop.curry(TDelegate.Make(_strLe))
addPrim prims, "string>=?", strBoolBinop.curry(TDelegate.Make(_strGe))
addPrim prims, "car", TDelegate.Make(Car)
addPrim prims, "cdr", TDelegate.Make(Cdr)
addPrim prims, "cons", TDelegate.Make(Cons)
addPrim prims, "eqv?", TDelegate.Make(EqvP)
addPrim prims, "eq?", TDelegate.Make(EqvP) 'eq? and equal? are allowed to be the same as eqv?, so they are
addPrim prims, "equal?", TDelegate.Make(EqvP)
Local mp:TDelegate = TDelegate.Make(_makePort), cp:TDelegate = TDelegate.Make(_closePort)
addPrim prims, "open-input-file", mp.curry("READMODE")
addPrim prims, "open-output-file", mp.curry("WRITEMODE")
addPrim prims, "close-input-port", cp
addPrim prims, "close-output-port", cp
addPrim prims, "read", TDelegate.Make(_read)
addPrim prims, "write", TDelegate.Make(_write)
addPrim prims, "read-all", TDelegate.Make(_readAll)
addPrim prims, "read-contents", TDelegate.Make(_readContents)
addPrim prims, "print", TDelegate.Make(_print)
addPrim prims, "apply", TDelegate.Make(_apply)
Return prims
End Function
End Type |
Comments
| ||
| Thanks a lot for this, I'll be studying it. I tried to write a LISP interpreter but ended up settling for FORTH. Maybe this can help me get LISP going... Question: Just how slow is this? |
| ||
| Glad you like. I need to remember to write up the individual lessons at some point (I kept the separate code for each one, but need to rewrite the prose so it's not about Haskell). It will be of more use then. However I should reiterate what I said above: this isn't a very good tutorial on how to implement Lisp, it's a tutorial on FP techniques, with an example application. This is a really terrible way to implement Lisp if you actually want to do it in BlitzMax - you should look up Scheme 9 From Empty Space or FemtoLisp or something else written in C. I haven't done any benchmarks (and would need to think about how to even make a fair one), but this will be very, very slow: my guess is at least 200 times slower than normal code, probably more like 2000. The main speed hit comes from the fact it's a tree-based interpreter rather than a bytecode interpreter; most of the rest comes from trying to shoehorn FP into BlitzMax (e.g. the fake pattern matching is going to be extremely expensive: it practically uses a mini-interpreter inside itself just to choose Case branches). Interestingly the basic principle is sound: I think the performance impact of the fundamentals (TDelegates, currying, etc.) should be negligible. You could write a decent functional program in BlitzMax, even though this isn't it. |
Code Archives Forum