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 json write --pretty=F (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 {}
79 x=4
80 {}
81 x=4
82 {"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 json write --pretty=F (pos)
108 json write --pretty=F (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 [1,2,3,1,2,3]
119 {"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 --pretty=F (cache)
319 json write (cache)
320
321 ## STDOUT:
322 hit: 1
323 hit: 2
324 hit: 3
325 hit: 4
326 hit: 5
327 hit: 6
328 hit: 7
329 hit: 8
330 55
331 [
332 [
333 7,
334 13
335 ],
336 [
337 9,
338 34
339 ],
340 [
341 8,
342 21
343 ],
344 [
345 10,
346 55
347 ]
348 ]
349 ## END
350
351 #### Varadic arguments, no other args
352 func f(...args) {
353 pp line (args)
354 }
355
356 call f()
357 call f(1)
358 call f(1, 2)
359 call f(1, 2, 3)
360 ## STDOUT:
361 (List) []
362 (List) [1]
363 (List) [1,2]
364 (List) [1,2,3]
365 ## END
366
367 #### Varadic arguments, other args
368 func f(a, b, ...args) {
369 pp line ([a, b, args])
370 }
371
372 call f(1, 2)
373 call f(1, 2, 3)
374 call f(1, 2, 3, 4)
375 ## STDOUT:
376 (List) [1,2,[]]
377 (List) [1,2,[3]]
378 (List) [1,2,[3,4]]
379 ## END
380
381 #### Varadic arguments, too few args
382 func f(a, b, ...args) {
383 = [a, b, args]
384 }
385
386 call f(1)
387 ## status: 3
388 ## STDOUT:
389 ## END
390
391 #### Userland max
392 func mymax (...args) {
393 if (len(args) === 0) {
394 error 'Requires 1 arg'
395 } elif (len(args) === 1) {
396 # TODO: assert List
397 var mylist = args[0]
398 var max = mylist[0]
399
400 for item in (mylist) {
401 if (item > max) {
402 setvar max = item
403 }
404 }
405
406 return (max)
407 } elif (len(args) === 2) {
408 if (args[0] >= args[1]) {
409 return (args[0])
410 } else {
411 return (args[1])
412 }
413 } else {
414 # max(1, 2, 3) doesn't work in YSH, but does in Python
415 error 'too many'
416 }
417 }
418
419 = mymax(5,6) # => 6
420 = mymax([5,6,7]) # => 7
421 = mymax(5,6,7,8) # error
422
423 ## status: 10
424 ## STDOUT:
425 (Int) 6
426 (Int) 7
427 ## END
428
429 #### Functions share a namespace with variables
430 func f(x) {
431 return (x * x)
432 }
433
434 var g = f
435 echo "g(2) -> $[g(2)]"
436 ## STDOUT:
437 g(2) -> 4
438 ## END
439
440 #### We can store funcs in dictionaries
441 func dog_speak() {
442 echo "Woof"
443 }
444
445 func dog_type() {
446 return ("DOG")
447 }
448
449 const Dog = {
450 speak: dog_speak,
451 type: dog_type,
452 }
453
454 func cat_speak() {
455 echo "Meow"
456 }
457
458 func cat_type() {
459 return ("CAT")
460 }
461
462 const Cat = {
463 speak: cat_speak,
464 type: cat_type,
465 }
466
467 # First class "modules"!
468 const animals = [Dog, Cat]
469 for animal in (animals) {
470 var type = animal.type()
471 echo This is a $type
472 call animal.speak()
473 }
474 ## STDOUT:
475 This is a DOG
476 Woof
477 This is a CAT
478 Meow
479 ## END
480
481 #### Functions cannot be nested
482 proc build {
483 func f(x) {
484 return (x)
485 }
486 }
487 ## status: 2
488 ## STDOUT:
489 ## END
490
491 #### Functions can be shadowed
492 func mysum(items) {
493 var mysum = 0
494 for x in (items) {
495 setvar mysum += x
496 }
497 return (mysum)
498 }
499
500 echo 1 + 2 + 3 = $[mysum([1, 2, 3])]
501
502 func inAnotherScope() {
503 # variable mysum has not yet shadowed func mysum in evaluation
504 var mysum = mysum([1, 2, 3])
505 echo mysum=$mysum
506 }
507 call inAnotherScope()
508
509 # We need a scope otherwise we'd overwrite `mysum` in the global scope
510 var mysum = mysum([1, 2, 3]) # will raise status=1
511 ## status: 1
512 ## STDOUT:
513 1 + 2 + 3 = 6
514 mysum=6
515 ## END
516
517 #### Function names cannot be redeclared
518 # Behaves like: const f = ...
519 func f(x) {
520 return (x)
521 }
522
523 var f = "some val"
524 ## status: 1
525 ## STDOUT:
526 ## END
527
528 #### Functions cannot be mutated
529 func f(x) {
530 return (x)
531 }
532
533 setvar f = "some val"
534 ## status: 1
535 ## STDOUT:
536 ## END