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

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