OILS / spec / ysh-builtin-eval.test.sh View on Github | oils.pub

859 lines, 482 significant
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
7var b = ^(echo obj)
8call io->eval (b)
9
10call io->eval (^(echo command literal))
11
12# Doesn't work because it's a positional arg
13eval { echo block }
14
15## status: 2
16## STDOUT:
17obj
18command literal
19## END
20
21
22#### Eval a block within a proc
23proc run (;;; block) {
24 call io->eval(block)
25}
26
27run {
28 echo 'In a block!'
29}
30## STDOUT:
31In a block!
32## END
33
34#### Eval block created by calling a proc
35proc lazy-block ( ; out; ; block) {
36 call out->setValue(block)
37}
38
39var myglobal = 0
40
41lazy-block (&my_block) {
42 json write (myglobal)
43}
44
45call io->eval(my_block)
46setvar myglobal = 1
47call io->eval(my_block)
48## STDOUT:
490
501
51## END
52
53#### io->eval with argv bindings
54call io->eval(^(echo "$@"), pos_args=:| foo bar baz |)
55call io->eval(^(pp test_ (:| $1 $2 $3 |)), pos_args=:| foo bar baz |)
56## STDOUT:
57foo bar baz
58(List) ["foo","bar","baz"]
59## END
60
61#### eval lines with argv bindings
62proc my-split (;;; block) {
63 while read --raw-line {
64 var cols = split(_reply)
65 call io->eval(block, pos_args=cols)
66 }
67}
68
69printf 'a b\nc d\n' | my-split {
70 echo "$2 $1"
71}
72
73printf '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
79proc p {
80 printf 'a b\nc d\n' | my-split {
81 var local2 = 'local2'
82 echo "$2 $1 $local2"
83 }
84}
85
86echo
87p
88
89## STDOUT:
90b a
91d c
92b a mylocal
93d c mylocal
94
95b a local2
96d c local2
97## END
98
99#### eval lines with var bindings
100
101proc 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
108printf '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
114proc p {
115 printf 'a b\nc d\n' | my-split {
116 var local2 = 'local2'
117 echo "$_line | $_first $local2"
118 }
119}
120
121echo
122p
123
124## STDOUT:
125a b | a mylocal
126c d | c mylocal
127
128a b | a local2
129c d | c local2
130## END
131
132#### eval with custom dollar0
133var b = ^(write $0)
134call io->eval(b, dollar0="my arg0")
135## STDOUT:
136my arg0
137## END
138
139#### eval with vars bindings
140var myVar = "abc"
141call io->eval(^(pp test_ (myVar)))
142call io->eval(^(pp test_ (myVar)), vars={ 'myVar': '123' })
143
144# eval doesn't modify it's environment
145call 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
154proc 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
164var mydicts = [{'a': 1}, {'b': 2}, {'c': 3}]
165foreach mydict in (mydicts) {
166 var mylocal = 'z'
167 setvar mydict.z = mylocal
168
169 pp test_ (mydict)
170 setvar mydict.d = 0
171}
172echo
173
174for 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
189proc __flag (short, long) {
190 echo "flag $short $long"
191}
192
193proc __arg (name) {
194 echo "arg $name"
195}
196
197proc parser (; spec ;; block) {
198 call io->eval(block, vars={ 'flag': __flag, 'arg': __arg })
199}
200
201parser (&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
209try { flag }
210if (_error.code !== 127) { error 'expected failure' }
211
212try { arg }
213if (_error.code !== 127) { error 'expected failure' }
214
215## STDOUT:
216flag -h --help
217arg file
218## END
219
220#### vars initializes the variable frame, but does not remember it
221var vars = { 'foo': 123 }
222call io->eval(^(var bar = 321;), vars=vars)
223pp test_ (vars)
224
225## STDOUT:
226(Dict) {"foo":123}
227## END
228
229#### eval pos_args must be strings
230call io->eval(^(true), pos_args=[1, 2, 3])
231## status: 3
232
233#### eval with vars follows same scoping as without
234
235proc 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
242var myVar = "baz"
243call io->eval(^(echo $myVar), vars={ someOtherVar: "bar" })
244call io->eval (^(echo $myVar))
245
246local-scope
247## STDOUT:
248baz
249baz
250foo
251foo
252## END
253
254#### eval 'mystring' vs. call io->eval(myblock)
255
256eval 'echo plain'
257echo plain=$?
258var b = ^(echo plain)
259call io->eval(b)
260echo plain=$?
261
262echo
263
264# This calls main_loop.Batch(), which catches
265# - error.Parse
266# - error.ErrExit
267# - error.FatalRuntime - glob errors, etc.?
268
269try {
270 eval 'echo one; false; echo two'
271}
272pp test_ (_error)
273
274# This calls CommandEvaluator.EvalCommand(), as blocks do
275
276var b = ^(echo one; false; echo two)
277try {
278 call io->eval(b)
279}
280pp test_ (_error)
281
282## STDOUT:
283plain
284plain=0
285plain
286plain=0
287
288one
289(Dict) {"code":1}
290one
291(Dict) {"code":1}
292## END
293
294#### io->eval(to_dict=true) - local and global
295
296var g = 'global'
297
298# in the global frame
299var d = io->eval(^(var foo = 42; var bar = g;), to_dict=true)
300pp test_ (d)
301
302# Same thing in a local frame
303proc 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}
315p 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
327proc 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
333var global = 'global'
334
335Dict (&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
346json 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
363proc 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
369var global = 'global'
370
371func 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
388var d = makeDict()
389json 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
407var g = 'global'
408var cmd = parseCommand('var x = 42; echo hi; var y = g')
409#var cmd = parseCommand('echo hi')
410
411pp test_ (cmd)
412#pp asdl_ (cmd)
413
414var d = io->eval(cmd, to_dict=true)
415
416pp test_ (d)
417
418## STDOUT:
419<Command>
420hi
421(Dict) {"x":42,"y":"global"}
422## END
423
424#### parseCommand with syntax error
425
426try {
427 var cmd = parseCommand('echo >')
428}
429pp 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
438proc 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
445var myglobal = 'global'
446var k = 'k-shadowed'
447var k2 = 'k2-shadowed'
448
449Dict (&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
468pp test_ (d)
469
470
471# Same problem as Hay test cases!
472const c = 99
473
474Dict (&d2) {
475 const c = 101
476}
477
478pp test_ (d2)
479
480exit
481
482# restored to the shadowed values
483echo k=$k
484echo k2=$k2
485
486proc 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
503proc Dict ( ; out; ; block) {
504 var d = io->eval(block, to_dict=true)
505 call out->setValue(d)
506}
507
508func f() {
509 var x = 42
510
511 Dict (&d) {
512 y = x + 1 # x is from outer scope
513 }
514 return (d)
515}
516
517var mydict = f()
518
519pp 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
527proc 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
539func f() {
540 var x = 42
541
542 yb-capture (&r) {
543 echo $[x + 1]
544 }
545
546 return (r)
547}
548
549var result = f()
550
551pp test_ (result)
552
553## STDOUT:
554(Dict) {"status":0,"stdout":"43\n"}
555## END
556
557
558#### Dict (&d) and setvar
559
560proc 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
570var outer = 'xx'
571
572Dict (&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
585pp test_ (d)
586echo "after Dict outer=$outer"
587
588echo
589
590
591# Now do the same thing inside a proc
592
593proc 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
605p
606
607echo "after p outer=$outer"
608
609## STDOUT:
610Dict proc global outer=xx
611inside Dict outer=xx
612(Dict) {"outer2":"outer2","not_declared":"yy"}
613after Dict outer=zz
614
615Dict proc global outer=zz
616(Dict) {"p":99}
617[p] after Dict outer=p-outer-mutated
618after p outer=zz
619## END
620
621#### Dict (&d) and setglobal
622
623proc Dict ( ; out; ; block) {
624 var d = io->eval(block, to_dict=true)
625 call out->setValue(d)
626}
627
628var g = 'xx'
629
630Dict (&d) {
631 setglobal g = 'zz'
632
633 a = 42
634 pp frame_vars_
635}
636echo
637
638pp test_ (d)
639echo g=$g
640
641#pp frame_vars_
642
643## STDOUT:
644 [frame_vars_] __E__ a
645
646(Dict) {"a":42}
647g=zz
648## END
649
650#### bindings created shvar persist, which is different than eval(to_dict=true)
651
652var a = 'a'
653shvar IFS=: a='b' {
654 echo a=$a
655 inner=z
656 var inner2 = 'z'
657}
658echo a=$a
659echo inner=$inner
660echo inner2=$inner2
661
662## STDOUT:
663a=b
664a=a
665inner=z
666inner2=z
667## END
668
669#### io->eval with in_captured_frame=true can express cd builtin
670
671proc 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
687var i = 42
688my-cd /tmp {
689 echo $PWD
690 var my_pwd = PWD
691 var j = i + 1
692}
693echo "my_pwd=$my_pwd"
694echo "j = $j"
695
696## STDOUT:
697/tmp
698my_pwd=/tmp
699j = 43
700## END
701
702
703#### io->eval with in_captured_frame=true can express cd builtin in different module
704
705echo >lib.ysh '''
706const __provide__ = :| my-cd |
707
708proc 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
717use ./lib.ysh
718
719var i = 42
720lib my-cd /tmp {
721 #echo $PWD
722 #var my_pwd = PWD
723 var j = i + 1
724}
725#echo "my_pwd=$my_pwd"
726echo "j = $j"
727
728## STDOUT:
729j = 43
730## END
731
732#### io->eval() has cleaner scoping than shell's eval builtin
733
734#shopt --unset ysh:all
735shopt --set ysh:upgrade
736
737sh-eval() {
738 local do_not_leak=42 # this is visible
739 eval $1
740}
741
742sh-eval 'echo "eval string do_not_leak=$do_not_leak"'
743
744proc ysh-eval (;;; block) {
745 var do_not_leak = 99
746 call io->eval(block, in_captured_frame=true)
747}
748
749ysh-eval {
750 echo "ysh block do_not_leak=$do_not_leak"
751}
752
753## status: 1
754## STDOUT:
755eval string do_not_leak=42
756## END
757
758#### io->eval with in_captured_frame=true and setglobal
759
760echo >lib.ysh '''
761const __provide__ = :| p x |
762
763var x = "lib"
764
765proc p (;;; block) {
766 call io->eval(block, in_captured_frame=true)
767}
768'''
769
770use ./lib.ysh
771
772var x = 'main'
773
774lib 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!
783pp test_ (x)
784pp test_ (lib.x)
785
786## STDOUT:
787block arg x = main
788mutated = ZZ
789(Str) "ZZ"
790(Str) "lib"
791## END
792
793#### eval should have a sandboxed mode
794
795proc 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
814p {
815 echo $this
816}
817
818## status: 1
819## STDOUT:
820TODO
821## END
822
823#### io->evalExpr() with vars, dollar0, pos_args
824
825var ex = ^["$0 $1 $2 " ++ myvar]
826
827var vars = {myvar: 'hello'}
828var s = io->evalExpr(ex, dollar0='z', pos_args=:|a b c|, vars=vars)
829
830echo $s
831
832proc 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
846seq 5 | my-where [_line ~== 2 or _line ~== 4]
847echo ---
848
849seq 5 10 | my-where [_line % 2 === 1]
850
851## STDOUT:
852z a b hello
8532
8544
855---
8565
8577
8589
859## END