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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +98 -0
- data/LICENSE-APACHE +202 -0
- data/LICENSE-MIT +21 -0
- data/README.md +1484 -0
- data/ext/haskell_match/Cargo.lock +33 -0
- data/ext/haskell_match/Cargo.toml +22 -0
- data/ext/haskell_match/extconf.rb +41 -0
- data/ext/haskell_match/src/core/ast.rs +190 -0
- data/ext/haskell_match/src/core/error.rs +52 -0
- data/ext/haskell_match/src/core/exhaust.rs +699 -0
- data/ext/haskell_match/src/core/hs/ast.rs +256 -0
- data/ext/haskell_match/src/core/hs/json.rs +225 -0
- data/ext/haskell_match/src/core/hs/layout.rs +346 -0
- data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
- data/ext/haskell_match/src/core/hs/mod.rs +14 -0
- data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
- data/ext/haskell_match/src/core/lexer.rs +590 -0
- data/ext/haskell_match/src/core/mod.rs +19 -0
- data/ext/haskell_match/src/core/parser.rs +1116 -0
- data/ext/haskell_match/src/core/pretty.rs +373 -0
- data/ext/haskell_match/src/core/resolve.rs +336 -0
- data/ext/haskell_match/src/core/tree.rs +921 -0
- data/ext/haskell_match/src/core/typecheck.rs +226 -0
- data/ext/haskell_match/src/core/types.rs +404 -0
- data/ext/haskell_match/src/lib.rs +19 -0
- data/ext/haskell_match/src/ruby/mod.rs +1195 -0
- data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
- data/lib/haskell_match/binding_plan.rb +84 -0
- data/lib/haskell_match/case_of.rb +71 -0
- data/lib/haskell_match/clauses.rb +354 -0
- data/lib/haskell_match/data.rb +417 -0
- data/lib/haskell_match/deep_call.rb +98 -0
- data/lib/haskell_match/deriving.rb +130 -0
- data/lib/haskell_match/dsl.rb +71 -0
- data/lib/haskell_match/errors.rb +85 -0
- data/lib/haskell_match/field_types.rb +140 -0
- data/lib/haskell_match/function.rb +240 -0
- data/lib/haskell_match/haskell/compiler.rb +961 -0
- data/lib/haskell_match/haskell.rb +326 -0
- data/lib/haskell_match/inspect.rb +45 -0
- data/lib/haskell_match/lazy_list.rb +210 -0
- data/lib/haskell_match/native_loader.rb +64 -0
- data/lib/haskell_match/pattern.rb +75 -0
- data/lib/haskell_match/pattern_ast.rb +394 -0
- data/lib/haskell_match/prelude.rb +448 -0
- data/lib/haskell_match/scope.rb +44 -0
- data/lib/haskell_match/version.rb +5 -0
- data/lib/haskell_match.rb +41 -0
- metadata +124 -0
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
//! Runtime evaluation of compiled decision trees over live Ruby values.
|
|
2
|
+
//!
|
|
3
|
+
//! The core `Tree` is translated once into an `RtTree` whose constructor
|
|
4
|
+
//! cases carry the Ruby class of each constructor and whose literal cases
|
|
5
|
+
//! carry a GC-pinned Ruby object for slow-path comparisons. Matching then
|
|
6
|
+
//! never allocates until a clause is selected: list tails are represented as
|
|
7
|
+
//! `(array, offset)` views and materialised with `rb_ary_subseq` (which
|
|
8
|
+
//! shares the backing store) only when bound to a variable.
|
|
9
|
+
|
|
10
|
+
use rutie::rubysys::{array, class, rproc, rstruct, value::ValueType};
|
|
11
|
+
use rutie::types::Value;
|
|
12
|
+
use rutie::{AnyObject, Object, VM};
|
|
13
|
+
|
|
14
|
+
use std::collections::HashMap;
|
|
15
|
+
|
|
16
|
+
use crate::core::ast::{HKey, Lit, LitKey, LitKind, Pat};
|
|
17
|
+
use crate::core::tree::{BindSrc, LazyPat, Leaf, Tree};
|
|
18
|
+
use crate::core::types::{TypeEnv, TypeId, TypeKind};
|
|
19
|
+
|
|
20
|
+
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
21
|
+
pub enum SlotKind {
|
|
22
|
+
/// An ordinary Ruby value.
|
|
23
|
+
Plain,
|
|
24
|
+
/// A list view: elements `off..` of the Array or String in `v`.
|
|
25
|
+
View,
|
|
26
|
+
/// Character `off` of the String in `v` (a list head not yet
|
|
27
|
+
/// materialised as a one-character String).
|
|
28
|
+
Char,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// A value slot of the decision tree. Kept to 16 bytes: slots live on the
|
|
32
|
+
/// native stack of every (possibly deeply recursive) call.
|
|
33
|
+
#[derive(Clone, Copy)]
|
|
34
|
+
pub struct Slot {
|
|
35
|
+
pub v: Value,
|
|
36
|
+
pub off: i32,
|
|
37
|
+
pub kind: SlotKind,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl Slot {
|
|
41
|
+
#[inline]
|
|
42
|
+
fn plain(v: Value) -> Slot {
|
|
43
|
+
Slot {
|
|
44
|
+
v,
|
|
45
|
+
off: 0,
|
|
46
|
+
kind: SlotKind::Plain,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
#[inline]
|
|
50
|
+
fn empty() -> Slot {
|
|
51
|
+
Slot::plain(Value::from(0))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
extern "C" {
|
|
56
|
+
fn rb_str_substr(
|
|
57
|
+
string: Value,
|
|
58
|
+
begin: std::os::raw::c_long,
|
|
59
|
+
len: std::os::raw::c_long,
|
|
60
|
+
) -> Value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// `HaskellMatch::LazyList` (a memoised lazy cons cell) and `Enumerator`,
|
|
64
|
+
/// looked up once. Values of these classes are matched by list patterns.
|
|
65
|
+
struct LazyClasses {
|
|
66
|
+
lazy_list: Value,
|
|
67
|
+
enumerator: Value,
|
|
68
|
+
force: rutie::types::Id,
|
|
69
|
+
from: rutie::types::Id,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static LAZY: std::sync::OnceLock<LazyClasses> = std::sync::OnceLock::new();
|
|
73
|
+
|
|
74
|
+
fn lazy_classes() -> &'static LazyClasses {
|
|
75
|
+
LAZY.get_or_init(|| {
|
|
76
|
+
let lazy_list = rutie::Module::from_existing("HaskellMatch").get_nested_class("LazyList");
|
|
77
|
+
let enumerator = rutie::Class::from_existing("Enumerator");
|
|
78
|
+
unsafe { rutie::rubysys::gc::rb_gc_register_mark_object(lazy_list.value()) };
|
|
79
|
+
let force = std::ffi::CString::new("force").unwrap();
|
|
80
|
+
let from = std::ffi::CString::new("from").unwrap();
|
|
81
|
+
LazyClasses {
|
|
82
|
+
lazy_list: lazy_list.value(),
|
|
83
|
+
enumerator: enumerator.value(),
|
|
84
|
+
force: unsafe { rutie::rubysys::symbol::rb_intern(force.as_ptr()) },
|
|
85
|
+
from: unsafe { rutie::rubysys::symbol::rb_intern(from.as_ptr()) },
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Force a lazy cell: `Ok(None)` for the empty list, `Ok(Some((head, tail)))`
|
|
91
|
+
/// for a cons, `Err(state)` when forcing raised.
|
|
92
|
+
unsafe fn force_lazy(cell: Value) -> Result<Option<(Value, Value)>, i32> {
|
|
93
|
+
let lc = lazy_classes();
|
|
94
|
+
let r =
|
|
95
|
+
call_protected(|| rutie::rubysys::vm::rb_funcallv(cell, lc.force, 0, std::ptr::null()))?;
|
|
96
|
+
if r.is_nil() || r.ty() != ValueType::Array || array::rb_ary_len(r) != 2 {
|
|
97
|
+
return Ok(None);
|
|
98
|
+
}
|
|
99
|
+
Ok(Some((array::rb_ary_entry(r, 0), array::rb_ary_entry(r, 1))))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Is `v` a lazy list (a cell, or an Enumerator to be wrapped as one)?
|
|
103
|
+
#[inline]
|
|
104
|
+
unsafe fn is_lazy(v: Value) -> bool {
|
|
105
|
+
if v.is_fixnum() || v.is_flonum() || v.is_nil() || v.is_true() || v.is_false() || v.is_symbol()
|
|
106
|
+
{
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
let lc = lazy_classes();
|
|
110
|
+
class::rb_obj_is_kind_of(v, lc.lazy_list).is_true()
|
|
111
|
+
|| class::rb_obj_is_kind_of(v, lc.enumerator).is_true()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Wrap an Enumerator in a fresh lazy list (cells are memoised, and a new
|
|
115
|
+
/// wrapper iterates from the start, so the same stream matches the same way
|
|
116
|
+
/// on every call).
|
|
117
|
+
unsafe fn as_lazy_cell(v: Value) -> Result<Value, i32> {
|
|
118
|
+
let lc = lazy_classes();
|
|
119
|
+
if class::rb_obj_is_kind_of(v, lc.lazy_list).is_true() {
|
|
120
|
+
return Ok(v);
|
|
121
|
+
}
|
|
122
|
+
call_protected(|| rutie::rubysys::vm::rb_funcallv(lc.lazy_list, lc.from, 1, &v))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
pub struct RtCase {
|
|
126
|
+
pub handle: usize,
|
|
127
|
+
pub tag: usize,
|
|
128
|
+
pub arity: usize,
|
|
129
|
+
pub base: usize,
|
|
130
|
+
pub tree: RtTree,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
pub struct RtLitCase {
|
|
134
|
+
pub lit: Lit,
|
|
135
|
+
/// Ruby object equal to the literal, pinned with
|
|
136
|
+
/// `rb_gc_register_mark_object`.
|
|
137
|
+
pub obj: Value,
|
|
138
|
+
pub tree: RtTree,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
pub enum RtTree {
|
|
142
|
+
Fail,
|
|
143
|
+
Leaf(Leaf),
|
|
144
|
+
Guard {
|
|
145
|
+
leaf: Leaf,
|
|
146
|
+
fallback: Box<RtTree>,
|
|
147
|
+
},
|
|
148
|
+
SwitchCon {
|
|
149
|
+
slot: usize,
|
|
150
|
+
ty: TypeId,
|
|
151
|
+
cases: Vec<RtCase>,
|
|
152
|
+
default: Option<Box<RtTree>>,
|
|
153
|
+
},
|
|
154
|
+
SwitchLit {
|
|
155
|
+
slot: usize,
|
|
156
|
+
kind: LitKind,
|
|
157
|
+
cases: Vec<RtLitCase>,
|
|
158
|
+
default: Box<RtTree>,
|
|
159
|
+
},
|
|
160
|
+
TestHash {
|
|
161
|
+
slot: usize,
|
|
162
|
+
/// Pinned key objects (Symbols / frozen Strings), in `keys` order.
|
|
163
|
+
key_objs: Vec<Value>,
|
|
164
|
+
keys: Vec<HKey>,
|
|
165
|
+
base: usize,
|
|
166
|
+
then: Box<RtTree>,
|
|
167
|
+
otherwise: Box<RtTree>,
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Why a match did not produce a clause.
|
|
172
|
+
pub enum RtError {
|
|
173
|
+
/// A guard or body left through a Ruby exception or a non-local jump
|
|
174
|
+
/// (`throw`, `break`, ...): the `rb_protect` state to resume with
|
|
175
|
+
/// `rb_jump_tag` once all Rust frames have been unwound.
|
|
176
|
+
Ruby(i32),
|
|
177
|
+
/// No clause matched (partial function or all guards failed).
|
|
178
|
+
NoMatch,
|
|
179
|
+
/// Value is not of the type the patterns in this position expect.
|
|
180
|
+
TypeMismatch { expected: String, got: Value },
|
|
181
|
+
/// A lazy (`~`) pattern failed to destructure after the clause was chosen.
|
|
182
|
+
LazyFailed { clause: usize, got: Value },
|
|
183
|
+
/// Wrong number of arguments.
|
|
184
|
+
Arity { expected: usize, got: usize },
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
pub struct Matcher {
|
|
188
|
+
pub env: TypeEnv,
|
|
189
|
+
pub tree: RtTree,
|
|
190
|
+
pub arity: usize,
|
|
191
|
+
pub n_slots: usize,
|
|
192
|
+
pub names: Vec<Vec<String>>,
|
|
193
|
+
pub guarded: Vec<bool>,
|
|
194
|
+
pub name: String,
|
|
195
|
+
/// Pinned Ruby objects for literals inside lazy patterns.
|
|
196
|
+
pub lazy_lits: HashMap<LitKey, Value>,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Literals occurring inside lazy (`~`) patterns anywhere in the tree.
|
|
200
|
+
pub fn lazy_literals(t: &Tree, out: &mut Vec<Lit>) {
|
|
201
|
+
fn in_pat(p: &Pat, out: &mut Vec<Lit>) {
|
|
202
|
+
match p {
|
|
203
|
+
Pat::Lit(l) => {
|
|
204
|
+
if !out.iter().any(|x| x.same(l)) {
|
|
205
|
+
out.push(l.clone());
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
Pat::As(_, p) | Pat::Lazy(p) => in_pat(p, out),
|
|
209
|
+
Pat::Con(_, args) => args.iter().for_each(|a| in_pat(a, out)),
|
|
210
|
+
Pat::Hash(fields) => fields.iter().for_each(|(_, a)| in_pat(a, out)),
|
|
211
|
+
Pat::Wild | Pat::Var(_) => {}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
fn in_leaf(l: &Leaf, out: &mut Vec<Lit>) {
|
|
215
|
+
l.lazies.iter().for_each(|lz| in_pat(&lz.pat, out));
|
|
216
|
+
}
|
|
217
|
+
match t {
|
|
218
|
+
Tree::Fail => {}
|
|
219
|
+
Tree::Leaf(l) => in_leaf(l, out),
|
|
220
|
+
Tree::Guard { leaf, fallback } => {
|
|
221
|
+
in_leaf(leaf, out);
|
|
222
|
+
lazy_literals(fallback, out);
|
|
223
|
+
}
|
|
224
|
+
Tree::SwitchCon { cases, default, .. } => {
|
|
225
|
+
cases.iter().for_each(|c| lazy_literals(&c.tree, out));
|
|
226
|
+
if let Some(d) = default {
|
|
227
|
+
lazy_literals(d, out);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
Tree::SwitchLit { cases, default, .. } => {
|
|
231
|
+
cases.iter().for_each(|(_, t)| lazy_literals(t, out));
|
|
232
|
+
lazy_literals(default, out);
|
|
233
|
+
}
|
|
234
|
+
Tree::TestHash {
|
|
235
|
+
then, otherwise, ..
|
|
236
|
+
} => {
|
|
237
|
+
lazy_literals(then, out);
|
|
238
|
+
lazy_literals(otherwise, out);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// The Ruby object for a Hash pattern key, pinned for the process lifetime.
|
|
244
|
+
pub fn key_object(k: &HKey) -> Value {
|
|
245
|
+
let v = match k {
|
|
246
|
+
HKey::Sym(s) => rutie::Symbol::new(s).value(),
|
|
247
|
+
HKey::Str(s) => unsafe {
|
|
248
|
+
rutie::rubysys::string::rb_str_freeze(rutie::RString::new_utf8(s).value())
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
unsafe { rutie::rubysys::gc::rb_gc_register_mark_object(v) };
|
|
252
|
+
v
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
pub fn literal_object(l: &Lit) -> Value {
|
|
256
|
+
let v = match l {
|
|
257
|
+
Lit::Int(i) => rutie::Integer::new(*i).value(),
|
|
258
|
+
Lit::Big(s) => {
|
|
259
|
+
let cs = std::ffi::CString::new(s.as_str()).expect("digits contain no NUL");
|
|
260
|
+
unsafe { rutie::rubysys::numeric::rb_cstr_to_inum(cs.as_ptr(), 10, 1) }
|
|
261
|
+
}
|
|
262
|
+
Lit::Float(f) => rutie::Float::new(*f).value(),
|
|
263
|
+
Lit::Char(c) => unsafe {
|
|
264
|
+
rutie::rubysys::string::rb_str_freeze(rutie::RString::new_utf8(&c.to_string()).value())
|
|
265
|
+
},
|
|
266
|
+
Lit::Sym(s) => rutie::Symbol::new(s).value(),
|
|
267
|
+
};
|
|
268
|
+
unsafe { rutie::rubysys::gc::rb_gc_register_mark_object(v) };
|
|
269
|
+
v
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
pub fn translate(env: &TypeEnv, t: &Tree) -> RtTree {
|
|
273
|
+
match t {
|
|
274
|
+
Tree::Fail => RtTree::Fail,
|
|
275
|
+
Tree::Leaf(l) => RtTree::Leaf(l.clone()),
|
|
276
|
+
Tree::Guard { leaf, fallback } => RtTree::Guard {
|
|
277
|
+
leaf: leaf.clone(),
|
|
278
|
+
fallback: Box::new(translate(env, fallback)),
|
|
279
|
+
},
|
|
280
|
+
Tree::SwitchCon {
|
|
281
|
+
slot,
|
|
282
|
+
ty,
|
|
283
|
+
cases,
|
|
284
|
+
default,
|
|
285
|
+
} => RtTree::SwitchCon {
|
|
286
|
+
slot: *slot,
|
|
287
|
+
ty: *ty,
|
|
288
|
+
cases: cases
|
|
289
|
+
.iter()
|
|
290
|
+
.map(|c| RtCase {
|
|
291
|
+
handle: env.con(c.con).handle,
|
|
292
|
+
tag: c.tag,
|
|
293
|
+
arity: c.arity,
|
|
294
|
+
base: c.base,
|
|
295
|
+
tree: translate(env, &c.tree),
|
|
296
|
+
})
|
|
297
|
+
.collect(),
|
|
298
|
+
default: default.as_ref().map(|d| Box::new(translate(env, d))),
|
|
299
|
+
},
|
|
300
|
+
Tree::SwitchLit {
|
|
301
|
+
slot,
|
|
302
|
+
kind,
|
|
303
|
+
cases,
|
|
304
|
+
default,
|
|
305
|
+
} => RtTree::SwitchLit {
|
|
306
|
+
slot: *slot,
|
|
307
|
+
kind: *kind,
|
|
308
|
+
cases: cases
|
|
309
|
+
.iter()
|
|
310
|
+
.map(|(l, t)| RtLitCase {
|
|
311
|
+
lit: l.clone(),
|
|
312
|
+
obj: literal_object(l),
|
|
313
|
+
tree: translate(env, t),
|
|
314
|
+
})
|
|
315
|
+
.collect(),
|
|
316
|
+
default: Box::new(translate(env, default)),
|
|
317
|
+
},
|
|
318
|
+
Tree::TestHash {
|
|
319
|
+
slot,
|
|
320
|
+
keys,
|
|
321
|
+
base,
|
|
322
|
+
then,
|
|
323
|
+
otherwise,
|
|
324
|
+
} => RtTree::TestHash {
|
|
325
|
+
slot: *slot,
|
|
326
|
+
key_objs: keys.iter().map(key_object).collect(),
|
|
327
|
+
keys: keys.clone(),
|
|
328
|
+
base: *base,
|
|
329
|
+
then: Box::new(translate(env, then)),
|
|
330
|
+
otherwise: Box::new(translate(env, otherwise)),
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
#[inline]
|
|
336
|
+
fn is_hash(v: Value) -> bool {
|
|
337
|
+
v.ty() == ValueType::Hash
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[inline]
|
|
341
|
+
fn qundef() -> Value {
|
|
342
|
+
Value::from(rutie::rubysys::value::RubySpecialConsts::Undef as usize)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// The value at `key`, or `None` when the Hash has no such key (a key whose
|
|
346
|
+
/// value is `nil` is present).
|
|
347
|
+
#[inline]
|
|
348
|
+
unsafe fn hash_fetch(h: Value, key: Value) -> Option<Value> {
|
|
349
|
+
let v = rutie::rubysys::hash::rb_hash_lookup2(h, key, qundef());
|
|
350
|
+
if v.value == qundef().value {
|
|
351
|
+
None
|
|
352
|
+
} else {
|
|
353
|
+
Some(v)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#[inline]
|
|
358
|
+
unsafe fn ary_len(v: Value) -> i64 {
|
|
359
|
+
array::rb_ary_len(v) as i64
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#[inline]
|
|
363
|
+
fn is_array(v: Value) -> bool {
|
|
364
|
+
v.ty() == ValueType::Array
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
#[inline]
|
|
368
|
+
fn is_string(v: Value) -> bool {
|
|
369
|
+
v.ty() == ValueType::RString
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
#[inline]
|
|
373
|
+
fn truthy(v: Value) -> bool {
|
|
374
|
+
!(v.is_nil() || v.is_false())
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/// Number of elements of a list view: Arrays and Strings (`String = [Char]`).
|
|
378
|
+
#[inline]
|
|
379
|
+
unsafe fn list_len(slot: Slot) -> Option<i64> {
|
|
380
|
+
if is_array(slot.v) {
|
|
381
|
+
Some(ary_len(slot.v) - slot.off as i64)
|
|
382
|
+
} else if is_string(slot.v) && slot.kind != SlotKind::Char {
|
|
383
|
+
Some(rutie::rubysys::string::rb_str_strlen(slot.v) as i64 - slot.off as i64)
|
|
384
|
+
} else {
|
|
385
|
+
None
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// Identify the constructor (by tag) of `slot` for type `ty`; `None` when the
|
|
390
|
+
/// value is not of that type at all.
|
|
391
|
+
#[inline]
|
|
392
|
+
unsafe fn identify(env: &TypeEnv, ty: TypeId, slot: Slot) -> Result<Option<usize>, RtError> {
|
|
393
|
+
Ok(match env.ty(ty).kind {
|
|
394
|
+
TypeKind::Adt => {
|
|
395
|
+
if slot.kind != SlotKind::Plain {
|
|
396
|
+
return Ok(None);
|
|
397
|
+
}
|
|
398
|
+
let klass = class::rb_obj_class(slot.v).value;
|
|
399
|
+
let dt = env.ty(ty);
|
|
400
|
+
for &c in &dt.cons {
|
|
401
|
+
if env.con(c).handle == klass {
|
|
402
|
+
return Ok(Some(env.con(c).tag));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
None
|
|
406
|
+
}
|
|
407
|
+
TypeKind::Bool => {
|
|
408
|
+
if slot.kind != SlotKind::Plain {
|
|
409
|
+
None
|
|
410
|
+
} else if slot.v.is_true() {
|
|
411
|
+
Some(1)
|
|
412
|
+
} else if slot.v.is_false() {
|
|
413
|
+
Some(0)
|
|
414
|
+
} else {
|
|
415
|
+
None
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
TypeKind::List => match list_len(slot) {
|
|
419
|
+
Some(n) => Some(if n == 0 { 0 } else { 1 }),
|
|
420
|
+
None if slot.kind == SlotKind::Plain && is_lazy(slot.v) => {
|
|
421
|
+
let cell = as_lazy_cell(slot.v).map_err(RtError::Ruby)?;
|
|
422
|
+
match force_lazy(cell).map_err(RtError::Ruby)? {
|
|
423
|
+
None => Some(0),
|
|
424
|
+
Some(_) => Some(1),
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
None => None,
|
|
428
|
+
},
|
|
429
|
+
TypeKind::Tuple(n) => {
|
|
430
|
+
if slot.kind != SlotKind::Char
|
|
431
|
+
&& is_array(slot.v)
|
|
432
|
+
&& (ary_len(slot.v) - slot.off as i64) as usize == n
|
|
433
|
+
{
|
|
434
|
+
Some(0)
|
|
435
|
+
} else {
|
|
436
|
+
None
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/// Write the fields of the constructor at `slot` into `slots[base..]`.
|
|
443
|
+
#[inline]
|
|
444
|
+
unsafe fn fields_into(
|
|
445
|
+
env: &TypeEnv,
|
|
446
|
+
ty: TypeId,
|
|
447
|
+
tag: usize,
|
|
448
|
+
slot: Slot,
|
|
449
|
+
arity: usize,
|
|
450
|
+
base: usize,
|
|
451
|
+
slots: &mut [Slot],
|
|
452
|
+
) -> Result<(), RtError> {
|
|
453
|
+
match env.ty(ty).kind {
|
|
454
|
+
TypeKind::Adt => {
|
|
455
|
+
if slot.v.ty() == ValueType::Struct {
|
|
456
|
+
// `Data` and `Struct` instances: fields by position
|
|
457
|
+
for i in 0..arity {
|
|
458
|
+
let idx = rutie::Integer::new(i as i64).value();
|
|
459
|
+
slots[base + i] = Slot::plain(rstruct::rb_struct_aref(slot.v, idx));
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
// any other class (a sealed hierarchy): fields are reader methods
|
|
463
|
+
let con = env.con(env.con_by_tag(ty, tag));
|
|
464
|
+
let names = con.fields.as_ref().ok_or_else(|| RtError::TypeMismatch {
|
|
465
|
+
expected: format!("{} with field readers", con.name),
|
|
466
|
+
got: slot.v,
|
|
467
|
+
})?;
|
|
468
|
+
for (i, name) in names.iter().enumerate().take(arity) {
|
|
469
|
+
let cname =
|
|
470
|
+
std::ffi::CString::new(name.as_str()).expect("field names have no NUL");
|
|
471
|
+
let id = rutie::rubysys::symbol::rb_intern(cname.as_ptr());
|
|
472
|
+
let v = call_protected(|| {
|
|
473
|
+
rutie::rubysys::vm::rb_funcallv(slot.v, id, 0, std::ptr::null())
|
|
474
|
+
})
|
|
475
|
+
.map_err(RtError::Ruby)?;
|
|
476
|
+
slots[base + i] = Slot::plain(v);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
TypeKind::Bool => {}
|
|
481
|
+
TypeKind::List => {
|
|
482
|
+
if arity == 2 {
|
|
483
|
+
if is_array(slot.v) {
|
|
484
|
+
slots[base] = Slot::plain(array::rb_ary_entry(slot.v, slot.off as _));
|
|
485
|
+
} else if is_string(slot.v) {
|
|
486
|
+
slots[base] = Slot {
|
|
487
|
+
v: slot.v,
|
|
488
|
+
off: slot.off,
|
|
489
|
+
kind: SlotKind::Char,
|
|
490
|
+
};
|
|
491
|
+
} else {
|
|
492
|
+
// a lazy cell, already forced by `identify` (memoised)
|
|
493
|
+
let cell = as_lazy_cell(slot.v).map_err(RtError::Ruby)?;
|
|
494
|
+
if let Some((head, tail)) = force_lazy(cell).map_err(RtError::Ruby)? {
|
|
495
|
+
slots[base] = Slot::plain(head);
|
|
496
|
+
slots[base + 1] = Slot::plain(tail);
|
|
497
|
+
}
|
|
498
|
+
return Ok(());
|
|
499
|
+
}
|
|
500
|
+
slots[base + 1] = Slot {
|
|
501
|
+
v: slot.v,
|
|
502
|
+
off: slot.off + 1,
|
|
503
|
+
kind: SlotKind::View,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
TypeKind::Tuple(_) => {
|
|
508
|
+
for i in 0..arity {
|
|
509
|
+
slots[base + i] = Slot::plain(array::rb_ary_entry(
|
|
510
|
+
slot.v,
|
|
511
|
+
(slot.off as i64 + i as i64) as _,
|
|
512
|
+
));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
Ok(())
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/// Turn a slot into the Ruby object a variable bound to it should see.
|
|
520
|
+
#[inline]
|
|
521
|
+
unsafe fn materialize(slot: Slot) -> Value {
|
|
522
|
+
match slot.kind {
|
|
523
|
+
SlotKind::Plain => slot.v,
|
|
524
|
+
SlotKind::View => {
|
|
525
|
+
if slot.off == 0 {
|
|
526
|
+
slot.v
|
|
527
|
+
} else if is_array(slot.v) {
|
|
528
|
+
let len = ary_len(slot.v) - slot.off as i64;
|
|
529
|
+
array::rb_ary_subseq(slot.v, slot.off as _, len.max(0) as _)
|
|
530
|
+
} else {
|
|
531
|
+
let len = rutie::rubysys::string::rb_str_strlen(slot.v) as i64 - slot.off as i64;
|
|
532
|
+
rb_str_substr(slot.v, slot.off as _, len.max(0) as _)
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
SlotKind::Char => rb_str_substr(slot.v, slot.off as _, 1),
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/// The character at `off` of an ASCII-only string, without allocating.
|
|
540
|
+
#[inline]
|
|
541
|
+
unsafe fn ascii_char_at(v: Value, off: i32) -> Option<u8> {
|
|
542
|
+
if rutie::rubysys::string::rb_enc_str_asciionly_p(v) == 0 {
|
|
543
|
+
return None;
|
|
544
|
+
}
|
|
545
|
+
let len = rutie::rubysys::string::rstring_len(v) as i32;
|
|
546
|
+
if off < 0 || off >= len {
|
|
547
|
+
return None;
|
|
548
|
+
}
|
|
549
|
+
let ptr = rutie::rubysys::string::rb_string_value_ptr(&v) as *const u8;
|
|
550
|
+
Some(*ptr.add(off as usize))
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
unsafe fn lit_matches(kind: LitKind, case: &RtLitCase, slot: Slot) -> bool {
|
|
554
|
+
match kind {
|
|
555
|
+
LitKind::Num => {
|
|
556
|
+
let v = slot.v;
|
|
557
|
+
if v.is_fixnum() {
|
|
558
|
+
let i = (v.value as i64) >> 1;
|
|
559
|
+
return match case.lit {
|
|
560
|
+
Lit::Int(j) => i == j,
|
|
561
|
+
Lit::Float(f) => (i as f64) == f,
|
|
562
|
+
_ => false,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
if v.is_flonum() || v.ty() == ValueType::Float {
|
|
566
|
+
let f = rutie::rubysys::float::rb_num2dbl(v);
|
|
567
|
+
return match case.lit {
|
|
568
|
+
Lit::Int(j) => f == (j as f64),
|
|
569
|
+
Lit::Float(g) => f == g,
|
|
570
|
+
Lit::Big(_) => class::rb_equal(v, case.obj).is_true(),
|
|
571
|
+
_ => false,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
class::rb_equal(v, case.obj).is_true()
|
|
575
|
+
}
|
|
576
|
+
LitKind::Char => {
|
|
577
|
+
let want = match case.lit {
|
|
578
|
+
Lit::Char(c) => c,
|
|
579
|
+
_ => return false,
|
|
580
|
+
};
|
|
581
|
+
if slot.kind == SlotKind::Char {
|
|
582
|
+
if let Some(b) = ascii_char_at(slot.v, slot.off) {
|
|
583
|
+
return want.is_ascii() && b == want as u8;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
rutie::rubysys::string::rb_str_equal(materialize(slot), case.obj).is_true()
|
|
587
|
+
}
|
|
588
|
+
LitKind::Sym => slot.v.value == case.obj.value,
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
unsafe fn lit_kind_ok(kind: LitKind, slot: Slot) -> bool {
|
|
593
|
+
match kind {
|
|
594
|
+
LitKind::Num => {
|
|
595
|
+
let v = slot.v;
|
|
596
|
+
slot.kind == SlotKind::Plain
|
|
597
|
+
&& (v.is_fixnum()
|
|
598
|
+
|| v.is_flonum()
|
|
599
|
+
|| matches!(v.ty(), ValueType::Float | ValueType::Bignum)
|
|
600
|
+
|| class::rb_obj_is_kind_of(v, rutie::rubysys::builtins::rb_cNumeric).is_true())
|
|
601
|
+
}
|
|
602
|
+
LitKind::Char => {
|
|
603
|
+
slot.kind == SlotKind::Char || (slot.kind == SlotKind::Plain && is_string(slot.v))
|
|
604
|
+
}
|
|
605
|
+
LitKind::Sym => {
|
|
606
|
+
slot.kind == SlotKind::Plain && (slot.v.is_symbol() || slot.v.ty() == ValueType::Symbol)
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/// Run `f` under `rb_protect`. On a Ruby exception or non-local jump the
|
|
612
|
+
/// pending state is returned untouched (`$!` is left set) so the caller can
|
|
613
|
+
/// resume it with `rb_jump_tag` after cleaning up.
|
|
614
|
+
pub fn call_protected(f: impl FnOnce() -> Value) -> Result<Value, i32> {
|
|
615
|
+
let mut f = Some(f);
|
|
616
|
+
match VM::protect(|| AnyObject::from((f.take().expect("called once"))())) {
|
|
617
|
+
Ok(v) => Ok(v.value()),
|
|
618
|
+
Err(state) => Err(state),
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/// Where a leaf's bound values ended up.
|
|
623
|
+
pub enum Bound {
|
|
624
|
+
/// `n` values in the caller-provided stack buffer.
|
|
625
|
+
Stack(usize),
|
|
626
|
+
/// Values in a Ruby array (used for clauses binding more variables than
|
|
627
|
+
/// the stack buffer holds).
|
|
628
|
+
Array(Value),
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/// Capacity of the stack buffer callers hand to `select`. Wider clauses use
|
|
632
|
+
/// a Ruby array instead; the buffer is small because it lives on the native
|
|
633
|
+
/// stack of every call.
|
|
634
|
+
pub const STACK_BINDS: usize = 16;
|
|
635
|
+
|
|
636
|
+
/// Slots kept on the native stack; matchers needing more use the heap.
|
|
637
|
+
const STACK_SLOTS: usize = 12;
|
|
638
|
+
|
|
639
|
+
#[inline]
|
|
640
|
+
fn qnil() -> Value {
|
|
641
|
+
Value::from(rutie::rubysys::value::RubySpecialConsts::Nil as usize)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/// Call `proc` with the bound values as positional arguments.
|
|
645
|
+
///
|
|
646
|
+
/// # Safety
|
|
647
|
+
/// `proc` must be a live Proc and `buf` the buffer `bound` refers to. Not
|
|
648
|
+
/// protected: callers must hold no Rust values needing `Drop` when they call
|
|
649
|
+
/// this, since the proc may leave by exception or non-local jump.
|
|
650
|
+
#[inline]
|
|
651
|
+
pub unsafe fn invoke(proc: Value, bound: &Bound, buf: &[Value]) -> Value {
|
|
652
|
+
match bound {
|
|
653
|
+
Bound::Stack(n) => rproc::rb_proc_call_with_block(proc, *n as _, buf.as_ptr(), qnil()),
|
|
654
|
+
Bound::Array(a) => rproc::rb_proc_call(proc, *a),
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/// The bound values as a fresh Ruby array.
|
|
659
|
+
///
|
|
660
|
+
/// # Safety
|
|
661
|
+
/// `buf` must be the buffer `bound` refers to, on a Ruby thread with the GVL.
|
|
662
|
+
pub unsafe fn bound_to_array(bound: &Bound, buf: &[Value]) -> Value {
|
|
663
|
+
match bound {
|
|
664
|
+
Bound::Stack(n) => array::rb_ary_new_from_values(*n as _, buf.as_ptr()),
|
|
665
|
+
Bound::Array(a) => *a,
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
impl Matcher {
|
|
670
|
+
pub fn type_name(&self, ty: TypeId) -> String {
|
|
671
|
+
self.env.ty(ty).name.clone()
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/// Match `args`, returning the selected clause and where its bindings
|
|
675
|
+
/// were written. `guards` is a Ruby array (or nil) of procs indexed by
|
|
676
|
+
/// clause; a missing or nil entry means "no guard".
|
|
677
|
+
///
|
|
678
|
+
/// # Safety
|
|
679
|
+
/// Must run on a Ruby thread holding the GVL; `args` must be live Ruby
|
|
680
|
+
/// values (rooted by the caller) and `guards` a live `Array` or `nil`.
|
|
681
|
+
pub unsafe fn select(
|
|
682
|
+
&self,
|
|
683
|
+
args: &[Value],
|
|
684
|
+
guards: Value,
|
|
685
|
+
buf: &mut [Value; STACK_BINDS],
|
|
686
|
+
) -> Result<(usize, Bound), RtError> {
|
|
687
|
+
if args.len() != self.arity {
|
|
688
|
+
return Err(RtError::Arity {
|
|
689
|
+
expected: self.arity,
|
|
690
|
+
got: args.len(),
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
let mut stack = [Slot::empty(); STACK_SLOTS];
|
|
694
|
+
let mut heap: Vec<Slot> = Vec::new();
|
|
695
|
+
let slots: &mut [Slot] = if self.n_slots <= STACK_SLOTS {
|
|
696
|
+
&mut stack[..self.n_slots.max(1)]
|
|
697
|
+
} else {
|
|
698
|
+
heap.resize(self.n_slots, Slot::empty());
|
|
699
|
+
&mut heap[..]
|
|
700
|
+
};
|
|
701
|
+
for (slot, arg) in slots.iter_mut().zip(args.iter()) {
|
|
702
|
+
*slot = Slot::plain(*arg);
|
|
703
|
+
}
|
|
704
|
+
let mut node = &self.tree;
|
|
705
|
+
loop {
|
|
706
|
+
match node {
|
|
707
|
+
RtTree::Fail => return Err(RtError::NoMatch),
|
|
708
|
+
RtTree::Leaf(leaf) => {
|
|
709
|
+
let bound = self.bind(leaf, slots, buf)?;
|
|
710
|
+
return Ok((leaf.clause, bound));
|
|
711
|
+
}
|
|
712
|
+
RtTree::Guard { leaf, fallback } => {
|
|
713
|
+
let bound = self.bind(leaf, slots, buf)?;
|
|
714
|
+
let guard = if guards.is_nil() || ary_len(guards) <= leaf.clause as i64 {
|
|
715
|
+
qnil()
|
|
716
|
+
} else {
|
|
717
|
+
array::rb_ary_entry(guards, leaf.clause as _)
|
|
718
|
+
};
|
|
719
|
+
let ok = if guard.is_nil() {
|
|
720
|
+
true
|
|
721
|
+
} else {
|
|
722
|
+
let r =
|
|
723
|
+
call_protected(|| invoke(guard, &bound, buf)).map_err(RtError::Ruby)?;
|
|
724
|
+
truthy(r)
|
|
725
|
+
};
|
|
726
|
+
if ok {
|
|
727
|
+
return Ok((leaf.clause, bound));
|
|
728
|
+
}
|
|
729
|
+
node = fallback;
|
|
730
|
+
}
|
|
731
|
+
RtTree::SwitchCon {
|
|
732
|
+
slot,
|
|
733
|
+
ty,
|
|
734
|
+
cases,
|
|
735
|
+
default,
|
|
736
|
+
} => {
|
|
737
|
+
let s = slots[*slot];
|
|
738
|
+
// A value that is not of this type matches no constructor
|
|
739
|
+
// pattern; wildcard rows (the default branch) still
|
|
740
|
+
// accept it, as `_` accepts anything in Haskell. Without
|
|
741
|
+
// such rows no clause could ever match it: report the
|
|
742
|
+
// type error Haskell would have found statically.
|
|
743
|
+
match identify(&self.env, *ty, s)? {
|
|
744
|
+
Some(tag) => match cases.iter().find(|c| c.tag == tag) {
|
|
745
|
+
Some(c) => {
|
|
746
|
+
fields_into(&self.env, *ty, c.tag, s, c.arity, c.base, slots)?;
|
|
747
|
+
node = &c.tree;
|
|
748
|
+
}
|
|
749
|
+
None => match default {
|
|
750
|
+
Some(d) => node = d,
|
|
751
|
+
None => return Err(RtError::NoMatch),
|
|
752
|
+
},
|
|
753
|
+
},
|
|
754
|
+
None => match default {
|
|
755
|
+
Some(d) if !matches!(**d, RtTree::Fail) => node = d,
|
|
756
|
+
_ => {
|
|
757
|
+
return Err(RtError::TypeMismatch {
|
|
758
|
+
expected: self.type_name(*ty),
|
|
759
|
+
got: materialize(s),
|
|
760
|
+
})
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
RtTree::SwitchLit {
|
|
766
|
+
slot,
|
|
767
|
+
kind,
|
|
768
|
+
cases,
|
|
769
|
+
default,
|
|
770
|
+
} => {
|
|
771
|
+
let s = slots[*slot];
|
|
772
|
+
if !lit_kind_ok(*kind, s) {
|
|
773
|
+
if matches!(**default, RtTree::Fail) {
|
|
774
|
+
return Err(RtError::TypeMismatch {
|
|
775
|
+
expected: kind.name().to_string(),
|
|
776
|
+
got: materialize(s),
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
node = default;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
let mut next: &RtTree = default;
|
|
783
|
+
for c in cases {
|
|
784
|
+
if lit_matches(*kind, c, s) {
|
|
785
|
+
next = &c.tree;
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
node = next;
|
|
790
|
+
}
|
|
791
|
+
RtTree::TestHash {
|
|
792
|
+
slot,
|
|
793
|
+
key_objs,
|
|
794
|
+
base,
|
|
795
|
+
then,
|
|
796
|
+
otherwise,
|
|
797
|
+
..
|
|
798
|
+
} => {
|
|
799
|
+
let s = slots[*slot];
|
|
800
|
+
if s.kind != SlotKind::Plain || !is_hash(s.v) {
|
|
801
|
+
if matches!(**otherwise, RtTree::Fail) {
|
|
802
|
+
return Err(RtError::TypeMismatch {
|
|
803
|
+
expected: "Hash".to_string(),
|
|
804
|
+
got: materialize(s),
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
node = otherwise;
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
let mut all = true;
|
|
811
|
+
for (i, k) in key_objs.iter().enumerate() {
|
|
812
|
+
match hash_fetch(s.v, *k) {
|
|
813
|
+
Some(v) => slots[*base + i] = Slot::plain(v),
|
|
814
|
+
None => {
|
|
815
|
+
all = false;
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
node = if all { then } else { otherwise };
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/// Write the bound values of a leaf into `buf` (or, for very wide
|
|
827
|
+
/// clauses, into a fresh Ruby array).
|
|
828
|
+
unsafe fn bind(
|
|
829
|
+
&self,
|
|
830
|
+
leaf: &Leaf,
|
|
831
|
+
slots: &[Slot],
|
|
832
|
+
buf: &mut [Value; STACK_BINDS],
|
|
833
|
+
) -> Result<Bound, RtError> {
|
|
834
|
+
let n = leaf.binds.len();
|
|
835
|
+
if n <= STACK_BINDS {
|
|
836
|
+
for (i, b) in leaf.binds.iter().enumerate() {
|
|
837
|
+
buf[i] = match b {
|
|
838
|
+
BindSrc::Slot(s) => materialize(slots[*s]),
|
|
839
|
+
BindSrc::Lazy(_) => qnil(),
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
for lz in &leaf.lazies {
|
|
843
|
+
let LazyPat { slot, pat } = lz;
|
|
844
|
+
let mut store = |i: usize, v: Value| buf[i] = v;
|
|
845
|
+
if !self.destructure(pat, slots[*slot], &mut store) {
|
|
846
|
+
return Err(RtError::LazyFailed {
|
|
847
|
+
clause: leaf.clause,
|
|
848
|
+
got: materialize(slots[*slot]),
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
Ok(Bound::Stack(n))
|
|
853
|
+
} else {
|
|
854
|
+
let out = array::rb_ary_new_capa(n as _);
|
|
855
|
+
for (i, b) in leaf.binds.iter().enumerate() {
|
|
856
|
+
let v = match b {
|
|
857
|
+
BindSrc::Slot(s) => materialize(slots[*s]),
|
|
858
|
+
BindSrc::Lazy(_) => qnil(),
|
|
859
|
+
};
|
|
860
|
+
array::rb_ary_store(out, i as _, v);
|
|
861
|
+
}
|
|
862
|
+
for lz in &leaf.lazies {
|
|
863
|
+
let LazyPat { slot, pat } = lz;
|
|
864
|
+
let mut store = |i: usize, v: Value| array::rb_ary_store(out, i as _, v);
|
|
865
|
+
if !self.destructure(pat, slots[*slot], &mut store) {
|
|
866
|
+
return Err(RtError::LazyFailed {
|
|
867
|
+
clause: leaf.clause,
|
|
868
|
+
got: materialize(slots[*slot]),
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
Ok(Bound::Array(out))
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/// Interpretive match used for lazy patterns; variables go to `store`.
|
|
877
|
+
unsafe fn destructure(
|
|
878
|
+
&self,
|
|
879
|
+
pat: &Pat,
|
|
880
|
+
slot: Slot,
|
|
881
|
+
store: &mut dyn FnMut(usize, Value),
|
|
882
|
+
) -> bool {
|
|
883
|
+
match pat {
|
|
884
|
+
Pat::Wild => true,
|
|
885
|
+
Pat::Var(v) => {
|
|
886
|
+
store(*v, materialize(slot));
|
|
887
|
+
true
|
|
888
|
+
}
|
|
889
|
+
Pat::As(v, inner) => {
|
|
890
|
+
store(*v, materialize(slot));
|
|
891
|
+
self.destructure(inner, slot, store)
|
|
892
|
+
}
|
|
893
|
+
Pat::Lazy(inner) => self.destructure(inner, slot, store),
|
|
894
|
+
Pat::Lit(l) => {
|
|
895
|
+
let kind = l.kind();
|
|
896
|
+
if !lit_kind_ok(kind, slot) {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
let obj = match self.lazy_lits.get(&l.key()) {
|
|
900
|
+
Some(o) => *o,
|
|
901
|
+
None => literal_object(l),
|
|
902
|
+
};
|
|
903
|
+
let case = RtLitCase {
|
|
904
|
+
lit: l.clone(),
|
|
905
|
+
obj,
|
|
906
|
+
tree: RtTree::Fail,
|
|
907
|
+
};
|
|
908
|
+
lit_matches(kind, &case, slot)
|
|
909
|
+
}
|
|
910
|
+
Pat::Hash(fields) => {
|
|
911
|
+
if slot.kind != SlotKind::Plain || !is_hash(slot.v) {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
fields
|
|
915
|
+
.iter()
|
|
916
|
+
.all(|(k, p)| match hash_fetch(slot.v, key_object(k)) {
|
|
917
|
+
Some(v) => self.destructure(p, Slot::plain(v), store),
|
|
918
|
+
None => false,
|
|
919
|
+
})
|
|
920
|
+
}
|
|
921
|
+
Pat::Con(c, args) => {
|
|
922
|
+
let con = self.env.con(*c);
|
|
923
|
+
let ty = con.ty;
|
|
924
|
+
match identify(&self.env, ty, slot) {
|
|
925
|
+
Ok(Some(tag)) if tag == con.tag => {
|
|
926
|
+
let mut tmp = vec![Slot::empty(); con.arity];
|
|
927
|
+
if fields_into(&self.env, ty, con.tag, slot, con.arity, 0, &mut tmp)
|
|
928
|
+
.is_err()
|
|
929
|
+
{
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
args.iter()
|
|
933
|
+
.zip(tmp.iter())
|
|
934
|
+
.all(|(a, s)| self.destructure(a, *s, store))
|
|
935
|
+
}
|
|
936
|
+
_ => false,
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/// Human-readable dump of a tree, for `Matcher#tree` and tests.
|
|
944
|
+
pub fn dump(env: &TypeEnv, t: &RtTree, names: &[Vec<String>], indent: usize) -> String {
|
|
945
|
+
let pad = " ".repeat(indent);
|
|
946
|
+
match t {
|
|
947
|
+
RtTree::Fail => format!("{}FAIL\n", pad),
|
|
948
|
+
RtTree::Leaf(l) => format!(
|
|
949
|
+
"{}=> clause {}{}\n",
|
|
950
|
+
pad,
|
|
951
|
+
l.clause + 1,
|
|
952
|
+
dump_binds(l, names)
|
|
953
|
+
),
|
|
954
|
+
RtTree::Guard { leaf, fallback } => format!(
|
|
955
|
+
"{}guard clause {}{}\n{}",
|
|
956
|
+
pad,
|
|
957
|
+
leaf.clause + 1,
|
|
958
|
+
dump_binds(leaf, names),
|
|
959
|
+
dump(env, fallback, names, indent)
|
|
960
|
+
),
|
|
961
|
+
RtTree::SwitchCon {
|
|
962
|
+
slot,
|
|
963
|
+
ty,
|
|
964
|
+
cases,
|
|
965
|
+
default,
|
|
966
|
+
} => {
|
|
967
|
+
let mut s = format!("{}switch slot {} ({})\n", pad, slot, env.ty(*ty).name);
|
|
968
|
+
for c in cases {
|
|
969
|
+
let con = env.con_by_tag(*ty, c.tag);
|
|
970
|
+
let fields: Vec<String> = (0..c.arity).map(|i| format!("{}", c.base + i)).collect();
|
|
971
|
+
s.push_str(&format!(
|
|
972
|
+
"{} {}{}:\n",
|
|
973
|
+
pad,
|
|
974
|
+
env.con(con).name,
|
|
975
|
+
if fields.is_empty() {
|
|
976
|
+
String::new()
|
|
977
|
+
} else {
|
|
978
|
+
format!(" -> slots [{}]", fields.join(", "))
|
|
979
|
+
}
|
|
980
|
+
));
|
|
981
|
+
s.push_str(&dump(env, &c.tree, names, indent + 2));
|
|
982
|
+
}
|
|
983
|
+
if let Some(d) = default {
|
|
984
|
+
s.push_str(&format!("{} _:\n", pad));
|
|
985
|
+
s.push_str(&dump(env, d, names, indent + 2));
|
|
986
|
+
}
|
|
987
|
+
s
|
|
988
|
+
}
|
|
989
|
+
RtTree::SwitchLit {
|
|
990
|
+
slot,
|
|
991
|
+
kind,
|
|
992
|
+
cases,
|
|
993
|
+
default,
|
|
994
|
+
} => {
|
|
995
|
+
let mut s = format!("{}switch slot {} ({})\n", pad, slot, kind.name());
|
|
996
|
+
for c in cases {
|
|
997
|
+
s.push_str(&format!("{} {}:\n", pad, c.lit));
|
|
998
|
+
s.push_str(&dump(env, &c.tree, names, indent + 2));
|
|
999
|
+
}
|
|
1000
|
+
s.push_str(&format!("{} _:\n", pad));
|
|
1001
|
+
s.push_str(&dump(env, default, names, indent + 2));
|
|
1002
|
+
s
|
|
1003
|
+
}
|
|
1004
|
+
RtTree::TestHash {
|
|
1005
|
+
slot,
|
|
1006
|
+
keys,
|
|
1007
|
+
base,
|
|
1008
|
+
then,
|
|
1009
|
+
otherwise,
|
|
1010
|
+
..
|
|
1011
|
+
} => {
|
|
1012
|
+
let shown: Vec<String> = keys.iter().map(|k| k.to_string()).collect();
|
|
1013
|
+
let slots: Vec<String> = (0..keys.len()).map(|i| format!("{}", base + i)).collect();
|
|
1014
|
+
let mut s = format!(
|
|
1015
|
+
"{}hash slot {} has keys {{{}}} -> slots [{}]\n",
|
|
1016
|
+
pad,
|
|
1017
|
+
slot,
|
|
1018
|
+
shown.join(", "),
|
|
1019
|
+
slots.join(", ")
|
|
1020
|
+
);
|
|
1021
|
+
s.push_str(&format!("{} yes:\n", pad));
|
|
1022
|
+
s.push_str(&dump(env, then, names, indent + 2));
|
|
1023
|
+
s.push_str(&format!("{} no:\n", pad));
|
|
1024
|
+
s.push_str(&dump(env, otherwise, names, indent + 2));
|
|
1025
|
+
s
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
fn dump_binds(l: &Leaf, names: &[Vec<String>]) -> String {
|
|
1031
|
+
let ns = &names[l.clause];
|
|
1032
|
+
if ns.is_empty() {
|
|
1033
|
+
return String::new();
|
|
1034
|
+
}
|
|
1035
|
+
let parts: Vec<String> = l
|
|
1036
|
+
.binds
|
|
1037
|
+
.iter()
|
|
1038
|
+
.enumerate()
|
|
1039
|
+
.map(|(i, b)| match b {
|
|
1040
|
+
BindSrc::Slot(s) => format!("{}=slot {}", ns[i], s),
|
|
1041
|
+
BindSrc::Lazy(_) => format!("{}=lazy", ns[i]),
|
|
1042
|
+
})
|
|
1043
|
+
.collect();
|
|
1044
|
+
format!(" [{}]", parts.join(", "))
|
|
1045
|
+
}
|