mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-14 02:57:52 +08:00
Eliminate verdict/predicates.
Right now ?(...) always evaluates to $true, it will be repurposed for exception catching. This fixes #319.
This commit is contained in:
parent
d8f81f4e30
commit
b6613d0f19
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
20
eval/eval.go
20
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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user