Last active
December 16, 2015 23:39
-
-
Save Meyermagic/5514934 to your computer and use it in GitHub Desktop.
Here it is...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"fmt" | |
"os" | |
"strconv" | |
"strings" | |
) | |
//type Environment *Scope | |
type Environment struct { | |
symbols map[SSymbol]SExp | |
parent *Environment | |
} | |
type SExp interface { | |
Eval(*Environment) SExp | |
String() string | |
} | |
func MakeEnvironment(parent *Environment) *Environment { | |
return &Environment{ | |
make(map[SSymbol]SExp, 13), | |
parent, | |
} | |
} | |
func (e *Environment) Set(sym SSymbol, val SExp) { | |
e.symbols[sym] = val | |
} | |
func (e *Environment) Unset(sym SSymbol) { | |
delete(e.symbols, sym) | |
} | |
func (e *Environment) Get(sym SSymbol) (SExp, bool) { | |
if exp, ok := e.symbols[sym]; ok { | |
return exp, true | |
} | |
if e.parent != nil { | |
return e.parent.Get(sym) | |
} | |
return nil, false | |
} | |
func (e *Environment) Global() bool { | |
return e.parent == nil | |
} | |
func (e *Environment) Enclosing() *Environment { | |
return e.parent | |
} | |
//Atoms | |
//Symbol | |
type SSymbol string | |
func (s SSymbol) Eval(env *Environment) SExp { | |
if exp, ok := env.Get(s); ok { | |
return exp | |
} | |
fmt.Printf("Symbol '%s' not found.\n", string(s)) | |
return nil | |
} | |
func (s SSymbol) String() string { | |
return string(s) | |
} | |
//Others | |
type SInt int64 | |
func (i SInt) Eval(env *Environment) SExp { | |
return i | |
} | |
func (i SInt) String() string { | |
return strconv.FormatInt(int64(i), 10) | |
} | |
type SFloat float64 | |
func (f SFloat) Eval(env *Environment) SExp { | |
return f | |
} | |
func (f SFloat) String() string { | |
return strconv.FormatFloat(float64(f), 'f', -1, 64) | |
} | |
type SString string | |
func (s SString) Eval(env *Environment) SExp { | |
return s | |
} | |
func (s SString) String() string { | |
return "\"" + string(s) + "\"" | |
} | |
type SFunc func(env *Environment, args ...SExp) SExp | |
func (f SFunc) Eval(env *Environment) SExp { | |
return f | |
} | |
func (f SFunc) String() string { | |
return "fuck_you" | |
} | |
/* | |
type SNothing struct{} | |
func (n SNothing) Eval(env *Environment) SExp { | |
return n | |
} | |
func (n SNothing) String() string { | |
return "" | |
}*/ | |
type SQuote struct { | |
e SExp | |
} | |
func (q SQuote) Eval(env *Environment) SExp { | |
return q.e | |
} | |
func (q SQuote) String() string { | |
return "'" + (q.e).String() | |
} | |
//List | |
type SList []SExp | |
func (s SList) Eval(env *Environment) SExp { | |
//This allows lists to be written without functions, I guess. | |
evaluated := make(SList, len(s)) | |
for i, e := range s { | |
if val := e.Eval(env); val != nil { | |
evaluated[i] = val | |
} else { | |
return nil | |
} | |
} | |
if f, ok := evaluated[0].(SFunc); ok { | |
return f(env, evaluated[1:]...) | |
} | |
return evaluated | |
} | |
func (s SList) String() string { | |
stringed := make([]string, len(s)) | |
for i, e := range s { | |
stringed[i] = e.String() | |
} | |
return "(" + strings.Join(stringed, " ") + ")" | |
} | |
func Atomize(token string) SExp { | |
if strings.HasPrefix(token, "\"") { | |
//fmt.Printf("String Atom: %s\n", token) | |
return SString(token[1 : len(token)-1]) | |
} | |
if i, err := strconv.ParseInt(token, 10, 64); err == nil { | |
//fmt.Printf("Int Atom: %s\n", token) | |
return SInt(i) | |
} | |
if f, err := strconv.ParseFloat(token, 64); err == nil { | |
//fmt.Printf("Float Atom: %s\n", token) | |
return SFloat(f) | |
} | |
//fmt.Printf("Symbol Atom: %s\n", token) | |
return SSymbol(token) | |
} | |
func WrapN(expr SExp, n int) SExp { | |
if n > 0 { | |
return WrapN(SQuote{expr}, n-1) | |
} | |
return expr | |
} | |
//Parsing | |
func ParseExpr(expr string) (SExp, int) { | |
top := []SExp{} | |
//tokens := []string{} | |
current := []rune{} | |
escp := false | |
qt := false | |
skip := 0 | |
alist := false | |
noeval := 0 | |
var temp SExp | |
for i, char := range expr { | |
if skip > 0 { | |
skip-- | |
continue | |
} | |
switch char { | |
case '\'': | |
if qt { | |
current = append(current, char) | |
} else { | |
noeval++ | |
} | |
break | |
case '(': | |
if qt { | |
current = append(current, char) | |
} else { | |
if i == 0 { | |
alist = true | |
continue | |
} | |
temp, skip = ParseExpr(expr[i:]) | |
temp = WrapN(temp, noeval) | |
noeval = 0 | |
top = append(top, temp) | |
temp = nil | |
} | |
break | |
case ')': | |
if qt { | |
current = append(current, char) | |
} else { | |
if alist { | |
if len(current) > 0 { | |
top = append(top, WrapN(Atomize(string(current)), noeval)) | |
current = []rune{} | |
noeval = 0 | |
} | |
return SList(top), i | |
} else { | |
return WrapN(Atomize(string(current)), noeval), i | |
} | |
} | |
break | |
case '"': | |
if escp { | |
current = append(current, char) | |
escp = false | |
continue | |
} | |
if qt { | |
current = append(current, char) | |
qt = false | |
} else { | |
current = append(current, char) | |
qt = true | |
} | |
break | |
case '\\': | |
if escp { | |
current = append(current, char) | |
escp = false | |
} else { | |
escp = true | |
} | |
break | |
case '\n': | |
case ' ': | |
if qt { | |
current = append(current, char) | |
} else { | |
if len(current) > 0 { | |
top = append(top, WrapN(Atomize(string(current)), noeval)) | |
noeval = 0 | |
current = []rune{} | |
} | |
} | |
break | |
default: | |
if escp { | |
//TODO: Proper handling of escaped chars | |
current = append(current, char) | |
escp = false | |
} else { | |
current = append(current, char) | |
} | |
} | |
} | |
if len(current) > 0 { | |
top = append(top, WrapN(Atomize(string(current)), noeval)) | |
} | |
if len(top) == 1 && !alist { | |
return top[0], -1 | |
} | |
return SList(top), -1 | |
} | |
//Builtins | |
func lambda(create_env *Environment, sig_args []SSymbol, body SExp) SExp { | |
return SFunc(func(call_env *Environment, call_args ...SExp) SExp { | |
env := MakeEnvironment(create_env) | |
//We could do partial evaluation, sort of (or real currying, with a bit more effort) | |
if len(sig_args) != len(call_args) { | |
fmt.Printf("Wrong number of arguments.\n") | |
return nil | |
} | |
for i, sym := range sig_args { | |
env.Set(sym, call_args[i]) | |
} | |
return body.Eval(env) | |
}) | |
} | |
func lambdaWrap(env *Environment, args ...SExp) SExp { | |
if len(args) != 2 { | |
fmt.Printf("Wrong number of arguments.\n") | |
return nil | |
} | |
explist, ok := args[0].(SList) | |
if !ok { | |
fmt.Printf("Wrong argument type.\n") | |
return nil | |
} | |
symlist := make([]SSymbol, len(explist)) | |
for i, e := range explist { | |
if sym, ok := e.(SSymbol); ok { | |
symlist[i] = sym | |
} else { | |
fmt.Printf("Argument 1 should be a list of symbols.\n") | |
return nil | |
} | |
} | |
return lambda(env, symlist, args[1]) | |
} | |
func eval(env *Environment, args ...SExp) SExp { | |
if len(args) != 1 { | |
fmt.Printf("Wrong number of arguments to eval.\n") | |
return nil | |
} | |
return args[0].Eval(env) | |
} | |
func sumInt(env *Environment, args ...SExp) SInt { | |
var total SInt = 0 | |
for _, e := range args { | |
total += e.(SInt) | |
} | |
return SInt(total) | |
} | |
func sumFloat(env *Environment, args ...SExp) SFloat { | |
var total SFloat = 0.0 | |
for _, e := range args { | |
total += e.(SFloat) | |
} | |
return SFloat(total) | |
} | |
func genSum(env *Environment, args ...SExp) SExp { | |
hasfloat := false | |
for _, e := range args { | |
if _, ok := e.(SFloat); ok { | |
hasfloat = true | |
} else if _, ok := e.(SInt); !ok { | |
fmt.Printf("Wrong argument type.\n") | |
return nil | |
} | |
} | |
if hasfloat { | |
return sumFloat(env, args...) | |
} | |
return sumInt(env, args...) | |
} | |
func let(env *Environment, sym SSymbol, val SExp) SExp { | |
env.Set(sym, val) | |
return SList{} | |
} | |
func unlet(env *Environment, sym SSymbol) SExp { | |
env.Unset(sym) | |
return SList{} | |
} | |
func head(env *Environment, args ...SExp) SExp { | |
if len(args) != 1 { | |
fmt.Printf("Wrong number of arguments to head.\n") | |
return nil | |
} | |
if lst, ok := args[0].(SList); ok { | |
return lst[0] | |
} | |
fmt.Printf("Argument to head must be a list.\n") | |
return nil | |
} | |
func tail(env *Environment, args ...SExp) SExp { | |
if len(args) != 1 { | |
fmt.Printf("Wrong number of arguments to tail.\n") | |
return nil | |
} | |
if lst, ok := args[0].(SList); ok { | |
return lst[1:] | |
} | |
fmt.Printf("Argument to tail must be a list.\n") | |
return nil | |
} | |
func length(env *Environment, args ...SExp) SExp { | |
if len(args) != 1 { | |
fmt.Printf("Wrong number of arguments to length.\n") | |
return nil | |
} | |
if lst, ok := args[0].(SList); ok { | |
return SInt(len(lst)) | |
} | |
fmt.Printf("Argument to length must be a list.\n") | |
return nil | |
} | |
func ifelse(env *Environment, args ...SExp) SExp { | |
if len(args) != 3 { | |
fmt.Printf("Wrong number of arguments to ifelse.\n") | |
return nil | |
} | |
if n, ok := args[0].(SInt); ok { | |
if n > 0 { | |
return args[1] | |
} else { | |
return args[2] | |
} | |
} | |
fmt.Printf("First argument to ifelse must be an int.\n") | |
return nil | |
} | |
func main() { | |
stdin := bufio.NewReader(os.Stdin) | |
//Make the global scope | |
global := MakeEnvironment(nil) | |
//Bind the builtins into it | |
global.Set("lambda", SFunc(lambdaWrap)) | |
global.Set("eval", SFunc(eval)) | |
global.Set("head", SFunc(head)) | |
global.Set("tail", SFunc(tail)) | |
global.Set("ifelse", SFunc(ifelse)) | |
global.Set("length", SFunc(length)) | |
global.Set("+", SFunc(genSum)) | |
//Make the current scope | |
current := MakeEnvironment(global) | |
depth := 1 | |
for { | |
fmt.Printf("lispy:%d$ ", depth) | |
line, err := stdin.ReadString('\n') | |
if err != nil { | |
fmt.Printf("Bye!\n") | |
break | |
} | |
expr, _ := ParseExpr(line) | |
//"Shell builtins" | |
//List builtins | |
if sl, ok := expr.(SList); ok { | |
if len(sl) > 0 { | |
if sym, ok := sl[0].(SSymbol); ok { | |
switch sym { | |
case "let": | |
if sym2, ok := sl[1].Eval(current).(SSymbol); ok { | |
current.Set(sym2, sl[2].Eval(current)) | |
} else { | |
fmt.Printf("Can only bind to a symbol.\n") | |
} | |
continue | |
case "unlet": | |
if sym2, ok := sl[1].Eval(current).(SSymbol); ok { | |
current.Unset(sym2) | |
} else { | |
fmt.Printf("Can only unbind a symbol.\n") | |
} | |
continue | |
} | |
} | |
} | |
} | |
//Single-symbol builtins | |
if sym, ok := expr.(SSymbol); ok { | |
switch sym { | |
case "pop": | |
if depth > 1 { | |
current = current.Enclosing() | |
depth-- | |
} else { | |
fmt.Printf("Already at lowest non-global scope.\n") | |
} | |
continue | |
case "push": | |
current = MakeEnvironment(current) | |
depth++ | |
continue | |
} | |
} | |
value := expr.Eval(current) | |
if value != nil { | |
fmt.Printf("%s\n", value) | |
} else { | |
fmt.Printf("You fucked up.\n") | |
} | |
} | |
} |
lispy:1$ let 'a 4
lispy:1$ let 'b 'a
lispy:1$ b
a
lispy:1$ a
4
lispy:1$ eval b
4
If functions took a **Environment instead of a *Environment, then I could write push and pop as normal functions.
meyer@meter:~/Code/go/bin$ ./lispy
lispy:1$ let 'sumlist (lambda '(lst) '(eval (ifelse (+ -1 (length lst)) '(+ (head lst) (sumlist (tail lst))) '(head lst))))
lispy:1$ sumlist (1 2 3 4 5 6 7 8 9 10)
55
lispy:1$ sumlist (2 2 2 5)
11
lispy:1$ (+ 1 2 3 4 5 6 7 8 9 10)
55
lispy:1$
Fuck, that is pretty ugly code.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
meyer@meter:~/Code/go/bin$ ./lispy
lispy:1$ let 'a 123
lispy:1$ let 'b 145
lispy:1$ (let 'double (lambda '(n) '(+ n n))
lispy:1$ push
lispy:2$ double a
246
lispy:2$ double b
290
lispy:2$ let 'b 6
lispy:2$ double b
12
lispy:2$ pop
lispy:1$ double b
290
lispy:1$ (6 7 2 4 1)
(6 7 2 4 1)
lispy:1$ let 'c 'b
lispy:1$ c
b
lispy:1$ let 'd c
lispy:1$ d
b
lispy:1$ let 'd ('c 'a 'b '(double a))
lispy:1$ d
(c a b (double a))
lispy:1$ >that feel when no eval function
Symbol '>that' not found.
You fucked up.