1 ## oils_failures_allowed: 1
2
3 #### GetValue scope and shopt --unset dynamic_scope
4 shopt --set parse_proc
5
6 f() {
7 echo "sh x=$x"
8 }
9
10 proc p {
11 echo "ysh x=$x"
12 }
13
14 demo() {
15 local x=dynamic
16 f
17 p
18
19 shopt --unset dynamic_scope
20 f
21 }
22
23 x=global
24 demo
25 echo x=$x
26
27 ## STDOUT:
28 sh x=dynamic
29 ysh x=global
30 sh x=global
31 x=global
32 ## END
33
34
35 #### SetValue scope and shopt --unset dynamic_scope
36 shopt --set parse_proc
37
38 f() {
39 x=f
40 }
41
42 proc p {
43 var x = 'p'
44 }
45
46 demo() {
47 local x=stack
48 echo x=$x
49 echo ---
50
51 f
52 echo f x=$x
53
54 x=stack
55 p
56 echo p x=$x
57
58 shopt --unset dynamic_scope
59 x=stack
60 f
61 echo funset x=$x
62 }
63
64 x=global
65 demo
66
67 echo ---
68 echo x=$x
69
70 ## STDOUT:
71 x=stack
72 ---
73 f x=f
74 p x=stack
75 funset x=stack
76 ---
77 x=global
78 ## END
79
80 #### read scope
81 set -o errexit
82
83 read-x() {
84 echo dynamic-scope | read x
85 }
86 demo() {
87 local x=42
88 echo x_before=$x
89 read-x
90 echo x_after=$x
91 }
92 demo
93 echo x=$x
94
95 echo ---
96
97 # Now 'read x' creates a local variable
98 shopt --unset dynamic_scope
99 demo
100 echo x=$x
101
102 ## STDOUT:
103 x_before=42
104 x_after=dynamic-scope
105 x=
106 ---
107 x_before=42
108 x_after=42
109 x=
110 ## END
111
112 #### printf -v x respects dynamic_scope
113 set -o errexit
114
115 set-x() {
116 printf -v x "%s" dynamic-scope
117 }
118 demo() {
119 local x=42
120 echo x=$x
121 set-x
122 echo x=$x
123 }
124 demo
125 echo x=$x
126
127 echo ---
128
129 shopt --unset dynamic_scope # should NOT affect read
130 demo
131 echo x=$x
132
133 ## STDOUT:
134 x=42
135 x=dynamic-scope
136 x=
137 ---
138 x=42
139 x=42
140 x=
141 ## END
142
143 #### printf -v a[i] respects dynamic_scope
144 set -o errexit
145
146 set-item() {
147 printf -v 'a[1]' "%s" dynamic-scope
148 }
149 demo() {
150 local -a a=(41 42 43)
151 echo "a[1]=${a[1]}"
152 set-item
153 echo "a[1]=${a[1]}"
154 }
155 demo
156 echo "a[1]=${a[1]}"
157
158 echo ---
159
160 shopt --unset dynamic_scope # should NOT affect read
161 demo
162 echo "a[1]=${a[1]}"
163
164 ## STDOUT:
165 a[1]=42
166 a[1]=dynamic-scope
167 a[1]=
168 ---
169 a[1]=42
170 a[1]=42
171 a[1]=
172 ## END
173
174 #### ${undef=a} and shopt --unset dynamic_scope
175
176 set-x() {
177 : ${x=new}
178 }
179 demo() {
180 local x
181 echo x=$x
182 set-x
183 echo x=$x
184 }
185
186 demo
187 echo x=$x
188
189 echo ---
190
191 # Now this IS affected?
192 shopt --unset dynamic_scope
193 demo
194 echo x=$x
195 ## STDOUT:
196 x=
197 x=new
198 x=
199 ---
200 x=
201 x=
202 x=
203 ## END
204
205 #### declare -p respects it
206
207 ___g=G
208
209 show-vars() {
210 local ___x=X
211 declare -p | grep '___'
212 echo status=$?
213
214 echo -
215 declare -p ___y | grep '___'
216 echo status=$?
217 }
218
219 demo() {
220 local ___y=Y
221
222 show-vars
223 echo ---
224 shopt --unset dynamic_scope
225 show-vars
226 }
227
228 demo
229
230 ## STDOUT:
231 declare -- ___g=G
232 declare -- ___x=X
233 declare -- ___y=Y
234 status=0
235 -
236 declare -- ___y=Y
237 status=0
238 ---
239 declare -- ___g=G
240 declare -- ___x=X
241 status=0
242 -
243 status=1
244 ## END
245
246
247 #### OshLanguageSetValue constructs
248
249 f() {
250 (( x = 42 ))
251 }
252 demo() {
253 f
254 echo x=$x
255 }
256
257 demo
258
259 echo ---
260
261 shopt --unset dynamic_scope
262
263 unset x
264
265 demo
266
267 echo --- global
268 echo x=$x
269 ## STDOUT:
270 x=42
271 ---
272 x=
273 --- global
274 x=
275 ## END
276
277
278 #### shell assignments 'neutered' inside 'proc'
279 shopt --set parse_proc
280
281 # They can't mutate globals or anything higher on the stack
282
283 proc p {
284 # TODO: declare should be disallowed in YSH, just like shell functions.
285
286 #declare g=PROC
287 #export e=PROC
288 var g = 'PROC'
289 var e = 'PROC'
290 }
291
292 f() {
293 g=SH
294 export e=SH
295 }
296
297 e=E
298 g=G
299 p
300 echo e=$e g=$g
301
302 p
303 echo e=$e g=$g
304
305 f
306 echo e=$e g=$g
307
308 ## STDOUT:
309 e=E g=G
310 e=E g=G
311 e=SH g=SH
312 ## END
313
314 #### setglobal still allows setting globals
315 shopt --set parse_proc
316
317 proc p {
318 setglobal new_global = 'p'
319 setglobal g = 'p'
320 }
321
322 var g = 'G'
323
324 p
325
326 echo g=$g new_global=$new_global
327 ## STDOUT:
328 g=p new_global=p
329 ## END
330
331 #### setglobal d[key] inside proc should mutate global (bug #1841)
332
333 shopt -s ysh:upgrade
334
335 var g = {}
336
337 proc mutate {
338 var g = {'local': 1} # shadows global var
339
340 setglobal g.key = 'mutated'
341 setglobal g['key2'] = 'mutated'
342
343 echo 'local that is ignored'
344 pp test_ (g)
345 }
346
347 echo 'BEFORE mutate global'
348 pp test_ (g)
349
350 mutate
351
352 echo 'AFTER mutate global'
353 pp test_ (g)
354
355 ## STDOUT:
356 BEFORE mutate global
357 (Dict) {}
358 local that is ignored
359 (Dict) {"local":1}
360 AFTER mutate global
361 (Dict) {"key":"mutated","key2":"mutated"}
362 ## END
363
364 #### setglobal a[i] inside proc
365 shopt -s ysh:upgrade
366
367 var a = [0]
368
369 proc mutate {
370 var a = [1] # shadows global var
371
372 echo 'local that is ignored'
373 setglobal a[0] = 42
374
375 pp test_ (a)
376 }
377
378 echo 'BEFORE mutate global'
379 pp test_ (a)
380
381 mutate
382
383 echo 'AFTER mutate global'
384 pp test_ (a)
385
386 ## STDOUT:
387 BEFORE mutate global
388 (List) [0]
389 local that is ignored
390 (List) [1]
391 AFTER mutate global
392 (List) [42]
393 ## END
394
395 #### setglobal a[i] += and d.key +=
396 shopt -s ysh:upgrade
397
398 var mylist = [0]
399 var mydict = {k: 0}
400
401 proc mutate {
402 # these locals are ignored
403 var mylist = []
404 var mydict = {}
405
406 setglobal mylist[0] += 5
407 setglobal mydict['k'] += 5
408 }
409
410 mutate
411
412 pp test_ (mylist)
413 pp test_ (mydict)
414
415 ## STDOUT:
416 (List) [5]
417 (Dict) {"k":5}
418 ## END
419
420 #### setglobal a[i] - i can be local or global
421 shopt -s ysh:upgrade
422
423 var mylist = [0, 1]
424 var mydict = {k: 0, n: 1}
425
426 var i = 0
427 var key = 'k'
428
429 proc mutate1 {
430 var mylist = [] # IGNORED
431 var mydict = {} # IGNORED
432
433 var i = 1
434 var key = 'n'
435
436 setglobal mylist[i] = 11
437 setglobal mydict[key] = 11
438 }
439
440 # Same thing without locals
441 proc mutate2 {
442 var mylist = [] # IGNORED
443 var mydict = {} # IGNORED
444
445 setglobal mylist[i] = 22
446 setglobal mydict[key] = 22
447 }
448
449 mutate1
450
451 pp test_ (mylist)
452 pp test_ (mydict)
453 echo
454
455 mutate2
456
457 pp test_ (mylist)
458 pp test_ (mydict)
459
460 ## STDOUT:
461 (List) [0,11]
462 (Dict) {"k":0,"n":11}
463
464 (List) [22,11]
465 (Dict) {"k":22,"n":11}
466 ## END
467
468 #### unset inside proc - closures and dynamic scope
469 shopt --set parse_brace
470 shopt --set parse_proc
471
472 shellfunc() {
473 unset x
474 }
475
476 proc unset-proc() {
477 unset x
478 }
479
480 proc unset-proc-dynamic-scope() {
481 shopt --set dynamic_scope { # turn it back on
482 unset x
483 }
484 }
485
486 x=foo
487 shellfunc
488 echo shellfunc x=$x
489
490 x=bar
491 unset-proc
492 echo unset-proc x=$x
493
494 x=spam
495 unset-proc
496 echo unset-proc-dynamic-scope x=$x
497
498 ## STDOUT:
499 shellfunc x=
500 unset-proc x=
501 unset-proc-dynamic-scope x=
502 ## END
503
504 #### unset composes when you turn on dynamic scope
505 shopt -s ysh:all
506
507 proc unset-two (v, w) {
508 shopt --set dynamic_scope {
509 unset $v
510 unset $w
511 }
512 }
513
514 demo() {
515 local x=X
516 local y=Y
517
518 echo "x=$x y=$y"
519
520 unset-two x y
521
522 shopt --unset nounset
523 echo "x=$x y=$y"
524 }
525
526 demo
527 ## STDOUT:
528 x=X y=Y
529 x= y=
530 ## END
531
532 #### Temp Bindings
533 shopt --set parse_proc
534
535 myfunc() {
536 echo myfunc FOO=$FOO
537 }
538 proc myproc() {
539 echo myproc FOO=$FOO
540 }
541
542 FOO=bar myfunc
543 FOO=bar myproc
544 FOO=bar echo inline FOO=$FOO
545 FOO=bar printenv.py FOO
546
547 ## STDOUT:
548 myfunc FOO=bar
549 myproc FOO=
550 inline FOO=
551 bar
552 ## END
553
554 #### cd blocks don't introduce new scopes
555 shopt --set ysh:upgrade
556
557 var x = 42
558 cd / {
559 var y = 0
560 var z = 1
561 echo $x $y $z
562 setvar y = 43
563 }
564 setvar z = 44
565 echo $x $y $z
566
567 ## STDOUT:
568 42 0 1
569 42 43 44
570 ## END
571
572 #### shvar IFS=x { myproc } rather than IFS=x myproc - no dynamic scope
573
574 # Note: osh/split.py uses dynamic scope to look up IFS
575 # TODO: Should use LANG example to demonstrate
576
577 #shopt --set ysh:upgrade # this would disable word splitting
578
579 shopt --set parse_proc
580 shopt --set parse_brace
581 #shopt --set env_obj
582
583 s='xzx zxz'
584
585 shellfunc() {
586 echo shellfunc IFS="$IFS"
587 argv.py $s
588 }
589
590 proc myproc() {
591 echo myproc IFS="$IFS"
592 argv.py $s
593 }
594
595 IFS=: $REPO_ROOT/spec/bin/printenv.py IFS
596
597 # default value
598 echo "$IFS" | od -A n -t x1
599
600 IFS=' z'
601 echo IFS="$IFS"
602 echo
603
604 shellfunc
605 echo
606
607 IFS=' x' shellfunc
608 echo
609
610 # Problem: $IFS in procs only finds GLOBAL values, so we get IFS=' z' rather than IFS=' x'.
611 # But when actually splitting, $IFS is a 'shvar' which respects DYNAMIC scope.
612 #
613 # Can use shvarGet('IFS') instead
614
615 IFS=' x' myproc
616 echo
617
618 # YSH solution to the problem
619 shvar IFS=' x' {
620 myproc
621 }
622
623 ## STDOUT:
624 :
625 20 09 0a 0a
626 IFS= z
627
628 shellfunc IFS= z
629 ['x', 'x', 'x']
630
631 shellfunc IFS= x
632 ['', 'z', 'z', 'z']
633
634 myproc IFS= z
635 ['x', 'x', 'x']
636
637 myproc IFS= x
638 ['', 'z', 'z', 'z']
639 ## END
640
641 #### shvar builtin syntax
642 shopt --set ysh:upgrade
643 shopt --unset errexit
644
645 # no block
646 shvar
647 echo status=$?
648
649 shvar { # no arg
650 true
651 }
652 echo status=$?
653
654 shvar foo { # should be name=value
655 true
656 }
657 echo status=$?
658 ## STDOUT:
659 status=2
660 status=2
661 status=2
662 ## END
663
664
665 #### shvar and shvarGet() obey dynamic scope
666
667 # On the other hand, in YSH
668 # - $x does local/closure/global scope
669 # - FOO=foo mycommand modifies the ENV object - shopt --set env_obj
670
671 shopt --set ysh:all
672
673 proc p3 {
674 echo FOO=$[shvarGet('FOO')] # dynamic scope
675 echo FOO=$FOO # fails, not dynamic scope
676 }
677
678 proc p2 {
679 p3
680 }
681
682 proc p {
683 shvar FOO=foo {
684 p2
685 }
686 }
687
688 p
689
690 ## status: 1
691 ## STDOUT:
692 FOO=foo
693 ## END
694
695
696 #### shvar global
697 shopt --set ysh:upgrade
698 shopt --unset nounset
699
700 echo _ESCAPER=$_ESCAPER
701 echo _DIALECT=$_DIALECT
702
703 shvar _ESCAPER=html _DIALECT=ninja {
704 echo block _ESCAPER=$_ESCAPER
705 echo block _DIALECT=$_DIALECT
706 }
707
708 echo _ESCAPER=$_ESCAPER
709 echo _DIALECT=$_DIALECT
710
711 # Now set them
712 _ESCAPER=foo
713 _DIALECT=bar
714
715 echo ___
716
717 echo _ESCAPER=$_ESCAPER
718 echo _DIALECT=$_DIALECT
719
720 shvar _ESCAPER=html _DIALECT=ninja {
721 echo block _ESCAPER=$_ESCAPER
722 echo block _DIALECT=$_DIALECT
723
724 shvar _ESCAPER=nested {
725 echo nested _ESCAPER=$_ESCAPER
726 echo nested _DIALECT=$_DIALECT
727 }
728 }
729
730 echo _ESCAPER=$_ESCAPER
731 echo _DIALECT=$_DIALECT
732
733 ## STDOUT:
734 _ESCAPER=
735 _DIALECT=
736 block _ESCAPER=html
737 block _DIALECT=ninja
738 _ESCAPER=
739 _DIALECT=
740 ___
741 _ESCAPER=foo
742 _DIALECT=bar
743 block _ESCAPER=html
744 block _DIALECT=ninja
745 nested _ESCAPER=nested
746 nested _DIALECT=ninja
747 _ESCAPER=foo
748 _DIALECT=bar
749 ## END
750
751 #### shvar local
752 shopt --set ysh:upgrade # blocks
753 shopt --unset simple_word_eval # test word splitting
754
755 proc foo {
756 shvar IFS=x MYTEMP=foo {
757 echo IFS="$IFS"
758 argv.py $s
759 echo MYTEMP=${MYTEMP:-undef}
760 }
761 }
762 var s = 'a b c'
763 argv.py $s
764 foo
765 argv.py $s
766 echo MYTEMP=${MYTEMP:-undef}
767 ## STDOUT:
768 ['a', 'b', 'c']
769 IFS=x
770 ['a b c']
771 MYTEMP=foo
772 ['a', 'b', 'c']
773 MYTEMP=undef
774 ## END
775
776 #### shvar IFS
777 shopt --set ysh:upgrade
778
779 proc myproc() {
780 echo "$IFS" | od -A n -t x1
781
782 local mylocal=x
783 shvar IFS=w {
784 echo inside IFS="$IFS"
785 echo mylocal="$mylocal" # I do NOT want a new scope!
786 }
787 echo "$IFS" | od -A n -t x1
788 }
789
790 myproc
791 ## STDOUT:
792 20 09 0a 0a
793 inside IFS=w
794 mylocal=x
795 20 09 0a 0a
796 ## END
797
798 #### Compare shell func vs. proc, $IFS vs. shvarGet('IFS')
799
800 shopt --set parse_proc
801
802 s='xzx zxz'
803
804 shellfunc() { # dynamic scope everywhere
805 echo shellfunc
806 echo IFS="$IFS"
807 echo shvarGet IFS=$[shvarGet('IFS')]
808 argv.py $s
809 }
810
811 proc myproc { # no dynamic scope
812
813 # Subtle behavior: we see 'x' rather than "temp frame" 'z' - I think because
814 # there is a CHAIN of __E__ enclosed scopes, up to the global frame.
815 #
816 # That frame comes FIRST. That seems OK, but it changed when procs became closures.
817 proc p2 {
818 echo "myproc -> p2"
819 echo IFS="$IFS"
820 echo shvarGet IFS=$[shvarGet('IFS')] # dynamic scope opt-in
821 argv.py $s # dynamic scope in osh/split.py
822 }
823
824 p2
825 }
826
827 IFS=x
828
829 IFS=z shellfunc
830 echo
831
832 # this makes a temp frame, but the proc can't see it?
833 IFS=z myproc
834 echo
835
836 # null
837 echo $[shvarGet('nonexistent')]
838
839 ## STDOUT:
840 shellfunc
841 IFS=z
842 shvarGet IFS=z
843 ['x', 'x ', 'x']
844
845 myproc -> p2
846 IFS=x
847 shvarGet IFS=x
848 ['', 'z', ' z', 'z']
849
850 null
851 ## END
852
853 #### func and proc are like var, with respect to closures
854 shopt --set ysh:all
855
856 proc test-var {
857 var x = 'outer'
858 proc inner {
859 var x = 'inner'
860 # note: static check is broken now
861 #setvar x = 'inner'
862 echo "inner $x"
863 }
864 inner
865 echo "outer $x"
866 }
867
868 # Note: state.YshDecl flag somehow doesn't make a difference here?
869 proc test-func {
870 func x() { return ('outer') }
871 proc inner2 {
872 func x() { return ('inner') }
873 echo "inner $[x()]"
874 }
875 inner2
876 echo "outer $[x()]"
877 }
878
879 proc test-proc {
880 proc x { echo 'outer' }
881 proc inner3 {
882 proc x { echo 'inner' }
883 x
884 }
885 inner3
886 x
887 }
888
889
890 test-var
891 echo
892
893 test-func
894 echo
895
896 test-proc
897
898 ## STDOUT:
899 inner inner
900 outer outer
901
902 inner inner
903 outer outer
904
905 inner
906 outer
907 ## END