haskell_match 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE-APACHE +202 -0
  4. data/LICENSE-MIT +21 -0
  5. data/README.md +1484 -0
  6. data/ext/haskell_match/Cargo.lock +33 -0
  7. data/ext/haskell_match/Cargo.toml +22 -0
  8. data/ext/haskell_match/extconf.rb +41 -0
  9. data/ext/haskell_match/src/core/ast.rs +190 -0
  10. data/ext/haskell_match/src/core/error.rs +52 -0
  11. data/ext/haskell_match/src/core/exhaust.rs +699 -0
  12. data/ext/haskell_match/src/core/hs/ast.rs +256 -0
  13. data/ext/haskell_match/src/core/hs/json.rs +225 -0
  14. data/ext/haskell_match/src/core/hs/layout.rs +346 -0
  15. data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
  16. data/ext/haskell_match/src/core/hs/mod.rs +14 -0
  17. data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
  18. data/ext/haskell_match/src/core/lexer.rs +590 -0
  19. data/ext/haskell_match/src/core/mod.rs +19 -0
  20. data/ext/haskell_match/src/core/parser.rs +1116 -0
  21. data/ext/haskell_match/src/core/pretty.rs +373 -0
  22. data/ext/haskell_match/src/core/resolve.rs +336 -0
  23. data/ext/haskell_match/src/core/tree.rs +921 -0
  24. data/ext/haskell_match/src/core/typecheck.rs +226 -0
  25. data/ext/haskell_match/src/core/types.rs +404 -0
  26. data/ext/haskell_match/src/lib.rs +19 -0
  27. data/ext/haskell_match/src/ruby/mod.rs +1195 -0
  28. data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
  29. data/lib/haskell_match/binding_plan.rb +84 -0
  30. data/lib/haskell_match/case_of.rb +71 -0
  31. data/lib/haskell_match/clauses.rb +354 -0
  32. data/lib/haskell_match/data.rb +417 -0
  33. data/lib/haskell_match/deep_call.rb +98 -0
  34. data/lib/haskell_match/deriving.rb +130 -0
  35. data/lib/haskell_match/dsl.rb +71 -0
  36. data/lib/haskell_match/errors.rb +85 -0
  37. data/lib/haskell_match/field_types.rb +140 -0
  38. data/lib/haskell_match/function.rb +240 -0
  39. data/lib/haskell_match/haskell/compiler.rb +961 -0
  40. data/lib/haskell_match/haskell.rb +326 -0
  41. data/lib/haskell_match/inspect.rb +45 -0
  42. data/lib/haskell_match/lazy_list.rb +210 -0
  43. data/lib/haskell_match/native_loader.rb +64 -0
  44. data/lib/haskell_match/pattern.rb +75 -0
  45. data/lib/haskell_match/pattern_ast.rb +394 -0
  46. data/lib/haskell_match/prelude.rb +448 -0
  47. data/lib/haskell_match/scope.rb +44 -0
  48. data/lib/haskell_match/version.rb +5 -0
  49. data/lib/haskell_match.rb +41 -0
  50. metadata +124 -0
@@ -0,0 +1,921 @@
1
+ //! Compilation of a clause matrix into a decision tree.
2
+ //!
3
+ //! The tree is evaluated against an environment of *slots*. Slots `0..arity`
4
+ //! hold the function arguments; every constructor test that succeeds copies
5
+ //! the constructor's fields into freshly numbered slots, so a leaf can name
6
+ //! each bound variable by slot. Each argument position is inspected at most
7
+ //! once per path through the tree.
8
+
9
+ use super::ast::{HKey, Lit, LitKind, Pat, VarId};
10
+ use super::types::{ConId, TypeEnv, TypeId};
11
+
12
+ #[derive(Clone, Debug, PartialEq)]
13
+ pub enum Tree {
14
+ /// No clause matches.
15
+ Fail,
16
+ Leaf(Leaf),
17
+ /// A clause whose pattern matched but which has a guard; if the guard
18
+ /// is false, evaluation continues with `fallback`.
19
+ Guard {
20
+ leaf: Leaf,
21
+ fallback: Box<Tree>,
22
+ },
23
+ SwitchCon {
24
+ slot: usize,
25
+ ty: TypeId,
26
+ cases: Vec<ConCase>,
27
+ default: Option<Box<Tree>>,
28
+ },
29
+ SwitchLit {
30
+ slot: usize,
31
+ kind: LitKind,
32
+ cases: Vec<(Lit, Tree)>,
33
+ default: Box<Tree>,
34
+ },
35
+ /// Does the Hash in `slot` have every key in `keys`? If so their values
36
+ /// go to slots `base..base+keys.len()` and matching continues with
37
+ /// `then`; otherwise (or for a non-Hash) with `otherwise`.
38
+ TestHash {
39
+ slot: usize,
40
+ keys: Vec<HKey>,
41
+ base: usize,
42
+ then: Box<Tree>,
43
+ otherwise: Box<Tree>,
44
+ },
45
+ }
46
+
47
+ #[derive(Clone, Debug, PartialEq)]
48
+ pub struct ConCase {
49
+ pub con: ConId,
50
+ pub tag: usize,
51
+ pub arity: usize,
52
+ /// First slot receiving this constructor's fields.
53
+ pub base: usize,
54
+ pub tree: Tree,
55
+ }
56
+
57
+ #[derive(Clone, Debug, PartialEq)]
58
+ pub struct Leaf {
59
+ pub clause: usize,
60
+ /// Where each variable of the clause (by `VarId`) gets its value.
61
+ pub binds: Vec<BindSrc>,
62
+ /// Irrefutable sub-patterns to destructure once the clause is chosen.
63
+ pub lazies: Vec<LazyPat>,
64
+ }
65
+
66
+ #[derive(Clone, Copy, Debug, PartialEq)]
67
+ pub enum BindSrc {
68
+ Slot(usize),
69
+ /// Filled in by evaluating `Leaf::lazies[i]`.
70
+ Lazy(usize),
71
+ }
72
+
73
+ #[derive(Clone, Debug, PartialEq)]
74
+ pub struct LazyPat {
75
+ pub slot: usize,
76
+ pub pat: Pat,
77
+ }
78
+
79
+ #[derive(Clone, Debug)]
80
+ pub struct Compiled {
81
+ pub tree: Tree,
82
+ pub arity: usize,
83
+ pub n_slots: usize,
84
+ }
85
+
86
+ #[derive(Clone, Debug)]
87
+ struct Row {
88
+ pats: Vec<Pat>,
89
+ clause: usize,
90
+ binds: Vec<Option<BindSrc>>,
91
+ lazies: Vec<LazyPat>,
92
+ guarded: bool,
93
+ }
94
+
95
+ struct Compiler<'a> {
96
+ env: &'a TypeEnv,
97
+ n_slots: usize,
98
+ }
99
+
100
+ /// Input clause: argument patterns, number of variables, guarded flag.
101
+ pub struct Clause {
102
+ pub pats: Vec<Pat>,
103
+ pub n_vars: usize,
104
+ pub guarded: bool,
105
+ }
106
+
107
+ pub fn compile(env: &TypeEnv, clauses: &[Clause], arity: usize) -> Compiled {
108
+ let mut c = Compiler {
109
+ env,
110
+ n_slots: arity,
111
+ };
112
+ let occs: Vec<usize> = (0..arity).collect();
113
+ let mut rows: Vec<Row> = clauses
114
+ .iter()
115
+ .enumerate()
116
+ .map(|(i, cl)| Row {
117
+ pats: cl.pats.clone(),
118
+ clause: i,
119
+ binds: vec![None; cl.n_vars],
120
+ lazies: Vec::new(),
121
+ guarded: cl.guarded,
122
+ })
123
+ .collect();
124
+ for r in rows.iter_mut() {
125
+ normalize(r, &occs);
126
+ }
127
+ let tree = c.compile(&occs, rows);
128
+ Compiled {
129
+ tree,
130
+ arity,
131
+ n_slots: c.n_slots,
132
+ }
133
+ }
134
+
135
+ /// Collect the variables bound inside a pattern.
136
+ fn vars_in(p: &Pat, out: &mut Vec<VarId>) {
137
+ match p {
138
+ Pat::Wild | Pat::Lit(_) => {}
139
+ Pat::Var(v) => out.push(*v),
140
+ Pat::As(v, inner) => {
141
+ out.push(*v);
142
+ vars_in(inner, out);
143
+ }
144
+ Pat::Lazy(inner) => vars_in(inner, out),
145
+ Pat::Con(_, args) => args.iter().for_each(|a| vars_in(a, out)),
146
+ Pat::Hash(fields) => fields.iter().for_each(|(_, a)| vars_in(a, out)),
147
+ }
148
+ }
149
+
150
+ fn hash_keys(fields: &[(HKey, Pat)]) -> Vec<HKey> {
151
+ fields.iter().map(|(k, _)| k.clone()).collect()
152
+ }
153
+
154
+ fn key_subset(a: &[HKey], b: &[HKey]) -> bool {
155
+ a.iter().all(|k| b.contains(k))
156
+ }
157
+
158
+ /// Strip binders from every column so each pattern is `Wild`, `Con` or
159
+ /// `Lit`, recording where the stripped variables get their values.
160
+ fn normalize(row: &mut Row, occs: &[usize]) {
161
+ for (i, slot) in occs.iter().enumerate() {
162
+ loop {
163
+ match std::mem::replace(&mut row.pats[i], Pat::Wild) {
164
+ Pat::Var(v) => {
165
+ row.binds[v] = Some(BindSrc::Slot(*slot));
166
+ break;
167
+ }
168
+ Pat::As(v, inner) => {
169
+ row.binds[v] = Some(BindSrc::Slot(*slot));
170
+ row.pats[i] = *inner;
171
+ }
172
+ Pat::Lazy(inner) => {
173
+ let idx = row.lazies.len();
174
+ let mut vs = Vec::new();
175
+ vars_in(&inner, &mut vs);
176
+ for v in vs {
177
+ row.binds[v] = Some(BindSrc::Lazy(idx));
178
+ }
179
+ row.lazies.push(LazyPat {
180
+ slot: *slot,
181
+ pat: *inner,
182
+ });
183
+ break;
184
+ }
185
+ other => {
186
+ row.pats[i] = other;
187
+ break;
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ impl<'a> Compiler<'a> {
195
+ fn alloc(&mut self, n: usize) -> usize {
196
+ let base = self.n_slots;
197
+ self.n_slots += n;
198
+ base
199
+ }
200
+
201
+ fn leaf(row: &Row) -> Leaf {
202
+ Leaf {
203
+ clause: row.clause,
204
+ binds: row
205
+ .binds
206
+ .iter()
207
+ .map(|b| b.expect("every clause variable is bound by its pattern"))
208
+ .collect(),
209
+ lazies: row.lazies.clone(),
210
+ }
211
+ }
212
+
213
+ fn compile(&mut self, occs: &[usize], rows: Vec<Row>) -> Tree {
214
+ if rows.is_empty() {
215
+ return Tree::Fail;
216
+ }
217
+ let first = &rows[0];
218
+ let col = match first.pats.iter().position(|p| !p.is_wild()) {
219
+ None => {
220
+ let leaf = Self::leaf(first);
221
+ if first.guarded {
222
+ let rest = rows[1..].to_vec();
223
+ return Tree::Guard {
224
+ leaf,
225
+ fallback: Box::new(self.compile(occs, rest)),
226
+ };
227
+ }
228
+ return Tree::Leaf(leaf);
229
+ }
230
+ Some(c) => c,
231
+ };
232
+ let slot = occs[col];
233
+ match &first.pats[col] {
234
+ Pat::Con(c0, _) => {
235
+ let ty = self.env.type_of_con(*c0);
236
+ let mut heads: Vec<ConId> = Vec::new();
237
+ for r in &rows {
238
+ if let Pat::Con(c, _) = &r.pats[col] {
239
+ if !heads.contains(c) {
240
+ heads.push(*c);
241
+ }
242
+ }
243
+ }
244
+ let mut cases = Vec::with_capacity(heads.len());
245
+ for c in &heads {
246
+ let arity = self.env.con(*c).arity;
247
+ let base = self.alloc(arity);
248
+ let mut new_occs: Vec<usize> = occs[..col].to_vec();
249
+ new_occs.extend(base..base + arity);
250
+ new_occs.extend_from_slice(&occs[col + 1..]);
251
+ let mut new_rows = Vec::new();
252
+ for r in &rows {
253
+ match &r.pats[col] {
254
+ Pat::Con(cc, args) if cc == c => {
255
+ let mut nr = r.clone();
256
+ nr.pats = r.pats[..col].to_vec();
257
+ nr.pats.extend(args.iter().cloned());
258
+ nr.pats.extend_from_slice(&r.pats[col + 1..]);
259
+ normalize(&mut nr, &new_occs);
260
+ new_rows.push(nr);
261
+ }
262
+ Pat::Wild => {
263
+ let mut nr = r.clone();
264
+ nr.pats = r.pats[..col].to_vec();
265
+ nr.pats.extend(std::iter::repeat_n(Pat::Wild, arity));
266
+ nr.pats.extend_from_slice(&r.pats[col + 1..]);
267
+ new_rows.push(nr);
268
+ }
269
+ _ => {}
270
+ }
271
+ }
272
+ cases.push(ConCase {
273
+ con: *c,
274
+ tag: self.env.con(*c).tag,
275
+ arity,
276
+ base,
277
+ tree: self.compile(&new_occs, new_rows),
278
+ });
279
+ }
280
+ let default = if self.env.is_complete(ty, &heads) {
281
+ None
282
+ } else {
283
+ Some(Box::new(self.compile_default(occs, col, &rows)))
284
+ };
285
+ Tree::SwitchCon {
286
+ slot,
287
+ ty,
288
+ cases,
289
+ default,
290
+ }
291
+ }
292
+ Pat::Lit(l0) => {
293
+ let kind = l0.kind();
294
+ let mut lits: Vec<Lit> = Vec::new();
295
+ for r in &rows {
296
+ if let Pat::Lit(l) = &r.pats[col] {
297
+ if !lits.iter().any(|x| x.same(l)) {
298
+ lits.push(l.clone());
299
+ }
300
+ }
301
+ }
302
+ let mut cases = Vec::with_capacity(lits.len());
303
+ let mut new_occs: Vec<usize> = occs[..col].to_vec();
304
+ new_occs.extend_from_slice(&occs[col + 1..]);
305
+ for l in &lits {
306
+ let mut new_rows = Vec::new();
307
+ for r in &rows {
308
+ let keep = match &r.pats[col] {
309
+ Pat::Lit(ll) => ll.same(l),
310
+ Pat::Wild => true,
311
+ _ => false,
312
+ };
313
+ if keep {
314
+ let mut nr = r.clone();
315
+ nr.pats.remove(col);
316
+ new_rows.push(nr);
317
+ }
318
+ }
319
+ cases.push((l.clone(), self.compile(&new_occs, new_rows)));
320
+ }
321
+ let default = Box::new(self.compile_default(occs, col, &rows));
322
+ Tree::SwitchLit {
323
+ slot,
324
+ kind,
325
+ cases,
326
+ default,
327
+ }
328
+ }
329
+ Pat::Hash(f0) => {
330
+ // Hash patterns overlap (a value may have the keys of several),
331
+ // so they are tested one key set at a time, in clause order.
332
+ let keys = hash_keys(f0);
333
+ let n = keys.len();
334
+ let base = self.alloc(n);
335
+ let mut then_occs: Vec<usize> = occs[..col].to_vec();
336
+ then_occs.extend(base..base + n);
337
+ then_occs.extend_from_slice(&occs[col + 1..]);
338
+ let mut then_rows = Vec::new();
339
+ let mut else_rows = Vec::new();
340
+ for r in &rows {
341
+ match &r.pats[col] {
342
+ Pat::Hash(f) => {
343
+ let ks = hash_keys(f);
344
+ if key_subset(&ks, &keys) {
345
+ // has every key the row needs: its sub-patterns
346
+ // take the key columns, unmentioned keys are `_`
347
+ let mut nr = r.clone();
348
+ nr.pats = r.pats[..col].to_vec();
349
+ for k in &keys {
350
+ nr.pats.push(
351
+ f.iter()
352
+ .find(|(kk, _)| kk == k)
353
+ .map(|(_, p)| p.clone())
354
+ .unwrap_or(Pat::Wild),
355
+ );
356
+ }
357
+ nr.pats.extend_from_slice(&r.pats[col + 1..]);
358
+ normalize(&mut nr, &then_occs);
359
+ then_rows.push(nr);
360
+ }
361
+ // a value lacking one of `keys` cannot match a row
362
+ // needing all of them; any other row may still match
363
+ if !key_subset(&keys, &ks) {
364
+ else_rows.push(r.clone());
365
+ }
366
+ }
367
+ Pat::Wild => {
368
+ let mut nr = r.clone();
369
+ nr.pats = r.pats[..col].to_vec();
370
+ nr.pats.extend(std::iter::repeat_n(Pat::Wild, n));
371
+ nr.pats.extend_from_slice(&r.pats[col + 1..]);
372
+ then_rows.push(nr);
373
+ else_rows.push(r.clone());
374
+ }
375
+ _ => {}
376
+ }
377
+ }
378
+ Tree::TestHash {
379
+ slot,
380
+ keys,
381
+ base,
382
+ then: Box::new(self.compile(&then_occs, then_rows)),
383
+ otherwise: Box::new(self.compile(occs, else_rows)),
384
+ }
385
+ }
386
+ _ => unreachable!("normalized rows contain only Wild, Con, Lit and Hash"),
387
+ }
388
+ }
389
+
390
+ fn compile_default(&mut self, occs: &[usize], col: usize, rows: &[Row]) -> Tree {
391
+ let mut new_occs: Vec<usize> = occs[..col].to_vec();
392
+ new_occs.extend_from_slice(&occs[col + 1..]);
393
+ let new_rows: Vec<Row> = rows
394
+ .iter()
395
+ .filter(|r| r.pats[col].is_wild())
396
+ .map(|r| {
397
+ let mut nr = r.clone();
398
+ nr.pats.remove(col);
399
+ nr
400
+ })
401
+ .collect();
402
+ self.compile(&new_occs, new_rows)
403
+ }
404
+ }
405
+
406
+ #[cfg(test)]
407
+ pub mod interp {
408
+ //! A reference interpreter over an abstract value model, used to test the
409
+ //! compiled trees without Ruby.
410
+ use super::*;
411
+ use crate::core::types::{TypeKind, CON_CONS, CON_FALSE, CON_NIL, CON_TRUE};
412
+
413
+ #[derive(Clone, Debug, PartialEq)]
414
+ pub enum Val {
415
+ Con(ConId, Vec<Val>),
416
+ List(Vec<Val>),
417
+ Tuple(Vec<Val>),
418
+ Bool(bool),
419
+ Int(i64),
420
+ Str(String),
421
+ Hash(Vec<(HKey, Val)>),
422
+ Other(String),
423
+ }
424
+
425
+ #[derive(Debug, PartialEq)]
426
+ pub enum Outcome {
427
+ Matched { clause: usize, binds: Vec<Val> },
428
+ NoMatch,
429
+ TypeMismatch(TypeId),
430
+ LazyFailed,
431
+ }
432
+
433
+ fn con_of(env: &TypeEnv, ty: TypeId, v: &Val) -> Option<(usize, Vec<Val>)> {
434
+ match (env.ty(ty).kind, v) {
435
+ (TypeKind::Adt, Val::Con(c, fields)) if env.type_of_con(*c) == ty => {
436
+ Some((env.con(*c).tag, fields.clone()))
437
+ }
438
+ (TypeKind::Bool, Val::Bool(b)) => Some((if *b { 1 } else { 0 }, vec![])),
439
+ (TypeKind::List, Val::List(items)) => {
440
+ if items.is_empty() {
441
+ Some((0, vec![]))
442
+ } else {
443
+ Some((1, vec![items[0].clone(), Val::List(items[1..].to_vec())]))
444
+ }
445
+ }
446
+ (TypeKind::List, Val::Str(s)) => {
447
+ let mut chars = s.chars();
448
+ match chars.next() {
449
+ None => Some((0, vec![])),
450
+ Some(c) => Some((1, vec![Val::Str(c.to_string()), Val::Str(chars.collect())])),
451
+ }
452
+ }
453
+ (TypeKind::Tuple(n), Val::Tuple(items)) if items.len() == n => Some((0, items.clone())),
454
+ _ => None,
455
+ }
456
+ }
457
+
458
+ fn lit_eq(l: &Lit, v: &Val) -> bool {
459
+ match (l, v) {
460
+ (Lit::Int(i), Val::Int(j)) => i == j,
461
+ (Lit::Char(c), Val::Str(t)) => t.chars().count() == 1 && t.starts_with(*c),
462
+ _ => false,
463
+ }
464
+ }
465
+
466
+ fn lit_kind_ok(k: LitKind, v: &Val) -> bool {
467
+ matches!(
468
+ (k, v),
469
+ (LitKind::Num, Val::Int(_)) | (LitKind::Char, Val::Str(_))
470
+ )
471
+ }
472
+
473
+ /// Match `pat` against `v` interpretively (for lazy patterns).
474
+ fn destructure(env: &TypeEnv, pat: &Pat, v: &Val, out: &mut Vec<Option<Val>>) -> bool {
475
+ match pat {
476
+ Pat::Wild => true,
477
+ Pat::Var(x) => {
478
+ out[*x] = Some(v.clone());
479
+ true
480
+ }
481
+ Pat::As(x, p) => {
482
+ out[*x] = Some(v.clone());
483
+ destructure(env, p, v, out)
484
+ }
485
+ Pat::Lazy(p) => destructure(env, p, v, out),
486
+ Pat::Lit(l) => lit_eq(l, v),
487
+ Pat::Hash(fields) => match v {
488
+ Val::Hash(entries) => fields.iter().all(|(k, p)| {
489
+ entries
490
+ .iter()
491
+ .find(|(kk, _)| kk == k)
492
+ .map(|(_, val)| destructure(env, p, val, out))
493
+ .unwrap_or(false)
494
+ }),
495
+ _ => false,
496
+ },
497
+ Pat::Con(c, args) => {
498
+ let ty = env.type_of_con(*c);
499
+ let _ = (CON_CONS, CON_NIL, CON_TRUE, CON_FALSE);
500
+ match con_of(env, ty, v) {
501
+ Some((tag, fields)) if tag == env.con(*c).tag => args
502
+ .iter()
503
+ .zip(fields.iter())
504
+ .all(|(a, f)| destructure(env, a, f, out)),
505
+ _ => false,
506
+ }
507
+ }
508
+ }
509
+ }
510
+
511
+ pub fn eval(
512
+ env: &TypeEnv,
513
+ compiled: &Compiled,
514
+ args: &[Val],
515
+ guards: &dyn Fn(usize, &[Val]) -> bool,
516
+ ) -> Outcome {
517
+ let mut slots: Vec<Option<Val>> = vec![None; compiled.n_slots];
518
+ for (i, a) in args.iter().enumerate() {
519
+ slots[i] = Some(a.clone());
520
+ }
521
+ let mut node = &compiled.tree;
522
+ loop {
523
+ match node {
524
+ Tree::Fail => return Outcome::NoMatch,
525
+ Tree::Leaf(leaf) => return bind(env, leaf, &slots),
526
+ Tree::Guard { leaf, fallback } => match bind(env, leaf, &slots) {
527
+ Outcome::Matched { clause, binds } => {
528
+ if guards(clause, &binds) {
529
+ return Outcome::Matched { clause, binds };
530
+ }
531
+ node = fallback;
532
+ }
533
+ other => return other,
534
+ },
535
+ Tree::SwitchCon {
536
+ slot,
537
+ ty,
538
+ cases,
539
+ default,
540
+ } => {
541
+ let v = slots[*slot].clone().unwrap();
542
+ match con_of(env, *ty, &v) {
543
+ None => return Outcome::TypeMismatch(*ty),
544
+ Some((tag, fields)) => match cases.iter().find(|c| c.tag == tag) {
545
+ Some(case) => {
546
+ for (i, f) in fields.into_iter().enumerate() {
547
+ slots[case.base + i] = Some(f);
548
+ }
549
+ node = &case.tree;
550
+ }
551
+ None => match default {
552
+ Some(d) => node = d,
553
+ None => panic!("complete switch without case for tag {}", tag),
554
+ },
555
+ },
556
+ }
557
+ }
558
+ Tree::SwitchLit {
559
+ slot,
560
+ kind,
561
+ cases,
562
+ default,
563
+ } => {
564
+ let v = slots[*slot].clone().unwrap();
565
+ if !lit_kind_ok(*kind, &v) {
566
+ return Outcome::TypeMismatch(usize::MAX);
567
+ }
568
+ match cases.iter().find(|(l, _)| lit_eq(l, &v)) {
569
+ Some((_, t)) => node = t,
570
+ None => node = default,
571
+ }
572
+ }
573
+ Tree::TestHash {
574
+ slot,
575
+ keys,
576
+ base,
577
+ then,
578
+ otherwise,
579
+ } => {
580
+ let v = slots[*slot].clone().unwrap();
581
+ let entries = match &v {
582
+ Val::Hash(e) => e,
583
+ _ => {
584
+ if matches!(**otherwise, Tree::Fail) {
585
+ return Outcome::TypeMismatch(usize::MAX - 1);
586
+ }
587
+ node = otherwise;
588
+ continue;
589
+ }
590
+ };
591
+ let mut all = true;
592
+ for (i, k) in keys.iter().enumerate() {
593
+ match entries.iter().find(|(kk, _)| kk == k) {
594
+ Some((_, val)) => slots[*base + i] = Some(val.clone()),
595
+ None => {
596
+ all = false;
597
+ break;
598
+ }
599
+ }
600
+ }
601
+ node = if all { then } else { otherwise };
602
+ }
603
+ }
604
+ }
605
+ }
606
+
607
+ fn bind(env: &TypeEnv, leaf: &Leaf, slots: &[Option<Val>]) -> Outcome {
608
+ let mut out: Vec<Option<Val>> = vec![None; leaf.binds.len()];
609
+ for lz in &leaf.lazies {
610
+ let v = slots[lz.slot].clone().unwrap();
611
+ if !destructure(env, &lz.pat, &v, &mut out) {
612
+ return Outcome::LazyFailed;
613
+ }
614
+ }
615
+ for (i, b) in leaf.binds.iter().enumerate() {
616
+ if let BindSrc::Slot(s) = b {
617
+ out[i] = slots[*s].clone();
618
+ }
619
+ }
620
+ Outcome::Matched {
621
+ clause: leaf.clause,
622
+ binds: out.into_iter().map(|v| v.expect("bound")).collect(),
623
+ }
624
+ }
625
+ }
626
+
627
+ #[cfg(test)]
628
+ mod tests {
629
+ use super::interp::{eval, Outcome, Val};
630
+ use super::*;
631
+ use crate::core::parser::parse_pattern;
632
+ use crate::core::resolve::resolve_clause;
633
+ use crate::core::types::ConSpec;
634
+
635
+ fn env() -> TypeEnv {
636
+ let mut env = TypeEnv::new();
637
+ env.register(
638
+ "Maybe",
639
+ &[
640
+ ConSpec {
641
+ name: "Nothing".into(),
642
+ arity: 0,
643
+ fields: None,
644
+ handle: 0,
645
+ },
646
+ ConSpec {
647
+ name: "Just".into(),
648
+ arity: 1,
649
+ fields: None,
650
+ handle: 0,
651
+ },
652
+ ],
653
+ )
654
+ .unwrap();
655
+ env
656
+ }
657
+
658
+ fn build(env: &mut TypeEnv, clauses: &[&[&str]]) -> (Compiled, Vec<Vec<String>>) {
659
+ let mut cs = Vec::new();
660
+ let mut names = Vec::new();
661
+ let mut arity = None;
662
+ for c in clauses {
663
+ let guarded = c.last() == Some(&"|");
664
+ let pats: Vec<&str> = c.iter().copied().filter(|s| *s != "|").collect();
665
+ arity = Some(pats.len());
666
+ let raws: Vec<_> = pats.iter().map(|s| parse_pattern(s).unwrap()).collect();
667
+ let (ps, b) = resolve_clause(env, &raws).unwrap();
668
+ cs.push(Clause {
669
+ pats: ps,
670
+ n_vars: b.names.len(),
671
+ guarded,
672
+ });
673
+ names.push(b.names);
674
+ }
675
+ (compile(env, &cs, arity.unwrap()), names)
676
+ }
677
+
678
+ fn just(env: &TypeEnv, v: Val) -> Val {
679
+ Val::Con(env.lookup_con("Just").unwrap(), vec![v])
680
+ }
681
+ fn nothing(env: &TypeEnv) -> Val {
682
+ Val::Con(env.lookup_con("Nothing").unwrap(), vec![])
683
+ }
684
+ fn list(items: Vec<Val>) -> Val {
685
+ Val::List(items)
686
+ }
687
+ fn no_guards(_: usize, _: &[Val]) -> bool {
688
+ true
689
+ }
690
+
691
+ fn matched(clause: usize, binds: Vec<Val>) -> Outcome {
692
+ Outcome::Matched { clause, binds }
693
+ }
694
+
695
+ #[test]
696
+ fn maybe_function() {
697
+ let mut env = env();
698
+ let (c, names) = build(&mut env, &[&["Nothing"], &["Just x"]]);
699
+ assert_eq!(names, vec![Vec::<String>::new(), vec!["x".to_string()]]);
700
+ assert_eq!(
701
+ eval(&env, &c, &[nothing(&env)], &no_guards),
702
+ matched(0, vec![])
703
+ );
704
+ assert_eq!(
705
+ eval(&env, &c, &[just(&env, Val::Int(5))], &no_guards),
706
+ matched(1, vec![Val::Int(5)])
707
+ );
708
+ assert_eq!(
709
+ eval(&env, &c, &[Val::Int(5)], &no_guards),
710
+ Outcome::TypeMismatch(env.lookup_type("Maybe").unwrap())
711
+ );
712
+ }
713
+
714
+ #[test]
715
+ fn list_functions() {
716
+ let mut env = env();
717
+ let (c, _) = build(&mut env, &[&["[]"], &["[x]"], &["(x:y:rest)"]]);
718
+ assert_eq!(
719
+ eval(&env, &c, &[list(vec![])], &no_guards),
720
+ matched(0, vec![])
721
+ );
722
+ assert_eq!(
723
+ eval(&env, &c, &[list(vec![Val::Int(1)])], &no_guards),
724
+ matched(1, vec![Val::Int(1)])
725
+ );
726
+ assert_eq!(
727
+ eval(
728
+ &env,
729
+ &c,
730
+ &[list(vec![Val::Int(1), Val::Int(2), Val::Int(3)])],
731
+ &no_guards
732
+ ),
733
+ matched(2, vec![Val::Int(1), Val::Int(2), list(vec![Val::Int(3)])])
734
+ );
735
+ // as-pattern
736
+ let (c, names) = build(&mut env, &[&["all@(x:_)"], &["[]"]]);
737
+ assert_eq!(names[0], vec!["all", "x"]);
738
+ let l = list(vec![Val::Int(7), Val::Int(8)]);
739
+ assert_eq!(
740
+ eval(&env, &c, std::slice::from_ref(&l), &no_guards),
741
+ matched(0, vec![l.clone(), Val::Int(7)])
742
+ );
743
+ }
744
+
745
+ #[test]
746
+ fn multi_argument_and_order() {
747
+ let mut env = env();
748
+ // zip
749
+ let (c, names) = build(&mut env, &[&["(x:xs)", "(y:ys)"], &["_", "_"]]);
750
+ assert_eq!(names[0], vec!["x", "xs", "y", "ys"]);
751
+ assert_eq!(
752
+ eval(
753
+ &env,
754
+ &c,
755
+ &[
756
+ list(vec![Val::Int(1), Val::Int(2)]),
757
+ list(vec![Val::Int(3)])
758
+ ],
759
+ &no_guards
760
+ ),
761
+ matched(
762
+ 0,
763
+ vec![
764
+ Val::Int(1),
765
+ list(vec![Val::Int(2)]),
766
+ Val::Int(3),
767
+ list(vec![])
768
+ ]
769
+ )
770
+ );
771
+ assert_eq!(
772
+ eval(
773
+ &env,
774
+ &c,
775
+ &[list(vec![]), list(vec![Val::Int(3)])],
776
+ &no_guards
777
+ ),
778
+ matched(1, vec![])
779
+ );
780
+ assert_eq!(
781
+ eval(
782
+ &env,
783
+ &c,
784
+ &[list(vec![Val::Int(3)]), list(vec![])],
785
+ &no_guards
786
+ ),
787
+ matched(1, vec![])
788
+ );
789
+ // first-match semantics with overlapping rows
790
+ let (c, _) = build(
791
+ &mut env,
792
+ &[&["(True, _)"], &["(_, True)"], &["(False, False)"]],
793
+ );
794
+ let t = |a, b| Val::Tuple(vec![Val::Bool(a), Val::Bool(b)]);
795
+ assert_eq!(
796
+ eval(&env, &c, &[t(true, true)], &no_guards),
797
+ matched(0, vec![])
798
+ );
799
+ assert_eq!(
800
+ eval(&env, &c, &[t(false, true)], &no_guards),
801
+ matched(1, vec![])
802
+ );
803
+ assert_eq!(
804
+ eval(&env, &c, &[t(false, false)], &no_guards),
805
+ matched(2, vec![])
806
+ );
807
+ assert_eq!(
808
+ eval(&env, &c, &[Val::Tuple(vec![Val::Bool(true)])], &no_guards),
809
+ Outcome::TypeMismatch(env.lookup_type("(,)").unwrap())
810
+ );
811
+ }
812
+
813
+ #[test]
814
+ fn literals_and_default() {
815
+ let mut env = env();
816
+ let (c, _) = build(&mut env, &[&["0"], &["1"], &["n"]]);
817
+ assert_eq!(
818
+ eval(&env, &c, &[Val::Int(0)], &no_guards),
819
+ matched(0, vec![])
820
+ );
821
+ assert_eq!(
822
+ eval(&env, &c, &[Val::Int(1)], &no_guards),
823
+ matched(1, vec![])
824
+ );
825
+ assert_eq!(
826
+ eval(&env, &c, &[Val::Int(9)], &no_guards),
827
+ matched(2, vec![Val::Int(9)])
828
+ );
829
+ assert_eq!(
830
+ eval(&env, &c, &[Val::Str("x".into())], &no_guards),
831
+ Outcome::TypeMismatch(usize::MAX)
832
+ );
833
+ let (c, _) = build(&mut env, &[&["Just \"a\""], &["Just s"], &["Nothing"]]);
834
+ assert_eq!(
835
+ eval(&env, &c, &[just(&env, Val::Str("a".into()))], &no_guards),
836
+ matched(0, vec![])
837
+ );
838
+ assert_eq!(
839
+ eval(&env, &c, &[just(&env, Val::Str("b".into()))], &no_guards),
840
+ matched(1, vec![Val::Str("b".into())])
841
+ );
842
+ // partial function
843
+ let (c, _) = build(&mut env, &[&["Just x"]]);
844
+ assert_eq!(
845
+ eval(&env, &c, &[nothing(&env)], &no_guards),
846
+ Outcome::NoMatch
847
+ );
848
+ }
849
+
850
+ #[test]
851
+ fn guards_fall_through() {
852
+ let mut env = env();
853
+ let (c, _) = build(&mut env, &[&["Just x", "|"], &["Just _"], &["Nothing"]]);
854
+ let positive =
855
+ |clause: usize, binds: &[Val]| clause != 0 || matches!(binds[0], Val::Int(i) if i > 0);
856
+ assert_eq!(
857
+ eval(&env, &c, &[just(&env, Val::Int(3))], &positive),
858
+ matched(0, vec![Val::Int(3)])
859
+ );
860
+ assert_eq!(
861
+ eval(&env, &c, &[just(&env, Val::Int(-3))], &positive),
862
+ matched(1, vec![])
863
+ );
864
+ assert_eq!(
865
+ eval(&env, &c, &[nothing(&env)], &positive),
866
+ matched(2, vec![])
867
+ );
868
+ // guard on the last clause with nothing after it
869
+ let (c, _) = build(&mut env, &[&["x", "|"]]);
870
+ assert_eq!(
871
+ eval(&env, &c, &[Val::Int(1)], &|_, _| false),
872
+ Outcome::NoMatch
873
+ );
874
+ }
875
+
876
+ #[test]
877
+ fn lazy_patterns() {
878
+ let mut env = env();
879
+ let (c, names) = build(&mut env, &[&["~(Just x)"]]);
880
+ assert_eq!(names[0], vec!["x"]);
881
+ assert_eq!(
882
+ eval(&env, &c, &[just(&env, Val::Int(1))], &no_guards),
883
+ matched(0, vec![Val::Int(1)])
884
+ );
885
+ // irrefutable: matches, but destructuring fails
886
+ assert_eq!(
887
+ eval(&env, &c, &[nothing(&env)], &no_guards),
888
+ Outcome::LazyFailed
889
+ );
890
+ // nested lazy with other binders
891
+ let (c, names) = build(&mut env, &[&["(a, ~(b, c))"]]);
892
+ assert_eq!(names[0], vec!["a", "b", "c"]);
893
+ let v = Val::Tuple(vec![
894
+ Val::Int(1),
895
+ Val::Tuple(vec![Val::Int(2), Val::Int(3)]),
896
+ ]);
897
+ assert_eq!(
898
+ eval(&env, &c, &[v], &no_guards),
899
+ matched(0, vec![Val::Int(1), Val::Int(2), Val::Int(3)])
900
+ );
901
+ }
902
+
903
+ #[test]
904
+ fn slots_are_allocated_per_case() {
905
+ let mut env = env();
906
+ let (c, _) = build(
907
+ &mut env,
908
+ &[&["Just (Just x)"], &["Just Nothing"], &["Nothing"]],
909
+ );
910
+ assert!(c.n_slots >= 3);
911
+ match &c.tree {
912
+ Tree::SwitchCon {
913
+ slot: 0,
914
+ cases,
915
+ default: None,
916
+ ..
917
+ } => assert_eq!(cases.len(), 2),
918
+ other => panic!("unexpected tree {:?}", other),
919
+ }
920
+ }
921
+ }