From 0520386674c7d2aaa87f032e52bbb4e3dafb72bd Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 21 Oct 2014 00:06:49 -0400 Subject: [PATCH] wrote a shitty eval --- eval/eval.go | 77 +++++++++++++++++++++++++++++++++++++++++ eval/eval_test.go | 50 ++++++++++++++++++++++++++ macros/pkgctx/pkgctx.go | 4 +-- 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 eval/eval.go create mode 100644 eval/eval_test.go diff --git a/eval/eval.go b/eval/eval.go new file mode 100644 index 0000000..95459a6 --- /dev/null +++ b/eval/eval.go @@ -0,0 +1,77 @@ +// The eval package encompasses all that is necesary to do runtime evaluation of +// ginger structures. These are different than macros in that they aren't turned +// into go code, instead the compiled go code evaluates them at runtime +package eval + +import ( + "fmt" + "os" + "time" + + "github.com/mediocregopher/ginger/macros/pkgctx" + "github.com/mediocregopher/ginger/seq" + "github.com/mediocregopher/ginger/types" +) + +// Evaler is a function which can be used inside of eval. It must take in its +// arguments as a sequence of Elems, and return a resulting Elem +type Evaler func(seq.Seq) types.Elem + +// Bail stops compilation. The given element should be the reason compilation +// has stopped +func Bail(el types.Elem, reason string) { + fmt.Fprintln(os.Stderr, reason) + time.Sleep(100 * time.Second) + os.Exit(1) +} + +// Bailf is like Bail, but takes in formatting +func Bailf(el types.Elem, format string, args ...interface{}) { + reason := fmt.Sprintf(format, args...) + Bail(el, reason) +} + +var colon = types.GoType{":"} + +// Eval takes in the pkgctx it is being executed in, as well as a single Elem to +// be evaluated, and returns the Elem it evaluates to +func Eval(p *pkgctx.PkgCtx, el types.Elem) types.Elem { + l, ok := el.(*seq.List) + if !ok { + return el + } + + first, rest, ok := l.FirstRest() + if !ok || !first.Equal(colon) { + return el + } + + fnEl, args, ok := rest.FirstRest() + if !ok { + Bail(el, "Empty list after colon, no function given") + } + + + var fnName string + if gt, ok := fnEl.(types.GoType); ok { + fnName, _ = gt.V.(string) + } + + if fnName == "" || fnName[0] != ':' { + Bail(el, "Must give a function reference to execute") + } + fnName = fnName[1:] + + fn, ok := p.CallMap[fnName] + if !ok { + Bailf(el, "Unknown function name %q", fnName) + } + + evalArgFn := func(el types.Elem) types.Elem { + return Eval(p, el) + } + + evaldArgs := seq.Map(evalArgFn, args) + + return fn.(Evaler)(evaldArgs) +} diff --git a/eval/eval_test.go b/eval/eval_test.go new file mode 100644 index 0000000..7888689 --- /dev/null +++ b/eval/eval_test.go @@ -0,0 +1,50 @@ +package eval + +import ( + . "testing" + + "github.com/mediocregopher/ginger/macros/pkgctx" + "github.com/mediocregopher/ginger/parse" + "github.com/mediocregopher/ginger/seq" + "github.com/mediocregopher/ginger/types" +) + +// This is NOT how I want eval to really work in the end, but I wanted to get +// something down before I kept thinking about it, so I would know what would +// work + +func shittyPlus(s seq.Seq) types.Elem { + fn := func(acc, el types.Elem) (types.Elem, bool) { + i := acc.(types.GoType).V.(int) + el.(types.GoType).V.(int) + return types.GoType{i}, false + } + + return seq.Reduce(fn, types.GoType{0}, s) +} + +func TestShittyPlus(t *T) { + p := &pkgctx.PkgCtx{ + CallMap: map[string]interface{}{ + "shittyPlus": Evaler(shittyPlus), + }, + } + + m := map[string]types.Elem{ + "(: shittyPlus)": types.GoType{0}, + "(: shittyPlus 1 2 3)": types.GoType{6}, + `(: shittyPlus 1 2 3 + (: shittyPlus 1 2 3))`: types.GoType{12}, + } + + for input, output := range m { + parsed, err := parse.ParseString(input) + if err != nil { + t.Fatal(err) + } + + evald := Eval(p, parsed) + if !evald.Equal(output) { + t.Fatalf("input: %q %#v != %#v", input, output, evald) + } + } +} diff --git a/macros/pkgctx/pkgctx.go b/macros/pkgctx/pkgctx.go index 5f1bcde..65c828e 100644 --- a/macros/pkgctx/pkgctx.go +++ b/macros/pkgctx/pkgctx.go @@ -9,10 +9,10 @@ type PkgCtx struct { // string for no alias) Packages map[string]string - // CallDict is a map used by Eval for making actual calls dynamically. The + // CallMap is a map used by Eval for making actual calls dynamically. The // key is the string representation of the call to be used (for example, // "fmt.Println") and must agree with the aliases being used in Packages. // The value need not be set during actual compilation, but it is useful to // use it during testing - CallDict map[string]interface{} + CallMap map[string]interface{} }