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

908 lines, 588 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
506shopt --unset no_osh_builtins
507
508proc unset-two (v, w) {
509 shopt --set dynamic_scope {
510 unset $v
511 unset $w
512 }
513}
514
515demo() {
516 local x=X
517 local y=Y
518
519 echo "x=$x y=$y"
520
521 unset-two x y
522
523 shopt --unset nounset
524 echo "x=$x y=$y"
525}
526
527demo
528## STDOUT:
529x=X y=Y
530x= y=
531## END
532
533#### Temp Bindings
534shopt --set parse_proc
535
536myfunc() {
537 echo myfunc FOO=$FOO
538}
539proc myproc() {
540 echo myproc FOO=$FOO
541}
542
543FOO=bar myfunc
544FOO=bar myproc
545FOO=bar echo inline FOO=$FOO
546FOO=bar printenv.py FOO
547
548## STDOUT:
549myfunc FOO=bar
550myproc FOO=
551inline FOO=
552bar
553## END
554
555#### cd blocks don't introduce new scopes
556shopt --set ysh:upgrade
557
558var x = 42
559cd / {
560 var y = 0
561 var z = 1
562 echo $x $y $z
563 setvar y = 43
564}
565setvar z = 44
566echo $x $y $z
567
568## STDOUT:
56942 0 1
57042 43 44
571## END
572
573#### shvar IFS=x { myproc } rather than IFS=x myproc - no dynamic scope
574
575# Note: osh/split.py uses dynamic scope to look up IFS
576# TODO: Should use LANG example to demonstrate
577
578#shopt --set ysh:upgrade # this would disable word splitting
579
580shopt --set parse_proc
581shopt --set parse_brace
582#shopt --set env_obj
583
584s='xzx zxz'
585
586shellfunc() {
587 echo shellfunc IFS="$IFS"
588 argv.py $s
589}
590
591proc myproc() {
592 echo myproc IFS="$IFS"
593 argv.py $s
594}
595
596IFS=: $REPO_ROOT/spec/bin/printenv.py IFS
597
598# default value
599echo "$IFS" | od -A n -t x1
600
601IFS=' z'
602echo IFS="$IFS"
603echo
604
605shellfunc
606echo
607
608IFS=' x' shellfunc
609echo
610
611# Problem: $IFS in procs only finds GLOBAL values, so we get IFS=' z' rather than IFS=' x'.
612# But when actually splitting, $IFS is a 'shvar' which respects DYNAMIC scope.
613#
614# Can use shvarGet('IFS') instead
615
616IFS=' x' myproc
617echo
618
619# YSH solution to the problem
620shvar IFS=' x' {
621 myproc
622}
623
624## STDOUT:
625:
626 20 09 0a 0a
627IFS= z
628
629shellfunc IFS= z
630['x', 'x', 'x']
631
632shellfunc IFS= x
633['', 'z', 'z', 'z']
634
635myproc IFS= z
636['x', 'x', 'x']
637
638myproc IFS= x
639['', 'z', 'z', 'z']
640## END
641
642#### shvar builtin syntax
643shopt --set ysh:upgrade
644shopt --unset errexit
645
646# no block
647shvar
648echo status=$?
649
650shvar { # no arg
651 true
652}
653echo status=$?
654
655shvar foo { # should be name=value
656 true
657}
658echo status=$?
659## STDOUT:
660status=2
661status=2
662status=2
663## END
664
665
666#### shvar and shvarGet() obey dynamic scope
667
668# On the other hand, in YSH
669# - $x does local/closure/global scope
670# - FOO=foo mycommand modifies the ENV object - shopt --set env_obj
671
672shopt --set ysh:all
673
674proc p3 {
675 echo FOO=$[shvarGet('FOO')] # dynamic scope
676 echo FOO=$FOO # fails, not dynamic scope
677}
678
679proc p2 {
680 p3
681}
682
683proc p {
684 shvar FOO=foo {
685 p2
686 }
687}
688
689p
690
691## status: 1
692## STDOUT:
693FOO=foo
694## END
695
696
697#### shvar global
698shopt --set ysh:upgrade
699shopt --unset nounset
700
701echo _ESCAPER=$_ESCAPER
702echo _DIALECT=$_DIALECT
703
704shvar _ESCAPER=html _DIALECT=ninja {
705 echo block _ESCAPER=$_ESCAPER
706 echo block _DIALECT=$_DIALECT
707}
708
709echo _ESCAPER=$_ESCAPER
710echo _DIALECT=$_DIALECT
711
712# Now set them
713_ESCAPER=foo
714_DIALECT=bar
715
716echo ___
717
718echo _ESCAPER=$_ESCAPER
719echo _DIALECT=$_DIALECT
720
721shvar _ESCAPER=html _DIALECT=ninja {
722 echo block _ESCAPER=$_ESCAPER
723 echo block _DIALECT=$_DIALECT
724
725 shvar _ESCAPER=nested {
726 echo nested _ESCAPER=$_ESCAPER
727 echo nested _DIALECT=$_DIALECT
728 }
729}
730
731echo _ESCAPER=$_ESCAPER
732echo _DIALECT=$_DIALECT
733
734## STDOUT:
735_ESCAPER=
736_DIALECT=
737block _ESCAPER=html
738block _DIALECT=ninja
739_ESCAPER=
740_DIALECT=
741___
742_ESCAPER=foo
743_DIALECT=bar
744block _ESCAPER=html
745block _DIALECT=ninja
746nested _ESCAPER=nested
747nested _DIALECT=ninja
748_ESCAPER=foo
749_DIALECT=bar
750## END
751
752#### shvar local
753shopt --set ysh:upgrade # blocks
754shopt --unset simple_word_eval # test word splitting
755
756proc foo {
757 shvar IFS=x MYTEMP=foo {
758 echo IFS="$IFS"
759 argv.py $s
760 echo MYTEMP=${MYTEMP:-undef}
761 }
762}
763var s = 'a b c'
764argv.py $s
765foo
766argv.py $s
767echo MYTEMP=${MYTEMP:-undef}
768## STDOUT:
769['a', 'b', 'c']
770IFS=x
771['a b c']
772MYTEMP=foo
773['a', 'b', 'c']
774MYTEMP=undef
775## END
776
777#### shvar IFS
778shopt --set ysh:upgrade
779
780proc myproc() {
781 echo "$IFS" | od -A n -t x1
782
783 local mylocal=x
784 shvar IFS=w {
785 echo inside IFS="$IFS"
786 echo mylocal="$mylocal" # I do NOT want a new scope!
787 }
788 echo "$IFS" | od -A n -t x1
789}
790
791myproc
792## STDOUT:
793 20 09 0a 0a
794inside IFS=w
795mylocal=x
796 20 09 0a 0a
797## END
798
799#### Compare shell func vs. proc, $IFS vs. shvarGet('IFS')
800
801shopt --set parse_proc parse_ysh_expr_sub
802
803s='xzx zxz'
804
805shellfunc() { # dynamic scope everywhere
806 echo shellfunc
807 echo IFS="$IFS"
808 echo shvarGet IFS=$[shvarGet('IFS')]
809 argv.py $s
810}
811
812proc myproc { # no dynamic scope
813
814 # Subtle behavior: we see 'x' rather than "temp frame" 'z' - I think because
815 # there is a CHAIN of __E__ enclosed scopes, up to the global frame.
816 #
817 # That frame comes FIRST. That seems OK, but it changed when procs became closures.
818 proc p2 {
819 echo "myproc -> p2"
820 echo IFS="$IFS"
821 echo shvarGet IFS=$[shvarGet('IFS')] # dynamic scope opt-in
822 argv.py $s # dynamic scope in osh/split.py
823 }
824
825 p2
826}
827
828IFS=x
829
830IFS=z shellfunc
831echo
832
833# this makes a temp frame, but the proc can't see it?
834IFS=z myproc
835echo
836
837# null
838echo $[shvarGet('nonexistent')]
839
840## STDOUT:
841shellfunc
842IFS=z
843shvarGet IFS=z
844['x', 'x ', 'x']
845
846myproc -> p2
847IFS=x
848shvarGet IFS=x
849['', 'z', ' z', 'z']
850
851null
852## END
853
854#### func and proc are like var, with respect to closures
855shopt --set ysh:all
856
857proc test-var {
858 var x = 'outer'
859 proc inner {
860 var x = 'inner'
861 # note: static check is broken now
862 #setvar x = 'inner'
863 echo "inner $x"
864 }
865 inner
866 echo "outer $x"
867}
868
869# Note: state.YshDecl flag somehow doesn't make a difference here?
870proc test-func {
871 func x() { return ('outer') }
872 proc inner2 {
873 func x() { return ('inner') }
874 echo "inner $[x()]"
875 }
876 inner2
877 echo "outer $[x()]"
878}
879
880proc test-proc {
881 proc x { echo 'outer' }
882 proc inner3 {
883 proc x { echo 'inner' }
884 x
885 }
886 inner3
887 x
888}
889
890
891test-var
892echo
893
894test-func
895echo
896
897test-proc
898
899## STDOUT:
900inner inner
901outer outer
902
903inner inner
904outer outer
905
906inner
907outer
908## END