Commits

Greg Ward  committed 381c534

types: change so String() returns quoted values; introduce ValueString()

At least for FuString, ValueString() replaces the old String(): it
returns a filename that can be opened, a command that can be run, etc.
String() returns a debugging-friendly value, using Fubsy syntax to
explain a value where possible. The rationale is that debug messages
(Printf() etc.) will use String() by default, and code that needs the
"true" value to interact with the system should know what it's doing
and use ValueString().

  • Participants
  • Parent commits 6d43599

Comments (0)

Files changed (13)

File src/fubsy/dag/findernode_test.go

 	// convert FuList of FileNode to slice of string
 	actualstr := make([]string, len(actualobj.List()))
 	for i, obj := range actualobj.List() {
-		actualstr[i] = obj.String()
+		actualstr[i] = obj.ValueString()
 	}
 	assert.Equal(t, expect, actualstr)
 }

File src/fubsy/dag/listnode.go

 	return self.nodebase.String()
 }
 
+func (self *ListNode) ValueString() string {
+	return self.nodebase.ValueString()
+}
+
 func (self *ListNode) CommandString() string {
 	return self.FuList.CommandString()
 }

File src/fubsy/dag/node.go

 	return self.name
 }
 
+func (self *nodebase) ValueString() string {
+	return self.String()
+}
+
 func (self *nodebase) CommandString() string {
 	return types.ShellQuote(self.name)
 }

File src/fubsy/runtime/action.go

 	if err != nil {
 		return []error{err}
 	}
-	log.Info("%s", self.expanded)
+	log.Info("%s", self.expanded.ValueString())
 
 	// Run commands with the shell because people expect redirection,
 	// pipes, etc. to work from their build scripts. (And besides, all
 	// XXX the error message doesn't say which command failed (and if
 	// it did, it would probably say "/bin/sh", which is useless): can
 	// we do better?
-	cmd := exec.Command("/bin/sh", "-c", self.expanded.String())
+	cmd := exec.Command("/bin/sh", "-c", self.expanded.ValueString())
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
 	err = cmd.Run()

File src/fubsy/runtime/buildrule_test.go

 	val, ok = ns.Lookup("SOURCES")
 	assert.True(t, ok)
 	assert.Equal(t, 2, len(val.List()))
-	assert.Equal(t, "[bar,qux]", val.String())
+	assert.Equal(t, "[bar, qux]", val.String())
 }

File src/fubsy/runtime/builtins.go

 		if i > 0 {
 			os.Stdout.WriteString(" ")
 		}
