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

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