diff --git a/skeam.go b/skeam.go index 9a3f89d..68f029f 100644 --- a/skeam.go +++ b/skeam.go @@ -18,6 +18,10 @@ type sexp struct { quotelvl int } +type callable interface { + call(*environment, []interface{}) (interface{}, error) +} + func newSexp() *sexp { return &sexp{ items: make([]interface{}, 0, 8), @@ -73,12 +77,12 @@ var universe = &environment{map[symbol]interface{}{ // "append" // special forms - "begin": special(begin), - "define": special(define), - "if": special(_if), - "lambda": special(mklambda), - "quote": special(quote), - "set!": special(set), + symbol(begin.name): begin, + symbol(define.name): define, + symbol(_if.name): _if, + symbol(mklambda.name): mklambda, + symbol(quote.name): quote, + symbol(set.name): set, }, nil} // parses the string lexeme into a value that can be eval'd @@ -181,35 +185,14 @@ func eval(v interface{}, env *environment) (interface{}, error) { return nil, err } - // check to see if this is a special form - if spec, ok := v.(special); ok { - debugPrint("special!") - if len(t.items) > 1 { - return spec(env, t.items[1:]...) - } else { - return spec(env) - } - } - - // exec builtin func if one exists - if b, ok := v.(builtin); ok { - if len(t.items) > 1 { - return b.call(env, t.items[1:]) - } else { - return b.call(env, nil) - } + c, ok := v.(callable) + if !ok { + return nil, fmt.Errorf(`expected special form or builtin procedure, received %v`, reflect.TypeOf(v)) } - - // exec lambda if possible - if l, ok := v.(lambda); ok { - if len(t.items) > 1 { - return l.call(env, t.items[1:]) - } else { - return l.call(env, nil) - } + if len(t.items) > 1 { + return c.call(env, t.items[1:]) } - - return nil, fmt.Errorf(`expected special form or builtin procedure, received %v`, reflect.TypeOf(v)) + return c.call(env, nil) default: debugPrint("default eval") diff --git a/special.go b/special.go index 1acbc67..8f25f35 100644 --- a/special.go +++ b/special.go @@ -9,7 +9,37 @@ import ( // type special is a callable outside of the normal execution workflow. That // is, a special receives its arguments unevaluated, unlike lambdas or builtin, // both of whose arguments are evaluated upon invocation. -type special func(*environment, ...interface{}) (interface{}, error) +// type special func(*environment, ...interface{}) (interface{}, error) +type special struct { + name string + arity int + variadic bool + fn func(*environment, []interface{}) (interface{}, error) +} + +func (s special) checkArity(n int) error { + if n == s.arity { + return nil + } + + if s.variadic && n > s.arity { + return nil + } + + return arityError{ + expected: s.arity, + received: n, + name: s.name, + variadic: s.variadic, + } +} + +func (s special) call(env *environment, rawArgs []interface{}) (interface{}, error) { + if err := s.checkArity(len(rawArgs)); err != nil { + return nil, err + } + return s.fn(env, rawArgs) +} // type arityError is used to store information related to arity errors. That // is, the invocation of a callable with the wrong number of arguments. @@ -50,23 +80,23 @@ func checkArity(arity int, args []interface{}, name string) error { // (define x 5) // // would create the symbol "x" and set its value to 5. -func define(env *environment, args ...interface{}) (interface{}, error) { - if err := checkArity(2, args, "define"); err != nil { - return nil, err - } - - s, ok := args[0].(symbol) - if !ok { - return nil, fmt.Errorf(`first argument to *define* must be symbol, received %v`, reflect.TypeOf(args[0])) - } +var define = special{ + name: "define", + arity: 2, + fn: func(env *environment, args []interface{}) (interface{}, error) { + s, ok := args[0].(symbol) + if !ok { + return nil, fmt.Errorf(`first argument to *define* must be symbol, received %v`, reflect.TypeOf(args[0])) + } - v, err := eval(args[1], env) - if err != nil { - return nil, err - } - env.set(s, v) + v, err := eval(args[1], env) + if err != nil { + return nil, err + } + env.set(s, v) - return nil, nil + return nil, nil + }, } // defines the built-in "quote" construct. e.g.: @@ -76,19 +106,19 @@ func define(env *environment, args ...interface{}) (interface{}, error) { // would evaluate to the list (1 2 3). That is, quote is a function of arity 1 // that is effectively a no-op; the input value is not evaluated, which // prevents evaluation of the first element of the list, in this case 1. -func quote(_ *environment, args ...interface{}) (interface{}, error) { - if err := checkArity(1, args, "quote"); err != nil { - return nil, err - } - - switch t := args[0].(type) { - case *sexp: - t.quotelvl++ - return t, nil - default: - return &sexp{items: []interface{}{t}, quotelvl: 1}, nil - } - panic("not reached") +var quote = special{ + name: "quote", + arity: 1, + fn: func(_ *environment, args []interface{}) (interface{}, error) { + switch t := args[0].(type) { + case *sexp: + t.quotelvl++ + return t, nil + default: + return &sexp{items: []interface{}{t}, quotelvl: 1}, nil + } + panic("not reached") + }, } // turns an arbitrary lisp value into a boolean. Apparently the sematics of @@ -110,20 +140,20 @@ func booleanize(v interface{}) bool { // (if #f "foo" "bar") // // would evaluate to "bar" -func _if(env *environment, args ...interface{}) (interface{}, error) { - if err := checkArity(3, args, "if"); err != nil { - return nil, err - } - - v, err := eval(args[0], env) - if err != nil { - return nil, err - } +var _if = special{ + name: "if", + arity: 3, + fn: func(env *environment, args []interface{}) (interface{}, error) { + v, err := eval(args[0], env) + if err != nil { + return nil, err + } - if booleanize(v) { - return eval(args[1], env) - } - return eval(args[2], env) + if booleanize(v) { + return eval(args[1], env) + } + return eval(args[2], env) + }, } // defines the built-in "set!" construct, which is used to set the value of an @@ -133,27 +163,27 @@ func _if(env *environment, args ...interface{}) (interface{}, error) { // // would set the symbol x to the value 5, if and only if the symbol x was // previously defined. -func set(env *environment, args ...interface{}) (interface{}, error) { - if err := checkArity(2, args, "set!"); err != nil { - return nil, err - } - - s, ok := args[0].(symbol) - if !ok { - return nil, fmt.Errorf(`first argument to *set!* must be symbol, received %v`, reflect.TypeOf(args[0])) - } +var set = special{ + name: "set!", + arity: 2, + fn: func(env *environment, args []interface{}) (interface{}, error) { + s, ok := args[0].(symbol) + if !ok { + return nil, fmt.Errorf(`first argument to *set!* must be symbol, received %v`, reflect.TypeOf(args[0])) + } - if !env.defined(s) { - return nil, fmt.Errorf(`cannot *set!* undefined symbol %v`, s) - } + if !env.defined(s) { + return nil, fmt.Errorf(`cannot *set!* undefined symbol %v`, s) + } - v, err := eval(args[1], env) - if err != nil { - return nil, err - } - env.set(s, v) + v, err := eval(args[1], env) + if err != nil { + return nil, err + } + env.set(s, v) - return nil, nil + return nil, nil + }, } type lambda struct { @@ -190,32 +220,33 @@ func (l lambda) call(env *environment, rawArgs []interface{}) (interface{}, erro // (lambda (x) (* x x)) // // would evaluate to a lambda that, when executed, squares its input. -func mklambda(env *environment, args ...interface{}) (interface{}, error) { - debugPrint("mklambda") - if err := checkArity(2, args, "lambda"); err != nil { - return nil, err - } +var mklambda = special{ + name: "lambda", + arity: 2, + fn: func(env *environment, args []interface{}) (interface{}, error) { + debugPrint("mklambda") - params, ok := args[0].(*sexp) - if !ok { - return nil, fmt.Errorf(`first argument to *lambda* must be sexp, received %v`, reflect.TypeOf(args[0])) - } - - arglabels := make([]symbol, 0, len(params.items)) - for _, v := range params.items { - s, ok := v.(symbol) + params, ok := args[0].(*sexp) if !ok { - return nil, fmt.Errorf(`lambda args must all be symbols; received invalid %v`, reflect.TypeOf(v)) + return nil, fmt.Errorf(`first argument to *lambda* must be sexp, received %v`, reflect.TypeOf(args[0])) } - arglabels = append(arglabels, s) - } - body, ok := args[1].(*sexp) - if !ok { - return nil, fmt.Errorf(`second argument to *lambda* must be sexp, received %v`, reflect.TypeOf(args[1])) - } + arglabels := make([]symbol, 0, len(params.items)) + for _, v := range params.items { + s, ok := v.(symbol) + if !ok { + return nil, fmt.Errorf(`lambda args must all be symbols; received invalid %v`, reflect.TypeOf(v)) + } + arglabels = append(arglabels, s) + } - return lambda{env, arglabels, body}, nil + body, ok := args[1].(*sexp) + if !ok { + return nil, fmt.Errorf(`second argument to *lambda* must be sexp, received %v`, reflect.TypeOf(args[1])) + } + + return lambda{env, arglabels, body}, nil + }, } // defines the built-in "begin" construct. A "begin" statement evaluates each @@ -225,17 +256,19 @@ func mklambda(env *environment, args ...interface{}) (interface{}, error) { // (begin (+ 1 1) (* 2 2) (+ 3 3)) // // would evaluate to 6. -func begin(env *environment, args ...interface{}) (interface{}, error) { - debugPrint("begin") - - var err error - var v interface{} - for _, arg := range args { - v, err = eval(arg, env) - if err != nil { - return nil, err +var begin = special{ + name: "begin", + variadic: true, + fn: func(env *environment, args []interface{}) (interface{}, error) { + debugPrint("begin") + var err error + var v interface{} + for _, arg := range args { + v, err = eval(arg, env) + if err != nil { + return nil, err + } } - } - - return v, nil + return v, nil + }, }