From b6613d0f19c79fdd5972fcd2ea7504b9309d784e Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Thu, 16 Feb 2017 04:32:14 +0000 Subject: [PATCH] Eliminate verdict/predicates. Right now ?(...) always evaluates to $true, it will be repurposed for exception catching. This fixes #319. --- eval/builtin-fn.go | 47 +++++++++++++++----------- eval/builtin-special.go | 2 +- eval/compile-op.go | 15 --------- eval/compile-value.go | 2 +- eval/eval.go | 20 +++-------- eval/eval_test.go | 75 ++++++++++++++++++----------------------- 6 files changed, 67 insertions(+), 94 deletions(-) diff --git a/eval/builtin-fn.go b/eval/builtin-fn.go index 735c7661..df458e59 100644 --- a/eval/builtin-fn.go +++ b/eval/builtin-fn.go @@ -55,8 +55,9 @@ func (b *BuiltinFn) Call(ec *EvalCtx, args []Value, opts map[string]Value) { func init() { // Needed to work around init loop. builtinFns = []*BuiltinFn{ - // Fundamental predicates - &BuiltinFn{"true", nop}, + // Trivial predicates + &BuiltinFn{"nop", nop}, + &BuiltinFn{"true", trueFn}, &BuiltinFn{"false", falseFn}, // Introspection @@ -387,12 +388,14 @@ func wrapStrCompare(cmp func(a, b string) bool) func(*EvalCtx, []Value, map[stri throw(ErrArgs) } } + result := true for i := 0; i < len(args)-1; i++ { if !cmp(string(args[i].(String)), string(args[i+1].(String))) { - ec.falsify() - return + result = false + break } } + ec.OutputChan() <- Bool(result) } } @@ -408,12 +411,14 @@ func wrapNumCompare(cmp func(a, b float64) bool) func(*EvalCtx, []Value, map[str maybeThrow(err) floats[i] = f } + result := true for i := 0; i < len(floats)-1; i++ { if !cmp(floats[i], floats[i+1]) { - ec.falsify() - return + result = false + break } } + ec.OutputChan() <- Bool(result) } } @@ -433,8 +438,12 @@ func mustGetOneString(args []Value) string { func nop(ec *EvalCtx, args []Value, opts map[string]Value) { } +func trueFn(ec *EvalCtx, args []Value, opts map[string]Value) { + ec.OutputChan() <- Bool(true) +} + func falseFn(ec *EvalCtx, args []Value, opts map[string]Value) { - ec.falsify() + ec.OutputChan() <- Bool(false) } func put(ec *EvalCtx, args []Value, opts map[string]Value) { @@ -563,15 +572,11 @@ func splits(ec *EvalCtx, sep, s String) { } func hasPrefix(ec *EvalCtx, s, prefix String) { - if !strings.HasPrefix(string(s), string(prefix)) { - ec.falsify() - } + ec.OutputChan() <- Bool(strings.HasPrefix(string(s), string(prefix))) } func hasSuffix(ec *EvalCtx, s, suffix String) { - if !strings.HasSuffix(string(s), string(suffix)) { - ec.falsify() - } + ec.OutputChan() <- Bool(strings.HasSuffix(string(s), string(suffix))) } // toJSON converts a stream of Value's to JSON data. @@ -967,12 +972,14 @@ func is(ec *EvalCtx, args []Value, opts map[string]Value) { if len(args) < 2 { throw(ErrArgs) } + result := true for i := 0; i+1 < len(args); i++ { if args[i] != args[i+1] { - ec.falsify() - return + result = false + break } } + ec.OutputChan() <- Bool(result) } func eq(ec *EvalCtx, args []Value, opts map[string]Value) { @@ -980,12 +987,14 @@ func eq(ec *EvalCtx, args []Value, opts map[string]Value) { if len(args) < 2 { throw(ErrArgs) } + result := true for i := 0; i+1 < len(args); i++ { if !DeepEq(args[i], args[i+1]) { - ec.falsify() - return + result = false + break } } + ec.OutputChan() <- Bool(result) } func resolveFn(ec *EvalCtx, cmd String) { @@ -995,9 +1004,7 @@ func resolveFn(ec *EvalCtx, cmd String) { func hasExternal(ec *EvalCtx, cmd string) { _, err := ec.Search(cmd) - if err != nil { - ec.falsify() - } + ec.OutputChan() <- Bool(err == nil) } func searchExternal(ec *EvalCtx, cmd string) { diff --git a/eval/builtin-special.go b/eval/builtin-special.go index af39c180..506d1ac5 100644 --- a/eval/builtin-special.go +++ b/eval/builtin-special.go @@ -201,7 +201,7 @@ func use(ec *EvalCtx, modname string, pfilename *string) { ec.Evaler, "module " + modname, filename, source, local, Namespace{}, - ec.ports, nil, true, + ec.ports, nil, 0, len(source), ec.addTraceback(), false, } diff --git a/eval/compile-op.go b/eval/compile-op.go index e183169e..812a83d6 100644 --- a/eval/compile-op.go +++ b/eval/compile-op.go @@ -54,7 +54,6 @@ func (cp *compiler) pipeline(n *parse.Pipeline) OpFunc { var wg sync.WaitGroup wg.Add(nforms) errors := make([]*Exception, nforms) - var verdict bool var nextIn *Port @@ -80,14 +79,10 @@ func (cp *compiler) pipeline(n *parse.Pipeline) OpFunc { } thisOp := op thisError := &errors[i] - isLast := i == nforms-1 go func() { err := newEc.PEval(thisOp) // Logger.Printf("closing ports of %s", newEc.context) ClosePorts(newEc.ports) - if isLast { - verdict = newEc.verdict - } if err != nil { *thisError = err.(*Exception) } @@ -104,9 +99,6 @@ func (cp *compiler) pipeline(n *parse.Pipeline) OpFunc { if err != nil { msg += ", errors = " + err.Error() } - if !verdict { - msg += ", pred = false" - } if ec.Editor != nil { m := ec.Editor.ActiveMutex() m.Lock() @@ -124,7 +116,6 @@ func (cp *compiler) pipeline(n *parse.Pipeline) OpFunc { } else { wg.Wait() maybeThrow(ComposeExceptionsFromPipeline(errors)) - ec.verdict = verdict } } } @@ -282,9 +273,6 @@ func (cp *compiler) form(n *parse.Form) OpFunc { redirOp.Exec(ec) } - // In case some arguments returned false. - ec.verdict = true - ec.begin, ec.end = begin, end if headFn != nil { @@ -313,8 +301,6 @@ func (cp *compiler) control(n *parse.Control) OpFunc { } if elseOp.Func != nil { elseOp.Exec(ec) - } else { - ec.verdict = true } } case parse.TryControl: @@ -388,7 +374,6 @@ func (cp *compiler) control(n *parse.Control) OpFunc { } } } - ec.verdict = true } case parse.ForControl: iteratorOp, restOp := cp.lvaluesOp(n.Iterator) diff --git a/eval/compile-value.go b/eval/compile-value.go index 26a94c44..508ca70b 100644 --- a/eval/compile-value.go +++ b/eval/compile-value.go @@ -317,7 +317,7 @@ func (cp *compiler) predCapture(n *parse.Chunk) ValuesOpFunc { op := cp.chunkOp(n) return func(ec *EvalCtx) []Value { op.Exec(ec) - return []Value{Bool(ec.verdict)} + return []Value{Bool(true)} } } diff --git a/eval/eval.go b/eval/eval.go index 3f3694ab..205a6431 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -54,7 +54,6 @@ type EvalCtx struct { local, up Namespace ports []*Port positionals []Value - verdict bool begin, end int traceback *util.SourceContext @@ -62,10 +61,6 @@ type EvalCtx struct { background bool } -func (ec *EvalCtx) falsify() { - ec.verdict = false -} - // NewEvaler creates a new Evaler. func NewEvaler(st *store.Store, dataDir string) *Evaler { return &Evaler{Namespace{}, map[string]Namespace{}, st, nil, dataDir, nil} @@ -88,7 +83,7 @@ func NewTopEvalCtx(ev *Evaler, name, text string, ports []*Port) *EvalCtx { ev, "top", name, text, ev.Global, Namespace{}, - ports, nil, true, + ports, nil, 0, len(text), nil, false, } } @@ -104,7 +99,7 @@ func (ec *EvalCtx) fork(name string) *EvalCtx { ec.Evaler, name, ec.srcName, ec.src, ec.local, ec.up, - newPorts, ec.positionals, true, + newPorts, ec.positionals, ec.begin, ec.end, ec.traceback, ec.background, } } @@ -138,10 +133,9 @@ func makeScope(s Namespace) scope { // eval evaluates a chunk node n. The supplied name and text are used in // diagnostic messages. -func (ev *Evaler) eval(op Op, ports []*Port, name, text string) (bool, error) { +func (ev *Evaler) eval(op Op, ports []*Port, name, text string) error { ec := NewTopEvalCtx(ev, name, text, ports) - err := ec.PEval(op) - return ec.verdict, err + return ec.PEval(op) } func (ec *EvalCtx) Interrupts() <-chan struct{} { @@ -207,16 +201,12 @@ func (ev *Evaler) Eval(op Op, name, text string) error { close(sigGoRoutineDone) }() - ret, err := ev.eval(op, ports, name, text) + err := ev.eval(op, ports, name, text) close(outCh) <-outDone close(stopSigGoroutine) <-sigGoRoutineDone - if !ret { - fmt.Println(falseIndicator) - } - // Put myself in foreground, in case some command has put me in background. // XXX Should probably use fd of /dev/tty instead of 0. if sys.IsATTY(0) { diff --git a/eval/eval_test.go b/eval/eval_test.go index eb1a23d0..f481ff89 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -24,7 +24,6 @@ var errAny = errors.New("") type more struct { wantBytesOut []byte wantError error - wantFalse bool } var noout = []Value{} @@ -80,10 +79,10 @@ var evalTests = []struct { {"if $false; then put 2; elif true; then put 2; else put 3; fi", strs("2"), nomore}, // try - {"try true; except; put bad; else; put good; tried", strs("good"), nomore}, + {"try nop; except; put bad; else; put good; tried", strs("good"), nomore}, {"try e:false; except; put bad; else; put good; tried", strs("bad"), nomore}, // while - {"x=0; while ?(< $x 4); do put $x; x=(+ $x 1); done", + {"x=0; while (< $x 4); do put $x; x=(+ $x 1); done", strs("0", "1", "2", "3"), nomore}, // for {"for x in tempora mores; do put 'O '$x; done", @@ -97,17 +96,12 @@ var evalTests = []struct { // begin/end {"begin; put lorem; put ipsum; end", strs("lorem", "ipsum"), nomore}, - // Predicates. - {"false", noout, more{wantFalse: true}}, - {"true | false", noout, more{wantFalse: true}}, - {"true | false | true", noout, nomore}, - // Redirections. {"f=`mktemp elvXXXXXX`; echo 233 > $f; cat < $f; rm $f", noout, more{wantBytesOut: []byte("233\n")}}, // Redirections from File object. {`fname=(mktemp elvXXXXXX); echo haha > $fname; - f=(fopen $fname); cat <$f; fclose $f; rm $fname`, noout, + f=(fopen $fname); cat <$f; fclose $f; rm $fname`, noout, more{wantBytesOut: []byte("haha\n")}}, // Redirections from Pipe object. {`p=(pipe); echo haha > $p; pwclose $p; cat < $p; prclose $p`, noout, @@ -132,9 +126,10 @@ var evalTests = []struct { // Output capture {"put (put lorem ipsum)", strs("lorem", "ipsum"), nomore}, - // Boolean capture - {"put ?(true) ?(false)", - []Value{Bool(true), Bool(false)}, nomore}, + /* + // Boolean capture + {"put (true) (false)", bools(true, false), nomore}, + */ // Variable and compounding {"x='SHELL'\nput 'WOW, SUCH '$x', MUCH COOL'\n", @@ -168,8 +163,8 @@ var evalTests = []struct { strs("lorem", "ipsum"), nomore}, // Closure captures new local variables every time {`fn f []{ x=0; put []{x=(+ $x 1)} []{put $x} } - {inc1,put1}=(f); $put1; $inc1; $put1 - {inc2,put2}=(f); $put2; $inc2; $put2`, + {inc1,put1}=(f); $put1; $inc1; $put1 + {inc2,put2}=(f); $put2; $inc2; $put2`, strs("0", "1", "0", "1"), nomore}, // Positional variables. {`{ put $1 } lorem ipsum`, strs("ipsum"), nomore}, @@ -205,8 +200,8 @@ var evalTests = []struct { // Builtin functions // ----------------- - {`true`, noout, nomore}, - {`false`, noout, more{wantFalse: true}}, + {`true`, bools(true), nomore}, + {`false`, bools(false), nomore}, {"kind-of bare 'str' [] [&] []{ }", strs("string", "string", "list", "map", "fn"), nomore}, @@ -238,14 +233,14 @@ var evalTests = []struct { {`joins : [/usr /bin /tmp]`, strs("/usr:/bin:/tmp"), nomore}, {`splits &sep=: /usr:/bin:/tmp`, strs("/usr", "/bin", "/tmp"), nomore}, - {`has-prefix golang go`, noout, nomore}, - {`has-prefix golang x`, noout, more{wantFalse: true}}, - {`has-suffix golang x`, noout, more{wantFalse: true}}, + {`has-prefix golang go`, bools(true), nomore}, + {`has-prefix golang x`, bools(false), nomore}, + {`has-suffix golang x`, bools(false), nomore}, - {`==s haha haha`, noout, nomore}, - {`==s 10 10.0`, noout, more{wantFalse: true}}, - {` 0x10 1`, noout, nomore}, + {`> 0x10 1`, bools(true), nomore}, - {`is 1 1`, noout, nomore}, - {`is [] []`, noout, more{wantFalse: true}}, - {`eq 1 1`, noout, nomore}, - {`eq [] []`, noout, nomore}, + {`is 1 1`, bools(true), nomore}, + {`is [] []`, bools(false), nomore}, + {`eq 1 1`, bools(true), nomore}, + {`eq [] []`, bools(true), nomore}, {`ord a`, strs("0x61"), nomore}, {`base 16 42 233`, strs("2a", "e9"), nomore}, @@ -324,7 +319,7 @@ func mustParseAndCompile(t *testing.T, ev *Evaler, name, text string) Op { return op } -func evalAndCollect(t *testing.T, texts []string, chsize int) ([]Value, []byte, bool, error) { +func evalAndCollect(t *testing.T, texts []string, chsize int) ([]Value, []byte, error) { name := "" ev := NewEvaler(nil, "") @@ -347,8 +342,7 @@ func evalAndCollect(t *testing.T, texts []string, chsize int) ([]Value, []byte, // Channel output outs := []Value{} - // Boolean return and error. Only those of the last text is saved. - var ret bool + // Eval error. Only that of the last text is saved. var ex error for _, text := range texts { @@ -369,7 +363,7 @@ func evalAndCollect(t *testing.T, texts []string, chsize int) ([]Value, []byte, {File: os.Stderr, Chan: BlackholeChan}, } - ret, ex = ev.eval(op, ports, name, text) + ex = ev.eval(op, ports, name, text) close(outCh) <-outDone } @@ -378,14 +372,14 @@ func evalAndCollect(t *testing.T, texts []string, chsize int) ([]Value, []byte, <-bytesDone pr.Close() - return outs, outBytes, ret, ex + return outs, outBytes, ex } func TestEval(t *testing.T) { for _, tt := range evalTests { // fmt.Printf("eval %q\n", tt.text) - out, bytesOut, ret, err := evalAndCollect(t, []string{tt.text}, len(tt.wantOut)) + out, bytesOut, err := evalAndCollect(t, []string{tt.text}, len(tt.wantOut)) good := true errorf := func(format string, args ...interface{}) { @@ -402,9 +396,6 @@ func TestEval(t *testing.T) { if string(bytesOut) != string(tt.wantBytesOut) { errorf("got bytesOut=%q, want %q", bytesOut, tt.wantBytesOut) } - if ret != !tt.wantFalse { - errorf("got ret=%v, want %v", ret, !tt.wantFalse) - } // Check exception cause. We accept errAny as a "wildcard" for all non-nil // errors. if err == nil { @@ -425,7 +416,7 @@ func TestEval(t *testing.T) { func TestMultipleEval(t *testing.T) { texts := []string{"x=hello", "put $x"} - outs, _, _, err := evalAndCollect(t, texts, 1) + outs, _, err := evalAndCollect(t, texts, 1) wanted := strs("hello") if err != nil { t.Errorf("eval %s => %v, want nil", texts, err)