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