aboutsummaryrefslogtreecommitdiff
path: root/interpreter/callable.go
diff options
context:
space:
mode:
authorJuan J. Martinez <jjm@usebox.net>2022-07-18 07:45:58 +0100
committerJuan J. Martinez <jjm@usebox.net>2022-07-18 07:45:58 +0100
commit8bb321f8b032dfaeffbe3d1b8dfeb215c12d3642 (patch)
treec53977d1284347bb1d5963ddb4dc7723c40c6e55 /interpreter/callable.go
downloadmicro-lang-8bb321f8b032dfaeffbe3d1b8dfeb215c12d3642.tar.gz
micro-lang-8bb321f8b032dfaeffbe3d1b8dfeb215c12d3642.zip
First public release
Diffstat (limited to 'interpreter/callable.go')
-rw-r--r--interpreter/callable.go227
1 files changed, 227 insertions, 0 deletions
diff --git a/interpreter/callable.go b/interpreter/callable.go
new file mode 100644
index 0000000..e9dc6aa
--- /dev/null
+++ b/interpreter/callable.go
@@ -0,0 +1,227 @@
+package interpreter
+
+import (
+ "fmt"
+
+ "usebox.net/lang/ast"
+ "usebox.net/lang/errors"
+ "usebox.net/lang/tokens"
+)
+
+type Callable interface {
+ Name() string
+ String() string
+ Call(interp *Interpreter, args []any, loc tokens.Location) (any, error)
+ Type() *ast.Type
+ Params() []ast.Type
+ Ret() *ast.Type
+}
+
+var builtInFuncs = map[string]Callable{
+ "len": builtInLen{},
+ "println": builtInPrintln{},
+ "panic": builtInPanic{},
+}
+
+func BuiltInTypes() map[string]ast.Type {
+ types := map[string]ast.Type{}
+ for k, v := range builtInFuncs {
+ types[k] = *v.Type()
+ }
+ return types
+}
+
+type builtInLen struct{}
+
+func (n builtInLen) Name() string {
+ return "'len'"
+}
+
+func (n builtInLen) Type() *ast.Type {
+ return ast.NewFuncType(tokens.Token{}, n.Params(), n.Ret())
+}
+
+func (n builtInLen) String() string {
+ return n.Type().String()
+}
+
+func (n builtInLen) Params() []ast.Type {
+ // won't be arity or type checked
+ return []ast.Type{ast.TypeArray}
+}
+
+func (n builtInLen) Ret() *ast.Type {
+ return &ast.TypeNumber
+}
+
+func (n builtInLen) Call(interp *Interpreter, args []any, loc tokens.Location) (any, error) {
+ vals, ok := args[0].([]any)
+ if !ok {
+ // shouldn't happen
+ return nil, errors.NewError(errors.Unexpected, loc, "type mismatch in call to 'len'")
+ }
+ // return
+ panic(&PanicJump{typ: PanicJumpReturn, value: ast.Number(len(vals))})
+}
+
+type builtInPrintln struct{}
+
+func (n builtInPrintln) Name() string {
+ return "'println'"
+}
+
+func (n builtInPrintln) Type() *ast.Type {
+ return ast.NewFuncType(tokens.Token{}, n.Params(), n.Ret())
+}
+
+func (n builtInPrintln) String() string {
+ return n.Type().String()
+}
+
+func (n builtInPrintln) Params() []ast.Type {
+ // won't be arity or type checked
+ return nil
+}
+
+func (n builtInPrintln) Ret() *ast.Type {
+ return &ast.TypeNumber
+}
+
+func (n builtInPrintln) Call(interp *Interpreter, args []any, loc tokens.Location) (any, error) {
+ var count int
+
+ for i := range args {
+ if args[i] == nil {
+ continue
+ }
+ written, err := fmt.Print(args[i])
+ if err != nil {
+ return nil, err
+ }
+ count += written
+ }
+ fmt.Println()
+ count++
+
+ // return
+ panic(&PanicJump{typ: PanicJumpReturn, value: ast.Number(count)})
+}
+
+type builtInPanic struct{}
+
+func (n builtInPanic) Name() string {
+ return "'panic'"
+}
+
+func (n builtInPanic) Type() *ast.Type {
+ return ast.NewFuncType(tokens.Token{}, n.Params(), n.Ret())
+}
+
+func (n builtInPanic) String() string {
+ return n.Type().String()
+}
+
+func (n builtInPanic) Params() []ast.Type {
+ return []ast.Type{ast.TypeString}
+}
+
+func (n builtInPanic) Ret() *ast.Type {
+ // no return (returns none)
+ return nil
+}
+
+func (n builtInPanic) Call(interp *Interpreter, args []any, loc tokens.Location) (any, error) {
+ return nil, fmt.Errorf("[%s] panic: %s", loc, args[0])
+}
+
+type Function struct {
+ fun ast.Func
+ closure *Env
+}
+
+func (f Function) Name() string {
+ return f.fun.Name.String()
+}
+
+func (f Function) Type() *ast.Type {
+ return ast.NewFuncType(f.fun.Name, f.Params(), f.Ret())
+}
+
+func (f Function) String() string {
+ return f.Type().String()
+}
+
+func (f Function) Params() []ast.Type {
+ params := make([]ast.Type, 0, 1)
+ for _, p := range f.fun.Params {
+ params = append(params, p.Type)
+ }
+ return params
+}
+
+func (f Function) Ret() *ast.Type {
+ return f.fun.Ret
+}
+
+func (f Function) Call(interp *Interpreter, args []any, loc tokens.Location) (result any, err error) {
+ pEnv := interp.env
+ interp.env = NewEnv(f.closure)
+ defer func() {
+ interp.env = pEnv
+ }()
+
+ for n, v := range f.fun.Params {
+ interp.env = interp.env.Set(v.Name.Value, Var{Value: args[n], Loc: v.Name.Loc})
+ }
+
+ // tail call optimization
+ for {
+ // wrap the evaluation in a function
+ var tailCall *PanicJump
+ tailCall, result, err = func() (tailCall *PanicJump, result any, err error) {
+ // handle tail call
+ // will call this function again without setting up a new call frame
+ defer func() {
+ if r := recover(); r != nil {
+ if val, ok := r.(*PanicJump); ok && val.typ == PanicJumpTailCall {
+ tailCall = val
+ return
+ }
+ panic(r)
+ }
+ }()
+ result, err = interp.evaluate(f.fun.Body)
+ return nil, result, err
+ }()
+ if tailCall == nil {
+ break
+ }
+
+ // XXX: can we optimize this?
+ // if the callee can be a variable expression, we probably can't
+ call := tailCall.value.(ast.Call)
+ callee, err := interp.evaluate(call.Callee)
+ if err != nil {
+ return nil, err
+ }
+ if fun, ok := callee.(Callable); !ok || fun.Name() != f.Name() {
+ // can't be optimized
+ val, err := interp.evaluate(call)
+ if err != nil {
+ return nil, err
+ }
+ panic(&PanicJump{typ: PanicJumpReturn, value: val, loc: tailCall.loc})
+ }
+
+ args, err := interp.evalArgs(f.Name(), call.Loc, f.Params(), call.Args)
+ if err != nil {
+ return nil, err
+ }
+
+ for n, v := range f.fun.Params {
+ interp.env.Update(v.Name.Value, args[n])
+ }
+ }
+
+ return result, err
+}