1 ## our_shell: ysh
2 ## oils_failures_allowed: 1
3 ## oils_cpp_failures_allowed: 3
4
5 #### eval builtin does not take a literal block - can restore this later
6
7 var b = ^(echo obj)
8 call io->eval (b)
9
10 call io->eval (^(echo command literal))
11
12 # Doesn't work because it's a positional arg
13 eval { echo block }
14
15 ## status: 2
16 ## STDOUT:
17 obj
18 command literal
19 ## END
20
21
22 #### Eval a block within a proc
23 proc run (;;; block) {
24 call io->eval(block)
25 }
26
27 run {
28 echo 'In a block!'
29 }
30 ## STDOUT:
31 In a block!
32 ## END
33
34 #### Eval block created by calling a proc
35 proc lazy-block ( ; out; ; block) {
36 call out->setValue(block)
37 }
38
39 var myglobal = 0
40
41 lazy-block (&my_block) {
42 json write (myglobal)
43 }
44
45 call io->eval(my_block)
46 setvar myglobal = 1
47 call io->eval(my_block)
48 ## STDOUT:
49 0
50 1
51 ## END
52
53 #### io->eval with argv bindings
54 call io->eval(^(echo "$@"), pos_args=:| foo bar baz |)
55 call io->eval(^(pp test_ (:| $1 $2 $3 |)), pos_args=:| foo bar baz |)
56 ## STDOUT:
57 foo bar baz
58 (List) ["foo","bar","baz"]
59 ## END
60
61 #### eval lines with argv bindings
62 proc my-split (;;; block) {
63 while read --raw-line {
64 var cols = split(_reply)
65 call io->eval(block, pos_args=cols)
66 }
67 }
68
69 printf 'a b\nc d\n' | my-split {
70 echo "$2 $1"
71 }
72
73 printf 'a b\nc d\n' | my-split {
74 var mylocal = 'mylocal'
75 echo "$2 $1 $mylocal"
76 }
77
78 # Now do the same thing inside a proc
79 proc p {
80 printf 'a b\nc d\n' | my-split {
81 var local2 = 'local2'
82 echo "$2 $1 $local2"
83 }
84 }
85
86 echo
87 p
88
89 ## STDOUT:
90 b a
91 d c
92 b a mylocal
93 d c mylocal
94
95 b a local2
96 d c local2
97 ## END
98
99 #### eval lines with var bindings
100
101 proc my-split (;;; block) {
102 while read --raw-line {
103 var cols = split(_reply)
104 call io->eval(block, vars={_line: _reply, _first: cols[0]})
105 }
106 }
107
108 printf 'a b\nc d\n' | my-split {
109 var mylocal = 'mylocal'
110 echo "$_line | $_first $mylocal"
111 }
112
113 # Now do the same thing inside a proc
114 proc p {
115 printf 'a b\nc d\n' | my-split {
116 var local2 = 'local2'
117 echo "$_line | $_first $local2"
118 }
119 }
120
121 echo
122 p
123
124 ## STDOUT:
125 a b | a mylocal
126 c d | c mylocal
127
128 a b | a local2
129 c d | c local2
130 ## END
131
132 #### eval with custom dollar0
133 var b = ^(write $0)
134 call io->eval(b, dollar0="my arg0")
135 ## STDOUT:
136 my arg0
137 ## END
138
139 #### eval with vars bindings
140 var myVar = "abc"
141 call io->eval(^(pp test_ (myVar)))
142 call io->eval(^(pp test_ (myVar)), vars={ 'myVar': '123' })
143
144 # eval doesn't modify it's environment
145 call io->eval(^(pp test_ (myVar)))
146
147 ## STDOUT:
148 (Str) "abc"
149 (Str) "123"
150 (Str) "abc"
151 ## END
152
153 #### dynamic binding names and mutation
154 proc foreach (binding, in_; list ;; block) {
155 if (in_ !== "in") {
156 error 'Must use the "syntax" `foreach <binding> in (<expr>) { ... }`'
157 }
158
159 for item in (list) {
160 call io->eval(block, vars={ [binding]: item })
161 }
162 }
163
164 var mydicts = [{'a': 1}, {'b': 2}, {'c': 3}]
165 foreach mydict in (mydicts) {
166 var mylocal = 'z'
167 setvar mydict.z = mylocal
168
169 pp test_ (mydict)
170 setvar mydict.d = 0
171 }
172 echo
173
174 for d in (mydicts) {
175 pp test_ (d)
176 }
177
178 ## STDOUT:
179 (Dict) {"a":1,"z":"z"}
180 (Dict) {"b":2,"z":"z"}
181 (Dict) {"c":3,"z":"z"}
182
183 (Dict) {"a":1,"z":"z","d":0}
184 (Dict) {"b":2,"z":"z","d":0}
185 (Dict) {"c":3,"z":"z","d":0}
186 ## END
187
188 #### binding procs in the eval-ed namespace
189 proc __flag (short, long) {
190 echo "flag $short $long"
191 }
192
193 proc __arg (name) {
194 echo "arg $name"
195 }
196
197 proc parser (; spec ;; block) {
198 call io->eval(block, vars={ 'flag': __flag, 'arg': __arg })
199 }
200
201 parser (&spec) {
202 flag -h --help
203 arg file
204 }
205
206 # but flag/arg are unavailable outside of `parser`
207 # _error.code = 127 is set on "command not found" errors
208
209 try { flag }
210 if (_error.code !== 127) { error 'expected failure' }
211
212 try { arg }
213 if (_error.code !== 127) { error 'expected failure' }
214
215 ## STDOUT:
216 flag -h --help
217 arg file
218 ## END
219
220 #### vars initializes the variable frame, but does not remember it
221 var vars = { 'foo': 123 }
222 call io->eval(^(var bar = 321;), vars=vars)
223 pp test_ (vars)
224
225 ## STDOUT:
226 (Dict) {"foo":123}
227 ## END
228
229 #### eval pos_args must be strings
230 call io->eval(^(true), pos_args=[1, 2, 3])
231 ## status: 3
232
233 #### eval with vars follows same scoping as without
234
235 proc local-scope {
236 var myVar = "foo"
237 call io->eval(^(echo $myVar), vars={ someOtherVar: "bar" })
238 call io->eval(^(echo $myVar))
239 }
240
241 # In global scope
242 var myVar = "baz"
243 call io->eval(^(echo $myVar), vars={ someOtherVar: "bar" })
244 call io->eval (^(echo $myVar))
245
246 local-scope
247 ## STDOUT:
248 baz
249 baz
250 foo
251 foo
252 ## END
253
254 #### eval 'mystring' vs. call io->eval(myblock)
255
256 eval 'echo plain'
257 echo plain=$?
258 var b = ^(echo plain)
259 call io->eval(b)
260 echo plain=$?
261
262 echo
263
264 # This calls main_loop.Batch(), which catches
265 # - error.Parse
266 # - error.ErrExit
267 # - error.FatalRuntime - glob errors, etc.?
268
269 try {
270 eval 'echo one; false; echo two'
271 }
272 pp test_ (_error)
273
274 # This calls CommandEvaluator.EvalCommand(), as blocks do
275
276 var b = ^(echo one; false; echo two)
277 try {
278 call io->eval(b)
279 }
280 pp test_ (_error)
281
282 ## STDOUT:
283 plain
284 plain=0
285 plain
286 plain=0
287
288 one
289 (Dict) {"code":1}
290 one
291 (Dict) {"code":1}
292 ## END
293
294 #### io->eval(to_dict=true) - local and global
295
296 var g = 'global'
297
298 # in the global frame
299 var d = io->eval(^(var foo = 42; var bar = g;), to_dict=true)
300 pp test_ (d)
301
302 # Same thing in a local frame
303 proc p (myparam) {
304 var mylocal = 'local'
305 # TODO: ^() needs to capture
306 var cmd = ^(
307 var foo = 42
308 var g = "-$g"
309 var p = "-$myparam"
310 var L = "-$mylocal"
311 )
312 var d = io->eval(cmd, to_dict=true)
313 pp test_ (d)
314 }
315 p param
316
317 ## STDOUT:
318 (Dict) {"foo":42,"bar":"global"}
319 (Dict) {"foo":42,"g":"-global","p":"-param","L":"-local"}
320 ## END
321
322 #### io->eval(to_dict=true) with dollar0, pos_args, vars - dict ordering bug
323
324 # TODO: mycpp/gc_dict.h should preserve insertion order, in the presence of
325 # deletions, like CPython
326
327 proc Dict ( ; out; ; block) {
328 var d = io->eval(block, dollar0='zero', pos_args=:|a b c|,
329 vars={X: 'X', _Y: '_Y'}, to_dict=true)
330 call out->setValue(d)
331 }
332
333 var global = 'global'
334
335 Dict (&d) {
336 foo = global
337 z = $0
338 one = $1
339 two = ${2}
340 three = "+$3"
341 # Note: X does NOT appear in the output, ctx_Eval makes it work.
342 x = X
343 y = _Y
344 }
345
346 json write (d)
347 #pp test_ (d)
348
349 ## STDOUT:
350 {
351 "foo": "global",
352 "z": "zero",
353 "one": "a",
354 "two": "b",
355 "three": "+c",
356 "x": "X",
357 "y": "_Y"
358 }
359 ## END
360
361 #### io->eval(to_dict=true) with in_captured_frame=true
362
363 proc Dict ( ; out; ; block) {
364 var d = io->eval(block, dollar0='zero', pos_args=:|a b c|,
365 vars={X: 'X', _Y: '_Y'}, in_captured_frame=true, to_dict=true)
366 call out->setValue(d)
367 }
368
369 var global = 'global'
370
371 func makeDict() {
372 var var_in_p_frame = 'p'
373
374 Dict (&d) {
375 foo = global
376 z = $0
377 one = $1
378 two = ${2}
379 three = "+$3"
380 # Note: X does NOT appear in the output, ctx_Eval makes it work.
381 x = X
382 y = _Y
383 }
384
385 return (d)
386 }
387
388 var d = makeDict()
389 json write (d)
390
391 ## STDOUT:
392 {
393 "var_in_p_frame": "p",
394 "foo": "global",
395 "z": "zero",
396 "one": "a",
397 "two": "b",
398 "three": "+c",
399 "x": "X",
400 "y": "_Y"
401 }
402 ## END
403
404
405 #### parseCommand then io->eval(to_dict=true) - in global scope
406
407 var g = 'global'
408 var cmd = parseCommand('var x = 42; echo hi; var y = g')
409 #var cmd = parseCommand('echo hi')
410
411 pp test_ (cmd)
412 #pp asdl_ (cmd)
413
414 var d = io->eval(cmd, to_dict=true)
415
416 pp test_ (d)
417
418 ## STDOUT:
419 <Command>
420 hi
421 (Dict) {"x":42,"y":"global"}
422 ## END
423
424 #### parseCommand with syntax error
425
426 try {
427 var cmd = parseCommand('echo >')
428 }
429 pp test_ (_error)
430
431 ## STDOUT:
432 (Dict) {"code":3,"message":"Syntax error in parseCommand()"}
433 ## END
434
435
436 #### Dict (&d) { ... } converts frame to dict
437
438 proc Dict ( ; out; ; block) {
439 var d = io->eval(block, to_dict=true)
440 call out->setValue(d)
441 }
442
443 # it can read f
444
445 var myglobal = 'global'
446 var k = 'k-shadowed'
447 var k2 = 'k2-shadowed'
448
449 Dict (&d) {
450 bare = 42
451
452 # uh these find the wrong one
453 # This is like redeclaring the one above, but WITHOUT the static error
454 # HM HM HM
455 var k = 'k-block'
456 setvar k = 'k-block-mutated'
457
458 # Finds the global, so not checked
459 setvar k2 = 'k2-block'
460
461 # This one is allowed
462 setvar k3 = 'k3'
463
464 # do we allow this?
465 setvar myglobal = 'global'
466 }
467
468 pp test_ (d)
469
470
471 # Same problem as Hay test cases!
472 const c = 99
473
474 Dict (&d2) {
475 const c = 101
476 }
477
478 pp test_ (d2)
479
480 exit
481
482 # restored to the shadowed values
483 echo k=$k
484 echo k2=$k2
485
486 proc p {
487 Dict (&d) {
488 var k = 'k-proc'
489 setvar k = 'k-proc-mutated'
490
491 # Not allowed STATICALLY, because o fproc check
492 #setvar k2 = 'k2-proc' # local, so it's checked
493 }
494 }
495
496 ## STDOUT:
497 (Dict) {"bare":42,"k":"k-block-mutated","k3":"k3"}
498 (Dict) {"c":101}
499 ## END
500
501 #### block in Dict (&d) { ... } can read from outer scope
502
503 proc Dict ( ; out; ; block) {
504 var d = io->eval(block, to_dict=true)
505 call out->setValue(d)
506 }
507
508 func f() {
509 var x = 42
510
511 Dict (&d) {
512 y = x + 1 # x is from outer scope
513 }
514 return (d)
515 }
516
517 var mydict = f()
518
519 pp test_ (mydict)
520
521 ## STDOUT:
522 (Dict) {"y":43}
523 ## END
524
525 #### block in yb-capture Dict (&d) can read from outer scope
526
527 proc yb-capture(; out; ; block) {
528 # capture status and stdout
529
530 var stdout = ''
531 try {
532 { call io->eval(block) } | read --all (&stdout)
533 }
534 var result = {status: _pipeline_status[0], stdout}
535
536 call out->setValue(result)
537 }
538
539 func f() {
540 var x = 42
541
542 yb-capture (&r) {
543 echo $[x + 1]
544 }
545
546 return (r)
547 }
548
549 var result = f()
550
551 pp test_ (result)
552
553 ## STDOUT:
554 (Dict) {"status":0,"stdout":"43\n"}
555 ## END
556
557
558 #### Dict (&d) and setvar
559
560 proc Dict ( ; out; ; block) {
561 echo "Dict proc global outer=$outer"
562 var d = io->eval(block, to_dict=true)
563
564 #pp frame_vars_
565
566 #echo "Dict outer2=$outer2"
567 call out->setValue(d)
568 }
569
570 var outer = 'xx'
571
572 Dict (&d) {
573 # new variable in the front frame
574 outer2 = 'outer2'
575
576 echo "inside Dict outer=$outer"
577 setvar outer = 'zz'
578
579 setvar not_declared = 'yy'
580
581 #echo 'inside Dict block'
582 #pp frame_vars_
583 }
584
585 pp test_ (d)
586 echo "after Dict outer=$outer"
587
588 echo
589
590
591 # Now do the same thing inside a proc
592
593 proc p {
594 var outer = 'p-outer'
595
596 Dict (&d) {
597 p = 99
598 setvar outer = 'p-outer-mutated'
599 }
600
601 pp test_ (d)
602 echo "[p] after Dict outer=$outer"
603 }
604
605 p
606
607 echo "after p outer=$outer"
608
609 ## STDOUT:
610 Dict proc global outer=xx
611 inside Dict outer=xx
612 (Dict) {"outer2":"outer2","not_declared":"yy"}
613 after Dict outer=zz
614
615 Dict proc global outer=zz
616 (Dict) {"p":99}
617 [p] after Dict outer=p-outer-mutated
618 after p outer=zz
619 ## END
620
621 #### Dict (&d) and setglobal
622
623 proc Dict ( ; out; ; block) {
624 var d = io->eval(block, to_dict=true)
625 call out->setValue(d)
626 }
627
628 var g = 'xx'
629
630 Dict (&d) {
631 setglobal g = 'zz'
632
633 a = 42
634 pp frame_vars_
635 }
636 echo
637
638 pp test_ (d)
639 echo g=$g
640
641 #pp frame_vars_
642
643 ## STDOUT:
644 [frame_vars_] __E__ a
645
646 (Dict) {"a":42}
647 g=zz
648 ## END
649
650 #### bindings created shvar persist, which is different than eval(to_dict=true)
651
652 var a = 'a'
653 shvar IFS=: a='b' {
654 echo a=$a
655 inner=z
656 var inner2 = 'z'
657 }
658 echo a=$a
659 echo inner=$inner
660 echo inner2=$inner2
661
662 ## STDOUT:
663 a=b
664 a=a
665 inner=z
666 inner2=z
667 ## END
668
669 #### io->eval with in_captured_frame=true can express cd builtin
670
671 proc my-cd (new_dir; ; ; block) {
672 pushd $new_dir >/dev/null
673
674 if (0) {
675 # Get calling frame. (The top-most frame, this one, has index -1)
676 # This idiom does NOT work with modules
677 var calling_frame = vm.getFrame(-2)
678 call io->evalInFrame(block, calling_frame)
679 } else {
680 #call io->evalInCapturedFrame(block)
681 call io->eval(block, in_captured_frame=true)
682 }
683
684 popd >/dev/null
685 }
686
687 var i = 42
688 my-cd /tmp {
689 echo $PWD
690 var my_pwd = PWD
691 var j = i + 1
692 }
693 echo "my_pwd=$my_pwd"
694 echo "j = $j"
695
696 ## STDOUT:
697 /tmp
698 my_pwd=/tmp
699 j = 43
700 ## END
701
702
703 #### io->eval with in_captured_frame=true can express cd builtin in different module
704
705 echo >lib.ysh '''
706 const __provide__ = :| my-cd |
707
708 proc my-cd (new_dir; ; ; block) {
709 pushd $new_dir >/dev/null
710
711 call io->eval(block, in_captured_frame=true)
712
713 popd >/dev/null
714 }
715 '''
716
717 use ./lib.ysh
718
719 var i = 42
720 lib my-cd /tmp {
721 #echo $PWD
722 #var my_pwd = PWD
723 var j = i + 1
724 }
725 #echo "my_pwd=$my_pwd"
726 echo "j = $j"
727
728 ## STDOUT:
729 j = 43
730 ## END
731
732 #### io->eval() has cleaner scoping than shell's eval builtin
733
734 #shopt --unset ysh:all
735 shopt --set ysh:upgrade
736
737 sh-eval() {
738 local do_not_leak=42 # this is visible
739 eval $1
740 }
741
742 sh-eval 'echo "eval string do_not_leak=$do_not_leak"'
743
744 proc ysh-eval (;;; block) {
745 var do_not_leak = 99
746 call io->eval(block, in_captured_frame=true)
747 }
748
749 ysh-eval {
750 echo "ysh block do_not_leak=$do_not_leak"
751 }
752
753 ## status: 1
754 ## STDOUT:
755 eval string do_not_leak=42
756 ## END
757
758 #### io->eval with in_captured_frame=true and setglobal
759
760 echo >lib.ysh '''
761 const __provide__ = :| p x |
762
763 var x = "lib"
764
765 proc p (;;; block) {
766 call io->eval(block, in_captured_frame=true)
767 }
768 '''
769
770 use ./lib.ysh
771
772 var x = 'main'
773
774 lib p {
775 echo "block arg x = $x"
776 setglobal x = "ZZ"
777 echo "mutated = $x"
778 var result = 'result'
779 }
780 #pp test_ (result)
781
782 # NOTE: we can modify our own x, but not lib.x!
783 pp test_ (x)
784 pp test_ (lib.x)
785
786 ## STDOUT:
787 block arg x = main
788 mutated = ZZ
789 (Str) "ZZ"
790 (Str) "lib"
791 ## END
792
793 #### eval should have a sandboxed mode
794
795 proc p (;;; block) {
796 var this = 42
797
798 # like --eval-pure, and like func?
799 with-pure {
800 # Do we still use io->eval?
801 call io->eval(block)
802 }
803
804 # Or maybe we have free functions, like func/eval
805 # There is no with-pure
806 call eval(cmd)
807 var d = eval(cmd, to_dict=true)
808
809 eval-pure --dump d {
810 var d = {}
811 }
812 }
813
814 p {
815 echo $this
816 }
817
818 ## status: 1
819 ## STDOUT:
820 TODO
821 ## END
822
823 #### io->evalExpr() with vars, dollar0, pos_args
824
825 var ex = ^["$0 $1 $2 " ++ myvar]
826
827 var vars = {myvar: 'hello'}
828 var s = io->evalExpr(ex, dollar0='z', pos_args=:|a b c|, vars=vars)
829
830 echo $s
831
832 proc my-where (; pred) {
833 # note: for line in (io.stdin) is messed up by spec test framework
834
835 while read --raw-line (&line) {
836 var vars = {_line: line}
837 #= line
838 var b = io->evalExpr(pred, vars=vars)
839 if (b) {
840 echo $line
841 }
842 }
843 }
844
845
846 seq 5 | my-where [_line ~== 2 or _line ~== 4]
847 echo ---
848
849 seq 5 10 | my-where [_line % 2 === 1]
850
851 ## STDOUT:
852 z a b hello
853 2
854 4
855 ---
856 5
857 7
858 9
859 ## END