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