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 test_ (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 test_ (pos)
108 pp test_ (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 allowed
140 func f() { return (0) }
141 func f() { return (1) }
142 ## status: 0
143 ## STDOUT:
144 ## END
145
146 #### Functions can redefine vars
147 var f = 0
148 func f() { return (1) }
149 pp test_ (f)
150 ## STDOUT:
151 <Func>
152 ## END
153
154 #### Multiple func calls
155
156 func inc(x) {
157 # increment
158
159 return (x + 1)
160 }
161
162 func dec(x) {
163 # decrement
164
165 return (x - 1)
166 }
167
168 echo $[inc(1)]
169 echo $[inc(inc(1))]
170 echo $[dec(inc(inc(1)))]
171
172 var y = dec(dec(1))
173 echo $[dec(y)]
174
175 ## STDOUT:
176 2
177 3
178 2
179 -2
180 ## END
181
182 #### Undefined var in function
183
184 func g(x) {
185 var z = y # make sure dynamic scope is off
186 return (x + z)
187 }
188
189 func f() {
190 var y = 42 # if dynamic scope were on, g() would see this
191 return (g(0))
192 }
193
194 echo $[f()]
195
196 ## status: 1
197 ## STDOUT:
198 ## END
199
200 #### Param binding semantics
201 # value
202 var x = 'foo'
203
204 func f(x) {
205 setvar x = 'bar'
206 }
207
208 pp test_ (x)
209 pp test_ (f(x))
210 pp test_ (x)
211
212 # reference
213 var y = ['a', 'b', 'c']
214
215 func g(y) {
216 setvar y[0] = 'z'
217 }
218
219 pp test_ (y)
220 pp test_ (g(y))
221 pp test_ (y)
222 ## STDOUT:
223 (Str) "foo"
224 (Null) null
225 (Str) "foo"
226 (List) ["a","b","c"]
227 (Null) null
228 (List) ["z","b","c"]
229 ## END
230
231 #### Recursive functions
232 func fib(n) {
233 # TODO: add assert n > 0
234 if (n < 2) {
235 return (n)
236 }
237
238 return (fib(n - 1) + fib(n - 2))
239 }
240
241 json write (fib(10))
242 ## STDOUT:
243 55
244 ## END
245
246 #### Recursive functions with LRU Cache
247 source $LIB_YSH/list.ysh
248
249 var cache = []
250 var maxSize = 4
251
252 func remove(l, i) {
253 for i in (i .. len(l) - 1) {
254 setvar l[i] = l[i + 1]
255 }
256
257 call l->pop() # remove duplicate last element
258 }
259
260 func fib(n) {
261 var i = len(cache) - 1
262 var j = 0;
263 while (i >= 0) {
264 var item = cache[i]
265
266 if (item[0] === n) {
267 call remove(cache, i)
268 call cache->append(item)
269
270 echo hit: $n
271 return (item[1])
272 }
273
274 setvar i = i - 1
275 setvar j += 1
276 }
277
278 var result = 0
279 if (n < 2) {
280 setvar result = n
281 } else {
282 setvar result = fib(n - 1) + fib(n - 2)
283 }
284
285 if (len(cache) >= maxSize) {
286 call remove(cache, 0)
287 }
288 call cache->append([n, result])
289
290 return (result)
291 }
292
293 json write (fib(10))
294 json write (cache)
295
296 ## STDOUT:
297 hit: 1
298 hit: 2
299 hit: 3
300 hit: 4
301 hit: 5
302 hit: 6
303 hit: 7
304 hit: 8
305 55
306 [
307 [
308 7,
309 13
310 ],
311 [
312 9,
313 34
314 ],
315 [
316 8,
317 21
318 ],
319 [
320 10,
321 55
322 ]
323 ]
324 ## END
325
326 #### Varadic arguments, no other args
327 func f(...args) {
328 pp test_ (args)
329 }
330
331 call f()
332 call f(1)
333 call f(1, 2)
334 call f(1, 2, 3)
335 ## STDOUT:
336 (List) []
337 (List) [1]
338 (List) [1,2]
339 (List) [1,2,3]
340 ## END
341
342 #### Varadic arguments, other args
343 func f(a, b, ...args) {
344 pp test_ ([a, b, args])
345 }
346
347 call f(1, 2)
348 call f(1, 2, 3)
349 call f(1, 2, 3, 4)
350 ## STDOUT:
351 (List) [1,2,[]]
352 (List) [1,2,[3]]
353 (List) [1,2,[3,4]]
354 ## END
355
356 #### Varadic arguments, too few args
357 func f(a, b, ...args) {
358 = [a, b, args]
359 }
360
361 call f(1)
362 ## status: 3
363 ## STDOUT:
364 ## END
365
366 #### Userland max
367 func mymax (...args) {
368 if (len(args) === 0) {
369 error 'Requires 1 arg'
370 } elif (len(args) === 1) {
371 # TODO: assert List
372 var mylist = args[0]
373 var max = mylist[0]
374
375 for item in (mylist) {
376 if (item > max) {
377 setvar max = item
378 }
379 }
380
381 return (max)
382 } elif (len(args) === 2) {
383 if (args[0] >= args[1]) {
384 return (args[0])
385 } else {
386 return (args[1])
387 }
388 } else {
389 # max(1, 2, 3) doesn't work in YSH, but does in Python
390 error 'too many'
391 }
392 }
393
394 = mymax(5,6) # => 6
395 = mymax([5,6,7]) # => 7
396 = mymax(5,6,7,8) # error
397
398 ## status: 10
399 ## STDOUT:
400 (Int) 6
401 (Int) 7
402 ## END
403
404 #### Functions share a namespace with variables
405 func f(x) {
406 return (x * x)
407 }
408
409 var g = f
410 echo "g(2) -> $[g(2)]"
411 ## STDOUT:
412 g(2) -> 4
413 ## END
414
415 #### We can store funcs in dictionaries
416 func dog_speak() {
417 echo "Woof"
418 }
419
420 func dog_type() {
421 return ("DOG")
422 }
423
424 const Dog = {
425 speak: dog_speak,
426 type: dog_type,
427 }
428
429 func cat_speak() {
430 echo "Meow"
431 }
432
433 func cat_type() {
434 return ("CAT")
435 }
436
437 const Cat = {
438 speak: cat_speak,
439 type: cat_type,
440 }
441
442 # First class "modules"!
443 const animals = [Dog, Cat]
444 for animal in (animals) {
445 var type = animal.type()
446 echo This is a $type
447 call animal.speak()
448 }
449 ## STDOUT:
450 This is a DOG
451 Woof
452 This is a CAT
453 Meow
454 ## END
455
456 #### Functions can be nested
457 proc build {
458 func f(x) {
459 return (x)
460 }
461
462 echo $[f(0)]
463 }
464 build
465 echo $[f(0)] # This will fail as f is locally scoped in `proc build`
466 ## status: 1
467 ## STDOUT:
468 0
469 ## END
470
471 #### Functions can be shadowed
472 func mysum(items) {
473 var mysum = 0
474 for x in (items) {
475 setvar mysum += x
476 }
477 return (mysum)
478 }
479
480 echo 1 + 2 + 3 = $[mysum([1, 2, 3])]
481
482 func inAnotherScope() {
483 # variable mysum has not yet shadowed func mysum in evaluation
484 var mysum = mysum([1, 2, 3])
485 echo mysum=$mysum
486 }
487 call inAnotherScope()
488
489 var mysum = mysum([0, 1])
490 echo mysum=$mysum
491
492 ## STDOUT:
493 1 + 2 + 3 = 6
494 mysum=6
495 mysum=1
496 ## END