Code archives/Algorithms/Lambda Calculus
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
| Lambda calculus "is a formal system for function definition, function application and recursion" (Wikipedia). This little parser/evaluator (hopefully) allows you to define programs in strings, using a fairly simple notation, and then evaluate them for a result. The interpreter is completely untyped, and uses a form of memoised lazy evaluation (so no expression is evaluated until it's actually needed, as opposed to languages like Blitz, where all arguments to a function are always evaluated before the function is applied). The syntax is pretty straightforward: \ (backslash) stands in for the lambda character. Function parameters can have multi-character names, so if you want to define a function with more than one parameter (really, nested one-parameter functions, as is correct), the names must be separated by spaces. Within a body expression, parentheses group values into subexpressions. The parameters are separated from the body of a function definition by a dot. For the sake of readability, there are also a couple of syntactic extensions: - strings (or block literals) can be defined within nested braces: {foo} . I haven't done anything with this but it provides a simple way to define structured data in literal form. - numbers, or at least any character sequence starting with a number, are interpreted as value literals. Because this lambda calculus is completely untyped, values are stored in long, double and string formats within Value objects (how you use these is up to you). - applying a value to an argument ignores the argument and returns the value, as if using the function \x.K . Is this correct? I don't know but it certainly simplifies things, and removes an area where otherwise type could have inadvertently appeared. (EDIT: That is to say, values are treated as functions that discard their argument and return a constant, so the expression (1 2) returns 1 and ignores the 2) - local variables can be defined using let-syntax, to make expressions a bit cleaner. A let definition is strictly equivalent to wrapping the expression in a lambda and applying it, but much easier to read. Let-syntax has two forms: you can either make a "let" definition a closed subexpression at the start of the expression where you want the binding to apply, or you can use "in" after the definition to make it slightly clearer. - newline ends an expression at the outermost scope, but has no effect within parentheses. Thus "let" definitions in the outermost scope can have their own lines, which looks nice. - anything between a semicolon and a newline is a comment. Here's an example program written three different ways to demonstrate the different let-syntaxes: let t=\x y.x ;let as separate statements let f=\x y.y let not=\p.p f t not f (let t=\x y.x) (let f=\x y.y) (let not=\p.p f t) not f ;Identical but on one line let t=\x y.x in let f=\x y.y in let not=\p.p f t in not f ;using "in" to end a definition ;... all three are converted to this after parsing: (\t.(\f.(\not.not f) \p.p f t) \x y.y) \x y.x You can mix and match these and they should have pretty much the same effect (adding or removing parentheses might change the structure very slightly, of course). Note that 1) let definitions are not recursive and can only refer to those that came before, not themselves or later definitions; and 2) "let" statements may not appear in any expression, including the outermost scope, after any kind of value expression. Finally, it's also possible to define built-in functions, using the mechanism at the bottom of the file, which can make some things a lot easier - although it's possible to define basic logic and the natural numbers and so on in terms of the lambda calculus (some examples given), it's not very easy to read or efficient. Built-in functions also provide the ability to do I/O, and in the case of the "do" expression, execute command lists like in an imperative (Blitz) programming language. Builtin functions can be eager or lazy, but must have a particular signature. Several tiny example programs are listed, but they're kinda hard to read on one line using escaped ~n, so here's one to illustrate syntax ("prog.txt"): let Y = \G.(\g.G(g g))(\g.G(g g)) ; Y combinator (allow recursion) ; Fibonacci function and simple printing wrapper let fib = \f.\n i a b.if (eq? i n) a (f n (+ i 1) (+ a b) a) let showfib = \n.print (Y fib n 1 1 0) ; Reverse loop function - repeat f(n) while decrementing n let loop = \l.\n f.if (eq? n 1) (f n) (do (l (- n 1) f) (f n)) ; Print the first ten Fibonacci numbers Y loop 10 showfib Within an expression, operators and functions are always written prefix (like in Lisp, which was inspired by this). Pretty much anything can be a variable name, as long as it's not "let", "in", "\", "=" or ".", which are special syntax. Variable names are case-sensitive. Apologies for the incredible mess that is the code, but in my defence this is my first BlitzMax program (I just wrote it to take a break from boring stuff, nothing practical). | |||||
'Lazy Lambda Calculus Interpreter
Framework brl.StandardIO
Import brl.Retro
SuperStrict
Local code:String = LoadText("prog.txt") 'Your code here
' Working example programs, increasing complexity:
'"let y = 7 ~n let x = 8 in x y"
'"(\x.(\y.x y))5 7"
'"if (- 1 1) (* 2 3) (- 9 2)"
'"do (print 1) (print 2) (print 3)"
'"(\f.f(f 9))((\x.x)(\x.x))"
'"let true = \x y.x in let false = \x y.y in true (false 6(true 1 3)) 8"
'"let true = \x y.x~nlet false = \x y.y~nlet if = \p a b.p a b~nif false (false 1 2) (true 3 4)"
'"let true = \x y.x~nlet false = \x y.y~nlet and = \p q.p q p~nand true false"
'"let t=\x y.x~nlet f=\x y.y~nlet not=\p.p f t~nnot f"
'"let t=\x y.x~nlet f=\x y.y~nlet or=\p q.p p q~nlet and=\p q.p q p~nor (and t f) (and t t)"
'"let t=\x y.x~nlet f=\x y.y~nlet if=\p a b.p a b~nlet not=\p.p f t~n(\p.if p (f p (not p)) p) t"
'"let t=\x y.x~nlet f=\x y.y~nlet zero=\f x.x~nlet succ=\n f x.f(n f x)~nlet is0=\n.n(\x.f)t~nis0 (succ zero)"
'"let t=\x y.x~nlet f=\x y.y~nlet if=\p a b.p a b~nlet not=\p.p f t~nlet Y = \G.(\g.G(g g))(\g.G(g g))~nY (\f p.if p (f (not p)) p) t"
'"let Y = \G.(\g.G(g g))(\g.G(g g))~nY (\f p.if p (f (- p 1)) p) 2"
'"let Y = \G.(\g.G(g g))(\g.G(g g))~nlet F = \f.\n. if(eq? n 0) 1 (* n (f(- n 1)))~nY F 10"
Print code + "~n"
Local e:Expression = Parse(code, Print, GlobalEnv())
If e <> Null
PrintParseTree e
Print "~nResult:"
Print Evaluate(e, DebugLog).ToString()
EndIf
Print "~n...done!"
Function Parse:Expression(code:String, errFunc(e:String), gEnv:Variable) 'Parse an input string into a tree
Local e:Expression = Null
Try
Local s:Source, t:Expression, defs:TList = New TList
code = Trim(code)
If code.Length
s = Source.Make(code) 'Appends one ~n, to ensure we can end easily
e = Expression.Make(gEnv, False)
While s.c < s.code.Length
t = ParseExpression(s, "~n", gEnv, False) 'Note gEnv - updates if vars are defined
If t <> Null 'Ignore null expressions... they're just blank lines
If t.isDef
If e.body.Count() Then LambdaError.Error "Cannot define variable after expression has begun", s.getLine()
defs.AddFirst(t) ; gEnv = t.env 'Shuffle the environments back round
Else
e.AddTerm(t) 'Add as expression term
End If
EndIf
Wend
If e.body.Count() = 0 Then LambdaError.Error "No expression to evaluate!"
For t = EachIn defs 'If any local variables were defined, wrap the expression in their lambda forms
e.env = t.env ; t.env = t.env.env 'We know that e isn't a lambda, here
t.body.AddFirst(e) ; e.ttype = Term.isLAM ; e = t
Next
EndIf
Catch err:LambdaError
If err.line
errFunc "Error on line " + err.line + ": " + err.MSG
Else
errFunc "Error: " + err.MSG
EndIf
e = Null
End Try
Return e
Function ParseExpression:Expression(s:Source, terminator:String, env:Variable, isDef:Int)
Local e:Expression, c:String, braceLevel:Int = 0, token:String = "", defs:TList = New TList, d:Expression
e = Expression.Make(env, isDef)
While s.c < s.code.Length
c = s.getChr()
If braceLevel = 0
Select c
Case ";" 'Comment
While s.c < s.code.Length
c = s.getChr() ; If c = "~n" Then s.c:-1; Exit 'Backup, the newline might be important
Wend
Case terminator
If token.Length Then e.AddToken(token, s.getLine()) ; token = "" 'Don't forget a token if there was no separator
If e.body.Count() = 0
If terminator = "~n" Then Return Null Else LambdaError.Error "Expression must have content", s.getLine()
Else
Exit
EndIf
Case ")" 'If terminator wasn't )
LambdaError.Error "Mismatched parentheses", s.getLine()
Case "="
LambdaError.Error "Unexpected character: ~q=~q", s.getLine()
Case "{"
If token.Length Then e.AddToken(token, s.getLine()) ; token = ""
braceLevel = 1
Case "}"
LambdaError.Error "Mismatched braces", s.getLine()
Case "("
If token.Length Then e.AddToken(token, s.getLine()) ; token = ""
d = ParseExpression(s, ")", e.env, False) 'e.env not env
If d.isDef
If e.body.Count() Then LambdaError.Error "Cannot define local variable after expression has begun", s.getLine()
defs.AddFirst(d) ; e.env = d.env 'Shuffle the environments back round
Else
e.AddTerm(d) 'Add as expression term
End If
Case "\"
If token.Length Then e.AddToken(token, s.getLine()) ; token = ""
If e.body.Count()
e.AddTerm ParseLambda(s, terminator, e.env, isDef) 'e.env not env
Else
e = ParseLambda(s, terminator, env, isDef) 'Simplify...
EndIf
Exit 'The expression has to end with the end of the lambda - lambda body already ate the terminator
Default
If c[0] > 32 'Build token
token:+c
ElseIf token.Length 'Whitespace
If token = "let" 'Name definition
If e.body.Count() Then LambdaError.Error "Cannot define variable in middle of expression", s.getLine()
d = ParseLet(s, terminator, e.env) ; token = "" 'Note: e.env not env
If d.isDef = 2 'If it's applied to this expression only with "in"
defs.AddFirst(d) ; e.env = d.env 'Don't exit - doesn't escape
Else
e = d ; Exit
EndIf
ElseIf token = "in" 'End of name definition
If isDef
e.isDef = 2 'Mark that we ended on "in"
If e.body.Count() = 0 Then LambdaError.Error("Empty definition", s.getLine()) Else Exit
Else
LambdaError.Error "~qin~q without ~qlet~q", s.getLine()
End If
Else
e.AddToken(token, s.getLine()) 'Unknown term type - check whether it's a value, a variable, or an error
token = ""
EndIf
End If
End Select
Else
If c = "{" Then braceLevel:+1 Else If c = "}" Then bracelevel:-1
If braceLevel
token:+c 'Don't add the final } if it reached zero
Else
e.AddTerm(Value.Make(token))', e.env))
token = ""
EndIf
EndIf
Wend
If s.c >= s.code.Length 'Reached the end of input?
If braceLevel Then LambdaError.Error "Mismatched braces: did not close", s.getLine()
If terminator[0] > 32 Then LambdaError.Error "Incomplete expression: expecting ~q" + terminator + "~q to close", s.getLine()
EndIf
For d = EachIn defs 'If any local variables were defined, wrap the expression in their lambda forms
If e.ttype = Term.isEXP Then e.env = d.env Else e.env.env = d.env 'Lambdas need special treatment
d.env = d.env.env
d.body.AddFirst(e) ; d.isDef = e.isDef
e.ttype = Term.isLAM ; e = d
Next
Return e
End Function
Function ParseLambda:Expression(s:Source, terminator:String, env:Variable, isDef:Int)
Local token:String = "", c:String
While s.c < s.code.Length
c = s.getChr()
Select c
Case ";" 'Comment
While s.c < s.code.Length
c = s.getChr() ; If c = "~n" Then s.c:-1; Exit 'Backup, the newline might be important
Wend
Case "(", ")", terminator, "{", "}", "\", "=" 'Note that newline is OK if parenthesised
LambdaError.Error "Expecting parameter name; found control character ~q" + c + "~q", s.getLine()
Case "."
If token.Length
Exit
Else
LambdaError.Error "Expecting parameter name; found control character ~q" + c + "~q", s.getLine()
EndIf
Default
If c[0] > 32 'Build token
token:+c
ElseIf token.Length 'Whitespace
Exit
End If
End Select
Wend
Local l:Expression = Expression.Make(Variable.Make(token, env), isDef) ; l.ttype = Term.isLAM
If c <> "." 'If we haven't had the start character yet, skip whitespace
While s.c < s.code.Length
c = s.getChr()
If c[0] > 32
If c[0] <> 46 Then s.c:-1 'Backup if not dot - the next character is probably important
Exit
EndIf
Wend
EndIf
If s.c = s.code.Length Then LambdaError.Error "Body of lambda abstraction not found!", s.getLine()
Local b:Expression
If c = "."
b = ParseExpression(s, terminator, l.env, isDef)
If b.env = l.env 'Only store the whole thing if it's a full lambda
l.body = b.body ; l.isDef = b.isDef
Else
l.body.AddFirst(b)
EndIf
Else 'Listing two or more parameters is literally read the same way as nesting the lambdas
b = ParseLambda(s, terminator, l.env, isDef)
l.isDef = b.isDef ; l.body.AddFirst b 'Push the lambda as only term
EndIf
If l.body = Null Then LambdaError.Error "Expecting body for lambda abstraction", s.getLine()
Return l
End Function
Function ParseLet:Expression(s:Source, terminator:String, env:Variable)
Local token:String = "", c:String
While s.c < s.code.Length
c = s.getChr()
Select c
Case ";" 'Comment
While s.c < s.code.Length
c = s.getChr() ; If c = "~n" Then s.c:-1; Exit 'Backup, the newline might be important
Wend
Case "(", ")", terminator, "{", "}", "\", "."
LambdaError.Error "Expecting parameter name; found control character ~q" + c + "~q", s.getLine()
Case "="
If token.Length
Exit
Else
LambdaError.Error "Expecting parameter name; found control character ~q" + c + "~q", s.getLine()
EndIf
Default
If c[0] > 32 'Build token
token:+c
ElseIf token.Length 'Whitespace
Exit
End If
End Select
Wend
Local n:Variable = Variable.Make(token, env)
If c <> "=" 'If we haven't had the definition character yet, skip whitespace
While s.c < s.code.Length
c = s.getChr()
If c = "=" Then Exit
Wend
EndIf
If s.c = s.code.Length Then LambdaError.Error "Expecting definition for variable ~q" + token + "~q", s.getLine()
Local d:Expression = ParseExpression(s, terminator, env, True) 'The var definition - note its env is not v
If d = Null Then LambdaError.Error "Expecting definition for ~qlet " + n.name + "~q = ..."
' If isREPL Then n.def = d 'This isn't circular if done right
Local l:Expression = Expression.Make(n, d.isDef)
If d.ttype = Term.isEXP And d.body.Count() = 1 Then l.body.AddFirst(d.body.First()) Else l.body.AddFirst(d)
Return l
End Function
End Function
Function PrintParseTree(t:Term, indent:Int = 0, nlev:Int = 0) 'Print a parsed expression tree to output
Local elem:Term
Select True
Case Term.isBIN = t.ttype 'Comes before isVAL and isLAM as it has those flags too
rPrint Builtin(t).name, indent
Case (Term.isEXP & t.ttype) > 0 'Note that lambdas also have isVal set, so this comes first
rPrint t.ttype + ": expr " + Expression(t).id + " (level " + nlev + ", " + Expression(t).env.ToString() + "):", indent
For elem = EachIn Expression(t).body
PrintParseTree elem, indent + 4, nlev + 1
Next
rPrint "(~~expr " + Expression(t).id + " level " + nlev + ")", indent
Case (Term.isVAL & t.ttype) > 0
rPrint Value(t).sval, indent
Case (Term.isVAR & t.ttype) > 0
rPrint Variable(t).name, indent
End Select
Function rPrint(txt:String, indent:Int)
Print RSet("", indent) + txt
End Function
End Function
Function Evaluate:Term(t:Term, errFunc(e:String)) 'Evaluate a parsed expression tree (non-recursive)
Try
Local eStack:TList = New TList ; eStack.AddFirst(t) 'Use a secondary stack (prevent overflow of call stack)
While (Term(eStack.Last()).ttype & Term.isVAL) = False 'While the bottom of the stack is a var or expression
Local e:Expression
t = Term(eStack.RemoveFirst()) 'Pop stack
If t.ttype & Term.isVAL 'Unapplied lambdas and literal values
e = Expression(eStack.First())
If e = Null Or e.ttype <> Term.isEXP Then LambdaError.Error "Unexpected error - missing expression"
e.body.AddFirst(t)
Else 'Expressions
e = Expression(t) ; If e.mutable = False Then e = e.Copy() 'This one might not fire often
Local fst:Term = Term(e.body.First()), snd:Term
If fst = Null Then LambdaError.Error "Unexpected error - empty expression"
Select fst.ttype
Case Term.isVAL
eStack.AddFirst(fst) 'If it's a pure value, just return it
Case Term.isVAR
LambdaError.Error "Unexpected error - unsubstituted variable ~q" + fst.ToString() + "~q"
Case Term.isEXP 'Still arguments to apply?
If e.body.FirstLink() <> e.body.LastLink() Then e.body.RemoveFirst() ; eStack.AddFirst(e)
eStack.AddFirst(fst)
Case Term.isLAM
Local l:Expression = Expression(fst)
If e.body.FirstLink() = e.body.LastLink() 'No arguments, so just return the lambda
If l.mutable = False Then l = l.Copy()
eStack.AddFirst(l)
Else 'Apply the lambda to the argument
e.body.RemoveFirst() ; snd = Term(e.body.RemoveFirst())
l = l.Apply(snd) 'Application always creates a copy of the function being applied
If e.body.First() <> Null 'If there are any other arguments in this expression
eStack.AddFirst(e) 'Put the expression back
Else
e.body.AddFirst(l)
EndIf
eStack.AddFirst(l)
End If
Case Term.isBIN
Local b:Builtin = Builtin(fst)
If e.body.FirstLink() = e.body.LastLink() 'No arguments, so just return the value
eStack.AddFirst(b)
Else 'Apply the function to the argument
e.body.RemoveFirst() ; snd = Term(e.body.RemoveFirst())
snd = b.Apply(snd) 'Store result in snd, which may be a curried copy of itself
If e.body.First() <> Null 'If there are any other arguments in this expression
eStack.AddFirst(e) 'Put the expression back
Else
e.body.AddFirst(snd)
EndIf
eStack.AddFirst(snd)
End If
End Select
EndIf
Wend
Return Term(eStack.RemoveLast()) 'Eventual expression value
Catch err:LambdaError
If errFunc <> Null Then errFunc "Error: " + err.MSG Else Throw err
End Try
End Function
Type Source
Field code:String
Field multiLine:Int
Field c:Int
Function Make:Source(code:String)
Local s:Source = New Source
s.c = 0; s.multiLine = code.Contains("~n") 'Don't give line numbers for a single-line expression
s.code = code + "~n"
Return s
End Function
Method getChr:String()
c:+1
Return Chr(code[c - 1])
End Method
Method getLine:Int() 'Get the line number of the current character
Local i:Int, l:Int = 1 'Start on line 1
For i = 0 To c - 1
If code[i] = 10 Then l:+1 'Count the newline characters before c
Next
Return l * multiLine
End Method
End Type
Type Term Abstract
Const isVAL:Int = 1, isVAR:Int = 2, isEXP:Int = 4, isLAM:Int = 1 + 4, isBIN:Int = 1 + 4 + 8
Field ttype:Int
Field env:Variable 'Argument, or evaluation context (depending on code)
End Type
Type Value Extends Term
Field sval:String, lval:Long, dval:Double
Function Make:Value(token:String)', env:Variable) 'Make a value literal object
Local v:Value = New Value
v.ttype = Term.isVAL
v.sval = token ; v.lval = token.ToLong() ; v.dval = token.ToDouble()
'v.env = env 'Err... does this still do anything? I forget
Return v
End Function
Method ToString:String()
Return sval
End Method
End Type
Type Variable Extends Term
Global uIDCount:Long
Field name:String, uniqueID:Long, def:Term
Method New()
ttype = Value.isVAR
End Method
Function Make:Variable(name:String, env:Variable)
Local v:Variable = New Variable
v.name = name
v.env = env
v.uniqueID = uIDCount 'Do we even need this? Don't think so
uIDCount:+1 'Honestly I can't be bothered to come up with a "more permanent" solution than this
Return v
End Function
Method ToString:String()
Return "var ~q" + name + "~q[" + String.FromLong(uniqueID) + "]"
End Method
Function GetByName:Variable(name:String, env:Variable)
While env <> Null
If env.name = name Then Return env
env = env.env
Wend
Return Null
End Function
Function GetByUID:Variable(uID:Long, env:Variable)
While env <> Null
If env.uniqueID = uID Then Return env
env = env.env
Wend
Return Null
End Function
End Type
Type Expression Extends Term
Field body:TList 'List of terms that makes up the expression
Field isDef:Int '1|2 if this is a var definition (helpful for rearranging things), 2 if it ended on "in"
Field mutable:Int 'True if this is safe to evaluate in place
Field inScope:Int 'True if this expression is in its original location and can have substitutions made
Field id:Int 'Debug purposes only - provides a recognisable ID, as all functions are nameless
Global uniquerefid:Int 'Similarly
Method New()
ttype = isEXP
body = New TList
mutable = False
inScope = True
End Method
Function Make:Expression(env:Variable, isDef:Int)
Local e:Expression = New Expression
e.env = env
e.isDef = isDef
uniquerefid:+1 ; e.id = uniquerefid 'DEBUG - safe to remove if not desired
Return e
End Function
Method AddToken(t:String, lNo:Int = 0) 'Undetermined token that may be a variable name, a value, or an error
Local v:Variable = Variable.GetByName(t, env)
If v = Null
If t.ToLong() Or t.ToDouble() 'Nonzero number
AddTerm(Value.Make(t))', env))
ElseIf t.Contains("0") 'First char is 0, or is $/%/-/. and then 0
If t[0] = 48 Or ((t[0] = 36 Or t[0] = 37 Or t[0] = 45 Or t[0] = 46) And t[1] = 48)
AddTerm(Value.Make(t))', env))
Else
LambdaError.Error "Unrecognised variable name: ~q" + t + "~q", lNo
EndIf
Else
LambdaError.Error "Unrecognised variable name: ~q" + t + "~q", lNo
EndIf
Else 'Variable, either defined or builtin
If v.def <> Null And v.def.ttype = Term.isBIN Then AddTerm(v.def) Else AddTerm(v)
End If
End Method
Method AddTerm(t:Term)
body.AddLast(t)
End Method
Method Copy:Expression() 'Perform a shallow copy of the expression object and term list
Local c:Expression = New Expression
c.ttype = ttype ; c.mutable = True ; c.inScope = inScope ; c.isDef = isDef
c.env = env ; c.body = body.Copy()
c.id = id 'Debug line (safe to remove)
Return c
End Method
Method Apply:Expression(arg:Term) 'This is now where substitution happens
Local l:Expression
l = Copy() 'l must always be unique at this step or errors could result
If arg.ttype & Term.isEXP And arg.ttype <> Term.isBIN
arg = Expression(arg).Copy() 'Make sure it's a copy
Expression(arg).inScope = False
EndIf
l.ttype = Term.isEXP
l.Subst(l.env, arg) 'Replace all references to l.env with arg within the body and nested expressions
Return l
End Method
Method Subst(v:Variable, t:Term) 'Go through the termlist and replace a variable with an argument
Local elem:Term, newBody:TList = New TList
For elem = EachIn body
If elem.ttype = Term.isVAR
If Variable(elem).uniqueID = v.uniqueID Then elem = t
ElseIf elem.ttype & Term.isEXP And elem.ttype <> Term.isBIN
Local sub:Expression = Expression(elem).Copy() 'Copy every expression term regardless, for safety
If sub.inScope Then sub.Subst(v, t) ; elem = sub
EndIf
newBody.AddLast(elem) 'Building a new list is cleaner than editing the old one in-place
Next
body = newBody
End Method
Method ToString:String()
If ttype = Term.isLAM Then Return "Lambda " + id + " (" + env.ToString() + ")" 'This is actually enough to ID a lambda
Return "Expression " + id + " (" + env.ToString() + ")" 'For an expr, not so much, but meh
End Method
End Type
Type Builtin Extends Term 'Builtin functionality for extra speed or convenience (or IO, side-effects, etc.)
Field arity:Int, aCount:Int, name:String 'Note that builtin functions may not have optional parameters
Field applied:Term[], lazy:Int
Field func:Term(args:Term[]) 'The BlitzMax function to call
Method New()
ttype = Term.isBIN
aCount = 0
End Method
Function Make:Builtin(func:Term(args:Term[]), arity:Int, name:String = "", lazy:Int = False)
Local b:Builtin = New Builtin
b.arity = arity
b.func = func
b.applied = New Term[arity]
b.name = name 'This is only important for printing the parse tree or similar tasks
b.lazy = lazy
Return b
End Function
Method Copy:Builtin()
Local c:Builtin = New Builtin
c.arity = arity ; c.func = func ; c.name = name
c.aCount = aCount ; c.lazy = lazy
c.applied = applied[..]
Return c
End Method
Method Apply:Term(arg:Term) 'Note that this creates a copy every time it's incompletely applied
If arg.ttype & Term.isEXP Then arg = Expression(arg).Copy()
If aCount < arity - 1 'Incomplete application
Local b:Builtin = Copy()
b.applied[aCount] = arg
b.aCount:+1
Return b
Else 'Complete - evaluate instead
Local args:Term[] = applied[..], i:Int
args[aCount] = arg
If lazy = False
For i = 0 To arity - 1
args[i] = Evaluate(args[i], Null)
Next
EndIf
Return func(args)
EndIf
End Method
End Type
Type LambdaError
Field msg:String
Field line:Int
Function Error(msg:String, line:Int = 0)
Local err:LambdaError = New LambdaError
err.line = line
err.msg = msg
Throw err
End Function
End Type
Function GlobalEnv:Variable() 'This is the place to add user-defined functions
Global gEnv:Variable
If gEnv <> Null Then Return gEnv 'Cache this so we don't rebuild the same list every time
gEnv = AddBuiltin("*", Multiply, 2, gEnv) 'Names can be pretty much anything - operators are mostly fine
gEnv = AddBuiltin("-", Subtract, 2, gEnv)
gEnv = AddBuiltin("eq?", Equality, 2, gEnv)
gEnv = AddBuiltin("+", lAdd, 2, gEnv)
gEnv = AddBuiltin("if", lIf, 3, gEnv, True)
gEnv = AddBuiltin("print", lPrint, 1, gEnv) 'Build a one-way list on gEnv
gEnv = AddBuiltin("do", lDo, 1, gEnv)
gEnv = Variable.Make("Global Top Level", gEnv) 'Outermost level
Return gEnv
'Use this to add functions - all must have this signature
Function AddBuiltin:Variable(name:String, func:Term(args:Term[]), arity:Int, env:Variable, lazy:Int = False)
Local v:Variable = Variable.Make(name, env)
v.def = Builtin.Make(func, arity, name, lazy)
Return v
End Function
'Some simple ones
Function Multiply:Term(args:Term[]) 'Multiply two integers (for factorial demo)
Return Value.Make(String.FromLong(Value(args[0]).lval * Value(args[1]).lval))
End Function
Function Subtract:Term(args:Term[]) 'Difference of two integers (for factorial demo)
Return Value.Make(String.FromLong(Value(args[0]).lval - Value(args[1]).lval))
End Function
Function Equality:Term(args:Term[]) 'Compare two integers (for factorial demo)
Return Value.Make(String.FromLong(Value(args[0]).lval = Value(args[1]).lval))
End Function
Function lAdd:Term(args:Term[]) 'Sum of two integers (for fibonacci demo)
Return Value.Make(String.FromLong(Value(args[0]).lval + Value(args[1]).lval))
End Function
Function lIf:Term(args:Term[]) 'A definition of If that accepts and returns an int like in BlitzMax
Local pred:Term = Evaluate(args[0], Null) 'Note that If is lazy and therefore evaluates args only now
If Value(pred).lval
Return Evaluate(args[1], Null)
Else
Return Evaluate(args[2], Null)
EndIf
End Function
Function lPrint:Term(args:Term[]) 'Print a value to output
Print Value(args[0]).sval
Return args[0] 'Just returns itself
End Function
Function lDo:Term(args:Term[]) 'Execute a a list of expressions imperatively
Global this:Term
If this = Null Then this = Builtin.Make(lDo, 1)
Return this 'Since the argument was already evaluated by Apply, all it has to do is return itself
End Function 'and it can continue to execute any number of commands
End Function |
Comments
| ||
| Very good! I will give this a go later. I'm not totally sure what you mean about applying values to arguments, but if you mean variable substitution then I think you're right. |
Code Archives Forum