veyron/tools/naming/simulator: simple interprerter for building
different veyron service configurations.

Change-Id: I6178008cf125ff7224978d444e96b30e6b1b6894
diff --git a/lib/testutil/modules/ls.go b/lib/testutil/modules/ls.go
index f94d277..daac425 100644
--- a/lib/testutil/modules/ls.go
+++ b/lib/testutil/modules/ls.go
@@ -91,7 +91,6 @@
 	for e := range ch {
 		reply = append(reply, fmt.Sprintf("%q", e.Name))
 	}
-	reply = append(reply, fmt.Sprintf("%d: items", len(reply)))
 	return reply, nil
 }
 
diff --git a/lib/testutil/modules/servers.go b/lib/testutil/modules/servers.go
index f21e508..9d93685 100644
--- a/lib/testutil/modules/servers.go
+++ b/lib/testutil/modules/servers.go
@@ -3,6 +3,7 @@
 import (
 	"fmt"
 	"os"
+	"strings"
 	"time"
 
 	"veyron/lib/testutil/blackbox"
@@ -122,7 +123,7 @@
 		return nil, nil, nil, err
 	}
 	v := make(Variables)
-	v.Update("TIME", r)
+	v.Update("TIME", strings.TrimRight(r, "\n"))
 	return v, []string{r}, nil, nil
 
 }
@@ -169,7 +170,7 @@
 		return nil, nil, nil, err
 	}
 	v := make(Variables)
-	v.Update("ECHO", r)
+	v.Update("ECHO", strings.TrimRight(r, "\n"))
 	return v, []string{r}, nil, nil
 }
 