-		_, err := os.Stdout.WriteString(val.String())
+		_, err := os.Stdout.WriteString(val.ValueString())
 		if err != nil {
 			// this shouldn't happen, so bail immediately
 			return nil, []error{err}
 func fn_mkdir(args types.ArgSource) (types.FuObject, []error) {
 	errs := make([]error, 0)
 	for _, name := range args.Args() {
-		err := os.MkdirAll(name.String(), 0755)
+		err := os.MkdirAll(name.ValueString(), 0755)
 		if err != nil {
 			errs = append(errs, err)
 		}
 func fn_remove(args types.ArgSource) (types.FuObject, []error) {
 	errs := make([]error, 0)
 	for _, name := range args.Args() {
-		err := os.RemoveAll(name.String())
+		err := os.RemoveAll(name.ValueString())
 		if err != nil {
 			errs = append(errs, err)
 		}
 }
 
 func fn_FileNode(args types.ArgSource) (types.FuObject, []error) {
-	name := args.Arg(0).String()
+	name := args.Arg(0).ValueString()
 	graph := args.(FunctionArgs).Graph()
 	return dag.MakeFileNode(graph, name), nil
 }
 
 func fn_ActionNode(args types.ArgSource) (types.FuObject, []error) {
-	basename := args.Arg(0).String()
+	basename := args.Arg(0).ValueString()
 	graph := args.(FunctionArgs).Graph()
 	return dag.MakeActionNode(graph, basename+":action"), nil
 }

File src/fubsy/runtime/execute_test.go

 	// call foo() correctly (no args)
 	ast := dsl.NewASTFunctionCall(fooname, noargs)
 	result, errors = rt.evaluateCall(ast, nil)
-	assert.Equal(t, "foo!", result.String())
+	assert.Equal(t, types.FuString("foo!"), result)
 	assert.Equal(t, 0, len(errors))
 	assert.Equal(t, []string{"foo"}, calls)
 
 	result, errors = rt.evaluateCall(ast, nil)
 	assert.Nil(t, result)
 	assert.Equal(t, 1, len(errors))
-	assert.Equal(t, "bar failed (meep)", errors[0].Error())
+	assert.Equal(t, "bar failed (\"meep\")", errors[0].Error())
 	assert.Equal(t, []string{"foo", "bar"}, calls)
 
 	// call bar() with an arg that needs to be expanded
 	result, errors = rt.evaluateCall(ast, nil)
 	assert.Nil(t, result)
 	assert.Equal(t, 1, len(errors))
-	assert.Equal(t, "bar failed (>main.c<)", errors[0].Error())
+	assert.Equal(t, "bar failed (\">main.c<\")", errors[0].Error())
 	assert.Equal(t, []string{"foo", "bar", "bar"}, calls)
 
 	// again, but this time expansion fails (undefined name)
 	assert.Equal(t, precalledArgs, types.MakeFuList("hello"))
 	assert.Nil(t, result)
 	if len(errs) == 1 {
-		assert.Equal(t, "c failed: receiver: FileNode b.txt, arg: string hello", errs[0].Error())
+		assert.Equal(t, "c failed: receiver: FileNode b.txt, arg: string \"hello\"", errs[0].Error())
 	} else {
 		t.Errorf("expected exactly 1 error, but got: %v", errs)
 	}

File src/fubsy/runtime/runtime.go

 
 // Convert a single FuObject (possibly a FuList) to a list of Nodes and
 // add them to the DAG.
-func (self *Runtime) nodify(targets_ types.FuObject) []dag.Node {
+func (self *Runtime) nodify(values types.FuObject) []dag.Node {
 	// Blecchh: specially handling every type here limits the
 	// extensibility of the type system. But I don't want each type to
 	// know how it becomes a node, because then the 'types' package
 	// depends on 'dag', which seems backwards to me. Hmmmm.
 	var result []dag.Node
-	switch targets := targets_.(type) {
+	switch values := values.(type) {
 	case types.FuString:
-		result = []dag.Node{dag.MakeFileNode(self.dag, targets.String())}
+		result = []dag.Node{dag.MakeFileNode(self.dag, values.ValueString())}
 	case types.FuList:
-		result = make([]dag.Node, 0, len(targets))
-		for _, val := range targets {
+		result = make([]dag.Node, 0, len(values))
+		for _, val := range values {
 			result = append(result, self.nodify(val)...)
 		}
 	case *dag.ListNode:
-		result = targets.Nodes()
+		result = values.Nodes()
 		for i, node := range result {
 			result[i] = self.dag.AddNode(node)
 		}
 	case dag.Node:
-		result = []dag.Node{self.dag.AddNode(targets)}
+		result = []dag.Node{self.dag.AddNode(values)}
 	}
 	return result
 }

File src/fubsy/runtime/runtime_test.go

 
 	// this seems *awfully* detailed and brittle, but DAG doesn't
 	// provide a good way to query what's in it (yet...)
-	expect := "" +
-		"0000: FileNode foo (state UNKNOWN)\n" +
-		"  action: cc -o $TARGET $src\n" +
-		"  parents:\n" +
-		"    0001: foo.c\n" +
-		"0001: FileNode foo.c (state UNKNOWN)\n"
+	expect := `0000: FileNode foo (state UNKNOWN)
+  action: "cc -o $TARGET $src"
+  parents:
+    0001: foo.c
+0001: FileNode foo.c (state UNKNOWN)
+`
 	var buf bytes.Buffer
 	rt.dag.Dump(&buf, "")
-	assert.Equal(t, expect, buf.String())
+	actual := buf.String()
+	if expect != actual {
+		t.Errorf("dag.Dump(): expected\n%v\nbut got\n%v", expect, actual)
+	}
 }
 
 func Test_Runtime_runMainPhase_error(t *testing.T) {

File src/fubsy/types/basictypes.go

 )
 
 type FuObject interface {
+	// Return a string representation of this object for
+	// debugging/diagnosis. When feasible, it should return the Fubsy
+	// syntax that would reproduce this value, i.e. with
+	// quotes/delimiters/escaping that would be accepted by the
+	// fubsy/dsl package.
 	String() string
 
+	// Return a string representation of this object to use when
+	// directly interacting with the OS: e.g. a filename for open() or
+	// a command for system().
+	ValueString() string
+
 	// Return a string representation of this object that is suitable
 	// for use in a shell command. Scalar values should supply quotes
 	// so the shell will see them as a single word -- e.g. values with
 type FuList []FuObject
 
 func (self FuString) String() string {
+	// need to worry about escaping when the DSL supports it!
+	return "\"" + string(self) + "\""
+}
+
+func (self FuString) ValueString() string {
 	return string(self)
 }
 
 	for i, obj := range self {
 		result[i] = obj.String()
 	}
-	return "[" + strings.Join(result, ",") + "]"
+	return "[" + strings.Join(result, ", ") + "]"
+}
+
+func (self FuList) ValueString() string {
+	// ValueString() doesn't make a lot of sense for FuList, since it
+	// doesn't contain a single filename to open or command to run ...
+	// but we have to provide *something*!
+	result := make([]string, len(self))
+	for i, obj := range self {
+		result[i] = obj.ValueString()
+	}
+	return strings.Join(result, " ")
 }
 
 func (self FuList) CommandString() string {

File src/fubsy/types/basictypes_test.go

 	"github.com/stretchrcom/testify/assert"
 )
 
-func Test_FuString_String(t *testing.T) {
-	s := FuString("bip bop")
-	assert.Equal(t, "bip bop", s.String())
-}
+func Test_FuString_stringify(t *testing.T) {
+	var s FuObject
 
-func Test_FuString_CommandString(t *testing.T) {
-	s := FuString("don't start")
+	s = FuString("hello")
+	assert.Equal(t, "\"hello\"", s.String())
+	assert.Equal(t, "hello", s.ValueString())
+	assert.Equal(t, "hello", s.CommandString())
+
+	s = FuString("bip bop")
+	assert.Equal(t, "\"bip bop\"", s.String())
+	assert.Equal(t, "bip bop", s.ValueString())
+	assert.Equal(t, "'bip bop'", s.CommandString())
+
+	s = FuString("don't start")
+	assert.Equal(t, "\"don't start\"", s.String())
+	assert.Equal(t, "don't start", s.ValueString())
 	assert.Equal(t, "\"don't start\"", s.CommandString())
 }
 
 	args := MakeFuList("-l", "-a", "foo")
 	result, err := cmd.Add(args)
 	assert.Nil(t, err)
-	assert.Equal(t, "[ls,-l,-a,foo]", result.String())
+	assert.Equal(t, `["ls", "-l", "-a", "foo"]`, result.String())
 }
 
 func Test_FuString_Lookup(t *testing.T) {
 	input = FuString("meep $foo blah")
 	output, err = input.ActionExpand(ns, nil)
 	assert.Nil(t, err)
-	assert.Equal(t, "meep hello blah", output.String())
+	assert.Equal(t, "meep hello blah", output.ValueString())
 
 	input = FuString("hello ${foo} $meep")
 	output, err = input.ActionExpand(ns, nil)
 	assert.Nil(t, err)
-	assert.Equal(t, "hello hello blorf", output.String())
+	assert.Equal(t, "hello hello blorf", output.ValueString())
 
 	ns.Assign("foo", nil)
 	output, err = input.ActionExpand(ns, nil)
 	assert.Nil(t, err)
-	assert.Equal(t, "hello  blorf", output.String())
+	assert.Equal(t, "hello  blorf", output.ValueString())
 
 	ns.Assign("foo", FuString("ping$pong"))
 	output, err = input.ActionExpand(ns, nil)
 	input := FuString("$CC -c $sources")
 	output, err := input.ActionExpand(ns, nil)
 	assert.Nil(t, err)
-	assert.Equal(t, expect, output.String())
+	assert.Equal(t, expect, output.ValueString())
 
 	// same thing, but now files is a list
 	ns.Assign("files", FuList([]FuObject{FuString("f1.c")}))
 	output, err = input.ActionExpand(ns, nil)
 	assert.Nil(t, err)
-	assert.Equal(t, expect, output.String())
+	assert.Equal(t, expect, output.ValueString())
 }
 
 func Test_FuString_ActionExpand_cycle(t *testing.T) {
 	assert.Equal(t, "cyclic variable reference: a -> a", err.Error())
 }
 
-func Test_FuList_String(t *testing.T) {
-	l := MakeFuList("beep", "meep")
-	assert.Equal(t, "[beep,meep]", l.String())
+func Test_FuList_stringify(t *testing.T) {
+	var l FuObject
+
+	l = MakeFuList("beep", "meep")
+	assert.Equal(t, `["beep", "meep"]`, l.String())
+	assert.Equal(t, `beep meep`, l.ValueString())
+	assert.Equal(t, `beep meep`, l.CommandString())
 
 	l = MakeFuList("beep", "", "meep")
-	assert.Equal(t, "[beep,,meep]", l.String())
-}
+	assert.Equal(t, `["beep", "", "meep"]`, l.String())
+	assert.Equal(t, `beep  meep`, l.ValueString())
+	assert.Equal(t, `beep '' meep`, l.CommandString())
 
-func Test_FuList_CommandString(t *testing.T) {
-	l := MakeFuList("foo", "*.c", "ding dong", "")
-	assert.Equal(t, "foo '*.c' 'ding dong' ''", l.CommandString())
+	l = MakeFuList("foo", "*.c", "ding dong", "")
+	assert.Equal(t, `["foo", "*.c", "ding dong", ""]`, l.String())
+	assert.Equal(t, `foo *.c ding dong `, l.ValueString())
+	assert.Equal(t, `foo '*.c' 'ding dong' ''`, l.CommandString())
 }
 
 func Test_FuList_Add_list(t *testing.T) {

File src/fubsy/types/callables.go

 	return self.name + "()"
 }
 
+func (self *FuFunction) ValueString() string {
+	return self.String()
+}
+
 func (self *FuFunction) CommandString() string {
 	// hmmm: perhaps CommandString needs an error return...
 	panic("functions should not be interpolated into command strings!")

File src/fubsy/types/namespace_test.go

 	ns.Assign("foo", FuString("blurp"))
 	val, ok = ns.Lookup("foo")
 	assert.True(t, ok)
-	assert.Equal(t, "blurp", val.String())
+	assert.Equal(t, "blurp", val.ValueString())
 	val, ok = ns.Lookup("bar")
 	assert.False(t, ok)
 }