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

814 lines, 463 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
271
272shopt --set ysh:upgrade
273
274for x in 1 2 {
275 proc p {
276 echo 'loop'
277 }
278}
279p
280
281{
282 proc p {
283 echo 'brace'
284 }
285}
286p
287
288## STDOUT:
289loop
290brace
291## END
292
293#### Block can be passed literally, or as expression in third arg group
294shopt --set ysh:upgrade
295
296proc p ( ; ; ; block) {
297 call io->eval(block)
298}
299
300p { echo literal }
301
302var block = ^(echo expression)
303p (; ; block)
304
305## STDOUT:
306literal
307expression
308## END
309
310#### Pass through all 4 kinds of args
311
312shopt --set ysh:upgrade
313
314proc p2 (...words; ...typed; ...named; block) {
315 pp test_ (words)
316 pp test_ (typed)
317 pp test_ (named)
318 #pp test_ (block)
319 # To avoid <Block 0x??> - could change pp test_
320 echo $[type(block)]
321}
322
323proc p1 (...words; ...typed; ...named; block) {
324 p2 @words (...typed; ...named; block)
325}
326
327p2 a b ('c', 'd', n=99) {
328 echo literal
329}
330echo
331
332# Same thing
333var block = ^(echo expression)
334
335# Note: you need the second explicit ;
336
337p2 a b ('c', 'd'; n=99; block)
338echo
339
340# what happens when you do this?
341p2 a b ('c', 'd'; n=99; block) {
342 echo duplicate
343}
344
345## status: 1
346## STDOUT:
347(List) ["a","b"]
348(List) ["c","d"]
349(Dict) {"n":99}
350Command
351
352(List) ["a","b"]
353(List) ["c","d"]
354(Dict) {"n":99}
355Command
356
357## END
358
359#### Global and local ARGV, like "$@"
360shopt -s parse_at
361argv.py "$@"
362argv.py @ARGV
363#argv.py "${ARGV[@]}" # not useful, but it works!
364
365set -- 'a b' c
366argv.py "$@"
367argv.py @ARGV # separate from the argv stack
368
369f() {
370 argv.py "$@"
371 argv.py @ARGV # separate from the argv stack
372}
373f 1 '2 3'
374## STDOUT:
375[]
376[]
377['a b', 'c']
378[]
379['1', '2 3']
380[]
381## END
382
383
384#### Mutating global ARGV
385
386$SH -c '
387shopt -s ysh:upgrade
388
389argv.py global @ARGV
390
391# should not be ignored
392call ARGV->append("GG")
393
394argv.py global @ARGV
395'
396## STDOUT:
397['global']
398['global', 'GG']
399## END
400
401#### Mutating local ARGV
402
403$SH -c '
404shopt -s ysh:upgrade
405
406argv.py global @ARGV
407
408proc p {
409 argv.py @ARGV
410 call ARGV->append("LL")
411 argv.py @ARGV
412}
413
414p local @ARGV
415
416argv.py global @ARGV
417
418' dummy0 'a b' c
419
420## STDOUT:
421['global', 'a b', 'c']
422['local', 'a b', 'c']
423['local', 'a b', 'c', 'LL']
424['global', 'a b', 'c']
425## END
426
427
428#### typed proc allows all kinds of args
429shopt -s ysh:upgrade
430
431typed proc p (w; t; n; block) {
432 pp test_ (w)
433 pp test_ (t)
434 pp test_ (n)
435 echo $[type(block)]
436}
437
438p word (42, n=99) {
439 echo block
440}
441
442
443## STDOUT:
444(Str) "word"
445(Int) 42
446(Int) 99
447Command
448## END
449
450#### can unset procs without -f
451shopt -s ysh:upgrade
452
453proc foo() {
454 echo bar
455}
456
457try { foo }
458echo status=$[_error.code]
459
460pp test_ (foo)
461unset foo
462#pp test_ (foo)
463
464try { foo }
465echo status=$[_error.code]
466
467## STDOUT:
468bar
469status=0
470<Proc>
471status=127
472## END
473
474#### procs shadow sh-funcs
475shopt -s ysh:upgrade
476
477f() {
478 echo sh-func
479}
480
481proc f {
482 echo proc
483}
484
485f
486## STDOUT:
487proc
488## END
489
490#### first word skips non-proc variables
491shopt -s ysh:upgrade
492
493grep() {
494 echo 'sh-func grep'
495}
496
497var grep = 'variable grep'
498
499grep
500
501# We first find `var grep`, but it's a Str not a Proc, so we skip it and then
502# find `function grep`.
503
504## STDOUT:
505sh-func grep
506## END
507
508#### proc resolution changes with the local scope
509shopt -s ysh:upgrade
510
511proc foo {
512 echo foo
513}
514
515proc bar {
516 echo bar
517}
518
519proc inner {
520 var foo = bar
521 foo # Will now reference `proc bar`
522}
523
524foo
525inner
526foo # Back to the global scope, foo still references `proc foo`
527
528# Without this behavior, features like `eval(b, vars={ flag: __flag })`, needed
529# by parseArgs, will not work. `eval` with `vars` adds a new frame to the end of
530# `mem.var_stack` with a local `flag` set to `proc __flag`. However, then we
531# cannot resolve `flag` by only checking `mem.var_stack[0]` like we could with
532# a proc declared normally, so we must search `mem.var_stack` from last to first.
533
534## STDOUT:
535foo
536bar
537foo
538## END
539
540
541#### procs are defined in local scope
542shopt -s ysh:upgrade
543
544proc gen-proc {
545 eval 'proc localproc { echo hi }'
546 pp frame_vars_
547
548}
549
550gen-proc
551
552# can't suppress 'grep' failure
553if false {
554 try {
555 pp frame_vars_ | grep localproc
556 }
557 pp test_ (_pipeline_status)
558 #pp test_ (PIPESTATUS)
559}
560
561## STDOUT:
562 [frame_vars_] ARGV localproc
563## END
564
565
566#### declare -f -F only prints shell functions
567shopt --set parse_proc
568
569myfunc() {
570 echo hi
571}
572
573proc myproc {
574 echo hi
575}
576
577declare -F
578echo ---
579
580declare -F myproc
581echo status=$?
582
583declare -f myproc
584echo status=$?
585
586## status: 0
587## STDOUT:
588declare -f myfunc
589---
590status=1
591status=1
592## END
593
594#### compgen -A function shows user-defined invokables - shell funcs, Proc, Obj
595shopt --set ysh:upgrade
596
597my-shell-func() {
598 echo hi
599}
600
601proc myproc {
602 echo hi
603}
604
605compgen -A function
606
607echo ---
608
609proc define-inner {
610 eval 'proc inner { echo inner }'
611 #eval 'proc myproc { echo inner }' # shadowed name
612 compgen -A function
613}
614define-inner
615
616echo ---
617
618proc myinvoke (w; self) {
619 pp test_ ([w, self])
620}
621
622var methods = Object(null, {__invoke__: myinvoke})
623var myobj = Object(methods, {})
624
625compgen -A function
626
627## STDOUT:
628my-shell-func
629myproc
630---
631define-inner
632inner
633my-shell-func
634myproc
635---
636define-inner
637my-shell-func
638myinvoke
639myobj
640myproc
641## END
642
643#### type / type -a builtin on invokables - shell func, proc, invokable
644shopt --set ysh:upgrade
645
646my-shell-func() {
647 echo hi
648}
649
650proc myproc {
651 echo hi
652}
653
654proc boundProc(; self) {
655 echo hi
656}
657
658var methods = Object(null, {__invoke__: boundProc})
659var invokable = Object(methods, {})
660
661type -t my-shell-func
662type -t myproc
663type -t invokable
664try {
665 type -t methods # not invokable!
666}
667echo $[_error.code]
668
669echo ---
670
671type my-shell-func
672type myproc
673type invokable
674try {
675 type methods # not invokable!
676}
677echo $[_error.code]
678
679echo ---
680
681type -a my-shell-func
682type -a myproc
683type -a invokable
684
685echo ---
686
687if false { # can't redefine right now
688 invokable() {
689 echo sh-func
690 }
691 type -a invokable
692}
693
694## STDOUT:
695function
696proc
697invokable
6981
699---
700my-shell-func is a shell function
701myproc is a YSH proc
702invokable is a YSH invokable
7031
704---
705my-shell-func is a shell function
706myproc is a YSH proc
707invokable is a YSH invokable
708---
709## END
710
711#### invokable Obj that doesn't declare self
712shopt --set ysh:upgrade
713
714proc boundProc(no_self; ) {
715 echo 'bad'
716}
717
718var methods = Object(null, {__invoke__: boundProc})
719var invokable = Object(methods, {x: 3, y: 5})
720
721invokable no_self
722
723## status: 3
724## STDOUT:
725## END
726
727#### invokable Obj is called with self
728shopt --set ysh:upgrade
729
730proc boundProc(; self) {
731 echo "sum = $[self.x + self.y]"
732}
733
734var methods = Object(null, {__invoke__: boundProc})
735var invokable = Object(methods, {x: 3, y: 5})
736
737invokable
738
739## STDOUT:
740sum = 8
741## END
742
743
744#### invokable Obj with more typed args
745shopt --set ysh:upgrade
746
747proc myInvoke (word1, word2; self, int1, int2) {
748 echo "sum = $[self.x + self.y]"
749 pp test_ (self)
750 pp test_ ([word1, word2, int1, int2])
751}
752
753# call it directly with 'self'
754myInvoke a b ({x: 0, y: 1}, 42, 43)
755echo
756
757var methods = Object(null, {__invoke__: myInvoke})
758
759var callable = Object(methods, {x: 2, y: 3})
760
761# call it through the obj
762callable a b (44, 45)
763
764## STDOUT:
765sum = 1
766(Dict) {"x":0,"y":1}
767(List) ["a","b",42,43]
768
769sum = 5
770(Obj) ("x":2,"y":3) --> ("__invoke__":<Proc>)
771(List) ["a","b",44,45]
772## END
773
774#### two different objects can share the same __invoke__
775shopt --set ysh:upgrade
776
777proc boundProc(; self, more) {
778 echo "sum = $[self.x + self.y + more]"
779}
780
781var methods = Object(null, {__invoke__: boundProc})
782
783var i1 = Object(methods, {x: 3, y: 5})
784var i2 = Object(methods, {x: 10, y: 42})
785
786i1 (1)
787i2 (1)
788
789## STDOUT:
790sum = 9
791sum = 53
792## END
793
794
795#### Stateful proc with counter
796shopt --set ysh:upgrade
797
798proc invokeCounter(; self, inc) {
799 setvar self.i += inc
800 echo "counter = $[self.i]"
801}
802
803var methods = Object(null, {__invoke__: invokeCounter})
804var counter = Object(methods, {i: 0})
805
806counter (1)
807counter (2)
808counter (3)
809
810## STDOUT:
811counter = 1
812counter = 3
813counter = 6
814## END