From e3334b9ef0918302b1dc2f97314e6f614fbd2c1d Mon Sep 17 00:00:00 2001 From: hellerve Date: Mon, 14 May 2018 20:59:24 +0200 Subject: [PATCH] all: make a ton of features work --- README.md | 49 --- ast/ast.go | 161 ++++++++-- eval/eval.go | 809 ++++++++++++++++++++++++++++++++--------------- lib/arc.arc | 22 +- main.go | 129 ++++---- parser/parser.go | 263 +++++++++------ 6 files changed, 919 insertions(+), 514 deletions(-) diff --git a/README.md b/README.md index 7200803..c2b7f05 100644 --- a/README.md +++ b/README.md @@ -7,61 +7,12 @@ ## TODOS - Destructuring assignment -- Defining functions -- Anonymous functions -- Datastructures as functions -- Characters -- `let` -- `with` -- `pr` -- `prn` -- `if` -- `do` -- `when` -- `and` -- `no` -- `is` -- `in` -- `case` -- `for` -- `each` -- `while` -- `repeat` -- `:` -- `~` -- `keep` -- `rem` -- `all` -- `some` -- `pos` -- `trues` -- `table` -- `listtab` -- `obj` -- `keys` -- `vals` - `maptable` -- `alref` -- `string` -- `tostring` -- `type` - `coerce` -- `pop` -- `push` -- `++` -- `--` -- `zap` -- `sort` -- `insort` -- `compare` - `o` -- `.` -- `apply` - `len` - `mac` - `\`` -- `defop` -- `asv` - `w/link` - `aform` - ... diff --git a/ast/ast.go b/ast/ast.go index 76dee5a..7bed41e 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -1,44 +1,155 @@ package ast import ( - "fmt" - "strings" + "fmt" + "strings" ) type AST struct { - Tag Tag - Val interface{} + Tag Tag + Val interface{} } type Tag int8 const ( - Symbol Tag = iota - List - String - Num - Bool + Symbol Tag = iota + List + String + Num + Bool + Fn + Char + Table + Prim + Quoted ) func (tag Tag) String() string { - names := []string{ - "Symbol", - "List", - "String", - "Num", - "Bool", - } + names := []string{ + "Symbol", + "List", + "String", + "Num", + "Bool", + "Fn", + "Char", + "Table", + "Prim", + "Quoted", + } - return names[tag] + return names[tag] } -func (ast AST) Pretty() string { - if ast.Tag == List { - var agg []string - for _, elem := range(ast.Val.([]AST)) { - agg = append(agg, elem.Pretty()) - } - return "(" + strings.Join(agg, " ") + ")" +func (ast *AST) Type() *AST { + names := []string{ + "sym", + "cons", + "string", + "num", + "bool", + "fn", + "char", + "table", + "fn", + "cons", + } + + if ast.Tag == Quoted { + return ast.Val.(*AST).Type() } - return fmt.Sprintf("%v", ast.Val) + + name := names[ast.Tag] + if ast.Tag == Num { + val := ast.Val.(float64) + if val == float64(int64(val)) { + name = "int" + } + } + + res := AST{Symbol, name} + return &res +} + +func (ast *AST) Pretty() string { + if ast.Tag == List { + var agg []string + for _, elem := range ast.Val.([]*AST) { + agg = append(agg, elem.Pretty()) + } + return "(" + strings.Join(agg, " ") + ")" + } else if ast.Tag == Quoted { + return "'" + ast.Val.(*AST).Pretty() + } else if ast.Tag == Fn { + val := ast.Val.(Func) + var agg []string + for _, elem := range val.Params { + agg = append(agg, elem) + } + opt := "" + if val.HasOpt() { + opt = " . " + *val.Opt + } + body := val.Body.Pretty() + return "(fn (" + strings.Join(agg, " ") + opt + ") " + body + ")" + } else if ast.Tag == Char { + return "#" + string(ast.Val.(rune)) + } else if ast.Tag == Table { + var agg []string + + for k, v := range ast.Val.(map[*AST]*AST) { + agg = append(agg, "("+k.Pretty()+" . "+v.Pretty()+")") + } + + return "#hash(" + strings.Join(agg, " ") + ")" + } else if ast.Tag == Prim { + return "#" + } + return fmt.Sprintf("%v", ast.Val) +} + +type Env struct { + parent *Env + Values map[string]*AST +} + +type PrimFn func([]*AST) (*AST, error) + +type Primitive struct { + Name string + Fn PrimFn +} + +func newEnv(parent *Env) Env { + return Env{parent, make(map[string]*AST)} +} + +func ParentEnv() Env { + return newEnv(nil) +} + +func (e *Env) Lookup(elem string) (*AST, error) { + res, ok := e.Values[elem] + + if !ok { + if e.parent == nil { + return nil, fmt.Errorf("Symbol not found: %s", elem) + } + + return e.parent.Lookup(elem) + } + + return res, nil +} + +type Func struct { + Params []string + Opt *string + Body *AST + Env Env +} + +func (f *Func) HasOpt() bool { + return f.Opt != nil } diff --git a/eval/eval.go b/eval/eval.go index b778d8f..61a9cde 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -1,308 +1,597 @@ package eval -// TODO: a lot of code duplication that can be solved by a primitive registry // TODO: a lot of code duplication that can be solved by a type checking helper import ( - "fmt" + "fmt" + "strings" - "github.com/hellerve/argos/ast" + "github.com/hellerve/argos/ast" ) var trueVal = ast.AST{ast.Bool, true} var falseVal = ast.AST{ast.Bool, false} -var nilVal = ast.AST{ast.List, []ast.AST{}} +var nilVal = ast.AST{ast.List, []*ast.AST{}} -type env struct { - parent *env - values map[string]*ast.AST +func RootEnv() ast.Env { + e := ast.ParentEnv() + + prims := map[string]ast.PrimFn{ + "cons": evalCons, + "car": evalCar, + "cdr": evalCdr, + "null?": evalNull, + "pr": evalPr, + "table": evalTable, + "type": evalType, + } + + for k, v := range prims { + prim := ast.AST{ast.Prim, ast.Primitive{k, v}} + e.Values[k] = &prim + } + + return e } -func newEnv(parent *env) env { - return env{parent, make(map[string]*ast.AST)} +func checkArity(input []*ast.AST, arity int, name string) error { + ilen := len(input) + if ilen != arity { + return fmt.Errorf("Argument count to '%s' must be %d, was %d", name, arity, ilen) + } + + return nil } -func ParentEnv() env { - return newEnv(nil) +func evalDef(input []*ast.AST, e ast.Env) (*ast.AST, error) { + err := checkArity(input, 2, "=") + + if err != nil { + return nil, err + } + + variable := input[0] + + if variable.Tag != ast.Symbol { + // TODO destructuring assignment + variable, err = Eval(variable, e) + + if err != nil { + return nil, err + } + + if variable.Tag != ast.Symbol { + variable, err = Eval(input[1], e) + return variable, err + } + } + + if err != nil { + return nil, err + } + + sym := variable.Val.(string) + + evald, err := Eval(input[1], e) + + if err != nil { + return nil, err + } + + e.Values[sym] = evald + + return evald, nil } -func (e env) Lookup(elem string) (*ast.AST, error) { - res, ok := e.values[elem] +func arithCast(input *ast.AST, e ast.Env) (float64, error) { + evald, err := Eval(input, e) + if err != nil { + return 0, err + } - if !ok { - if e.parent == nil { - return nil, fmt.Errorf("Symbol not found: %s", elem) + if evald.Tag != ast.Num { + return 0, fmt.Errorf("Cannot perform arithmetic on ", evald.Pretty()) + } + + return evald.Val.(float64), nil +} + +func evalArith(input []*ast.AST, e ast.Env, fn func(float64, float64) float64) (*ast.AST, error) { + ilen := len(input) + if ilen < 2 { + return nil, fmt.Errorf("Arithmetic functions take at least 2 arguments, got %d", ilen) + } + + acc, err := arithCast(input[0], e) + + if err != nil { + return nil, err + } + + for _, elem := range input[1:] { + val, err := arithCast(elem, e) + + if err != nil { + return nil, err + } + acc = fn(acc, val) + } + + res := ast.AST{ast.Num, acc} + return &res, nil +} + +func evalLog(input []*ast.AST, e ast.Env, fn func(float64, float64) bool) (*ast.AST, error) { + ilen := len(input) + if ilen < 2 { + return nil, fmt.Errorf("Logic functions take at least 2 arguments, got %d", ilen) + } + + old, err := arithCast(input[0], e) + + if err != nil { + return nil, err + } + + acc := true + for _, elem := range input[1:] { + val, err := arithCast(elem, e) + + if err != nil { + return nil, err + } + acc = acc && fn(old, val) + old = val + } + + if acc { + return &trueVal, nil + } + return &falseVal, nil +} + +func evalEq(input []*ast.AST, e ast.Env) (*ast.AST, error) { + err := checkArity(input, 2, "is") + + if err != nil { + return nil, err + } + + x, err := Eval(input[0], e) + + if err != nil { + return nil, err + } + + y, err := Eval(input[1], e) + + if err != nil { + return nil, err + } + + if x.Tag != y.Tag { + return &falseVal, nil + } + + if x.Val == y.Val { + return &trueVal, nil + } + return &falseVal, nil +} + +func evalCons(input []*ast.AST) (*ast.AST, error) { + err := checkArity(input, 2, "cons") + + if err != nil { + return nil, err + } + + lst := input[1] + + if lst.Tag != ast.Quoted { + return nil, fmt.Errorf("Cannot cons to non-list %s", lst.Pretty()) + } + + fst := input[0] + + res := ast.AST{ast.List, append([]*ast.AST{fst}, lst.Val.(*ast.AST).Val.([]*ast.AST)...)} + + return &res, nil +} + +func evalCar(input []*ast.AST) (*ast.AST, error) { + err := checkArity(input, 1, "car") + + if err != nil { + return nil, err + } + + lst := input[0] + + if lst.Tag != ast.Quoted { + return nil, fmt.Errorf("Cannot car from non-list %s", lst.Pretty()) + } + + res := lst.Val.(*ast.AST).Val.([]*ast.AST)[0] + return res, nil +} + +func evalCdr(input []*ast.AST) (*ast.AST, error) { + err := checkArity(input, 1, "cdr") + + if err != nil { + return nil, err + } + + lst := input[0] + + if lst.Tag != ast.Quoted { + return nil, fmt.Errorf("Cannot cdr from non-list %s", lst.Pretty()) + } + + res := ast.AST{ast.List, lst.Val.(*ast.AST).Val.([]*ast.AST)[1:]} + return &res, nil +} + +func evalNull(input []*ast.AST) (*ast.AST, error) { + err := checkArity(input, 1, "null") + + if err != nil { + return nil, err + } + + lst := input[0] + + if lst.Tag != ast.Quoted { + return nil, fmt.Errorf("Cannot call null? on non-list %s", lst.Pretty()) + } + + res := ast.AST{ast.Bool, len(lst.Val.(*ast.AST).Val.([]*ast.AST)) == 0} + return &res, nil +} + +func evalFn(input []*ast.AST, e ast.Env) (*ast.AST, error) { + err := checkArity(input, 2, "fn") + + if err != nil { + return nil, err + } + + args := input[0] + + if args.Tag != ast.List { + return nil, fmt.Errorf("Cannot call fn with argument list %s", args.Pretty()) + } + + var argsStr []string + var opt *string = nil + + argslst := args.Val.([]*ast.AST) + for i, a := range argslst { + if a.Tag != ast.Symbol { + return nil, fmt.Errorf("Argument list cannot contain %s", a.Pretty()) + } + val := a.Val.(string) + // TODO: error handling + if val == "." { + str := argslst[i+1].Val.(string) + opt = &str + break } - return e.parent.Lookup(elem) - } + argsStr = append(argsStr, val) + } - return res, nil + body := input[1] + + res := ast.AST{ast.Fn, ast.Func{argsStr, opt, body, e}} + return &res, nil } -func checkArity(input []ast.AST, arity int, name string) error { - ilen := len(input) - if ilen != arity+1 { - return fmt.Errorf("Argument count to '%s' must be %d, was %d", name, arity, ilen) +func funcApply(f ast.Func, args []*ast.AST, e ast.Env) (*ast.AST, error) { + plen := len(f.Params) + alen := len(args) + + if plen != alen && !f.HasOpt() { + return nil, fmt.Errorf("Function expected %d arguments, was called with %d.", plen, alen) + } + + for i, a := range f.Params { + f.Env.Values[a] = args[i] + } + + if f.HasOpt() { + lst := ast.AST{ast.List, args[plen:]} + quoted := ast.AST{ast.Quoted, &lst} + f.Env.Values[*f.Opt] = "ed } - return nil + return Eval(f.Body, f.Env) } -func evalDef(input []ast.AST, e env) (*ast.AST, error) { - err := checkArity(input, 2, "=") +func evalPr(l []*ast.AST) (*ast.AST, error) { + var toPrint []string + for _, elem := range l { + toPrint = append(toPrint, elem.Pretty()) + } + fmt.Print(strings.Join(toPrint, " ")) + return &nilVal, nil +} - if err != nil { - return nil, err +func evalIf(l []*ast.AST, e ast.Env) (*ast.AST, error) { + i := 0 + for i < len(l) { + if i == len(l)-1 { + return Eval(l[i], e) + } + cond, err := Eval(l[i], e) + + if err != nil { + return nil, err + } + + if cond == &trueVal { + return Eval(l[i+1], e) + } + i += 2 + } + return &nilVal, nil +} + +func evalDo(l []*ast.AST, e ast.Env) (*ast.AST, error) { + var res *ast.AST + var err error + for _, elem := range l { + res, err = Eval(elem, e) + + if err != nil { + return nil, err + } + } + return res, nil +} + +func evalApply(l []*ast.AST, e ast.Env) (*ast.AST, error) { + err := checkArity(l, 2, "apply") + + if err != nil { + return nil, err + } + + // TODO: error handling + args, err := Eval(l[1], e) + + if err != nil { + return nil, err + } + + if args.Tag != ast.Quoted { + return nil, fmt.Errorf("Argument 2 to apply must be a list, got %s", args.Pretty()) } - variable := &input[1] + argslst := args.Val.(*ast.AST).Val.([]*ast.AST) + return evalList(append([]*ast.AST{l[0]}, argslst...), e) +} - if variable.Tag != ast.Symbol { - variable, err = Eval(variable, e) +func evalWhile(l []*ast.AST, e ast.Env) (*ast.AST, error) { + err := checkArity(l, 2, "while") - if variable.Tag != ast.Symbol { - return nil, fmt.Errorf("First argument to 'def' must be symbol, was %v", variable.Tag) + if err != nil { + return nil, err + } + + var res *ast.AST + + for { + cond, err := Eval(l[0], e) + + if err != nil { + return nil, err + } + + if cond == &falseVal { + break + } + res, err = Eval(l[1], e) + + if err != nil { + return nil, err + } + } + return res, nil +} + +func evalTable(l []*ast.AST) (*ast.AST, error) { + err := checkArity(l, 0, "table") + + if err != nil { + return nil, err + } + + res := ast.AST{ast.Table, make(map[*ast.AST]*ast.AST)} + return &res, nil +} + +func evalType(l []*ast.AST) (*ast.AST, error) { + err := checkArity(l, 1, "type") + + if err != nil { + return nil, err + } + + return l[0].Type(), nil +} + +func evalSymbolList(l []*ast.AST, e ast.Env) (*ast.AST, error) { + sym := l[0].Val.(string) + + l = l[1:] + // TODO: make more of these primitives + switch sym { + case "=": + return evalDef(l, e) + case "is": + return evalEq(l, e) + case "apply": + return evalApply(l, e) + case "quote": + lst := l[0] + res := ast.AST{ast.Quoted, lst} + return &res, nil + case "fn": + return evalFn(l, e) + case "if": + return evalIf(l, e) + case "do": + return evalDo(l, e) + case "while": + return evalWhile(l, e) + case "+": + return evalArith(l, e, func(x float64, y float64) float64 { return x + y }) + case "-": + return evalArith(l, e, func(x float64, y float64) float64 { return x - y }) + case "*": + return evalArith(l, e, func(x float64, y float64) float64 { return x * y }) + case "/": + return evalArith(l, e, func(x float64, y float64) float64 { return x / y }) + case "%": + return evalArith(l, e, func(x float64, y float64) float64 { return float64(int64(x) % int64(y)) }) + case "<": + return evalLog(l, e, func(x float64, y float64) bool { return x < y }) + case ">": + return evalLog(l, e, func(x float64, y float64) bool { return x > y }) + } + res, err := e.Lookup(sym) + + if err != nil { + return nil, err + } + + return evalList(append([]*ast.AST{res}, l...), e) +} + +func evalIdx(l []*ast.AST, e ast.Env) (*ast.AST, error) { + head := l[0] + + err := checkArity(l, 2, "indexing") + + if err != nil { + return nil, err + } + + arg, err := Eval(l[1], e) + + if err != nil { + return nil, err + } + + switch head.Tag { + case ast.Quoted: + if arg.Tag != ast.Num { + return nil, fmt.Errorf("Cannot index using %s", arg.Pretty()) + } + + idx := int64(arg.Val.(float64)) + var actualLen int64 + val := head.Val.(*ast.AST).Val.([]*ast.AST) + actualLen = int64(len(val)) + actualIdx := idx + if idx < 0 { + actualIdx = actualLen + idx + } + if actualLen > actualIdx && actualIdx > 0 { + return val[actualIdx], nil + } + return nil, fmt.Errorf("Out of bounds access (idx %d at %s)", idx, head.Pretty()) + case ast.String: + if arg.Tag != ast.Num { + return nil, fmt.Errorf("Cannot index using %s", arg.Pretty()) + } + + idx := int64(arg.Val.(float64)) + var actualLen int64 + val := []rune(head.Val.(string)) + actualLen = int64(len(val)) + actualIdx := idx + if idx < 0 { + actualIdx = actualLen + idx + } + if actualLen > actualIdx && actualIdx > 0 { + res := ast.AST{ast.Char, val[actualIdx]} + return &res, nil + } + return nil, fmt.Errorf("Out of bounds access (idx %d at %s)", idx, head.Pretty()) + case ast.Table: + val := head.Val.(map[*ast.AST]*ast.AST) + res, ok := val[arg] + if !ok { + return &nilVal, nil + } + return res, nil + } + return nil, fmt.Errorf("Cannot index %s", head.Pretty()) +} + +func primCall(f ast.Primitive, l []*ast.AST, e ast.Env) (*ast.AST, error) { + var args []*ast.AST + + for _, a := range l { + res, err := Eval(a, e) + if err != nil { + return nil, err } + args = append(args, res) } - if err != nil { - return nil, err - } - - sym := variable.Val.(string) - - evald, err := Eval(&input[2], e) - - if err != nil { - return nil, err - } - - e.values[sym] = evald - - return evald, nil + return f.Fn(args) } -func arithCast(input ast.AST, e env) (float64, error) { - evald, err := Eval(&input, e) - if err != nil { - return 0, err - } +func evalList(l []*ast.AST, e ast.Env) (*ast.AST, error) { + head := l[0] + var err error - if evald.Tag != ast.Num { - return 0, fmt.Errorf("Cannot perform arithmetic on ", evald.Pretty()) - } - - return evald.Val.(float64), nil -} - -func evalArith(input []ast.AST, e env, fn (func(float64, float64) float64)) (*ast.AST, error) { - ilen := len(input) - if ilen < 3 { - return nil, fmt.Errorf("Arithmetic functions take at least 2 arguments, got %d", ilen) - } - - acc, err := arithCast(input[1], e) - - if err != nil { - return nil, err - } - - for _, elem := range input[2:] { - val, err := arithCast(elem, e) + if head.Tag == ast.List { + head, err = Eval(head, e) if err != nil { return nil, err } - acc = fn(acc, val) } - res := ast.AST{ast.Num, acc} - return &res, nil + switch head.Tag { + case ast.Symbol: + return evalSymbolList(l, e) + case ast.Fn: + return funcApply(head.Val.(ast.Func), l[1:], e) + case ast.Prim: + return primCall(head.Val.(ast.Primitive), l[1:], e) + case ast.String, ast.Quoted, ast.Table: + return evalIdx(append([]*ast.AST{head}, l[1:]...), e) + } + return nil, fmt.Errorf("Cannot perform call on %s", head.Pretty()) } -func evalEq(input []ast.AST, e env) (*ast.AST, error) { - err := checkArity(input, 2, "iso") +func Eval(input *ast.AST, e ast.Env) (*ast.AST, error) { + if input.Tag == ast.List { + l := input.Val.([]*ast.AST) + return evalList(l, e) + } + if input.Tag == ast.Symbol { + val := input.Val.(string) - if err != nil { - return nil, err - } - - x, err := Eval(&input[1], e) - - if err != nil { - return nil, err - } - - y, err := Eval(&input[2], e) - - if err != nil { - return nil, err - } - - if x.Tag != y.Tag { - return &falseVal, nil - } - - if x.Val == y.Val { - return &trueVal, nil - } - return &falseVal, nil -} - -func evalCons(input []ast.AST, e env) (*ast.AST, error) { - err := checkArity(input, 2, "cons") - - if err != nil { - return nil, err - } - - lst, err := Eval(&input[2], e) - - if err != nil { - return nil, err - } - - if lst.Tag != ast.List { - return nil, fmt.Errorf("Cannot cons to non-list %s", lst.Pretty()) - } - - fst, err := Eval(&input[1], e) - - if err != nil { - return nil, err - } - - res := ast.AST{ast.List, append([]ast.AST{*fst}, lst.Val.([]ast.AST)...)} - - return &res, nil -} - -func evalCar(input []ast.AST, e env) (*ast.AST, error) { - err := checkArity(input, 1, "car") - - if err != nil { - return nil, err - } - - lst, err := Eval(&input[1], e) - - if err != nil { - return nil, err - } - - if lst.Tag != ast.List { - return nil, fmt.Errorf("Cannot car from non-list %s", lst.Pretty()) - } - - res := lst.Val.([]ast.AST)[0] - return &res, nil -} - -func evalCdr(input []ast.AST, e env) (*ast.AST, error) { - err := checkArity(input, 1, "cdr") - - if err != nil { - return nil, err - } - - lst, err := Eval(&input[1], e) - - if err != nil { - return nil, err - } - - if lst.Tag != ast.List { - return nil, fmt.Errorf("Cannot cdr from non-list %s", lst.Pretty()) - } - - res := ast.AST{ast.List, lst.Val.([]ast.AST)[1:]} - return &res, nil -} - -func evalNull(input []ast.AST, e env) (*ast.AST, error) { - err := checkArity(input, 1, "null") - - if err != nil { - return nil, err - } - - lst, err := Eval(&input[1], e) - - if err != nil { - return nil, err - } - - if lst.Tag != ast.List { - return nil, fmt.Errorf("Cannot call null? on non-list %s", lst.Pretty()) - } - - res := ast.AST{ast.Bool, len(lst.Val.([]ast.AST)) == 0} - return &res, nil -} - -func evalList(input *ast.AST, e env) (*ast.AST, error) { - l := input.Val.([]ast.AST) - head := l[0] - - if head.Tag != ast.Symbol { - err := fmt.Errorf("Calling non-symbol: %s", head.Pretty()) - - return nil, err - } - - sym := head.Val.(string) - - switch sym { - case "=": - return evalDef(l, e) - case "iso": - return evalEq(l, e) - case "quote": - res := l[1] - return &res, nil - case "cons": - return evalCons(l, e) - case "car": - return evalCar(l, e) - case "cdr": - return evalCdr(l, e) - case "null?": - return evalNull(l, e) - case "+": - return evalArith(l, e, func(x float64, y float64) float64 { return x + y }) - case "-": - return evalArith(l, e, func(x float64, y float64) float64 { return x - y }) - case "*": - return evalArith(l, e, func(x float64, y float64) float64 { return x * y }) - case "/": - return evalArith(l, e, func(x float64, y float64) float64 { return x / y }) - case "%": - return evalArith(l, e, func(x float64, y float64) float64 { return float64(int64(x) % int64(y)) }) - } - return input, nil -} - -func Eval(input *ast.AST, e env) (*ast.AST, error) { - if (input.Tag == ast.List) { - return evalList(input, e) - } - if (input.Tag == ast.Symbol) { - val := input.Val.(string) - - var err error - var res *ast.AST - switch val { - case "true": - res = &trueVal - case "false": - res = &falseVal - case "nil": - res = &nilVal - default: - res, err = e.Lookup(val) - } - return res, err - } - return input, nil + var err error + var res *ast.AST + switch val { + case "true": + res = &trueVal + case "false": + res = &falseVal + case "nil": + res = &nilVal + default: + res, err = e.Lookup(val) + } + return res, err + } + return input, nil } diff --git a/lib/arc.arc b/lib/arc.arc index ef11fc9..c738647 100644 --- a/lib/arc.arc +++ b/lib/arc.arc @@ -21,22 +21,8 @@ ; not sure this is a mistake; strings may be subtly different from ; lists of chars - -(assign do (annotate 'mac - (fn args `((fn () ,@args))))) - -(assign safeset (annotate 'mac - (fn (var val) - `(do (if (bound ',var) - (do (disp "*** redefining " (stderr)) - (disp ',var (stderr)) - (disp #\newline (stderr)))) - (assign ,var ,val))))) - -(assign def (annotate 'mac - (fn (name parms . body) - `(do (sref sig ',parms ',name) - (safeset ,name (fn ,parms ,@body)))))) +(mac def (name params body) + (list '= name (list 'fn params body))) (def caar (xs) (car (car xs))) (def cadr (xs) (car (cdr xs))) @@ -54,8 +40,8 @@ ; (def list args args) (def copylist (xs) - (if (no xs) - nil + (if (no xs) + nil (cons (car xs) (copylist (cdr xs))))) (def list args (copylist args)) diff --git a/main.go b/main.go index bb16468..d2d9a48 100644 --- a/main.go +++ b/main.go @@ -1,96 +1,99 @@ package main import ( - "fmt" - "io/ioutil" - "log" - "os" - "strings" + "fmt" + "io/ioutil" + "log" + "os" + "strings" - "github.com/chzyer/readline" + "github.com/chzyer/readline" - "github.com/hellerve/argos/eval" - "github.com/hellerve/argos/parser" + "github.com/hellerve/argos/eval" + "github.com/hellerve/argos/parser" ) func runRepl() { - rl, err := readline.New("argos> ") + rl, err := readline.New("argos> ") - if err != nil { - log.Fatal(err) - } + if err != nil { + log.Fatal(err) + } - defer rl.Close() + defer rl.Close() - e := eval.ParentEnv() - for { - input, err := rl.Readline() + e := eval.RootEnv() + for { + input, err := rl.Readline() - if err != nil { - return - } + if err != nil { + return + } - parsed, err, unconsumed := parser.Parse(input) + if len(input) == 0 { + continue + } - if err != nil { - fmt.Println(err) - continue - } + parsed, err, unconsumed := parser.Parse(input) - if len(unconsumed) != 0 { - fmt.Println("Unconsumed input:", strings.Join(unconsumed, " ")) - continue - } + if err != nil { + fmt.Println(err) + continue + } - // TODO: how to avoid bindings on error? - evald, err := eval.Eval(parsed, e) + if len(unconsumed) != 0 { + fmt.Println("Unconsumed input:", strings.Join(unconsumed, " ")) + continue + } + // TODO: how to avoid bindings on error? + evald, err := eval.Eval(parsed, e) - if err != nil { - fmt.Println(err) - continue - } + if err != nil { + fmt.Println(err) + continue + } - fmt.Println(evald.Pretty()) - } + fmt.Println(evald.Pretty()) + } } func runFile(path string) { - e := eval.ParentEnv() - input, err := ioutil.ReadFile(path) + e := eval.RootEnv() + input, err := ioutil.ReadFile(path) - if err != nil { - fmt.Println(err) - return - } + if err != nil { + fmt.Println(err) + return + } - parsed, err, unconsumed := parser.Parse(string(input)) + parsed, err, unconsumed := parser.Parse(string(input)) - if err != nil { - fmt.Println(err) - return - } + if err != nil { + fmt.Println(err) + return + } - if len(unconsumed) != 0 { - fmt.Println("Unconsumed input:", strings.Join(unconsumed, " ")) - return - } + if len(unconsumed) != 0 { + fmt.Println("Unconsumed input:", strings.Join(unconsumed, " ")) + return + } - evald, err := eval.Eval(parsed, e) + evald, err := eval.Eval(parsed, e) - if err != nil { - fmt.Println(err) - return - } + if err != nil { + fmt.Println(err) + return + } - fmt.Println(evald.Pretty()) + fmt.Println(evald.Pretty()) } func main() { - args := os.Args - if len(args) == 1 { - runRepl() - } else { - runFile(os.Args[1]) - } + args := os.Args + if len(args) == 1 { + runRepl() + } else { + runFile(os.Args[1]) + } } diff --git a/parser/parser.go b/parser/parser.go index 498b083..460747c 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1,140 +1,205 @@ package parser import ( - "errors" - "regexp" - "strconv" - "strings" + "errors" + "regexp" + "strconv" + "strings" - "github.com/hellerve/argos/ast" + "github.com/hellerve/argos/ast" ) func withoutEmpty(input []string) []string { - var r []string - for _, str := range input { - if str != "" { - r = append(r, str) - } - } - return r + var r []string + for _, str := range input { + if str != "" { + r = append(r, str) + } + } + return r } var comments = regexp.MustCompile(";.*") var whitespace = regexp.MustCompile("\\s") func explode(s string, start string, end string) string { - return strings.Replace(strings.Replace(s, start, " "+start+" ", -1), end, " "+end+" ", -1) + return strings.Replace(strings.Replace(s, start, " "+start+" ", -1), end, " "+end+" ", -1) } func tokenize(input string) []string { - withoutComments := string(comments.ReplaceAll([]byte(input), []byte(""))) - explodedParens := explode(withoutComments, "(", ")") - explodedBrackets := explode(explodedParens, "[", "]") - return withoutEmpty(whitespace.Split(explodedBrackets, -1)) + withoutComments := string(comments.ReplaceAll([]byte(input), []byte(""))) + explodedParens := explode(withoutComments, "(", ")") + explodedBrackets := explode(explodedParens, "[", "]") + return withoutEmpty(whitespace.Split(explodedBrackets, -1)) +} + +func withEscape(input string) rune { + switch input[1:] { + case "\\a": + return '\a' + case "\\b": + return '\b' + case "newline": + return '\n' + case "tab": + return '\t' + case "\\f": + return '\f' + case "\\r": + return '\r' + case "\\v": + return '\v' + case "\\\\": + return '\\' + case "\\'": + return '\'' + case "\\\"": + return '"' + } + return []rune(input)[1] } func parseValue(input []string) (*ast.AST, error, []string) { - var res ast.AST + var res ast.AST - f, err := strconv.ParseFloat(input[0], 64) + f, err := strconv.ParseFloat(input[0], 64) - if err == nil { - res = ast.AST{ast.Num, f} - return &res, nil, input[1:] - } + if err == nil { + res = ast.AST{ast.Num, f} + return &res, nil, input[1:] + } - if input[0][0] == '"' { - var agg []string - for { - if len(input) == 0 { - return nil, errors.New("Unmatched \""), input - } - token := input[0] - input = input[1:] - agg = append(agg, token) - if token[len(token)-1] == '"' { - break - } - } - res = ast.AST{ast.String, strings.Join(agg, " ")} - return &res, nil, input - } + if input[0][0] == '#' { + res := ast.AST{ast.Char, withEscape(input[0])} + return &res, nil, input[1:] + } - res = ast.AST{ast.Symbol, input[0]} - return &res, nil, input[1:] + if input[0][0] == '"' { + var agg []string + for { + if len(input) == 0 { + return nil, errors.New("Unmatched \""), input + } + token := input[0] + input = input[1:] + agg = append(agg, token) + if token[len(token)-1] == '"' { + break + } + } + joined := strings.Join(agg, " ") + res = ast.AST{ast.String, joined[1 : len(joined)-1]} + return &res, nil, input + } + + if input[0][0] == '~' { + fn := ast.AST{ast.Symbol, input[0][1:]} + complement := ast.AST{ast.Symbol, "complement"} + res = ast.AST{ast.List, []*ast.AST{&complement, &fn}} + return &res, nil, input[1:] + } + + if strings.Contains(input[0], ":") { + comp := ast.AST{ast.Symbol, "compose"} + fns := []*ast.AST{&comp} + for _, fn := range strings.Split(input[0], ":") { + astfn := ast.AST{ast.Symbol, fn} + fns = append(fns, &astfn) + } + res = ast.AST{ast.List, fns} + return &res, nil, input[1:] + } + + res = ast.AST{ast.Symbol, input[0]} + return &res, nil, input[1:] } -func makeFn(bodyStatements []ast.AST) ast.AST { - body := ast.AST{ast.List, bodyStatements} - args := ast.AST{ast.List, []ast.AST{ast.AST{ast.Symbol, "_"}}} +func makeFn(bodyStatements []*ast.AST) ast.AST { + body := ast.AST{ast.List, bodyStatements} + underscore := ast.AST{ast.Symbol, "_"} + args := ast.AST{ast.List, []*ast.AST{&underscore}} + fn := ast.AST{ast.Symbol, "fn"} - return ast.AST{ast.List, []ast.AST{ast.AST{ast.Symbol, "fn"}, args, body}} + return ast.AST{ast.List, []*ast.AST{&fn, &args, &body}} } func parseToken(input []string) (*ast.AST, error, []string) { - if len(input) == 0 { - return nil, errors.New("Unmatched '(' or '['"), input - } + if len(input) == 0 { + return nil, errors.New("Unmatched '(' or '['"), input + } - if input[0][0] == '\'' { - if (input[0] == "'") { - input = input[1:] - } else { - input = append([]string{input[0][1:]}, input[1:]...) - } - tmp, err, input := parseToken(input) + if input[0][0] == '\'' { + if input[0] == "'" { + input = input[1:] + } else { + input = append([]string{input[0][1:]}, input[1:]...) + } + tmp, err, input := parseToken(input) - if err != nil { - return nil, err, input - } + if err != nil { + return nil, err, input + } - res := ast.AST{ast.List, []ast.AST{ast.AST{ast.Symbol, "quote"}, *tmp}} - return &res, nil, input - } + quote := ast.AST{ast.Symbol, "quote"} + res := ast.AST{ast.List, []*ast.AST{"e, tmp}} + return &res, nil, input + } - switch input[0] { - case "(": { - var l []ast.AST - input = input[1:] - for input[0] != ")" { - elem, err, newInput := parseToken(input) + switch input[0] { + case "(": + { + var l []*ast.AST + input = input[1:] + for input[0] != ")" { + elem, err, newInput := parseToken(input) - if err != nil { - return nil, err, input - } + if err != nil { + return nil, err, input + } - l = append(l, *elem) - input = newInput - } - res := ast.AST{ast.List, l} - return &res, nil, input[1:] - } - case ")": { - return nil, errors.New("Unmatched ')'"), input - } - case "[": { - var l []ast.AST - input = input[1:] - for input[0] != "]" { - elem, err, newInput := parseToken(input) + if len(newInput) == 0 { + return nil, errors.New("Unmatched '('"), input + } - if err != nil { - return nil, err, input - } + l = append(l, elem) + input = newInput + } + res := ast.AST{ast.List, l} + return &res, nil, input[1:] + } + case ")": + { + return nil, errors.New("Unmatched ')'"), input + } + case "[": + { + var l []*ast.AST + input = input[1:] + for input[0] != "]" { + elem, err, newInput := parseToken(input) - l = append(l, *elem) - input = newInput - } - res := makeFn(l) - return &res, nil, input[1:] - } - case "]": { - return nil, errors.New("Unmatched ']'"), input - } - } - return parseValue(input) + if err != nil { + return nil, err, input + } + + if len(newInput) == 0 { + return nil, errors.New("Unmatched '['"), input + } + + l = append(l, elem) + input = newInput + } + res := makeFn(l) + return &res, nil, input[1:] + } + case "]": + { + return nil, errors.New("Unmatched ']'"), input + } + } + return parseValue(input) } func Parse(input string) (*ast.AST, error, []string) { - return parseToken(tokenize(input)) + return parseToken(tokenize(input)) }