OILS / spec / ysh-scope.test.sh View on Github | oils.pub

907 lines, 587 significant
1## oils_failures_allowed: 1
2
3#### GetValue scope and shopt --unset dynamic_scope
4shopt --set parse_proc
5
6f() {
7 echo "sh x=$x"
8}
9
10proc p {
11 echo "ysh x=$x"
12}
13
14demo() {
15 local x=dynamic
16 f
17 p
18
19 shopt --unset dynamic_scope
20 f
21}
22
23x=global
24demo
25echo x=$x
26
27## STDOUT:
28sh x=dynamic
29ysh x=global
30sh x=global
31x=global
32## END
33
34
35#### SetValue scope and shopt --unset dynamic_scope
36shopt --set parse_proc
37
38f() {
39 x=f
40}
41
42proc p {
43 var x = 'p'
44}
45
46demo() {
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
64x=global
65demo
66
67echo ---
68echo x=$x
69
70## STDOUT:
71x=stack
72---
73f x=f
74p x=stack
75funset x=stack
76---
77x=global
78## END
79
80#### read scope
81set -o errexit
82
83read-x() {
84 echo dynamic-scope | read x
85}
86demo() {
87 local x=42
88 echo x_before=$x
89 read-x
90 echo x_after=$x
91}
92demo
93echo x=$x
94
95echo ---
96
97# Now 'read x' creates a local variable
98shopt --unset dynamic_scope
99demo
100echo x=$x
101
102## STDOUT:
103x_before=42
104x_after=dynamic-scope
105x=
106---
107x_before=42
108x_after=42
109x=
110## END
111
112#### printf -v x respects dynamic_scope
113set -o errexit
114
115set-x() {
116 printf -v x "%s" dynamic-scope
117}
118demo() {
119 local x=42
120 echo x=$x
121 set-x
122 echo x=$x
123}
124demo
125echo x=$x
126
127echo ---
128
129shopt --unset dynamic_scope # should NOT affect read
130demo
131echo x=$x
132
133## STDOUT:
134x=42
135x=dynamic-scope
136x=
137---
138x=42
139x=42
140x=
141## END
142
143#### printf -v a[i] respects dynamic_scope
144set -o errexit
145
146set-item() {
147 printf -v 'a[1]' "%s" dynamic-scope
148}
149demo() {
150 local -a a=(41 42 43)
151 echo "a[1]=${a[1]}"
152 set-item
153 echo "a[1]=${a[1]}"
154}
155demo
156echo "a[1]=${a[1]}"
157
158echo ---
159
160shopt --unset dynamic_scope # should NOT affect read
161demo
162echo "a[1]=${a[1]}"
163
164## STDOUT:
165a[1]=42
166a[1]=dynamic-scope
167a[1]=
168---
169a[1]=42
170a[1]=42
171a[1]=
172## END
173
174#### ${undef=a} and shopt --unset dynamic_scope
175
176set-x() {
177 : ${x=new}
178}
179demo() {
180 local x
181 echo x=$x
182 set-x
183 echo x=$x
184}
185
186demo
187echo x=$x
188
189echo ---
190
191# Now this IS affected?
192shopt --unset dynamic_scope
193demo
194echo x=$x
195## STDOUT:
196x=
197x=new
198x=
199---
200x=
201x=
202x=
203## END
204
205#### declare -p respects it
206
207___g=G
208
209show-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
219demo() {
220 local ___y=Y
221
222 show-vars
223 echo ---
224 shopt --unset dynamic_scope
225 show-vars
226}
227
228demo
229
230## STDOUT:
231declare -- ___g=G
232declare -- ___x=X
233declare -- ___y=Y
234status=0
235-
236declare -- ___y=Y
237status=0
238---
239declare -- ___g=G
240declare -- ___x=X
241status=0
242-
243status=1
244## END
245
246
247#### OshLanguageSetValue constructs
248
249f() {
250 (( x = 42 ))
251}
252demo() {
253 f
254 echo x=$x
255}
256
257demo
258
259echo ---
260
261shopt --unset dynamic_scope
262
263unset x
264
265demo
266
267echo --- global
268echo x=$x
269## STDOUT:
270x=42
271---
272x=
273--- global
274x=
275## END
276
277
278#### shell assignments 'neutered' inside 'proc'
279shopt --set parse_proc
280
281# They can't mutate globals or anything higher on the stack
282
283proc 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
292f() {
293 g=SH
294 export e=SH
295}
296
297e=E
298g=G
299p
300echo e=$e g=$g
301
302p
303echo e=$e g=$g
304
305f
306echo e=$e g=$g
307
308## STDOUT:
309e=E g=G
310e=E g=G
311e=SH g=SH
312## END
313
314#### setglobal still allows setting globals
315shopt --set parse_proc
316
317proc p {
318 setglobal new_global = 'p'
319 setglobal g = 'p'
320}
321
322var g = 'G'
323
324p
325
326echo g=$g new_global=$new_global
327## STDOUT:
328g=p new_global=p
329## END
330
331#### setglobal d[key] inside proc should mutate global (bug #1841)
332
333shopt -s ysh:upgrade
334
335var g = {}
336
337proc 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
347echo 'BEFORE mutate global'
348pp test_ (g)
349
350mutate
351
352echo 'AFTER mutate global'
353pp test_ (g)
354
355## STDOUT:
356BEFORE mutate global
357(Dict) {}
358local that is ignored
359(Dict) {"local":1}
360AFTER mutate global
361(Dict) {"key":"mutated","key2":"mutated"}
362## END
363
364#### setglobal a[i] inside proc
365shopt -s ysh:upgrade
366
367var a = [0]
368
369proc 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
378echo 'BEFORE mutate global'
379pp test_ (a)
380
381mutate
382
383echo 'AFTER mutate global'
384pp test_ (a)
385
386## STDOUT:
387BEFORE mutate global
388(List) [0]
389local that is ignored
390(List) [1]
391AFTER mutate global
392(List) [42]
393## END
394
395#### setglobal a[i] += and d.key +=
396shopt -s ysh:upgrade
397
398var mylist = [0]
399var mydict = {k: 0}
400
401proc 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
410mutate
411
412pp test_ (mylist)
413pp 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
421shopt -s ysh:upgrade
422
423var mylist = [0, 1]
424var mydict = {k: 0, n: 1}
425
426var i = 0
427var key = 'k'
428
429proc 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
441proc mutate2 {
442 var mylist = [] # IGNORED
443 var mydict = {} # IGNORED
444
445 setglobal mylist[i] = 22
446 setglobal mydict[key] = 22
447}
448
449mutate1
450
451pp test_ (mylist)
452pp test_ (mydict)
453echo
454
455mutate2
456
457pp test_ (mylist)
458pp 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
469shopt --set parse_brace
470shopt --set parse_proc
471
472shellfunc() {
473 unset x
474}
475
476proc unset-proc() {
477 unset x
478}
479
480proc unset-proc-dynamic-scope() {
481 shopt --set dynamic_scope { # turn it back on
482 unset x
483 }
484}
485
486x=foo
487shellfunc
488echo shellfunc x=$x
489
490x=bar
491unset-proc
492echo unset-proc x=$x
493
494x=spam
495unset-proc
496echo unset-proc-dynamic-scope x=$x
497
498## STDOUT:
499shellfunc x=
500unset-proc x=
501unset-proc-dynamic-scope x=
502## END
503
504#### unset composes when you turn on dynamic scope
505shopt -s ysh:all
506
507proc unset-two (v, w) {
508 shopt --set dynamic_scope {
509 unset $v
510 unset $w
511 }
512}
513
514demo() {
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
526demo
527## STDOUT:
528x=X y=Y
529x= y=
530## END
531
532#### Temp Bindings
533shopt --set parse_proc
534
535myfunc() {
536 echo myfunc FOO=$FOO
537}
538proc myproc() {
539 echo myproc FOO=$FOO
540}
541
542FOO=bar myfunc
543FOO=bar myproc
544FOO=bar echo inline FOO=$FOO
545FOO=bar printenv.py FOO
546
547## STDOUT:
548myfunc FOO=bar
549myproc FOO=
550inline FOO=
551bar
552## END
553
554#### cd blocks don't introduce new scopes
555shopt --set ysh:upgrade
556
557var x = 42
558cd / {
559 var y = 0
560 var z = 1
561 echo $x $y $z
562 setvar y = 43
563}
564setvar z = 44
565echo $x $y $z
566
567## STDOUT:
56842 0 1
56942 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
579shopt --set parse_proc
580shopt --set parse_brace
581#shopt --set env_obj
582
583s='xzx zxz'
584
585shellfunc() {
586 echo shellfunc IFS="$IFS"
587 argv.py $s
588}
589
590proc myproc() {
591 echo myproc IFS="$IFS"
592 argv.py $s
593}
594
595IFS=: $REPO_ROOT/spec/bin/printenv.py IFS
596
597# default value
598echo "$IFS" | od -A n -t x1
599
600IFS=' z'
601echo IFS="$IFS"
602echo
603
604shellfunc
605echo
606
607IFS=' x' shellfunc
608echo
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
615IFS=' x' myproc
616echo
617
618# YSH solution to the problem
619shvar IFS=' x' {
620 myproc
621}
622
623## STDOUT:
624:
625 20 09 0a 0a
626IFS= z
627
628shellfunc IFS= z
629['x', 'x', 'x']
630
631shellfunc IFS= x
632['', 'z', 'z', 'z']
633
634myproc IFS= z
635['x', 'x', 'x']
636
637myproc IFS= x
638['', 'z', 'z', 'z']
639## END
640
641#### shvar builtin syntax
642shopt --set ysh:upgrade
643shopt --unset errexit
644
645# no block
646shvar
647echo status=$?
648
649shvar { # no arg
650 true
651}
652echo status=$?
653
654shvar foo { # should be name=value
655 true
656}
657echo status=$?
658## STDOUT:
659status=2
660status=2
661status=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
671shopt --set ysh:all
672
673proc p3 {
674 echo FOO=$[shvarGet('FOO')] # dynamic scope
675 echo FOO=$FOO # fails, not dynamic scope
676}
677
678proc p2 {
679 p3
680}
681
682proc p {
683 shvar FOO=foo {
684 p2
685 }
686}
687
688p
689
690## status: 1
691## STDOUT:
692FOO=foo
693## END
694
695
696#### shvar global
697shopt --set ysh:upgrade
698shopt --unset nounset
699
700echo _ESCAPER=$_ESCAPER
701echo _DIALECT=$_DIALECT
702
703shvar _ESCAPER=html _DIALECT=ninja {
704 echo block _ESCAPER=$_ESCAPER
705 echo block _DIALECT=$_DIALECT
706}
707
708echo _ESCAPER=$_ESCAPER
709echo _DIALECT=$_DIALECT
710
711# Now set them
712_ESCAPER=foo
713_DIALECT=bar
714
715echo ___
716
717echo _ESCAPER=$_ESCAPER
718echo _DIALECT=$_DIALECT
719
720shvar _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
730echo _ESCAPER=$_ESCAPER
731echo _DIALECT=$_DIALECT
732
733## STDOUT:
734_ESCAPER=
735_DIALECT=
736block _ESCAPER=html
737block _DIALECT=ninja
738_ESCAPER=
739_DIALECT=
740___
741_ESCAPER=foo
742_DIALECT=bar
743block _ESCAPER=html
744block _DIALECT=ninja
745nested _ESCAPER=nested
746nested _DIALECT=ninja
747_ESCAPER=foo
748_DIALECT=bar
749## END
750
751#### shvar local
752shopt --set ysh:upgrade # blocks
753shopt --unset simple_word_eval # test word splitting
754
755proc foo {
756 shvar IFS=x MYTEMP=foo {
757 echo IFS="$IFS"
758 argv.py $s
759 echo MYTEMP=${MYTEMP:-undef}
760 }
761}
762var s = 'a b c'
763argv.py $s
764foo
765argv.py $s
766echo MYTEMP=${MYTEMP:-undef}
767## STDOUT:
768['a', 'b', 'c']
769IFS=x
770['a b c']
771MYTEMP=foo
772['a', 'b', 'c']
773MYTEMP=undef
774## END
775
776#### shvar IFS
777shopt --set ysh:upgrade
778
779proc 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
790myproc
791## STDOUT:
792 20 09 0a 0a
793inside IFS=w
794mylocal=x
795 20 09 0a 0a
796## END
797
798#### Compare shell func vs. proc, $IFS vs. shvarGet('IFS')
799
800shopt --set parse_proc
801
802s='xzx zxz'
803
804shellfunc() { # dynamic scope everywhere
805 echo shellfunc
806 echo IFS="$IFS"
807 echo shvarGet IFS=$[shvarGet('IFS')]
808 argv.py $s
809}
810
811proc 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
827IFS=x
828
829IFS=z shellfunc
830echo
831
832# this makes a temp frame, but the proc can't see it?
833IFS=z myproc
834echo
835
836# null
837echo $[shvarGet('nonexistent')]
838
839## STDOUT:
840shellfunc
841IFS=z
842shvarGet IFS=z
843['x', 'x ', 'x']
844
845myproc -> p2
846IFS=x
847shvarGet IFS=x
848['', 'z', ' z', 'z']
849
850null
851## END
852
853#### func and proc are like var, with respect to closures
854shopt --set ysh:all
855
856proc 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?
869proc 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
879proc 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
890test-var
891echo
892
893test-func
894echo
895
896test-proc
897
898## STDOUT:
899inner inner
900outer outer
901
902inner inner
903outer outer
904
905inner
906outer
907## END