1 # spec/ysh-func
2
3 ## our_shell: ysh
4
5 #### Identity function
6 func id(x) {
7 return (x)
8 }
9
10 json write (id("ysh"))
11
12 ## STDOUT:
13 "ysh"
14 ## END
15
16 #### Too many args
17 func f(x) { return (x + 1) }
18
19 = f(0, 1)
20 ## status: 3
21 ## STDOUT:
22 ## END
23
24 #### Too few args
25 func f(x) { return (x + 1) }
26
27 = f()
28 ## status: 3
29 ## STDOUT:
30 ## END
31
32 #### Positional args
33
34 func f(x, y, ...rest) {
35 echo "pos $x $y"
36 echo rest @rest
37 }
38
39 call f(1, 2, 3, 4)
40
41 # This is an error
42 #call f(1, 2, m=2, n=3)
43
44 ## STDOUT:
45 pos 1 2
46 rest 3 4
47 ## END
48
49
50 #### named args
51 func f(; x=3) {
52 echo x=$x
53 }
54
55 call f()
56
57 call f(x=4)
58
59 ## STDOUT:
60 x=3
61 x=4
62 ## END
63
64 #### Named args with ...rest
65 func f(; x=3, ...named) {
66 echo x=$x
67 pp line (named)
68 }
69
70 call f()
71
72 call f(x=4)
73
74 call f(x=4, y=5)
75
76 ## STDOUT:
77 x=3
78 (Dict) {}
79 x=4
80 (Dict) {}
81 x=4
82 (Dict) {"y":5}
83 ## END
84
85 #### Spread/splat of named args: f(...more)
86
87 func f(; x, y) {
88 echo "$x $y"
89 }
90
91 call f(; x=9, y=10)
92
93 var args = {x: 3, y: 4}
94
95 call f(; ...args)
96
97
98 ## STDOUT:
99 9 10
100 3 4
101 ## END
102
103
104 #### Multiple spreads
105
106 func f(...pos; ...named) {
107 pp line (pos)
108 pp line (named)
109 }
110
111 var a = [1,2,3]
112 var d = {m: 'spam', n: 'no'}
113 var e = {p: 5, q: 6}
114
115 call f(...a, ...a; ...d, ...e)
116
117 ## STDOUT:
118 (List) [1,2,3,1,2,3]
119 (Dict) {"m":"spam","n":"no","p":5,"q":6}
120 ## END
121
122
123 #### Proc-style return in a func is error
124 func t() { return 0 }
125
126 = t()
127 ## status: 2
128 ## STDOUT:
129 ## END
130
131 #### Typed return in a proc is error
132 proc t() { return (0) }
133
134 = t()
135 ## status: 2
136 ## STDOUT:
137 ## END
138
139 #### Redefining functions is not allowed (with shopt -u redefine_proc_func)
140 shopt -u redefine_proc_func
141 func f() { return (0) }
142 func f() { return (1) }
143 ## status: 1
144 ## STDOUT:
145 ## END
146
147 #### Redefining functions is allowed (with shopt -s redefine_proc_func)
148 shopt -s redefine_proc_func
149 func f() { return (0) }
150 func f() { return (1) }
151 ## status: 0
152 ## STDOUT:
153 ## END
154
155 #### Functions cannot redefine readonly vars (even with shopt -s redefine_proc_func)
156 shopt -s redefine_proc_func
157 const f = 0
158 func f() { return (1) }
159 ## status: 1
160 ## STDOUT:
161 ## END
162
163 #### Functions can redefine non-readonly vars
164 var f = 0
165 func f() { return (1) }
166 ## status: 0
167 ## STDOUT:
168 ## END
169
170 #### Vars cannot redefine functions (even with shopt -s redefine_proc_func)
171 shopt -s redefine_proc_func
172 func f() { return (1) }
173 const f = 0
174 ## status: 1
175 ## STDOUT:
176 ## END
177
178 #### Multiple func calls
179
180 func inc(x) {
181 # increment
182
183 return (x + 1)
184 }
185
186 func dec(x) {
187 # decrement
188
189 return (x - 1)
190 }
191
192 echo $[inc(1)]
193 echo $[inc(inc(1))]
194 echo $[dec(inc(inc(1)))]
195
196 var y = dec(dec(1))
197 echo $[dec(y)]
198
199 ## STDOUT:
200 2
201 3
202 2
203 -2
204 ## END
205
206 #### Undefined var in function
207
208 func g(x) {
209 var z = y # make sure dynamic scope is off
210 return (x + z)
211 }
212
213 func f() {
214 var y = 42 # if dynamic scope were on, g() would see this
215 return (g(0))
216 }
217
218 echo $[f()]
219
220 ## status: 1
221 ## STDOUT:
222 ## END
223
224 #### Param binding semantics
225 # value
226 var x = 'foo'
227
228 func f(x) {
229 setvar x = 'bar'
230 }
231
232 pp line (x)
233 pp line (f(x))
234 pp line (x)
235
236 # reference
237 var y = ['a', 'b', 'c']
238
239 func g(y) {
240 setvar y[0] = 'z'
241 }
242
243 pp line (y)
244 pp line (g(y))
245 pp line (y)
246 ## STDOUT:
247 (Str) "foo"
248 (Null) null
249 (Str) "foo"
250 (List) ["a","b","c"]
251 (Null) null
252 (List) ["z","b","c"]
253 ## END
254
255 #### Recursive functions
256 func fib(n) {
257 # TODO: add assert n > 0
258 if (n < 2) {
259 return (n)
260 }
261
262 return (fib(n - 1) + fib(n - 2))
263 }
264
265 json write (fib(10))
266 ## STDOUT:
267 55
268 ## END
269
270 #### Recursive functions with LRU Cache
271 source --builtin list.ysh
272
273 var cache = []
274 var maxSize = 4
275
276 func remove(l, i) {
277 for i in (i .. len(l) - 1) {
278 setvar l[i] = l[i + 1]
279 }
280
281 call l->pop() # remove duplicate last element
282 }
283
284 func fib(n) {
285 var i = len(cache) - 1
286 var j = 0;
287 while (i >= 0) {
288 var item = cache[i]
289
290 if (item[0] === n) {
291 call remove(cache, i)
292 call cache->append(item)
293
294 echo hit: $n
295 return (item[1])
296 }
297
298 setvar i = i - 1
299 setvar j += 1
300 }
301
302 var result = 0
303 if (n < 2) {
304 setvar result = n
305 } else {
306 setvar result = fib(n - 1) + fib(n - 2)
307 }
308
309 if (len(cache) >= maxSize) {
310 call remove(cache, 0)
311 }
312 call cache->append([n, result])
313
314 return (result)
315 }
316
317 json write (fib(10))
318 json write (cache)
319
320 ## STDOUT:
321 hit: 1
322 hit: 2
323 hit: 3
324 hit: 4
325 hit: 5
326 hit: 6
327 hit: 7
328 hit: 8
329 55
330 [
331 [
332 7,
333 13
334 ],
335 [
336 9,
337 34
338 ],
339 [
340 8,
341 21
342 ],
343 [
344 10,
345 55
346 ]
347 ]
348 ## END
349
350 #### Varadic arguments, no other args
351 func f(...args) {
352 pp line (args)
353 }
354
355 call f()
356 call f(1)
357 call f(1, 2)
358 call f(1, 2, 3)
359 ## STDOUT:
360 (List) []
361 (List) [1]
362 (List) [1,2]
363 (List) [1,2,3]
364 ## END
365
366 #### Varadic arguments, other args
367 func f(a, b, ...args) {
368 pp line ([a, b, args])
369 }
370
371 call f(1, 2)
372 call f(1, 2, 3)
373 call f(1, 2, 3, 4)
374 ## STDOUT:
375 (List) [1,2,[]]
376 (List) [1,2,[3]]
377 (List) [1,2,[3,4]]
378 ## END
379
380 #### Varadic arguments, too few args
381 func f(a, b, ...args) {
382 = [a, b, args]
383 }
384
385 call f(1)
386 ## status: 3
387 ## STDOUT:
388 ## END
389
390 #### Userland max
391 func mymax (...args) {
392 if (len(args) === 0) {
393 error 'Requires 1 arg'
394 } elif (len(args) === 1) {
395 # TODO: assert List
396 var mylist = args[0]
397 var max = mylist[0]
398
399 for item in (mylist) {
400 if (item > max) {
401 setvar max = item
402 }
403 }
404
405 return (max)
406 } elif (len(args) === 2) {
407 if (args[0] >= args[1]) {
408 return (args[0])
409 } else {
410 return (args[1])
411 }
412 } else {
413 # max(1, 2, 3) doesn't work in YSH, but does in Python
414 error 'too many'
415 }
416 }
417
418 = mymax(5,6) # => 6
419 = mymax([5,6,7]) # => 7
420 = mymax(5,6,7,8) # error
421
422 ## status: 10
423 ## STDOUT:
424 (Int) 6
425 (Int) 7
426 ## END
427
428 #### Functions share a namespace with variables
429 func f(x) {
430 return (x * x)
431 }
432
433 var g = f
434 echo "g(2) -> $[g(2)]"
435 ## STDOUT:
436 g(2) -> 4
437 ## END
438
439 #### We can store funcs in dictionaries
440 func dog_speak() {
441 echo "Woof"
442 }
443
444 func dog_type() {
445 return ("DOG")
446 }
447
448 const Dog = {
449 speak: dog_speak,
450 type: dog_type,
451 }
452
453 func cat_speak() {
454 echo "Meow"
455 }
456
457 func cat_type() {
458 return ("CAT")
459 }
460
461 const Cat = {
462 speak: cat_speak,
463 type: cat_type,
464 }
465
466 # First class "modules"!
467 const animals = [Dog, Cat]
468 for animal in (animals) {
469 var type = animal.type()
470 echo This is a $type
471 call animal.speak()
472 }
473 ## STDOUT:
474 This is a DOG
475 Woof
476 This is a CAT
477 Meow
478 ## END
479
480 #### Functions cannot be nested
481 proc build {
482 func f(x) {
483 return (x)
484 }
485 }
486 ## status: 2
487 ## STDOUT:
488 ## END
489
490 #### Functions can be shadowed
491 func mysum(items) {
492 var mysum = 0
493 for x in (items) {
494 setvar mysum += x
495 }
496 return (mysum)
497 }
498
499 echo 1 + 2 + 3 = $[mysum([1, 2, 3])]
500
501 func inAnotherScope() {
502 # variable mysum has not yet shadowed func mysum in evaluation
503 var mysum = mysum([1, 2, 3])
504 echo mysum=$mysum
505 }
506 call inAnotherScope()
507
508 # We need a scope otherwise we'd overwrite `mysum` in the global scope
509 var mysum = mysum([1, 2, 3]) # will raise status=1
510 ## status: 1
511 ## STDOUT:
512 1 + 2 + 3 = 6
513 mysum=6
514 ## END
515
516 #### Function names cannot be redeclared
517 # Behaves like: const f = ...
518 func f(x) {
519 return (x)
520 }
521
522 var f = "some val"
523 ## status: 1
524 ## STDOUT:
525 ## END
526
527 #### Functions cannot be mutated
528 func f(x) {
529 return (x)
530 }
531
532 setvar f = "some val"
533 ## status: 1
534 ## STDOUT:
535 ## END