OILS / spec / ysh-builtin-eval.test.sh View on Github | oilshell.org

699 lines, 387 significant
1# YSH specific features of eval
2
3## our_shell: ysh
4## oils_failures_allowed: 4
5
6#### eval builtin does not take a literal block - can restore this later
7
8var b = ^(echo obj)
9call io->eval (b)
10
11call io->eval (^(echo command literal))
12
13# Doesn't work because it's a positional arg
14eval { echo block }
15
16## status: 2
17## STDOUT:
18obj
19command literal
20## END
21
22
23#### Eval a block within a proc
24proc run (;;; block) {
25 call io->eval(block)
26}
27
28run {
29 echo 'In a block!'
30}
31## STDOUT:
32In a block!
33## END
34
35#### Eval block created by calling a proc
36proc lazy-block ( ; out; ; block) {
37 call out->setValue(block)
38}
39
40var myglobal = 0
41
42lazy-block (&my_block) {
43 json write (myglobal)
44}
45
46call io->eval(my_block)
47setvar myglobal = 1
48call io->eval(my_block)
49## STDOUT:
500
511
52## END
53
54#### io->eval with argv bindings
55call io->eval(^(echo "$@"), pos_args=:| foo bar baz |)
56call io->eval(^(pp test_ (:| $1 $2 $3 |)), pos_args=:| foo bar baz |)
57## STDOUT:
58foo bar baz
59(List) ["foo","bar","baz"]
60## END
61
62#### eval lines with argv bindings
63proc my-split (;;; block) {
64 while read --raw-line {
65 var cols = split(_reply)
66 call io->eval(block, pos_args=cols)
67 }
68}
69
70printf 'a b\nc d\n' | my-split {
71 echo "$2 $1"
72}
73
74printf 'a b\nc d\n' | my-split {
75 var mylocal = 'mylocal'
76 echo "$2 $1 $mylocal"
77}
78
79# Now do the same thing inside a proc
80proc p {
81 printf 'a b\nc d\n' | my-split {
82 var local2 = 'local2'
83 echo "$2 $1 $local2"
84 }
85}
86
87echo
88p
89
90## STDOUT:
91b a
92d c
93b a mylocal
94d c mylocal
95
96b a local2
97d c local2
98## END
99
100#### eval lines with var bindings
101
102proc my-split (;;; block) {
103 while read --raw-line {
104 var cols = split(_reply)
105 call io->eval(block, vars={_line: _reply, _first: cols[0]})
106 }
107}
108
109printf 'a b\nc d\n' | my-split {
110 var mylocal = 'mylocal'
111 echo "$_line | $_first $mylocal"
112}
113
114# Now do the same thing inside a proc
115proc p {
116 printf 'a b\nc d\n' | my-split {
117 var local2 = 'local2'
118 echo "$_line | $_first $local2"
119 }
120}
121
122echo
123p
124
125## STDOUT:
126a b | a mylocal
127c d | c mylocal
128
129a b | a local2
130c d | c local2
131## END
132
133#### eval with custom dollar0
134var b = ^(write $0)
135call io->eval(b, dollar0="my arg0")
136## STDOUT:
137my arg0
138## END
139
140#### eval with vars bindings
141var myVar = "abc"
142call io->eval(^(pp test_ (myVar)))
143call io->eval(^(pp test_ (myVar)), vars={ 'myVar': '123' })
144
145# eval doesn't modify it's environment
146call io->eval(^(pp test_ (myVar)))
147
148## STDOUT:
149(Str) "abc"
150(Str) "123"
151(Str) "abc"
152## END
153
154#### dynamic binding names and mutation
155proc foreach (binding, in_; list ;; block) {
156 if (in_ !== "in") {
157 error 'Must use the "syntax" `foreach <binding> in (<expr>) { ... }`'
158 }
159
160 for item in (list) {
161 call io->eval(block, vars={ [binding]: item })
162 }
163}
164
165var mydicts = [{'a': 1}, {'b': 2}, {'c': 3}]
166foreach mydict in (mydicts) {
167 var mylocal = 'z'
168 setvar mydict.z = mylocal
169
170 pp test_ (mydict)
171 setvar mydict.d = 0
172}
173echo
174
175for d in (mydicts) {
176 pp test_ (d)
177}
178
179## STDOUT:
180(Dict) {"a":1,"z":"z"}
181(Dict) {"b":2,"z":"z"}
182(Dict) {"c":3,"z":"z"}
183
184(Dict) {"a":1,"z":"z","d":0}
185(Dict) {"b":2,"z":"z","d":0}
186(Dict) {"c":3,"z":"z","d":0}
187## END
188
189#### binding procs in the eval-ed namespace
190proc __flag (short, long) {
191 echo "flag $short $long"
192}
193
194proc __arg (name) {
195 echo "arg $name"
196}
197
198proc parser (; spec ;; block) {
199 call io->eval(block, vars={ 'flag': __flag, 'arg': __arg })
200}
201
202parser (&spec) {
203 flag -h --help
204 arg file
205}
206
207# but flag/arg are unavailable outside of `parser`
208# _error.code = 127 is set on "command not found" errors
209
210try { flag }
211if (_error.code !== 127) { error 'expected failure' }
212
213try { arg }
214if (_error.code !== 127) { error 'expected failure' }
215
216## STDOUT:
217flag -h --help
218arg file
219## END
220
221#### vars initializes the variable frame, but does not remember it
222var vars = { 'foo': 123 }
223call io->eval(^(var bar = 321;), vars=vars)
224pp test_ (vars)
225
226## STDOUT:
227(Dict) {"foo":123}
228## END
229
230#### eval pos_args must be strings
231call io->eval(^(true), pos_args=[1, 2, 3])
232## status: 3
233
234#### eval with vars follows same scoping as without
235
236proc local-scope {
237 var myVar = "foo"
238 call io->eval(^(echo $myVar), vars={ someOtherVar: "bar" })
239 call io->eval(^(echo $myVar))
240}
241
242# In global scope
243var myVar = "baz"
244call io->eval(^(echo $myVar), vars={ someOtherVar: "bar" })
245call io->eval (^(echo $myVar))
246
247local-scope
248## STDOUT:
249baz
250baz
251foo
252foo
253## END
254
255#### eval 'mystring' vs. call io->eval(myblock)
256
257eval 'echo plain'
258echo plain=$?
259var b = ^(echo plain)
260call io->eval(b)
261echo plain=$?
262
263echo
264
265# This calls main_loop.Batch(), which catches
266# - error.Parse
267# - error.ErrExit
268# - error.FatalRuntime - glob errors, etc.?
269
270try {
271 eval 'echo one; false; echo two'
272}
273pp test_ (_error)
274
275# This calls CommandEvaluator.EvalCommand(), as blocks do
276
277var b = ^(echo one; false; echo two)
278try {
279 call io->eval(b)
280}
281pp test_ (_error)
282
283## STDOUT:
284plain
285plain=0
286plain
287plain=0
288
289one
290(Dict) {"code":1}
291one
292(Dict) {"code":1}
293## END
294
295#### io->evalToDict() - local and global
296
297var g = 'global'
298
299# in the global frame
300var d = io->evalToDict(^(var foo = 42; var bar = g;))
301pp test_ (d)
302
303# Same thing in a local frame
304proc p (myparam) {
305 var mylocal = 'local'
306 # TODO: ^() needs to capture
307 var cmd = ^(
308 var foo = 42
309 var g = "-$g"
310 var p = "-$myparam"
311 var L = "-$mylocal"
312 )
313 var d = io->evalToDict(cmd)
314 pp test_ (d)
315}
316p param
317
318## STDOUT:
319(Dict) {"foo":42,"bar":"global"}
320(Dict) {"foo":42,"g":"-global","p":"-param","L":"-local"}
321## END
322
323#### parseCommand then io->evalToDict() - in global scope
324
325var g = 'global'
326var cmd = parseCommand('var x = 42; echo hi; var y = g')
327#var cmd = parseCommand('echo hi')
328
329pp test_ (cmd)
330#pp asdl_ (cmd)
331
332var d = io->evalToDict(cmd)
333
334pp test_ (d)
335
336## STDOUT:
337<Command>
338hi
339(Dict) {"x":42,"y":"global"}
340## END
341
342#### parseCommand with syntax error
343
344try {
345 var cmd = parseCommand('echo >')
346}
347pp test_ (_error)
348
349## STDOUT:
350(Dict) {"code":3,"message":"Syntax error in parseCommand()"}
351## END
352
353
354#### Dict (&d) { ... } converts frame to dict
355
356proc Dict ( ; out; ; block) {
357 var d = io->evalToDict(block)
358 call out->setValue(d)
359}
360
361# it can read f
362
363var myglobal = 'global'
364var k = 'k-shadowed'
365var k2 = 'k2-shadowed'
366
367Dict (&d) {
368 bare = 42
369
370 # uh these find the wrong one
371 # This is like redeclaring the one above, but WITHOUT the static error
372 # HM HM HM
373 var k = 'k-block'
374 setvar k = 'k-block-mutated'
375
376 # Finds the global, so not checked
377 setvar k2 = 'k2-block'
378
379 # This one is allowed
380 setvar k3 = 'k3'
381
382 # do we allow this?
383 setvar myglobal = 'global'
384}
385
386pp test_ (d)
387
388exit
389
390# restored to the shadowed values
391echo k=$k
392echo k2=$k2
393
394proc p {
395 Dict (&d) {
396 var k = 'k-proc'
397 setvar k = 'k-proc-mutated'
398
399 # Not allowed STATICALLY, because o fproc check
400 #setvar k2 = 'k2-proc' # local, so it's checked
401 }
402}
403
404## STDOUT:
405## END
406
407#### block in Dict (&d) { ... } can read from outer scope
408
409proc Dict ( ; out; ; block) {
410 var d = io->evalToDict(block)
411 call out->setValue(d)
412}
413
414func f() {
415 var x = 42
416
417 Dict (&d) {
418 y = x + 1 # x is from outer scope
419 }
420 return (d)
421}
422
423var mydict = f()
424
425pp test_ (mydict)
426
427## STDOUT:
428(Dict) {"y":43}
429## END
430
431#### block in yb-capture Dict (&d) can read from outer scope
432
433proc yb-capture(; out; ; block) {
434 # capture status and stdout
435
436 var stdout = ''
437 try {
438 { call io->eval(block) } | read --all (&stdout)
439 }
440 var result = {status: _pipeline_status[0], stdout}
441
442 call out->setValue(result)
443}
444
445func f() {
446 var x = 42
447
448 yb-capture (&r) {
449 echo $[x + 1]
450 }
451
452 return (r)
453}
454
455var result = f()
456
457pp test_ (result)
458
459## STDOUT:
460(Dict) {"status":0,"stdout":"43\n"}
461## END
462
463
464#### Dict (&d) and setvar
465
466proc Dict ( ; out; ; block) {
467 echo "Dict proc global outer=$outer"
468 var d = io->evalToDict(block)
469
470 #echo 'proc Dict frame after evalToDict'
471 #pp frame_vars_
472
473 #echo "Dict outer2=$outer2"
474 call out->setValue(d)
475}
476
477var outer = 'xx'
478
479Dict (&d) {
480 # new variable in the front frame
481 outer2 = 'outer2'
482
483 echo "inside Dict outer=$outer"
484 setvar outer = 'zz'
485
486 setvar not_declared = 'yy'
487
488 #echo 'inside Dict block'
489 #pp frame_vars_
490}
491
492pp test_ (d)
493echo "after Dict outer=$outer"
494
495echo
496
497
498# Now do the same thing inside a proc
499
500proc p {
501 var outer = 'p-outer'
502
503 Dict (&d) {
504 p = 99
505 setvar outer = 'p-outer-mutated'
506 }
507
508 pp test_ (d)
509 echo "[p] after Dict outer=$outer"
510}
511
512p
513
514echo "after p outer=$outer"
515
516## STDOUT:
517Dict proc global outer=xx
518inside Dict outer=xx
519(Dict) {"outer2":"outer2","not_declared":"yy"}
520after Dict outer=zz
521
522Dict proc global outer=zz
523(Dict) {"p":99}
524[p] after Dict outer=p-outer-mutated
525after p outer=zz
526## END
527
528#### Dict (&d) and setglobal
529
530proc Dict ( ; out; ; block) {
531 var d = io->evalToDict(block)
532 call out->setValue(d)
533}
534
535var g = 'xx'
536
537Dict (&d) {
538 setglobal g = 'zz'
539
540 a = 42
541 pp frame_vars_
542}
543echo
544
545pp test_ (d)
546echo g=$g
547
548#pp frame_vars_
549
550## STDOUT:
551 [frame_vars_] __E__ a
552
553(Dict) {"a":42}
554g=zz
555## END
556
557#### bindings created shvar persist, which is different than evalToDict()
558
559var a = 'a'
560shvar IFS=: a='b' {
561 echo a=$a
562 inner=z
563 var inner2 = 'z'
564}
565echo a=$a
566echo inner=$inner
567echo inner2=$inner2
568
569## STDOUT:
570a=b
571a=a
572inner=z
573inner2=z
574## END
575
576#### io->evalInFrame() can express try, cd builtins
577
578var frag = ^(echo $i)
579
580proc my-cd (new_dir; ; ; block) {
581 pushd $new_dir
582
583 var calling_frame = vm.getFrame(-2)
584
585 # could call this "unbound"? or unbind()? What about procs and funcs and
586 # exprs?
587 var frag = getCommandFrag(block)
588
589 call io->evalInFrame(frag, calling_frame)
590
591 popd
592}
593
594var i = 42
595my-cd /tmp {
596 echo $PWD
597 var j = i + 1
598}
599echo "j = $j"
600
601## STDOUT:
602x: i = 0, j = 2
603x: i = 1, j = 3
604x: i = 2, j = 4
605## END
606
607
608#### parseCommand(), io->evalInFrame(frag, frame) can behave like eval $mystr
609
610# NO LONGER WORKS, but is this a feature rather than a bug?
611
612proc p2(code_str) {
613 var mylocal = 42
614 eval $code_str
615}
616
617p2 'echo mylocal=$mylocal'
618
619proc p (;;; block) {
620 # To behave like eval $code_str, without variable capture:
621 #
622 # var frag = getCommandFrag(block)
623 # var this_frame = vm.getFrame(-1)
624 # call io->evalInFrame(frag, this_frame)
625
626 var mylocal = 99
627 call io->eval(block)
628}
629
630p {
631 echo mylocal=$mylocal
632}
633
634
635## STDOUT:
636mylocal=42
637mylocal=99
638## END
639
640#### eval should have a sandboxed mode
641
642proc p (;;; block) {
643 var this = 42
644
645 # like push-registers? Not sure
646 # We could use state.ctx_Temp ? There's also ctx_FuncCall etc.
647 #
648 # I think we want to provide full control over the stack.
649 push-frame {
650 call io->eval(block)
651 }
652}
653
654p {
655 echo $this
656}
657
658## status: 1
659## STDOUT:
660TODO
661## END
662
663#### io->evalExpr() with vars, dollar0, pos_args
664
665var ex = ^["$0 $1 $2 " ++ myvar]
666
667var vars = {myvar: 'hello'}
668var s = io->evalExpr(ex, dollar0='z', pos_args=:|a b c|, vars=vars)
669
670echo $s
671
672proc my-where (; pred) {
673 # note: for line in (io.stdin) is messed up by spec test framework
674
675 while read --raw-line (&line) {
676 var vars = {_line: line}
677 #= line
678 var b = io->evalExpr(pred, vars=vars)
679 if (b) {
680 echo $line
681 }
682 }
683}
684
685
686seq 5 | my-where [_line ~== 2 or _line ~== 4]
687echo ---
688
689seq 5 10 | my-where [_line % 2 === 1]
690
691## STDOUT:
692z a b hello
6932
6944
695---
6965
6977
6989
699## END