Commits

Greg Ward committed 70ffef5

runtime: add BuiltinList, a new Namespace implementation for builtins

It preserves order, which will be useful for calling builtins from C
(e.g. embedded Python, Lua) -- we'll just look them up in a table,
which is easy in C. It also disallows assignment by panic()ing.

Comments (0)

Files changed (4)

 exclude="fubsy/testutils,\
 github.com/stretchrcom/testify/assert,\
 code.google.com/p/go-bit/bit,\
-github.com/ogier/pflag"
+github.com/ogier/pflag,\
+github.com/sbinet/go-python"
 
 echo "testing packages: $packages"
 build1=".build/1"

src/fubsy/runtime/builtins.go

 import (
 	"errors"
 	"fmt"
+	"io"
 	"os"
 
 	"fubsy/dag"
 
 // builtin functions (and other objects?)
 
-// Add all builtin functions to ns.
-func defineBuiltins(ns types.Namespace) {
-	functions := []*types.FuFunction{
+// implements types.Namespace, plugins.BuiltinList
+type BuiltinList struct {
+	builtins []types.FuCallable
+}
+
+// Namespace methods
+func (self BuiltinList) Lookup(name string) (types.FuObject, bool) {
+	idx, callable := self.find(name)
+	if idx == -1 {
+		return nil, false
+	}
+	return callable, true
+}
+
+func (self BuiltinList) Assign(name string, value types.FuObject) {
+	panic("list of builtin functions is immutable")
+}
+
+func (self BuiltinList) ForEach(
+	callback func(name string, value types.FuObject) error) error {
+	for _, b := range self.builtins {
+		err := callback(b.Name(), b)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (self BuiltinList) Dump(writer io.Writer, indent string) {
+	for _, b := range self.builtins {
+		fmt.Fprintf(writer, "%s%s = %s\n", indent, b.Name(), b)
+	}
+}
+
+// private methods
+func (self BuiltinList) find(name string) (int, types.FuCallable) {
+	for i, b := range self.builtins {
+		if b.Name() == name {
+			return i, b
+		}
+	}
+	return -1, nil
+}
+
+// methods of plugins.BuiltinList
+func (self BuiltinList) NumBuiltins() int {
+	return len(self.builtins)
+}
+
+func (self BuiltinList) Builtin(idx int) (string, types.FuCode) {
+	// panic if idx out of range
+	b := self.builtins[idx]
+	return b.Name(), b.Code()
+}
+
+// Return a new namespace containing all builtin functions.
+func defineBuiltins() BuiltinList {
+	builtins := []types.FuCallable{
 		types.NewVariadicFunction("println", 0, -1, fn_println),
 		types.NewVariadicFunction("mkdir", 0, -1, fn_mkdir),
 		types.NewVariadicFunction("remove", 0, -1, fn_remove),
 		types.NewFixedFunction("FileNode", 1, fn_FileNode),
 		types.NewFixedFunction("ActionNode", 1, fn_ActionNode),
 	}
-
-	for _, function := range functions {
-		ns.Assign(function.Name(), function)
-	}
+	return BuiltinList{builtins}
 }
 
 func fn_println(argsource types.ArgSource) (types.FuObject, []error) {

src/fubsy/runtime/builtins_test.go

 package runtime
 
 import (
+	"errors"
 	//"fmt"
 	"io/ioutil"
 	"os"
 	"fubsy/types"
 )
 
+func Test_BuiltinList(t *testing.T) {
+	blist := BuiltinList{}
+	fn, ok := blist.Lookup("foo")
+	assert.False(t, ok)
+	assert.Nil(t, fn)
+
+	callable := types.NewFixedFunction("foo", 3, nil)
+	blist.builtins = append(blist.builtins, callable)
+	fn, ok = blist.Lookup("foo")
+	assert.True(t, ok)
+	assert.Equal(t, callable, fn)
+
+	blist.builtins = append(
+		blist.builtins, types.NewFixedFunction("bar", 0, nil))
+	blist.builtins = append(
+		blist.builtins, types.NewFixedFunction("bop", 0, nil))
+	blist.builtins = append(
+		blist.builtins, types.NewFixedFunction("bam", 0, nil))
+	blist.builtins = append(
+		blist.builtins, types.NewFixedFunction("pow", 0, nil))
+
+	assert.Equal(t, 5, blist.NumBuiltins())
+	actual := make([]string, 0, 5)
+	visit := func(name string, code types.FuObject) error {
+		actual = append(actual, name)
+		if name == "bam" {
+			return errors.New("bam!")
+		}
+		return nil
+	}
+	err := blist.ForEach(visit)
+	assert.Equal(t, []string{"foo", "bar", "bop", "bam"}, actual)
+	assert.Equal(t, "bam!", err.Error())
+}
+
 func Test_defineBuiltins(t *testing.T) {
-	ns := types.NewValueMap()
-	defineBuiltins(ns)
+	ns := defineBuiltins()
 
 	fn, ok := ns.Lookup("println")
 	assert.True(t, ok)
 	assert.True(t, ok)
 	assert.NotNil(t, fn)
 	assert.Equal(t, fn.(types.FuCallable).Code(), types.FuCode(fn_remove))
+
+	// there will never be a builtin with this name: guaranteed!
+	fn, ok = ns.Lookup("sad425.-afgasdf")
+	assert.False(t, ok)
+	assert.Nil(t, fn)
 }
 
 func Test_println(t *testing.T) {

src/fubsy/runtime/runtime.go

 	script  string // filename
 	ast     *dsl.ASTRoot
 
-	builtins types.ValueMap
+	builtins BuiltinList
 	stack    *types.ValueStack
 	dag      *dag.DAG
 }
 	options build.BuildOptions, script string, ast *dsl.ASTRoot) *Runtime {
 	stack := types.NewValueStack()
 
-	builtins := types.NewValueMap()
-	defineBuiltins(builtins)
+	builtins := defineBuiltins()
 	stack.Push(builtins)
 
 	// Local variables are per-script, but we only support a single