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

647 lines, 361 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(block) can read variables like eval ''
55
56# NO LONGER WORKS, but is this a feature rather than a bug?
57
58proc p2(code_str) {
59 var mylocal = 42
60 eval $code_str
61}
62
63p2 'echo mylocal=$mylocal'
64
65proc p (;;; block) {
66 var mylocal = 99
67 call io->eval(block)
68}
69
70p {
71 echo mylocal=$mylocal
72}
73
74
75## STDOUT:
76mylocal=42
77mylocal=99
78## END
79
80#### eval should have a sandboxed mode
81
82proc 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
94p {
95 echo $this
96}
97
98## status: 1
99## STDOUT:
100TODO
101## END
102
103#### io->eval with argv bindings
104call io->eval(^(echo "$@"), pos_args=:| foo bar baz |)
105call io->eval(^(pp test_ (:| $1 $2 $3 |)), pos_args=:| foo bar baz |)
106## STDOUT:
107foo bar baz
108(List) ["foo","bar","baz"]
109## END
110
111#### eval lines with argv bindings
112proc my-split (;;; block) {
113 while read --raw-line {
114 var cols = split(_reply)
115 call io->eval(block, pos_args=cols)
116 }
117}
118
119printf 'a b\nc d\n' | my-split {
120 echo "$2 $1"
121}
122
123printf '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
129proc p {
130 printf 'a b\nc d\n' | my-split {
131 var local2 = 'local2'
132 echo "$2 $1 $local2"
133 }
134}
135
136echo
137p
138
139## STDOUT:
140b a
141d c
142b a mylocal
143d c mylocal
144
145b a local2
146d c local2
147## END
148
149#### eval lines with var bindings
150
151proc 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
158printf '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
164proc p {
165 printf 'a b\nc d\n' | my-split {
166 var local2 = 'local2'
167 echo "$_line | $_first $local2"
168 }
169}
170
171echo
172p
173
174## STDOUT:
175a b | a mylocal
176c d | c mylocal
177
178a b | a local2
179c d | c local2
180## END
181
182#### eval with custom dollar0
183var b = ^(write $0)
184call io->eval(b, dollar0="my arg0")
185## STDOUT:
186my arg0
187## END
188
189#### eval with vars bindings
190var myVar = "abc"
191call io->eval(^(pp test_ (myVar)))
192call io->eval(^(pp test_ (myVar)), vars={ 'myVar': '123' })
193
194# eval doesn't modify it's environment
195call 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
204proc 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
214var mydicts = [{'a': 1}, {'b': 2}, {'c': 3}]
215foreach mydict in (mydicts) {
216 var mylocal = 'z'
217 setvar mydict.z = mylocal
218
219 pp test_ (mydict)
220 setvar mydict.d = 0
221}
222echo
223
224for 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
239proc __flag (short, long) {
240 echo "flag $short $long"
241}
242
243proc __arg (name) {
244 echo "arg $name"
245}
246
247proc parser (; spec ;; block) {
248 call io->eval(block, vars={ 'flag': __flag, 'arg': __arg })
249}
250
251parser (&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
259try { flag }
260if (_error.code !== 127) { error 'expected failure' }
261
262try { arg }
263if (_error.code !== 127) { error 'expected failure' }
264
265## STDOUT:
266flag -h --help
267arg file
268## END
269
270#### vars initializes the variable frame, but does not remember it
271var vars = { 'foo': 123 }
272call io->eval(^(var bar = 321;), vars=vars)
273pp test_ (vars)
274
275## STDOUT:
276(Dict) {"foo":123}
277## END
278
279#### eval pos_args must be strings
280call io->eval(^(true), pos_args=[1, 2, 3])
281## status: 3
282
283#### eval with vars follows same scoping as without
284
285proc 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
292var myVar = "baz"
293call io->eval(^(echo $myVar), vars={ someOtherVar: "bar" })
294call io->eval (^(echo $myVar))
295
296local-scope
297## STDOUT:
298baz
299baz
300foo
301foo
302## END
303
304#### eval 'mystring' vs. call io->eval(myblock)
305
306eval 'echo plain'
307echo plain=$?
308var b = ^(echo plain)
309call io->eval(b)
310echo plain=$?
311
312echo
313
314# This calls main_loop.Batch(), which catches
315# - error.Parse
316# - error.ErrExit
317# - error.FatalRuntime - glob errors, etc.?
318
319try {
320 eval 'echo one; false; echo two'
321}
322pp test_ (_error)
323
324# This calls CommandEvaluator.EvalCommand(), as blocks do
325
326var b = ^(echo one; false; echo two)
327try {
328 call io->eval(b)
329}
330pp test_ (_error)
331
332## STDOUT:
333plain
334plain=0
335plain
336plain=0
337
338one
339(Dict) {"code":1}
340one
341(Dict) {"code":1}
342## END
343
344#### io->evalToDict() - local and global
345
346var g = 'global'
347
348# in the global frame
349var d = io->evalToDict(^(var foo = 42; var bar = g;))
350pp test_ (d)
351
352# Same thing in a local frame
353proc 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}
365p 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
374var g = 'global'
375var cmd = parseCommand('var x = 42; echo hi; var y = g')
376#var cmd = parseCommand('echo hi')
377
378pp test_ (cmd)
379#pp asdl_ (cmd)
380
381var d = io->evalToDict(cmd)
382
383pp test_ (d)
384
385## STDOUT:
386<Block>
387hi
388(Dict) {"x":42,"y":"global"}
389## END
390
391#### parseCommand with syntax error
392
393try {
394 var cmd = parseCommand('echo >')
395}
396pp 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
405proc Dict ( ; out; ; block) {
406 var d = io->evalToDict(block)
407 call out->setValue(d)
408}
409
410# it can read f
411
412var myglobal = 'global'
413var k = 'k-shadowed'
414var k2 = 'k2-shadowed'
415
416Dict (&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
435pp test_ (d)
436
437exit
438
439# restored to the shadowed values
440echo k=$k
441echo k2=$k2
442
443proc 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
458proc Dict ( ; out; ; block) {
459 var d = io->evalToDict(block)
460 call out->setValue(d)
461}
462
463func f() {
464 var x = 42
465
466 Dict (&d) {
467 y = x + 1 # x is from outer scope
468 }
469 return (d)
470}
471
472var mydict = f()
473
474pp 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
482proc 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
494func f() {
495 var x = 42
496
497 yb-capture (&r) {
498 echo $[x + 1]
499 }
500
501 return (r)
502}
503
504var result = f()
505
506pp test_ (result)
507
508## STDOUT:
509(Dict) {"status":0,"stdout":"43\n"}
510## END
511
512
513#### Dict (&d) and setvar
514
515proc 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
526var outer = 'xx'
527
528Dict (&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
541pp test_ (d)
542echo "after Dict outer=$outer"
543
544echo
545
546
547# Now do the same thing inside a proc
548
549proc 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
561p
562
563echo "after p outer=$outer"
564
565## STDOUT:
566Dict proc global outer=xx
567inside Dict outer=xx
568(Dict) {"outer2":"outer2","not_declared":"yy"}
569after Dict outer=zz
570
571Dict proc global outer=zz
572(Dict) {"p":99}
573[p] after Dict outer=p-outer-mutated
574after p outer=zz
575## END
576
577#### Dict (&d) and setglobal
578
579proc Dict ( ; out; ; block) {
580 var d = io->evalToDict(block)
581 call out->setValue(d)
582}
583
584var g = 'xx'
585
586Dict (&d) {
587 setglobal g = 'zz'
588
589 a = 42
590 pp frame_vars_
591}
592echo
593
594pp test_ (d)
595echo g=$g
596
597#pp frame_vars_
598
599## STDOUT:
600 [frame_vars_] __rear__ a
601
602(Dict) {"a":42}
603g=zz
604## END
605
606#### bindings created shvar persist, which is different than evalToDict()
607
608var a = 'a'
609shvar IFS=: a='b' {
610 echo a=$a
611 inner=z
612 var inner2 = 'z'
613}
614echo a=$a
615echo inner=$inner
616echo inner2=$inner2
617
618## STDOUT:
619a=b
620a=a
621inner=z
622inner2=z
623## END
624
625#### Block Closures in a Loop !
626
627proc task (; tasks; ; b) {
628 call tasks->append(b)
629}
630
631func makeTasks() {
632 var tasks = []
633 for i in (0 .. 3) {
634 task (tasks) { echo "i = $i" }
635 }
636 return (tasks)
637}
638
639var blocks = makeTasks()
640#= blocks
641
642for b in (blocks) {
643 call io->eval(b)
644}
645
646## STDOUT:
647## END