Eliminate verdict/predicates.

Right now ?(...) always evaluates to $true, it will be repurposed for
exception catching.

This fixes #319.
This commit is contained in:
Qi Xiao 2017-02-16 04:32:14 +00:00
parent d8f81f4e30
commit b6613d0f19
6 changed files with 67 additions and 94 deletions

View File

@ -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) {

View File

@ -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,
}

View File

@ -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)

View File

@ -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)}
}
}

View File

@ -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) {

View File

@ -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}},
{`<s a b`, noout, nomore},
{`<s 2 10`, noout, more{wantFalse: true}},
{`==s haha haha`, bools(true), nomore},
{`==s 10 10.0`, bools(false), nomore},
{`<s a b`, bools(true), nomore},
{`<s 2 10`, bools(false), nomore},
{`fail haha`, noout, more{wantError: errAny}},
{`return`, noout, more{wantError: Return}},
@ -255,8 +250,8 @@ var evalTests = []struct {
{`put 1 233 | each put`, strs("1", "233"), nomore},
{`echo "1\n233" | each put`, strs("1", "233"), nomore},
{`each put [1 233]`, strs("1", "233"), nomore},
{`range 10 | each { if ?(== $0 4); then break; fi; put $0 }`, strs("0", "1", "2", "3"), nomore},
{`range 10 | each { if ?(== $0 4); then fail haha; fi; put $0 }`, strs("0", "1", "2", "3"), more{wantError: errAny}},
{`range 10 | each { if (== $0 4); then break; fi; put $0 }`, strs("0", "1", "2", "3"), nomore},
{`range 10 | each { if (== $0 4); then fail haha; fi; put $0 }`, strs("0", "1", "2", "3"), more{wantError: errAny}},
// TODO: test peach
{`range 3`, strs("0", "1", "2"), nomore},
@ -281,15 +276,15 @@ var evalTests = []struct {
{"^ 16 2", strs("256"), nomore},
{"% 23 7", strs("2"), nomore},
{`== 1 1.0`, noout, nomore},
{`== 10 0xa`, noout, nomore},
{`== 1 1.0`, bools(true), nomore},
{`== 10 0xa`, bools(true), nomore},
{`== a a`, noout, more{wantError: errAny}},
{`> 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 := "<eval test>"
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)