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
8 var b = ^(echo obj)
9 call io->eval (b)
10
11 call io->eval (^(echo command literal))
12
13 # Doesn't work because it's a positional arg
14 eval { echo block }
15
16 ## status: 2
17 ## STDOUT:
18 obj
19 command literal
20 ## END
21
22
23 #### Eval a block within a proc
24 proc run (;;; block) {
25 call io->eval(block)
26 }
27
28 run {
29 echo 'In a block!'
30 }
31 ## STDOUT:
32 In a block!
33 ## END
34
35 #### Eval block created by calling a proc
36 proc lazy-block ( ; out; ; block) {
37 call out->setValue(block)
38 }
39
40 var myglobal = 0
41
42 lazy-block (&my_block) {
43 json write (myglobal)
44 }
45
46 call io->eval(my_block)
47 setvar myglobal = 1
48 call io->eval(my_block)
49 ## STDOUT:
50 0
51 1
52 ## END
53
54 #### io->eval with argv bindings
55 call io->eval(^(echo "$@"), pos_args=:| foo bar baz |)
56 call io->eval(^(pp test_ (:| $1 $2 $3 |)), pos_args=:| foo bar baz |)
57 ## STDOUT:
58 foo bar baz
59 (List) ["foo","bar","baz"]
60 ## END
61
62 #### eval lines with argv bindings
63 proc my-split (;;; block) {
64 while read --raw-line {
65 var cols = split(_reply)
66 call io->eval(block, pos_args=cols)
67 }
68 }
69
70 printf 'a b\nc d\n' | my-split {
71 echo "$2 $1"
72 }
73
74 printf '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
80 proc p {
81 printf 'a b\nc d\n' | my-split {
82 var local2 = 'local2'
83 echo "$2 $1 $local2"
84 }
85 }
86
87 echo
88 p
89
90 ## STDOUT:
91 b a
92 d c
93 b a mylocal
94 d c mylocal
95
96 b a local2
97 d c local2
98 ## END
99
100 #### eval lines with var bindings
101
102 proc 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
109 printf '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
115 proc p {
116 printf 'a b\nc d\n' | my-split {
117 var local2 = 'local2'
118 echo "$_line | $_first $local2"
119 }
120 }
121
122 echo
123 p
124
125 ## STDOUT:
126 a b | a mylocal
127 c d | c mylocal
128
129 a b | a local2
130 c d | c local2
131 ## END
132
133 #### eval with custom dollar0
134 var b = ^(write $0)
135 call io->eval(b, dollar0="my arg0")
136 ## STDOUT:
137 my arg0
138 ## END
139
140 #### eval with vars bindings
141 var myVar = "abc"
142 call io->eval(^(pp test_ (myVar)))
143 call io->eval(^(pp test_ (myVar)), vars={ 'myVar': '123' })
144
145 # eval doesn't modify it's environment
146 call 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
155 proc 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
165 var mydicts = [{'a': 1}, {'b': 2}, {'c': 3}]
166 foreach mydict in (mydicts) {
167 var mylocal = 'z'
168 setvar mydict.z = mylocal
169
170 pp test_ (mydict)
171 setvar mydict.d = 0
172 }
173 echo
174
175 for 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
190 proc __flag (short, long) {
191 echo "flag $short $long"
192 }
193
194 proc __arg (name) {
195 echo "arg $name"
196 }
197
198 proc parser (; spec ;; block) {
199 call io->eval(block, vars={ 'flag': __flag, 'arg': __arg })
200 }
201
202 parser (&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
210 try { flag }
211 if (_error.code !== 127) { error 'expected failure' }
212
213 try { arg }
214 if (_error.code !== 127) { error 'expected failure' }
215
216 ## STDOUT:
217 flag -h --help
218 arg file
219 ## END
220
221 #### vars initializes the variable frame, but does not remember it
222 var vars = { 'foo': 123 }
223 call io->eval(^(var bar = 321;), vars=vars)
224 pp test_ (vars)
225
226 ## STDOUT:
227 (Dict) {"foo":123}
228 ## END
229
230 #### eval pos_args must be strings
231 call io->eval(^(true), pos_args=[1, 2, 3])
232 ## status: 3
233
234 #### eval with vars follows same scoping as without
235
236 proc 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
243 var myVar = "baz"
244 call io->eval(^(echo $myVar), vars={ someOtherVar: "bar" })
245 call io->eval (^(echo $myVar))
246
247 local-scope
248 ## STDOUT:
249 baz
250 baz
251 foo
252 foo
253 ## END
254
255 #### eval 'mystring' vs. call io->eval(myblock)
256
257 eval 'echo plain'
258 echo plain=$?
259 var b = ^(echo plain)
260 call io->eval(b)
261 echo plain=$?
262
263 echo
264
265 # This calls main_loop.Batch(), which catches
266 # - error.Parse
267 # - error.ErrExit
268 # - error.FatalRuntime - glob errors, etc.?
269
270 try {
271 eval 'echo one; false; echo two'
272 }
273 pp test_ (_error)
274
275 # This calls CommandEvaluator.EvalCommand(), as blocks do
276
277 var b = ^(echo one; false; echo two)
278 try {
279 call io->eval(b)
280 }
281 pp test_ (_error)
282
283 ## STDOUT:
284 plain
285 plain=0
286 plain
287 plain=0
288
289 one
290 (Dict) {"code":1}
291 one
292 (Dict) {"code":1}
293 ## END
294
295 #### io->evalToDict() - local and global
296
297 var g = 'global'
298
299 # in the global frame
300 var d = io->evalToDict(^(var foo = 42; var bar = g;))
301 pp test_ (d)
302
303 # Same thing in a local frame
304 proc 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 }
316 p 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
325 var g = 'global'
326 var cmd = parseCommand('var x = 42; echo hi; var y = g')
327 #var cmd = parseCommand('echo hi')
328
329 pp test_ (cmd)
330 #pp asdl_ (cmd)
331
332 var d = io->evalToDict(cmd)
333
334 pp test_ (d)
335
336 ## STDOUT:
337 <Command>
338 hi
339 (Dict) {"x":42,"y":"global"}
340 ## END
341
342 #### parseCommand with syntax error
343
344 try {
345 var cmd = parseCommand('echo >')
346 }
347 pp 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
356 proc Dict ( ; out; ; block) {
357 var d = io->evalToDict(block)
358 call out->setValue(d)
359 }
360
361 # it can read f
362
363 var myglobal = 'global'
364 var k = 'k-shadowed'
365 var k2 = 'k2-shadowed'
366
367 Dict (&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
386 pp test_ (d)
387
388 exit
389
390 # restored to the shadowed values
391 echo k=$k
392 echo k2=$k2
393
394 proc 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
409 proc Dict ( ; out; ; block) {
410 var d = io->evalToDict(block)
411 call out->setValue(d)
412 }
413
414 func f() {
415 var x = 42
416
417 Dict (&d) {
418 y = x + 1 # x is from outer scope
419 }
420 return (d)
421 }
422
423 var mydict = f()
424
425 pp 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
433 proc 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
445 func f() {
446 var x = 42
447
448 yb-capture (&r) {
449 echo $[x + 1]
450 }
451
452 return (r)
453 }
454
455 var result = f()
456
457 pp test_ (result)
458
459 ## STDOUT:
460 (Dict) {"status":0,"stdout":"43\n"}
461 ## END
462
463
464 #### Dict (&d) and setvar
465
466 proc 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
477 var outer = 'xx'
478
479 Dict (&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
492 pp test_ (d)
493 echo "after Dict outer=$outer"
494
495 echo
496
497
498 # Now do the same thing inside a proc
499
500 proc 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
512 p
513
514 echo "after p outer=$outer"
515
516 ## STDOUT:
517 Dict proc global outer=xx
518 inside Dict outer=xx
519 (Dict) {"outer2":"outer2","not_declared":"yy"}
520 after Dict outer=zz
521
522 Dict proc global outer=zz
523 (Dict) {"p":99}
524 [p] after Dict outer=p-outer-mutated
525 after p outer=zz
526 ## END
527
528 #### Dict (&d) and setglobal
529
530 proc Dict ( ; out; ; block) {
531 var d = io->evalToDict(block)
532 call out->setValue(d)
533 }
534
535 var g = 'xx'
536
537 Dict (&d) {
538 setglobal g = 'zz'
539
540 a = 42
541 pp frame_vars_
542 }
543 echo
544
545 pp test_ (d)
546 echo g=$g
547
548 #pp frame_vars_
549
550 ## STDOUT:
551 [frame_vars_] __E__ a
552
553 (Dict) {"a":42}
554 g=zz
555 ## END
556
557 #### bindings created shvar persist, which is different than evalToDict()
558
559 var a = 'a'
560 shvar IFS=: a='b' {
561 echo a=$a
562 inner=z
563 var inner2 = 'z'
564 }
565 echo a=$a
566 echo inner=$inner
567 echo inner2=$inner2
568
569 ## STDOUT:
570 a=b
571 a=a
572 inner=z
573 inner2=z
574 ## END
575
576 #### io->evalInFrame() can express try, cd builtins
577
578 var frag = ^(echo $i)
579
580 proc 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
594 var i = 42
595 my-cd /tmp {
596 echo $PWD
597 var j = i + 1
598 }
599 echo "j = $j"
600
601 ## STDOUT:
602 x: i = 0, j = 2
603 x: i = 1, j = 3
604 x: 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
612 proc p2(code_str) {
613 var mylocal = 42
614 eval $code_str
615 }
616
617 p2 'echo mylocal=$mylocal'
618
619 proc 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
630 p {
631 echo mylocal=$mylocal
632 }
633
634
635 ## STDOUT:
636 mylocal=42
637 mylocal=99
638 ## END
639
640 #### eval should have a sandboxed mode
641
642 proc 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
654 p {
655 echo $this
656 }
657
658 ## status: 1
659 ## STDOUT:
660 TODO
661 ## END
662
663 #### io->evalExpr() with vars, dollar0, pos_args
664
665 var ex = ^["$0 $1 $2 " ++ myvar]
666
667 var vars = {myvar: 'hello'}
668 var s = io->evalExpr(ex, dollar0='z', pos_args=:|a b c|, vars=vars)
669
670 echo $s
671
672 proc 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
686 seq 5 | my-where [_line ~== 2 or _line ~== 4]
687 echo ---
688
689 seq 5 10 | my-where [_line % 2 === 1]
690
691 ## STDOUT:
692 z a b hello
693 2
694 4
695 ---
696 5
697 7
698 9
699 ## END