Skip to content

Instantly share code, notes, and snippets.

@Meyermagic
Last active December 16, 2015 23:39
Show Gist options
  • Save Meyermagic/5514934 to your computer and use it in GitHub Desktop.
Save Meyermagic/5514934 to your computer and use it in GitHub Desktop.
Here it is...
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")
}
}
}
@Meyermagic
Copy link
Author

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.

@Meyermagic
Copy link
Author

lispy:1$ let 'a 4
lispy:1$ let 'b 'a
lispy:1$ b
a
lispy:1$ a
4
lispy:1$ eval b
4

@Meyermagic
Copy link
Author

If functions took a **Environment instead of a *Environment, then I could write push and pop as normal functions.

@Meyermagic
Copy link
Author

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$

@Meyermagic
Copy link
Author

Fuck, that is pretty ugly code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment