also looking at this
fix(interpreter): match range and or patterns in match statements
#930
2 files
+90
-0
| @@ -12392,6 +12392,30 @@ pub const Interpreter = struct { | ||
| 12392 | 12392 | |
| 12393 | 12393 | return true; |
| 12394 | 12394 | }, |
| 12395 | .Range => |range_pattern| { | |
| 12396 | // Range pattern: start..end (exclusive) or start..=end (inclusive) | |
| 12397 | if (value != .Int) return false; | |
| 12398 | const start = try self.evaluateExpression(range_pattern.start, env); | |
| 12399 | const end = try self.evaluateExpression(range_pattern.end, env); | |
| 12400 | if (start != .Int or end != .Int) return false; | |
| 12401 | if (value.Int < start.Int) return false; | |
| 12402 | return if (range_pattern.inclusive) value.Int <= end.Int else value.Int < end.Int; | |
| 12403 | }, | |
| 12404 | .Or => |or_patterns| { | |
| 12405 | // Or pattern: matches if any alternative matches. Each | |
| 12406 | // alternative binds into a scratch scope so a failed one | |
| 12407 | // leaves no bindings behind; the winner's are copied to env. | |
| 12408 | for (or_patterns) |alt_pattern| { | |
| 12409 | var alt_env = Environment.init(self.arena.allocator(), env); | |
| 12410 | defer alt_env.deinit(); | |
| 12411 | if (try self.matchPatternNode(alt_pattern, value, &alt_env)) { | |
| 12412 | var it = alt_env.bindings.iterator(); | |
| 12413 | while (it.next()) |entry| try env.define(entry.key_ptr.*, entry.value_ptr.*); | |
| 12414 | return true; | |
| 12415 | } | |
| 12416 | } | |
| 12417 | return false; | |
| 12418 | }, | |
| 12395 | 12419 | else => { |
| 12396 | 12420 | // Other pattern types not yet implemented |
| 12397 | 12421 | return false; |
| @@ -75,6 +75,72 @@ test "eval: multiple statements and a returning function" { | ||
| 75 | 75 | ); |
| 76 | 76 | } |
| 77 | 77 | |
| 78 | test "eval: match statement range and or patterns" { | |
| 79 | try runProgram( | |
| 80 | \\fn classify(x: int): int { | |
| 81 | \\ match x { | |
| 82 | \\ 0..=9 => return 1, | |
| 83 | \\ 10..20 => return 2, | |
| 84 | \\ 30 | 40 => return 3, | |
| 85 | \\ 60 | 70..=75 => return 4, | |
| 86 | \\ 50 => return 5, | |
| 87 | \\ n if n > 100 => return n, | |
| 88 | \\ _ => return 0, | |
| 89 | \\ } | |
| 90 | \\ return -1 | |
| 91 | \\} | |
| 92 | \\ | |
| 93 | \\fn main() { | |
| 94 | \\ assert(classify(0) == 1) | |
| 95 | \\ assert(classify(9) == 1) | |
| 96 | \\ assert(classify(10) == 2) | |
| 97 | \\ assert(classify(19) == 2) | |
| 98 | \\ assert(classify(20) == 0) | |
| 99 | \\ assert(classify(30) == 3) | |
| 100 | \\ assert(classify(40) == 3) | |
| 101 | \\ assert(classify(41) == 0) | |
| 102 | \\ assert(classify(60) == 4) | |
| 103 | \\ assert(classify(75) == 4) | |
| 104 | \\ assert(classify(76) == 0) | |
| 105 | \\ assert(classify(50) == 5) | |
| 106 | \\ assert(classify(101) == 101) | |
| 107 | \\ assert(classify(-1) == 0) | |
| 108 | \\} | |
| 109 | ); | |
| 110 | } | |
| 111 | ||
| 112 | test "eval: failed or-pattern alternatives leave no bindings" { | |
| 113 | try runProgram( | |
| 114 | \\struct P { | |
| 115 | \\ a: int, | |
| 116 | \\ b: int, | |
| 117 | \\} | |
| 118 | \\ | |
| 119 | \\fn after_failed_alternatives(p: P): int { | |
| 120 | \\ let x = 99 | |
| 121 | \\ match p { | |
| 122 | \\ P { a: x, b: 0 } | P { a: x, b: 3 } => return -1, | |
| 123 | \\ _ => return x, | |
| 124 | \\ } | |
| 125 | \\ return -2 | |
| 126 | \\} | |
| 127 | \\ | |
| 128 | \\fn from_matching_alternative(p: P): int { | |
| 129 | \\ match p { | |
| 130 | \\ P { a: y, b: 0 } | P { a: y, b: 2 } => return y, | |
| 131 | \\ _ => return -1, | |
| 132 | \\ } | |
| 133 | \\ return -2 | |
| 134 | \\} | |
| 135 | \\ | |
| 136 | \\fn main() { | |
| 137 | \\ let p = P { a: 1, b: 2 } | |
| 138 | \\ assert(after_failed_alternatives(p) == 99) | |
| 139 | \\ assert(from_matching_alternative(p) == 1) | |
| 140 | \\} | |
| 141 | ); | |
| 142 | } | |
| 143 | ||
| 78 | 144 | test "eval: finite recursive calls remain valid" { |
| 79 | 145 | try runProgram( |
| 80 | 146 | \\fn countdown(n: i32): i32 { |