plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-examples/closures.plum
| b7071c9 | 1 | import std/Str |
| 73b5e55 | 2 | import std/Bool |
| ca5fd6f | 3 | import std/Number |
| b7071c9 | 4 | |
| 0fe3528 | 5 | fun each(cb: fn(Int) -> Int) -> Int = |
| 6341c74 | 6 | cb(5) |
| 6341c74 | 7 | |
| 0fe3528 | 8 | fun useCapturingClosure() -> Int = |
| a271f34 | 9 | offset := 100 |
| a271f34 | 10 | cb := |v| |
| 6341c74 | 11 | v + offset |
| 6341c74 | 12 | cb(5) |
| 6341c74 | 13 | |
| 0fe3528 | 14 | fun main() -> Int = |
| 6341c74 | 15 | each(|v| v * 3) + useCapturingClosure() |
| a271f34 | 16 | |
| a43f3af | 17 | fun useSnapshotClosure() -> Int = |
| a43f3af | 18 | x = 10 |
| a43f3af | 19 | snapshotCb = |v| |
| a43f3af | 20 | x + v |
| a43f3af | 21 | x = 999 |
| a43f3af | 22 | each(snapshotCb) |
| a43f3af | 23 | |
| a43f3af | 24 | fun eachF(cb: fn(Float) -> Float) -> Float = |
| a43f3af | 25 | cb(0.0) |
| a43f3af | 26 | |
| a43f3af | 27 | fun useFloatClosure() -> Float = |
| a43f3af | 28 | offset = 2.5 |
| a43f3af | 29 | floatCb = |v| |
| a43f3af | 30 | offset + v |
| a43f3af | 31 | eachF(floatCb) |
| a43f3af | 32 | |
| 5f2f962 | 33 | enum ClosureCat = |
| 5f2f962 | 34 | | ClosureCat(age: Int) |
| a43f3af | 35 | |
| a43f3af | 36 | fun eachCat(cb: fn(ClosureCat) -> Int) -> Int = |
| a43f3af | 37 | cb(ClosureCat(age: 7)) |
| a43f3af | 38 | |
| a43f3af | 39 | fun useClassClosure() -> Int = |
| a43f3af | 40 | classCb = |c| |
| a43f3af | 41 | c.age |
| a43f3af | 42 | eachCat(classCb) |
| a43f3af | 43 | |
| a43f3af | 44 | fun identity(value: T) -> T = |
| a43f3af | 45 | value |
| a43f3af | 46 | |
| a43f3af | 47 | fun eachViaGeneric(cb: fn(Int) -> Int) -> Int = |
| a43f3af | 48 | cb(identity(7)) |
| a43f3af | 49 | |
| a43f3af | 50 | # `inner` (nested inside `outer`'s body) needs `offset` — a name from |
| a43f3af | 51 | # `useNestedClosure`'s scope, two levels up from `inner` itself, and not |
| a43f3af | 52 | # referenced by `outer` directly — exercising the multi-level capture chain: |
| a43f3af | 53 | # `outer` must itself capture `offset` purely because `inner` needs it. |
| a43f3af | 54 | fun useNestedClosure() -> Int = |
| a43f3af | 55 | offset = 100 |
| a43f3af | 56 | outer = |v| |
| a43f3af | 57 | inner = |w| |
| a43f3af | 58 | w + offset |
| a43f3af | 59 | inner(v) |
| a43f3af | 60 | each(outer) |
| a43f3af | 61 | |
| 8de13fd | 62 | fun doubleForClosureTest(x: Int) -> Int = |
| 8de13fd | 63 | x * 2 |
| 8de13fd | 64 | |
| 8de13fd | 65 | fun classifySign(n: Int) -> Str = |
| 8de13fd | 66 | cb = |v| v > 0 ? "pos" : "nonpos" |
| 8de13fd | 67 | cb(n) |
| 8de13fd | 68 | |
| 8de13fd | 69 | test "closures example computes correctly" |
| 8de13fd | 70 | assert main() == 120 |
| 8de13fd | 71 | |
| 8de13fd | 72 | # ---- closure regression tests ---- |
| 8de13fd | 73 | |
| 8de13fd | 74 | test "non capturing closure passed and called runs correctly" |
| 8de13fd | 75 | assert each(|v| v) == 5 |
| 8de13fd | 76 | |
| 8de13fd | 77 | # Regression test: the external scanner used to never emit a dedent for a |
| 8de13fd | 78 | # multi-line closure body immediately followed by `)` on the same line as |
| 8de13fd | 79 | # the body's last statement, so this shape didn't parse at all before. |
| 8de13fd | 80 | test "multi line closure call argument with closing paren on last statement line runs correctly" |
| 8de13fd | 81 | multiLineResult = each(|v| |
| 8de13fd | 82 | bumped = v + 1 |
| 8de13fd | 83 | bumped * 2) |
| 8de13fd | 84 | assert multiLineResult == 12 |
| 8de13fd | 85 | |
| 8de13fd | 86 | test "capturing closure snapshots value at creation time runs correctly" |
| 8de13fd | 87 | # each calls its callback with 5, so a correct snapshot of x==10 (its value |
| 8de13fd | 88 | # when the closure was created, not 999 — its value when `each(cb)` is |
| 8de13fd | 89 | # actually called) gives 10 + 5 = 15. |
| 8de13fd | 90 | assert useSnapshotClosure() == 15 |
| 8de13fd | 91 | |
| 8de13fd | 92 | test "closure assigned then called at float type runs correctly" |
| 8de13fd | 93 | assert useFloatClosure() == 2.5 |
| 8de13fd | 94 | |
| 8de13fd | 95 | test "closure assigned then called at class type runs correctly" |
| 8de13fd | 96 | assert useClassClosure() == 7 |
| 8de13fd | 97 | |
| 8de13fd | 98 | test "closure passed through already generic higher order function runs correctly" |
| 8de13fd | 99 | assert eachViaGeneric(|v| v * 2) == 14 |
| 8de13fd | 100 | |
| a43f3af | 101 | test "nested closure literal runs correctly" |
| a9a0147 | 102 | assert useNestedClosure() == 105 |
| a43f3af | 103 | |
| a43f3af | 104 | test "nested closure literal passed directly as call argument runs correctly" |
| a9a0147 | 105 | assert each(|v| |
| a43f3af | 106 | each(|w| w + v)) == 10 |
| a43f3af | 107 | |
| a43f3af | 108 | test "named function used as a closure typed value runs correctly" |
| a9a0147 | 109 | assert each(doubleForClosureTest) == 10 |
| a43f3af | 110 | |
| a43f3af | 111 | test "named function used as a value assigned then called runs correctly" |
| a43f3af | 112 | f = doubleForClosureTest |
| a9a0147 | 113 | assert f(21) == 42 |
| a43f3af | 114 | |
| a43f3af | 115 | test "named function used as a value alongside a closure at the same call type runs correctly" |
| a43f3af | 116 | # Proves the trampoline shares the same call_indirect type as an ordinary |
| a43f3af | 117 | # closure of the same signature (both must resolve to the same wasm |
| a43f3af | 118 | # function type, since both flow through the exact same `cb(...)` call site). |
| a9a0147 | 119 | assert each(doubleForClosureTest) + each(|v| v + 1) == 16 |
| 04d2270 | 120 | |
| 04d2270 | 121 | test "closure with an inline ternary body runs correctly" |
| 04d2270 | 122 | # A ternary as a closure's own inline body used to parse as an OUTER |
| 04d2270 | 123 | # ternary wrapping the whole closure instead (`(|v| v > 0) ? "pos" : |
| 04d2270 | 124 | # "nonpos"`), a grammar precedence bug — see the closure/PREC.conditional |
| 04d2270 | 125 | # fix in tooling/tree-sitter-plum/grammar.js. |
| a9a0147 | 126 | assert classifySign(5) == "pos" |
| a9a0147 | 127 | assert classifySign(-3) == "nonpos" |