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

783 lines, 447 significant
1## oils_failures_allowed: 0
2
3#### Open proc (any number of args)
4shopt --set parse_proc
5
6proc f {
7 var x = 42
8 return $x
9}
10# this gets called with 3 args then?
11f a b c
12echo status=$?
13## STDOUT:
14status=42
15## END
16
17#### Closed proc with no args, passed too many
18shopt --set parse_proc
19
20proc f() {
21 return 42
22}
23f
24echo status=$?
25
26f a b # status 2
27
28## status: 3
29## STDOUT:
30status=42
31## END
32
33#### Open proc has ARGV
34shopt -s ysh:all
35proc foo {
36 echo ARGV @ARGV
37 # do we care about this? I think we want to syntactically remove it from YSH
38 # but it can still be used for legacy
39 echo dollar-at "$@"
40}
41builtin set -- a b c
42foo x y z
43## STDOUT:
44ARGV x y z
45dollar-at a b c
46## END
47
48#### Closed proc has empty "$@" or ARGV
49shopt -s ysh:all
50
51proc foo(d, e, f) {
52 write params $d $e $f
53 argv.py dollar-at "$@"
54 argv.py ARGV @ARGV
55}
56builtin set -- a b c
57foo x y z
58## STDOUT:
59params
60x
61y
62z
63['dollar-at', 'a', 'b', 'c']
64['ARGV']
65## END
66
67#### Proc with default args
68shopt --set parse_proc
69
70proc f(x='foo') {
71 echo x=$x
72}
73f
74## STDOUT:
75x=foo
76## END
77
78#### Proc with word params
79shopt --set parse_proc
80
81# doesn't require ysh:all
82proc f(x, y, z) {
83 echo $x $y $z
84 var ret = 42
85 return $ret
86}
87# this gets called with 3 args then?
88f a b c
89echo status=$?
90## STDOUT:
91a b c
92status=42
93## END
94
95#### Proc with ... "rest" word params
96
97# TODO: opts goes with this
98# var opt = grep_opts.parse(ARGV)
99#
100# func(**opt) # Assumes keyword args match?
101# parse :grep_opts :opt @ARGV
102
103shopt -s ysh:all
104
105proc f(...names) {
106 write names: @names
107}
108# this gets called with 3 args then?
109f a b c
110echo status=$?
111## STDOUT:
112names:
113a
114b
115c
116status=0
117## END
118
119#### word rest params 2
120shopt --set ysh:all
121
122proc f(first, ...rest) { # @ means "the rest of the arguments"
123 write --sep ' ' -- $first
124 write --sep ' ' -- @rest # @ means "splice this array"
125}
126f a b c
127## STDOUT:
128a
129b c
130## END
131
132#### proc with typed args
133shopt --set ysh:upgrade
134
135# TODO: duplicate param names aren't allowed
136proc p (a; mylist, mydict; opt Int = 42) {
137 pp test_ (a)
138 pp test_ (mylist)
139 pp test_ (mydict)
140 #pp test_ (opt)
141}
142
143p WORD ([1,2,3], {name: 'bob'})
144
145echo ---
146
147p x (:| a b |, {bob: 42}, a = 5)
148
149## STDOUT:
150(Str) "WORD"
151(List) [1,2,3]
152(Dict) {"name":"bob"}
153---
154(Str) "x"
155(List) ["a","b"]
156(Dict) {"bob":42}
157## END
158
159#### Proc name-with-hyphen
160shopt --set parse_proc parse_at
161
162proc name-with-hyphen {
163 echo @ARGV
164}
165name-with-hyphen x y z
166## STDOUT:
167x y z
168## END
169
170#### Proc with block arg
171shopt --set ysh:upgrade
172
173# TODO: Test more of this
174proc f(x, y ; ; ; block) {
175 echo f word $x $y
176
177 if (block) {
178 call io->eval(block)
179 }
180}
181f a b { echo FFF }
182
183# With varargs and block
184shopt --set parse_proc
185
186proc g(x, y, ...rest ; ; ; block) {
187 echo g word $x $y
188 echo g rest @rest
189
190 if (block) {
191 call io->eval(block)
192 }
193}
194g a b c d {
195 echo GGG
196}
197
198## STDOUT:
199f word a b
200FFF
201g word a b
202g rest c d
203GGG
204## END
205
206#### proc returning wrong type
207shopt --set parse_proc
208
209# this should print an error message
210proc f {
211 var a = %(one two)
212 return $a
213}
214f
215## status: 3
216## STDOUT:
217## END
218
219#### proc returning invalid string
220shopt --set parse_proc
221
222# this should print an error message
223proc f {
224 var s = 'not an integer status'
225 return $s
226}
227f
228## status: 1
229## STDOUT:
230## END
231
232#### 'return' doesn't accept expressions
233proc p {
234 return 1 + 2
235}
236p
237## status: 2
238## STDOUT:
239## END
240
241#### procs are in same namespace as variables
242shopt --set parse_proc
243
244proc myproc {
245 echo hi
246}
247
248echo "myproc is a $[type(myproc)]"
249
250## STDOUT:
251myproc is a Proc
252## END
253
254#### Nested proc is allowed
255shopt --set parse_proc
256
257proc f {
258 proc g {
259 echo 'G'
260 }
261 g
262}
263f
264g # g is defined in the local scope of f
265## status: 127
266## STDOUT:
267G
268## END
269
270#### Procs defined inside compound statements (with redefine_proc)
271
272shopt --set ysh:upgrade
273shopt --set redefine_proc_func
274
275for x in 1 2 {
276 proc p {
277 echo 'loop'
278 }
279}
280p
281
282{
283 proc p {
284 echo 'brace'
285 }
286}
287p
288
289## STDOUT:
290loop
291brace
292## END
293
294#### Block can be passed literally, or as expression in third arg group
295shopt --set ysh:upgrade
296
297proc p ( ; ; ; block) {
298 call io->eval(block)
299}
300
301p { echo literal }
302
303var block = ^(echo expression)
304p (; ; block)
305
306## STDOUT:
307literal
308expression
309## END
310
311#### Pass through all 4 kinds of args
312
313shopt --set ysh:upgrade
314
315proc p2 (...words; ...typed; ...named; block) {
316 pp test_ (words)
317 pp test_ (typed)
318 pp test_ (named)
319 #pp test_ (block)
320 # To avoid <Block 0x??> - could change pp test_
321 echo $[type(block)]
322}
323
324proc p1 (...words; ...typed; ...named; block) {
325 p2 @words (...typed; ...named; block)
326}
327
328p2 a b ('c', 'd', n=99) {
329 echo literal
330}
331echo
332
333# Same thing
334var block = ^(echo expression)
335
336# Note: you need the second explicit ;
337
338p2 a b ('c', 'd'; n=99; block)
339echo
340
341# what happens when you do this?
342p2 a b ('c', 'd'; n=99; block) {
343 echo duplicate
344}
345
346## status: 1
347## STDOUT:
348(List) ["a","b"]
349(List) ["c","d"]
350(Dict) {"n":99}
351Block
352
353(List) ["a","b"]
354(List) ["c","d"]
355(Dict) {"n":99}
356Command
357
358## END
359
360#### Global and local ARGV, like "$@"
361shopt -s parse_at
362argv.py "$@"
363argv.py @ARGV
364#argv.py "${ARGV[@]}" # not useful, but it works!
365
366set -- 'a b' c
367argv.py "$@"
368argv.py @ARGV # separate from the argv stack
369
370f() {
371 argv.py "$@"
372 argv.py @ARGV # separate from the argv stack
373}
374f 1 '2 3'
375## STDOUT:
376[]
377[]
378['a b', 'c']
379[]
380['1', '2 3']
381[]
382## END
383
384
385#### Mutating global ARGV
386
387$SH -c '
388shopt -s ysh:upgrade
389
390argv.py global @ARGV
391
392# should not be ignored
393call ARGV->append("GG")
394
395argv.py global @ARGV
396'
397## STDOUT:
398['global']
399['global', 'GG']
400## END
401
402#### Mutating local ARGV
403
404$SH -c '
405shopt -s ysh:upgrade
406
407argv.py global @ARGV
408
409proc p {
410 argv.py @ARGV
411 call ARGV->append("LL")
412 argv.py @ARGV
413}
414
415p local @ARGV
416
417argv.py global @ARGV
418
419' dummy0 'a b' c
420
421## STDOUT:
422['global', 'a b', 'c']
423['local', 'a b', 'c']
424['local', 'a b', 'c', 'LL']
425['global', 'a b', 'c']
426## END
427
428
429#### typed proc allows all kinds of args
430shopt -s ysh:upgrade
431
432typed proc p (w; t; n; block) {
433 pp test_ (w)
434 pp test_ (t)
435 pp test_ (n)
436 echo $[type(block)]
437}
438
439p word (42, n=99) {
440 echo block
441}
442
443
444## STDOUT:
445(Str) "word"
446(Int) 42
447(Int) 99
448Block
449## END
450
451#### can unset procs without -f
452shopt -s ysh:upgrade
453
454proc foo() {
455 echo bar
456}
457
458try { foo }
459echo status=$[_error.code]
460
461pp test_ (foo)
462unset foo
463#pp test_ (foo)
464
465try { foo }
466echo status=$[_error.code]
467
468## STDOUT:
469bar
470status=0
471<Proc>
472status=127
473## END
474
475#### procs shadow sh-funcs
476shopt -s ysh:upgrade redefine_proc_func
477
478f() {
479 echo sh-func
480}
481
482proc f {
483 echo proc
484}
485
486f
487## STDOUT:
488proc
489## END
490
491#### first word skips non-proc variables
492shopt -s ysh:upgrade
493
494grep() {
495 echo 'sh-func grep'
496}
497
498var grep = 'variable grep'
499
500grep
501
502# We first find `var grep`, but it's a Str not a Proc, so we skip it and then
503# find `function grep`.
504
505## STDOUT:
506sh-func grep
507## END
508
509#### proc resolution changes with the local scope
510shopt -s ysh:upgrade
511
512proc foo {
513 echo foo
514}
515
516proc bar {
517 echo bar
518}
519
520proc inner {
521 var foo = bar
522 foo # Will now reference `proc bar`
523}
524
525foo
526inner
527foo # Back to the global scope, foo still references `proc foo`
528
529# Without this behavior, features like `eval(b, vars={ flag: __flag })`, needed
530# by parseArgs, will not work. `eval` with `vars` adds a new frame to the end of
531# `mem.var_stack` with a local `flag` set to `proc __flag`. However, then we
532# cannot resolve `flag` by only checking `mem.var_stack[0]` like we could with
533# a proc declared normally, so we must search `mem.var_stack` from last to first.
534
535## STDOUT:
536foo
537bar
538foo
539## END
540
541
542#### procs are defined in local scope
543shopt -s ysh:upgrade
544
545proc gen-proc {
546 eval 'proc localproc { echo hi }'
547 pp frame_vars_
548
549}
550
551gen-proc
552
553# can't suppress 'grep' failure
554if false {
555 try {
556 pp frame_vars_ | grep localproc
557 }
558 pp test_ (_pipeline_status)
559 #pp test_ (PIPESTATUS)
560}
561
562## STDOUT:
563 [frame_vars_] ARGV localproc
564## END
565
566
567#### declare -f -F only prints shell functions
568shopt --set parse_proc
569
570myfunc() {
571 echo hi
572}
573
574proc myproc {
575 echo hi
576}
577
578declare -F
579echo ---
580
581declare -F myproc
582echo status=$?
583
584declare -f myproc
585echo status=$?
586
587## status: 0
588## STDOUT:
589declare -f myfunc
590---
591status=1
592status=1
593## END
594
595#### compgen -A function shows user-defined invokables - shell funcs, Proc, Obj
596shopt --set ysh:upgrade
597
598my-shell-func() {
599 echo hi
600}
601
602proc myproc {
603 echo hi
604}
605
606compgen -A function
607
608echo ---
609
610proc define-inner {
611 eval 'proc inner { echo inner }'
612 #eval 'proc myproc { echo inner }' # shadowed name
613 compgen -A function
614}
615define-inner
616
617echo ---
618
619proc myinvoke (w; self) {
620 pp test_ ([w, self])
621}
622
623var methods = Object(null, {__invoke__: myinvoke})
624var myobj = Object(methods, {})
625
626compgen -A function
627
628## STDOUT:
629my-shell-func
630myproc
631---
632define-inner
633inner
634my-shell-func
635myproc
636---
637define-inner
638my-shell-func
639myinvoke
640myobj
641myproc
642## END
643
644#### type / type -a builtin on invokables - shell func, proc, invokable
645shopt --set ysh:upgrade
646
647my-shell-func() {
648 echo hi
649}
650
651proc myproc {
652 echo hi
653}
654
655proc boundProc(; self) {
656 echo hi
657}
658
659var methods = Object(null, {__invoke__: boundProc})
660var invokable = Object(methods, {})
661
662type -t my-shell-func
663type -t myproc
664type -t invokable
665try {
666 type -t methods # not invokable!
667}
668echo $[_error.code]
669
670echo ---
671
672type my-shell-func
673type myproc
674type invokable
675try {
676 type methods # not invokable!
677}
678echo $[_error.code]
679
680echo ---
681
682type -a my-shell-func
683type -a myproc
684type -a invokable
685
686echo ---
687
688if false { # can't redefine right now
689 invokable() {
690 echo sh-func
691 }
692 type -a invokable
693}
694
695## STDOUT:
696function
697proc
698invokable
6991
700---
701my-shell-func is a shell function
702myproc is a YSH proc
703invokable is a YSH invokable
7041
705---
706my-shell-func is a shell function
707myproc is a YSH proc
708invokable is a YSH invokable
709---
710## END
711
712#### invokable Obj that doesn't declare self
713shopt --set ysh:upgrade
714
715proc boundProc(no_self; ) {
716 echo 'bad'
717}
718
719var methods = Object(null, {__invoke__: boundProc})
720var invokable = Object(methods, {x: 3, y: 5})
721
722invokable no_self
723
724## status: 3
725## STDOUT:
726## END
727
728#### call invokable Obj with self
729shopt --set ysh:upgrade
730
731proc boundProc(; self) {
732 echo "sum = $[self.x + self.y]"
733}
734
735var methods = Object(null, {__invoke__: boundProc})
736var invokable = Object(methods, {x: 3, y: 5})
737
738invokable
739
740## STDOUT:
741sum = 8
742## END
743
744#### two different objects can share the same __invoke__
745shopt --set ysh:upgrade
746
747proc boundProc(; self, more) {
748 echo "sum = $[self.x + self.y + more]"
749}
750
751var methods = Object(null, {__invoke__: boundProc})
752
753var i1 = Object(methods, {x: 3, y: 5})
754var i2 = Object(methods, {x: 10, y: 42})
755
756i1 (1)
757i2 (1)
758
759## STDOUT:
760sum = 9
761sum = 53
762## END
763
764
765#### Stateful proc with counter
766shopt --set ysh:upgrade
767proc invokeCounter(; self, inc) {
768 setvar self.i += inc
769 echo "counter = $[self.i]"
770}
771
772var methods = Object(null, {__invoke__: invokeCounter})
773var counter = Object(methods, {i: 0})
774
775counter (1)
776counter (2)
777counter (3)
778
779## STDOUT:
780counter = 1
781counter = 3
782counter = 6
783## END