diff --git a/tools/naming/simulator/commands.go b/tools/naming/simulator/commands.go
new file mode 100644
index 0000000..937990c
--- /dev/null
+++ b/tools/naming/simulator/commands.go
@@ -0,0 +1,143 @@
+package main
+
+import (
+	"fmt"
+	"time"
+
+	"veyron/lib/testutil/modules"
+
+	"veyron2/vlog"
+)
+
+type tag int
+
+const (
+	helpTag tag = iota
+	getTag
+	setTag
+	printTag
+	sleepTag
+)
+
+type builtin struct{ tag }
+
+func helpF() modules.T {
+	return &builtin{helpTag}
+}
+
+func sleepF() modules.T {
+	return &builtin{sleepTag}
+}
+
+func getF() modules.T {
+	return &builtin{getTag}
+}
+
+func setF() modules.T {
+	return &builtin{setTag}
+}
+
+func printF() modules.T {
+	return &builtin{printTag}
+}
+
+func (b *builtin) Help() string {
+	switch b.tag {
+	case helpTag:
+		return "[command]"
+	case sleepTag:
+		return `[duration]
+	sleep for a time (in go time.Duration format): defaults to 1s`
+	case getTag:
+		return "[<global variable name>]*"
+	case setTag:
+		return "[<var>=<val>]+"
+	case printTag:
+		return "[$<var>]*"
+	default:
+		return fmt.Sprintf("unrecognised tag for builtin: %d", b.tag)
+	}
+}
+
+func (*builtin) Daemon() bool { return false }
+
+func (b *builtin) Run(args []string) (modules.Variables, []string, modules.Handle, error) {
+	switch b.tag {
+	case helpTag:
+		return helpCmd(args)
+	case sleepTag:
+		return sleep(args)
+	case getTag:
+		return get(args)
+	case setTag:
+		return set(args)
+	case printTag:
+		return print(args)
+	default:
+		return nil, nil, nil, fmt.Errorf("unrecognised tag for builtin: %d",
+			b.tag)
+	}
+}
+
+func helpCmd([]string) (modules.Variables, []string, modules.Handle, error) {
+	for k, v := range commands {
+		if k == "help" {
+			continue
+		}
+		h := v().Help()
+		if len(h) > 0 {
+			fmt.Printf("%s %s\n\n", k, h)
+		} else {
+			fmt.Println(k)
+		}
+	}
+	return nil, nil, nil, nil
+}
+
+func sleep(args []string) (modules.Variables, []string, modules.Handle, error) {
+	if len(args) == 0 {
+		vlog.Infof("Sleeping for %s", time.Second)
+		time.Sleep(time.Second)
+		return nil, nil, nil, nil
+	}
+	if d, err := time.ParseDuration(args[0]); err != nil {
+		return nil, nil, nil, err
+	} else {
+		vlog.Infof("Sleeping for %s", d)
+		time.Sleep(d)
+	}
+	return nil, nil, nil, nil
+}
+
+func get(args []string) (modules.Variables, []string, modules.Handle, error) {
+	var r []string
+	if len(args) == 0 {
+		for k, v := range globals {
+			r = append(r, fmt.Sprintf("\t%q=%q\n", k, v))
+		}
+	} else {
+		for _, a := range args {
+			if v, present := globals[a]; present {
+				r = append(r, fmt.Sprintf("\t%q=%q\n", a, v))
+			} else {
+				return nil, nil, nil, fmt.Errorf("unknown variable %q", a)
+			}
+		}
+	}
+	return nil, r, nil, nil
+}
+
+func set(args []string) (modules.Variables, []string, modules.Handle, error) {
+	for _, a := range args {
+		globals.UpdateFromString(a)
+	}
+	return nil, nil, nil, nil
+}
+
+func print(args []string) (modules.Variables, []string, modules.Handle, error) {
+	var r []string
+	for _, a := range args {
+		r = append(r, a)
+	}
+	return nil, r, nil, nil
+}
diff --git a/tools/naming/simulator/driver.go b/tools/naming/simulator/driver.go
new file mode 100644
index 0000000..4a48737
--- /dev/null
+++ b/tools/naming/simulator/driver.go
@@ -0,0 +1,242 @@
+// This app provides a simple scripted environment for running common veyron
+// services as subprocesses and testing interactions between them. It is
+// structured as an interpreter, with global variables and variable
+// expansion, but no control flow. The command set that it supports is
+// extendable by adding new 'modules' that implement the API defined
+// by veyron/lib/testutil/modules.
+package main
+
+import (
+	"bufio"
+	"flag"
+	"fmt"
+	"os"
+	"strings"
+	"unicode"
+
+	"veyron/lib/testutil/modules"
+
+	"veyron2/rt"
+)
+
+type commandFunc func() modules.T
+
+var (
+	commands    map[string]commandFunc
+	globals     modules.Variables
+	debug       bool
+	interactive bool
+)
+
+func init() {
+	flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode")
+	flag.BoolVar(&debug, "debug", false, "set debug mode")
+
+	commands = make(map[string]commandFunc)
+
+	// We maintaing a single, global, dictionary for variables.
+	globals = make(modules.Variables)
+
+	// 'bultins'
+	commands["help"] = helpF
+	commands["get"] = getF
+	commands["set"] = setF
+	commands["print"] = printF
+	commands["sleep"] = sleepF
+
+	// TODO(cnicolaou): add 'STOP' command to shutdown a running server,
+	// need to return the handle and then call Stop on it.
+
+	// modules
+	commands["rootMT"] = modules.NewRootMT
+	commands["nodeMT"] = modules.NewNodeMT
+	commands["setLocalRoots"] = modules.NewSetRoot
+	commands["ls"] = modules.NewGlob
+	commands["lsat"] = modules.NewGlobAt
+	commands["lsmt"] = modules.NewGlobAtMT
+	commands["resolve"] = modules.NewResolve
+	commands["resolveMT"] = modules.NewResolveMT
+	commands["echoServer"] = modules.NewEchoServer
+	commands["echo"] = modules.NewEchoClient
+	commands["clockServer"] = modules.NewClockServer
+	commands["time"] = modules.NewClockClient
+}
+
+func prompt(lineno int) {
+	if interactive {
+		fmt.Printf("%d> ", lineno)
+	}
+}
+
+func main() {
+	modules.InModule()
+	rt.Init()
+
+	scanner := bufio.NewScanner(os.Stdin)
+	lineno := 1
+	prompt(lineno)
+	for scanner.Scan() {
+		line := scanner.Text()
+		if !strings.HasPrefix(line, "#") && len(line) > 0 {
+			if err := process(line, lineno); err != nil {
+				if debug {
+					fmt.Printf("%d> %s: %v\n", lineno, line, err)
+				} else {
+					fmt.Printf("%d> %v\n", lineno, err)
+				}
+			}
+		}
+		lineno++
+		prompt(lineno)
+	}
+	if err := scanner.Err(); err != nil {
+		fmt.Printf("error reading input: %v\n", err)
+	}
+
+	modules.Cleanup()
+}
+
+func process(line string, lineno int) error {
+	fields, err := splitQuotedFields(line)
+	if err != nil {
+		return err
+	}
+	if len(fields) == 0 {
+		return fmt.Errorf("no input")
+	}
+	name := fields[0]
+
+	var args []string
+	if len(fields) > 1 {
+		args = fields[1:]
+	} else {
+		args = []string{}
+	}
+
+	sub, err := subVariables(args, globals)
+	if err != nil {
+		return err
+	}
+
+	factory := commands[name]
+	if factory == nil {
+		return fmt.Errorf("unrecognised command %q", name)
+	}
+
+	if vars, output, _, err := factory().Run(sub); err != nil {
+		return err
+	} else {
+		if debug || interactive {
+			if !interactive {
+				fmt.Printf("%d> %s\n", lineno, line)
+			}
+			if len(output) > 0 {
+				fmt.Printf("%s\n", strings.Join(output, " "))
+			}
+		}
+		if debug && len(vars) > 0 {
+			for k, v := range vars {
+				fmt.Printf("\t%s=%q .... \n", k, v)
+			}
+			fmt.Println()
+		}
+		globals.UpdateFromVariables(vars)
+	}
+	return nil
+}
+
+// splitQuotedFields a line into fields, allowing for quoted strings.
+func splitQuotedFields(line string) ([]string, error) {
+	fields := []string{}
+	inquote := false
+	var field []rune
+	for _, c := range line {
+		switch {
+		case c == '"':
+			if inquote {
+				fields = append(fields, string(field))
+				field = nil
+				inquote = false
+			} else {
+				inquote = true
+			}
+		case unicode.IsSpace(c):
+			if inquote {
+				field = append(field, c)
+			} else {
+				if len(field) > 0 {
+					fields = append(fields, string(field))
+				}
+				field = nil
+			}
+		default:
+			field = append(field, c)
+		}
+	}
+	if inquote {
+		return nil, fmt.Errorf("unterminated quoted input")
+	}
+
+	if len(field) > 0 {
+		fields = append(fields, string(field))
+	}
+	return fields, nil
+}
+
+// subVariables substitutes variables that occur in the string slice
+// args with values from vars.
+func subVariables(args []string, vars modules.Variables) ([]string, error) {
+	var results []string
+	for _, a := range args {
+		if r, err := subVariablesInArgument(a, vars); err != nil {
+			return results, err
+		} else {
+			results = append(results, r)
+		}
+	}
+	return results, nil
+}
+
+// subVariablesInArgument substitutes variables that occur in the string
+// parameter with values from vars.
+//
+// A variable, is introduced by $, terminated by \t, space, / , : or !.
+// Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding
+// within strings.
+func subVariablesInArgument(a string, vars modules.Variables) (string, error) {
+	first := strings.Index(a, "$")
+	if first < 0 {
+		return a, nil
+	}
+	parts := strings.Split(a, "$")
+	result := parts[0]
+	vn := ""
+	rem := 0
+	for _, p := range parts[1:] {
+		start := 0
+		end := -1
+		if strings.HasPrefix(p, "{") {
+			start = 1
+			end = strings.Index(p, "}")
+			if end < 0 {
+				return "", fmt.Errorf("unterminated variable: %q", p)
+			}
+			rem = end + 1
+		} else {
+			end = strings.IndexAny(p, "\t/,:! ")
+			if end < 0 {
+				end = len(p)
+			}
+			rem = end
+		}
+		vn = p[start:end]
+		r := p[rem:]
+		v, present := vars[vn]
+		if !present {
+			return "", fmt.Errorf("unknown variable: %q", vn)
+		}
+		result += v
+		result += r
+	}
+	return result, nil
+}
diff --git a/tools/naming/simulator/driver_test.go b/tools/naming/simulator/driver_test.go
new file mode 100644
index 0000000..ed6f168
--- /dev/null
+++ b/tools/naming/simulator/driver_test.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+func TestFields(t *testing.T) {
+	cases := []struct {
+		input  string
+		output []string
+	}{
+		{"", []string{}},
+		{"a", []string{"a"}},
+		{"  z", []string{"z"}},
+		{"  zz  zz", []string{"zz", "zz"}},
+		{"ab", []string{"ab"}},
+		{"a b", []string{"a", "b"}},
+		{`a " b"`, []string{"a", " b"}},
+		{`a "  b  zz"`, []string{"a", "  b  zz"}},
+		{`a "  b		zz"`, []string{"a", "  b		zz"}},
+		{`a " b" cc`, []string{"a", " b", "cc"}},
+		{`a "z b" cc`, []string{"a", "z b", "cc"}},
+	}
+	for i, c := range cases {
+		got, err := splitQuotedFields(c.input)
+		if err != nil {
+			t.Errorf("%d: %q: unexpected error: %v", i, c.input, err)
+		}
+		if !reflect.DeepEqual(got, c.output) {
+			t.Errorf("%d: %q: got %#v, want %#v", i, c.input, got, c.output)
+		}
+	}
+	if _, err := splitQuotedFields(`a b "c`); err == nil {
+		t.Errorf("expected error for unterminated quote")
+	}
+}
+
+func TestVariables(t *testing.T) {
+	globals["foo"] = "bar"
+	cases := []struct {
+		input  string
+		output []string
+	}{
+		{"a b", []string{"a", "b"}},
+		{"a $foo", []string{"a", "bar"}},
+		{"$foo a", []string{"bar", "a"}},
+		{`a "$foo "`, []string{"a", "bar "}},
+		{"a xx$foo", []string{"a", "xxbar"}},
+		{"a xx${foo}yy", []string{"a", "xxbaryy"}},
+		{`a "foo"`, []string{"a", "foo"}},
+	}
+	for i, c := range cases {
+		fields, err := splitQuotedFields(c.input)
+		if err != nil {
+			t.Errorf("%d: %q: unexpected error: %v", i, c.input, err)
+		}
+		got, err := subVariables(fields, globals)
+		if err != nil {
+			t.Errorf("%d: %q: unexpected error: %v", i, c.input, err)
+		}
+		if !reflect.DeepEqual(got, c.output) {
+			t.Errorf("%d: %q: got %#v, want %#v", i, c.input, got, c.output)
+		}
+	}
+
+	errors := []struct {
+		input string
+		err   error
+	}{
+		{"$foox", fmt.Errorf("unknown variable: %q", "foox")},
+		{"${fo", fmt.Errorf("unterminated variable: %q", "{fo")},
+	}
+	for i, c := range errors {
+		_, got := subVariables([]string{c.input}, globals)
+		if got.Error() != c.err.Error() {
+			t.Errorf("%d: %q: expected error: got %q, want %q", i, c.input, got, c.err)
+		}
+	}
+}