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,1195 @@
|
|
|
1
|
+
//! Ruby-facing API: the `HaskellMatch::Native` module and
|
|
2
|
+
//! `HaskellMatch::Native::Matcher` class.
|
|
3
|
+
//!
|
|
4
|
+
//! Matcher methods are defined as raw `extern "C"` functions (argc/argv
|
|
5
|
+
//! style) rather than through Rutie's `methods!` macro so that the hot path
|
|
6
|
+
//! allocates nothing on the Rust side and clause bodies are invoked from a
|
|
7
|
+
//! frame that owns no values needing `Drop` (a body may leave by exception,
|
|
8
|
+
//! `throw`, `break` or `return`).
|
|
9
|
+
|
|
10
|
+
pub mod runtime;
|
|
11
|
+
|
|
12
|
+
use std::ffi::CString;
|
|
13
|
+
use std::os::raw::c_int;
|
|
14
|
+
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
15
|
+
use std::sync::{Mutex, OnceLock};
|
|
16
|
+
|
|
17
|
+
use rutie::rubysys::typed_data::{RUBY_TYPED_FREE_IMMEDIATELY, RUBY_TYPED_FROZEN_SHAREABLE};
|
|
18
|
+
use rutie::rubysys::{array, class as rclass, rstruct, value::ValueType};
|
|
19
|
+
use rutie::typed_data::DataTypeWrapper;
|
|
20
|
+
use rutie::types::{CallbackPtr, DataType, DataTypeFunction, Id, Value};
|
|
21
|
+
use rutie::{
|
|
22
|
+
AnyException, AnyObject, Array, Boolean, Class, Exception, Integer, Module, NilClass, Object,
|
|
23
|
+
RString, VM,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
use crate::core::error::{CoreError, ErrorKind};
|
|
27
|
+
use crate::core::exhaust;
|
|
28
|
+
use crate::core::parser::{parse_data, parse_pattern};
|
|
29
|
+
use crate::core::pretty::{render_pat, render_witness};
|
|
30
|
+
use crate::core::resolve::resolve_clause;
|
|
31
|
+
use crate::core::tree;
|
|
32
|
+
use crate::core::typecheck;
|
|
33
|
+
use crate::core::types::{ConSpec, TypeEnv};
|
|
34
|
+
use runtime::{Bound, Matcher, RtError, STACK_BINDS};
|
|
35
|
+
|
|
36
|
+
lazy_static! {
|
|
37
|
+
/// Type scopes: index 0 is the global registry; every other scope is a
|
|
38
|
+
/// snapshot of the global one at its creation plus its own declarations
|
|
39
|
+
/// (a compiled Haskell module gets one, like a Haskell module namespace).
|
|
40
|
+
static ref SCOPES: Mutex<Vec<TypeEnv>> = Mutex::new(vec![TypeEnv::new()]);
|
|
41
|
+
static ref MATCHER_TYPE: MatcherType = MatcherType::new();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Interned ivar names read on every call.
|
|
45
|
+
struct Ids {
|
|
46
|
+
bodies: Id,
|
|
47
|
+
guards: Id,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static IDS: OnceLock<Ids> = OnceLock::new();
|
|
51
|
+
|
|
52
|
+
/// `HaskellMatch::TailCall`, the marker a clause body returns to request a
|
|
53
|
+
/// tail call (looked up once, on first use).
|
|
54
|
+
static TAIL_CALL: OnceLock<usize> = OnceLock::new();
|
|
55
|
+
|
|
56
|
+
fn tail_call_class() -> usize {
|
|
57
|
+
*TAIL_CALL.get_or_init(|| {
|
|
58
|
+
let k = hm_class("TailCall");
|
|
59
|
+
unsafe { rutie::rubysys::gc::rb_gc_register_mark_object(k.value()) };
|
|
60
|
+
k.value().value
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn ids() -> &'static Ids {
|
|
65
|
+
IDS.get_or_init(|| unsafe {
|
|
66
|
+
let b = CString::new("@bodies").unwrap();
|
|
67
|
+
let g = CString::new("@guards").unwrap();
|
|
68
|
+
Ids {
|
|
69
|
+
bodies: rutie::rubysys::symbol::rb_intern(b.as_ptr()),
|
|
70
|
+
guards: rutie::rubysys::symbol::rb_intern(g.as_ptr()),
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
extern "C" {
|
|
76
|
+
/// Resume a pending exception or non-local jump captured by `rb_protect`.
|
|
77
|
+
fn rb_jump_tag(state: c_int) -> !;
|
|
78
|
+
fn rb_fiber_new(func: rutie::rubysys::types::BlockCallFunction, obj: Value) -> Value;
|
|
79
|
+
fn rb_fiber_resume(fiber: Value, argc: c_int, argv: *const Value) -> Value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
thread_local! {
|
|
83
|
+
/// Nesting depth of native calls on this thread.
|
|
84
|
+
static DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Every this many nested native calls, the next clause body runs in a fresh
|
|
88
|
+
/// Fiber. Each Fiber brings its own VM stack and machine stack, so chained
|
|
89
|
+
/// segments make the recursion depth a matter of memory rather than of
|
|
90
|
+
/// Ruby's fixed stack size. 0 disables segmentation.
|
|
91
|
+
static STACK_SEGMENT: std::sync::atomic::AtomicUsize =
|
|
92
|
+
std::sync::atomic::AtomicUsize::new(DEFAULT_STACK_SEGMENT);
|
|
93
|
+
const DEFAULT_STACK_SEGMENT: usize = 100;
|
|
94
|
+
|
|
95
|
+
/// Maximum nesting depth of native calls before `StackOverflowError` is
|
|
96
|
+
/// raised, so a runaway recursion fails instead of consuming all memory
|
|
97
|
+
/// (GHC has the same kind of limit). 0 means unlimited.
|
|
98
|
+
static MAX_DEPTH: std::sync::atomic::AtomicUsize =
|
|
99
|
+
std::sync::atomic::AtomicUsize::new(DEFAULT_MAX_DEPTH);
|
|
100
|
+
const DEFAULT_MAX_DEPTH: usize = 250_000;
|
|
101
|
+
|
|
102
|
+
rutie_callback! {
|
|
103
|
+
/// Body of a stack-segment Fiber: `data` is `[body, values]`.
|
|
104
|
+
fn segment_fiber_body(
|
|
105
|
+
_yielded: Value,
|
|
106
|
+
data: Value,
|
|
107
|
+
_argc: c_int,
|
|
108
|
+
_argv: *const Value,
|
|
109
|
+
_block: Value,
|
|
110
|
+
) -> Value {
|
|
111
|
+
unsafe {
|
|
112
|
+
let body = array::rb_ary_entry(data, 0);
|
|
113
|
+
let vals = array::rb_ary_entry(data, 1);
|
|
114
|
+
rutie::rubysys::rproc::rb_proc_call(body, vals)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Invoke a clause body, either directly or (every `STACK_SEGMENT` levels)
|
|
120
|
+
/// in a fresh Fiber. Exceptions and non-local jumps are returned as the
|
|
121
|
+
/// `rb_protect` state for the caller to resume once it has cleaned up.
|
|
122
|
+
unsafe fn invoke_body(body: Value, bound: &Bound, buf: &[Value]) -> Result<Value, i32> {
|
|
123
|
+
let segment = STACK_SEGMENT.load(std::sync::atomic::Ordering::Relaxed);
|
|
124
|
+
let depth = DEPTH.with(|d| d.get());
|
|
125
|
+
let max = MAX_DEPTH.load(std::sync::atomic::Ordering::Relaxed);
|
|
126
|
+
if max > 0 && depth >= max {
|
|
127
|
+
VM::raise_ex(exception(
|
|
128
|
+
"StackOverflowError",
|
|
129
|
+
&format!(
|
|
130
|
+
"recursion deeper than HaskellMatch.max_depth ({}); use tail calls, defer, or raise the limit",
|
|
131
|
+
max
|
|
132
|
+
),
|
|
133
|
+
));
|
|
134
|
+
}
|
|
135
|
+
DEPTH.with(|d| d.set(depth + 1));
|
|
136
|
+
let result = if segment > 0 && (depth + 1).is_multiple_of(segment) {
|
|
137
|
+
let vals = runtime::bound_to_array(bound, buf);
|
|
138
|
+
let pair = [body, vals];
|
|
139
|
+
let data = array::rb_ary_new_from_values(2, pair.as_ptr());
|
|
140
|
+
let fiber = rb_fiber_new(
|
|
141
|
+
segment_fiber_body as rutie::rubysys::types::BlockCallFunction,
|
|
142
|
+
data,
|
|
143
|
+
);
|
|
144
|
+
runtime::call_protected(|| rb_fiber_resume(fiber, 0, std::ptr::null()))
|
|
145
|
+
} else {
|
|
146
|
+
runtime::call_protected(|| runtime::invoke(body, bound, buf))
|
|
147
|
+
};
|
|
148
|
+
DEPTH.with(|d| d.set(depth));
|
|
149
|
+
result
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Typed-data definition for `Matcher`. Unlike Rutie's `wrappable_struct!`
|
|
153
|
+
/// it sets `RUBY_TYPED_FROZEN_SHAREABLE`, so a frozen matcher (and the
|
|
154
|
+
/// `Function` objects built on it) can be passed to `Ractor.make_shareable`.
|
|
155
|
+
pub struct MatcherType {
|
|
156
|
+
data_type: DataType,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
unsafe impl Send for MatcherType {}
|
|
160
|
+
unsafe impl Sync for MatcherType {}
|
|
161
|
+
|
|
162
|
+
impl MatcherType {
|
|
163
|
+
fn new() -> Self {
|
|
164
|
+
let name = CString::new("HaskellMatch/Matcher").unwrap();
|
|
165
|
+
MatcherType {
|
|
166
|
+
data_type: DataType {
|
|
167
|
+
wrap_struct_name: name.into_raw(),
|
|
168
|
+
parent: std::ptr::null(),
|
|
169
|
+
data: std::ptr::null_mut(),
|
|
170
|
+
flags: Value::from(RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE),
|
|
171
|
+
function: DataTypeFunction {
|
|
172
|
+
dmark: None,
|
|
173
|
+
dfree: Some(rutie::typed_data::free::<Matcher>),
|
|
174
|
+
dsize: None,
|
|
175
|
+
reserved: [std::ptr::null_mut(), std::ptr::null_mut()],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
impl DataTypeWrapper<Matcher> for MatcherType {
|
|
183
|
+
fn data_type(&self) -> &DataType {
|
|
184
|
+
&self.data_type
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
class!(RMatcher);
|
|
189
|
+
module!(Native);
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// error helpers
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
fn hm_class(name: &str) -> Class {
|
|
196
|
+
Module::from_existing("HaskellMatch").get_nested_class(name)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fn exception(class_name: &str, message: &str) -> AnyException {
|
|
200
|
+
AnyException::from_class(&hm_class(class_name), message)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// A scope id argument (raises ArgumentError when malformed).
|
|
204
|
+
fn scope_arg(scope: Result<Integer, rutie::AnyException>) -> usize {
|
|
205
|
+
let n = scope
|
|
206
|
+
.unwrap_or_else(|_| raise_arg("scope must be an Integer"))
|
|
207
|
+
.to_i64();
|
|
208
|
+
if n < 0 {
|
|
209
|
+
raise_arg("scope must not be negative");
|
|
210
|
+
}
|
|
211
|
+
n as usize
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// The type environment of scope `id`.
|
|
215
|
+
fn scope_env(scopes: &mut [TypeEnv], id: usize) -> Result<&mut TypeEnv, String> {
|
|
216
|
+
scopes
|
|
217
|
+
.get_mut(id)
|
|
218
|
+
.ok_or_else(|| format!("unknown type scope {}", id))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn error_class(kind: ErrorKind) -> &'static str {
|
|
222
|
+
match kind {
|
|
223
|
+
ErrorKind::Syntax => "PatternSyntaxError",
|
|
224
|
+
ErrorKind::UnknownConstructor => "UnknownConstructorError",
|
|
225
|
+
ErrorKind::Arity => "ArityError",
|
|
226
|
+
ErrorKind::Type => "PatternTypeError",
|
|
227
|
+
ErrorKind::DuplicateVariable => "DuplicateVariableError",
|
|
228
|
+
ErrorKind::Field => "FieldError",
|
|
229
|
+
ErrorKind::DataDeclaration => "DataDeclarationError",
|
|
230
|
+
ErrorKind::ClauseArity => "ClauseArityError",
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn raise_core(e: CoreError) -> ! {
|
|
235
|
+
VM::raise_ex(exception(error_class(e.kind), &e.message));
|
|
236
|
+
unreachable!()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fn raise_arg(msg: &str) -> ! {
|
|
240
|
+
VM::raise_ex(AnyException::new("ArgumentError", Some(msg)));
|
|
241
|
+
unreachable!()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fn inspect(v: Value) -> String {
|
|
245
|
+
let s = unsafe { rutie::rubysys::object::rb_inspect(v) };
|
|
246
|
+
RString::from(s).to_string()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
fn class_name(v: Value) -> String {
|
|
250
|
+
let k = unsafe { rclass::rb_obj_class(v) };
|
|
251
|
+
Class::from(k)
|
|
252
|
+
.name()
|
|
253
|
+
.map(|s| s.to_string())
|
|
254
|
+
.unwrap_or_else(|| "?".into())
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fn raise_rt(name: &str, e: RtError) -> ! {
|
|
258
|
+
match e {
|
|
259
|
+
RtError::Ruby(state) => unsafe { rb_jump_tag(state) },
|
|
260
|
+
RtError::NoMatch => VM::raise_ex(exception(
|
|
261
|
+
"MatchError",
|
|
262
|
+
&format!("Non-exhaustive patterns in {}", name),
|
|
263
|
+
)),
|
|
264
|
+
RtError::TypeMismatch { expected, got } => VM::raise_ex(exception(
|
|
265
|
+
"TypeMismatchError",
|
|
266
|
+
&format!(
|
|
267
|
+
"{}: expected a value of type {} but got {} ({})",
|
|
268
|
+
name,
|
|
269
|
+
expected,
|
|
270
|
+
inspect(got),
|
|
271
|
+
class_name(got)
|
|
272
|
+
),
|
|
273
|
+
)),
|
|
274
|
+
RtError::LazyFailed { clause, got } => VM::raise_ex(exception(
|
|
275
|
+
"IrrefutablePatternError",
|
|
276
|
+
&format!(
|
|
277
|
+
"{}: irrefutable pattern failed for clause {}: {} does not match",
|
|
278
|
+
name,
|
|
279
|
+
clause + 1,
|
|
280
|
+
inspect(got)
|
|
281
|
+
),
|
|
282
|
+
)),
|
|
283
|
+
RtError::Arity { expected, got } => VM::raise_ex(AnyException::new(
|
|
284
|
+
"ArgumentError",
|
|
285
|
+
Some(&format!(
|
|
286
|
+
"wrong number of arguments (given {}, expected {})",
|
|
287
|
+
got, expected
|
|
288
|
+
)),
|
|
289
|
+
)),
|
|
290
|
+
}
|
|
291
|
+
unreachable!()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
fn raise_panic(payload: Box<dyn std::any::Any + Send>) -> ! {
|
|
295
|
+
let msg = if let Some(m) = payload.downcast_ref::<&str>() {
|
|
296
|
+
(*m).to_string()
|
|
297
|
+
} else if let Some(m) = payload.downcast_ref::<String>() {
|
|
298
|
+
m.clone()
|
|
299
|
+
} else {
|
|
300
|
+
"unknown panic".to_string()
|
|
301
|
+
};
|
|
302
|
+
drop(payload);
|
|
303
|
+
VM::raise_ex(AnyException::new(
|
|
304
|
+
"RuntimeError",
|
|
305
|
+
Some(&format!("haskell_match internal error: {}", msg)),
|
|
306
|
+
));
|
|
307
|
+
unreachable!()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
fn str_arg(v: &AnyObject, what: &str) -> Result<String, String> {
|
|
311
|
+
v.try_convert_to::<RString>()
|
|
312
|
+
.map(|s| s.to_string())
|
|
313
|
+
.map_err(|_| format!("{} must be a String, got {}", what, inspect(v.value())))
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
fn array_arg(v: &AnyObject, what: &str) -> Result<Array, String> {
|
|
317
|
+
v.try_convert_to::<Array>()
|
|
318
|
+
.map_err(|_| format!("{} must be an Array, got {}", what, inspect(v.value())))
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
fn rstrings(items: &[String]) -> Array {
|
|
322
|
+
let mut a = Array::with_capacity(items.len());
|
|
323
|
+
for s in items {
|
|
324
|
+
a.push(RString::new_utf8(s));
|
|
325
|
+
}
|
|
326
|
+
a
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// module functions
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
methods!(
|
|
334
|
+
Native,
|
|
335
|
+
_rtself,
|
|
336
|
+
// parse_data(decl) -> [name, tyvars, [[con, arity, fields_or_nil, types], ...], deriving]
|
|
337
|
+
fn native_parse_data(decl: RString) -> Array {
|
|
338
|
+
let decl = decl.unwrap_or_else(|_| raise_arg("data declaration must be a String"));
|
|
339
|
+
let d = parse_data(&decl.to_string()).unwrap_or_else(|e| raise_core(e));
|
|
340
|
+
let mut cons = Array::with_capacity(d.cons.len());
|
|
341
|
+
for c in &d.cons {
|
|
342
|
+
let mut row = Array::with_capacity(4);
|
|
343
|
+
row.push(RString::new_utf8(&c.name));
|
|
344
|
+
row.push(Integer::new(c.arity as i64));
|
|
345
|
+
match &c.fields {
|
|
346
|
+
Some(fs) => row.push(rstrings(fs).to_any_object()),
|
|
347
|
+
None => row.push(NilClass::new().to_any_object()),
|
|
348
|
+
};
|
|
349
|
+
row.push(rstrings(&c.types));
|
|
350
|
+
cons.push(row);
|
|
351
|
+
}
|
|
352
|
+
let mut out = Array::with_capacity(4);
|
|
353
|
+
out.push(RString::new_utf8(&d.name));
|
|
354
|
+
out.push(rstrings(&d.tyvars));
|
|
355
|
+
out.push(cons);
|
|
356
|
+
out.push(rstrings(&d.deriving));
|
|
357
|
+
out
|
|
358
|
+
},
|
|
359
|
+
// new_scope -> Integer: a type scope seeded from the global registry
|
|
360
|
+
fn native_new_scope() -> Integer {
|
|
361
|
+
let mut scopes = SCOPES.lock().unwrap_or_else(|p| p.into_inner());
|
|
362
|
+
let seed = scopes[0].clone();
|
|
363
|
+
scopes.push(seed);
|
|
364
|
+
Integer::new((scopes.len() - 1) as i64)
|
|
365
|
+
},
|
|
366
|
+
// import_scope_in(dst, src, names_or_nil) -> [type names imported]
|
|
367
|
+
fn native_import_scope(dst: Integer, src: Integer, names: AnyObject) -> AnyObject {
|
|
368
|
+
let dst = scope_arg(dst);
|
|
369
|
+
let src = scope_arg(src);
|
|
370
|
+
let names = names.unwrap_or_else(|_| raise_arg("names must be an Array or nil"));
|
|
371
|
+
let wanted: Option<Vec<String>> = if names.is_nil() {
|
|
372
|
+
None
|
|
373
|
+
} else {
|
|
374
|
+
let arr = array_arg(&names, "type names").unwrap_or_else(|m| raise_arg(&m));
|
|
375
|
+
Some(
|
|
376
|
+
arr.into_iter()
|
|
377
|
+
.map(|n| str_arg(&n, "type name").unwrap_or_else(|m| raise_arg(&m)))
|
|
378
|
+
.collect(),
|
|
379
|
+
)
|
|
380
|
+
};
|
|
381
|
+
let mut scopes = SCOPES.lock().unwrap_or_else(|p| p.into_inner());
|
|
382
|
+
if src >= scopes.len() || dst >= scopes.len() {
|
|
383
|
+
raise_arg("unknown type scope");
|
|
384
|
+
}
|
|
385
|
+
let types = scopes[src].user_types();
|
|
386
|
+
let env = &mut scopes[dst];
|
|
387
|
+
let mut imported = Vec::new();
|
|
388
|
+
for (name, cons) in types {
|
|
389
|
+
if let Some(w) = &wanted {
|
|
390
|
+
if !w.contains(&name) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if env.has_same_type(&name, &cons) {
|
|
395
|
+
imported.push(name);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
match env.register(&name, &cons) {
|
|
399
|
+
Ok(_) => imported.push(name),
|
|
400
|
+
Err(e) => raise_core(e),
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
rstrings(&imported).to_any_object()
|
|
404
|
+
},
|
|
405
|
+
// register_type(name, [[con_name, arity, fields_or_nil, klass], ...], scope) -> nil
|
|
406
|
+
fn native_register_type(name: RString, specs: Array, scope: Integer) -> NilClass {
|
|
407
|
+
let name = name
|
|
408
|
+
.unwrap_or_else(|_| raise_arg("type name must be a String"))
|
|
409
|
+
.to_string();
|
|
410
|
+
let specs = specs.unwrap_or_else(|_| raise_arg("constructor specs must be an Array"));
|
|
411
|
+
let scope = scope_arg(scope);
|
|
412
|
+
let outcome = (|| -> Result<Result<(), CoreError>, String> {
|
|
413
|
+
let mut cons = Vec::new();
|
|
414
|
+
for spec in specs.into_iter() {
|
|
415
|
+
let row = array_arg(&spec, "constructor spec")?;
|
|
416
|
+
if row.length() != 4 {
|
|
417
|
+
return Err("constructor spec must be [name, arity, fields, class]".into());
|
|
418
|
+
}
|
|
419
|
+
let cname = str_arg(&row.at(0), "constructor name")?;
|
|
420
|
+
let arity = row
|
|
421
|
+
.at(1)
|
|
422
|
+
.try_convert_to::<Integer>()
|
|
423
|
+
.map_err(|_| "constructor arity must be an Integer".to_string())?
|
|
424
|
+
.to_i64();
|
|
425
|
+
if arity < 0 {
|
|
426
|
+
return Err("constructor arity must not be negative".into());
|
|
427
|
+
}
|
|
428
|
+
let fields_obj = row.at(2);
|
|
429
|
+
let fields = if fields_obj.is_nil() {
|
|
430
|
+
None
|
|
431
|
+
} else {
|
|
432
|
+
let fa = array_arg(&fields_obj, "field names")?;
|
|
433
|
+
let mut names = Vec::new();
|
|
434
|
+
for f in fa.into_iter() {
|
|
435
|
+
names.push(str_arg(&f, "field name")?);
|
|
436
|
+
}
|
|
437
|
+
Some(names)
|
|
438
|
+
};
|
|
439
|
+
let klass = row.at(3);
|
|
440
|
+
if klass.is_nil() {
|
|
441
|
+
return Err("constructor class must not be nil".into());
|
|
442
|
+
}
|
|
443
|
+
cons.push(ConSpec {
|
|
444
|
+
name: cname,
|
|
445
|
+
arity: arity as usize,
|
|
446
|
+
fields,
|
|
447
|
+
handle: klass.value().value,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
let mut scopes = SCOPES.lock().unwrap_or_else(|p| p.into_inner());
|
|
451
|
+
let env = scope_env(&mut scopes, scope)?;
|
|
452
|
+
let r = env.register(&name, &cons);
|
|
453
|
+
if r.is_ok() {
|
|
454
|
+
for c in &cons {
|
|
455
|
+
// keep constructor classes alive for as long as the
|
|
456
|
+
// process runs: compiled matchers refer to them by VALUE
|
|
457
|
+
unsafe {
|
|
458
|
+
rutie::rubysys::gc::rb_gc_register_mark_object(Value::from(c.handle))
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
Ok(r.map(|_| ()))
|
|
463
|
+
})();
|
|
464
|
+
match outcome {
|
|
465
|
+
Err(msg) => raise_arg(&msg),
|
|
466
|
+
Ok(Err(e)) => raise_core(e),
|
|
467
|
+
Ok(Ok(())) => NilClass::new(),
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
// render_pattern(src) -> canonical Haskell rendering (checks the pattern)
|
|
471
|
+
fn native_render_pattern(src: RString, scope: Integer) -> RString {
|
|
472
|
+
let src = src
|
|
473
|
+
.unwrap_or_else(|_| raise_arg("pattern must be a String"))
|
|
474
|
+
.to_string();
|
|
475
|
+
let scope = scope_arg(scope);
|
|
476
|
+
let result = (|| -> Result<String, CoreError> {
|
|
477
|
+
let raw = parse_pattern(&src)?;
|
|
478
|
+
let mut scopes = SCOPES.lock().unwrap_or_else(|p| p.into_inner());
|
|
479
|
+
let env = scope_env(&mut scopes, scope).unwrap_or_else(|m| raise_arg(&m));
|
|
480
|
+
let (pats, b) = resolve_clause(env, &[raw])?;
|
|
481
|
+
Ok(render_pat(env, &pats[0], &b.names))
|
|
482
|
+
})();
|
|
483
|
+
match result {
|
|
484
|
+
Ok(s) => RString::new_utf8(&s),
|
|
485
|
+
Err(e) => raise_core(e),
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
// stack_segment -> Integer (0 = disabled)
|
|
489
|
+
fn native_stack_segment() -> Integer {
|
|
490
|
+
Integer::new(STACK_SEGMENT.load(std::sync::atomic::Ordering::Relaxed) as i64)
|
|
491
|
+
},
|
|
492
|
+
// stack_segment = n
|
|
493
|
+
fn native_set_stack_segment(n: Integer) -> Integer {
|
|
494
|
+
let n = n.map(|i| i.to_i64()).unwrap_or(-1);
|
|
495
|
+
if n < 0 {
|
|
496
|
+
raise_arg("stack_segment must be a non-negative Integer (0 disables segmentation)");
|
|
497
|
+
}
|
|
498
|
+
STACK_SEGMENT.store(n as usize, std::sync::atomic::Ordering::Relaxed);
|
|
499
|
+
Integer::new(n)
|
|
500
|
+
},
|
|
501
|
+
// max_depth -> Integer (0 = unlimited)
|
|
502
|
+
fn native_max_depth() -> Integer {
|
|
503
|
+
Integer::new(MAX_DEPTH.load(std::sync::atomic::Ordering::Relaxed) as i64)
|
|
504
|
+
},
|
|
505
|
+
// max_depth = n
|
|
506
|
+
fn native_set_max_depth(n: Integer) -> Integer {
|
|
507
|
+
let n = n.map(|i| i.to_i64()).unwrap_or(-1);
|
|
508
|
+
if n < 0 {
|
|
509
|
+
raise_arg("max_depth must be a non-negative Integer (0 means unlimited)");
|
|
510
|
+
}
|
|
511
|
+
MAX_DEPTH.store(n as usize, std::sync::atomic::Ordering::Relaxed);
|
|
512
|
+
Integer::new(n)
|
|
513
|
+
},
|
|
514
|
+
// parse_haskell(source) -> JSON string of the module AST
|
|
515
|
+
fn native_parse_haskell(src: RString) -> RString {
|
|
516
|
+
let src = src
|
|
517
|
+
.unwrap_or_else(|_| raise_arg("Haskell source must be a String"))
|
|
518
|
+
.to_string();
|
|
519
|
+
match crate::core::hs::parse_module(&src) {
|
|
520
|
+
Ok(m) => RString::new_utf8(&crate::core::hs::json::module(&m)),
|
|
521
|
+
Err(e) => raise_core(e),
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
// constructor_info(name) -> [type_name, arity, fields_or_nil] or nil
|
|
525
|
+
fn native_constructor_info(name: RString, scope: Integer) -> AnyObject {
|
|
526
|
+
let name = name
|
|
527
|
+
.unwrap_or_else(|_| raise_arg("constructor name must be a String"))
|
|
528
|
+
.to_string();
|
|
529
|
+
let scope = scope_arg(scope);
|
|
530
|
+
let mut scopes = SCOPES.lock().unwrap_or_else(|p| p.into_inner());
|
|
531
|
+
let env = scope_env(&mut scopes, scope).unwrap_or_else(|m| raise_arg(&m));
|
|
532
|
+
match env.lookup_con(&name) {
|
|
533
|
+
Some(c) => {
|
|
534
|
+
let con = env.con(c);
|
|
535
|
+
let mut row = Array::with_capacity(3);
|
|
536
|
+
row.push(RString::new_utf8(&env.ty(con.ty).name));
|
|
537
|
+
row.push(Integer::new(con.arity as i64));
|
|
538
|
+
match &con.fields {
|
|
539
|
+
Some(fs) => row.push(rstrings(fs).to_any_object()),
|
|
540
|
+
None => row.push(NilClass::new().to_any_object()),
|
|
541
|
+
};
|
|
542
|
+
row.to_any_object()
|
|
543
|
+
}
|
|
544
|
+
None => NilClass::new().to_any_object(),
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
// constructors(type_name) -> [names] or nil
|
|
548
|
+
fn native_constructors(name: RString, scope: Integer) -> AnyObject {
|
|
549
|
+
let name = name
|
|
550
|
+
.unwrap_or_else(|_| raise_arg("type name must be a String"))
|
|
551
|
+
.to_string();
|
|
552
|
+
let scope = scope_arg(scope);
|
|
553
|
+
let mut scopes = SCOPES.lock().unwrap_or_else(|p| p.into_inner());
|
|
554
|
+
let env = scope_env(&mut scopes, scope).unwrap_or_else(|m| raise_arg(&m));
|
|
555
|
+
match env.lookup_type(&name) {
|
|
556
|
+
Some(t) => {
|
|
557
|
+
let names: Vec<String> = env
|
|
558
|
+
.ty(t)
|
|
559
|
+
.cons
|
|
560
|
+
.iter()
|
|
561
|
+
.map(|c| env.con(*c).name.clone())
|
|
562
|
+
.collect();
|
|
563
|
+
rstrings(&names).to_any_object()
|
|
564
|
+
}
|
|
565
|
+
None => NilClass::new().to_any_object(),
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
// ---------------------------------------------------------------------------
|
|
571
|
+
// Matcher construction
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
|
|
574
|
+
struct Built {
|
|
575
|
+
matcher: Matcher,
|
|
576
|
+
missing: Vec<String>,
|
|
577
|
+
truncated: bool,
|
|
578
|
+
redundant: Vec<usize>,
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
enum BuildError {
|
|
582
|
+
Arg(String),
|
|
583
|
+
Core(CoreError),
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
impl From<CoreError> for BuildError {
|
|
587
|
+
fn from(e: CoreError) -> Self {
|
|
588
|
+
BuildError::Core(e)
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
fn build(
|
|
593
|
+
name: String,
|
|
594
|
+
clauses: &Array,
|
|
595
|
+
guarded: &Array,
|
|
596
|
+
limit: usize,
|
|
597
|
+
scope: usize,
|
|
598
|
+
) -> Result<Built, BuildError> {
|
|
599
|
+
let n = clauses.length();
|
|
600
|
+
if n == 0 {
|
|
601
|
+
return Err(BuildError::Arg("at least one clause is required".into()));
|
|
602
|
+
}
|
|
603
|
+
if guarded.length() != n {
|
|
604
|
+
return Err(BuildError::Arg(
|
|
605
|
+
"guard flags must have one entry per clause".into(),
|
|
606
|
+
));
|
|
607
|
+
}
|
|
608
|
+
let mut scopes = SCOPES.lock().unwrap_or_else(|p| p.into_inner());
|
|
609
|
+
let env = scope_env(&mut scopes, scope).map_err(BuildError::Arg)?;
|
|
610
|
+
let mut resolved: Vec<(Vec<crate::core::ast::Pat>, bool)> = Vec::with_capacity(n);
|
|
611
|
+
let mut names: Vec<Vec<String>> = Vec::with_capacity(n);
|
|
612
|
+
let mut guard_flags: Vec<bool> = Vec::with_capacity(n);
|
|
613
|
+
let mut arity: Option<usize> = None;
|
|
614
|
+
for (i, clause) in clauses.dup().into_iter().enumerate() {
|
|
615
|
+
let pats = clause.try_convert_to::<Array>().map_err(|_| {
|
|
616
|
+
CoreError::syntax(format!(
|
|
617
|
+
"clause {} must be an Array of pattern strings",
|
|
618
|
+
i + 1
|
|
619
|
+
))
|
|
620
|
+
})?;
|
|
621
|
+
let mut raws = Vec::with_capacity(pats.length());
|
|
622
|
+
for p in pats.into_iter() {
|
|
623
|
+
let src = p
|
|
624
|
+
.try_convert_to::<RString>()
|
|
625
|
+
.map_err(|_| {
|
|
626
|
+
CoreError::syntax(format!("clause {}: patterns must be Strings", i + 1))
|
|
627
|
+
})?
|
|
628
|
+
.to_string();
|
|
629
|
+
let raw = parse_pattern(&src)
|
|
630
|
+
.map_err(|e| CoreError::new(e.kind, format!("clause {}: {}", i + 1, e.message)))?;
|
|
631
|
+
raws.push(raw);
|
|
632
|
+
}
|
|
633
|
+
if raws.is_empty() {
|
|
634
|
+
return Err(CoreError::new(
|
|
635
|
+
ErrorKind::ClauseArity,
|
|
636
|
+
format!(
|
|
637
|
+
"clause {} has no patterns (a function needs at least one argument)",
|
|
638
|
+
i + 1
|
|
639
|
+
),
|
|
640
|
+
)
|
|
641
|
+
.into());
|
|
642
|
+
}
|
|
643
|
+
match arity {
|
|
644
|
+
None => arity = Some(raws.len()),
|
|
645
|
+
Some(a) if a != raws.len() => {
|
|
646
|
+
return Err(CoreError::new(
|
|
647
|
+
ErrorKind::ClauseArity,
|
|
648
|
+
format!(
|
|
649
|
+
"equations for '{}' have different numbers of arguments: clause 1 has {}, clause {} has {}",
|
|
650
|
+
name,
|
|
651
|
+
a,
|
|
652
|
+
i + 1,
|
|
653
|
+
raws.len()
|
|
654
|
+
),
|
|
655
|
+
)
|
|
656
|
+
.into());
|
|
657
|
+
}
|
|
658
|
+
_ => {}
|
|
659
|
+
}
|
|
660
|
+
let (ps, b) = resolve_clause(env, &raws)
|
|
661
|
+
.map_err(|e| CoreError::new(e.kind, format!("clause {}: {}", i + 1, e.message)))?;
|
|
662
|
+
let g = guarded.at(i as i64);
|
|
663
|
+
let g = !(g.is_nil() || g.value().is_false());
|
|
664
|
+
resolved.push((ps, g));
|
|
665
|
+
names.push(b.names);
|
|
666
|
+
guard_flags.push(g);
|
|
667
|
+
}
|
|
668
|
+
let arity = arity.expect("at least one clause");
|
|
669
|
+
let only_pats: Vec<Vec<crate::core::ast::Pat>> =
|
|
670
|
+
resolved.iter().map(|(p, _)| p.clone()).collect();
|
|
671
|
+
typecheck::check(env, &only_pats)?;
|
|
672
|
+
let analysis = exhaust::analyze(env, &resolved, limit);
|
|
673
|
+
let missing: Vec<String> = analysis
|
|
674
|
+
.missing
|
|
675
|
+
.iter()
|
|
676
|
+
.map(|w| render_witness(env, w))
|
|
677
|
+
.collect();
|
|
678
|
+
let clauses_for_tree: Vec<tree::Clause> = resolved
|
|
679
|
+
.iter()
|
|
680
|
+
.zip(names.iter())
|
|
681
|
+
.map(|((pats, g), ns)| tree::Clause {
|
|
682
|
+
pats: pats.clone(),
|
|
683
|
+
n_vars: ns.len(),
|
|
684
|
+
guarded: *g,
|
|
685
|
+
})
|
|
686
|
+
.collect();
|
|
687
|
+
let compiled = tree::compile(env, &clauses_for_tree, arity);
|
|
688
|
+
let snapshot: TypeEnv = env.clone();
|
|
689
|
+
drop(scopes);
|
|
690
|
+
let rt = runtime::translate(&snapshot, &compiled.tree);
|
|
691
|
+
let mut lits = Vec::new();
|
|
692
|
+
runtime::lazy_literals(&compiled.tree, &mut lits);
|
|
693
|
+
let lazy_lits = lits
|
|
694
|
+
.into_iter()
|
|
695
|
+
.map(|l| (l.key(), runtime::literal_object(&l)))
|
|
696
|
+
.collect();
|
|
697
|
+
Ok(Built {
|
|
698
|
+
matcher: Matcher {
|
|
699
|
+
env: snapshot,
|
|
700
|
+
tree: rt,
|
|
701
|
+
arity,
|
|
702
|
+
n_slots: compiled.n_slots,
|
|
703
|
+
names,
|
|
704
|
+
guarded: guard_flags,
|
|
705
|
+
name,
|
|
706
|
+
lazy_lits,
|
|
707
|
+
},
|
|
708
|
+
missing,
|
|
709
|
+
truncated: analysis.truncated,
|
|
710
|
+
redundant: analysis.redundant,
|
|
711
|
+
})
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
#[inline]
|
|
715
|
+
fn matcher_of(obj: Value) -> &'static Matcher {
|
|
716
|
+
// `get_data` checks the typed-data tag and raises TypeError otherwise.
|
|
717
|
+
let any = AnyObject::from(obj);
|
|
718
|
+
let m: &Matcher = any.get_data(&*MATCHER_TYPE);
|
|
719
|
+
// SAFETY: the matcher lives as long as the Ruby object, which the caller
|
|
720
|
+
// keeps alive for the duration of the call.
|
|
721
|
+
unsafe { &*(m as *const Matcher) }
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// ---------------------------------------------------------------------------
|
|
725
|
+
// Matcher methods (raw extern "C")
|
|
726
|
+
// ---------------------------------------------------------------------------
|
|
727
|
+
|
|
728
|
+
/// Matcher.new(name, clauses, guarded_flags, witness_limit)
|
|
729
|
+
unsafe extern "C" fn matcher_new(
|
|
730
|
+
klass: Value,
|
|
731
|
+
name: Value,
|
|
732
|
+
clauses: Value,
|
|
733
|
+
guarded: Value,
|
|
734
|
+
limit: Value,
|
|
735
|
+
scope: Value,
|
|
736
|
+
) -> Value {
|
|
737
|
+
let outcome = catch_unwind(AssertUnwindSafe(
|
|
738
|
+
|| -> Result<(AnyObject, Vec<String>, bool, Vec<usize>), BuildError> {
|
|
739
|
+
let name = AnyObject::from(name)
|
|
740
|
+
.try_convert_to::<RString>()
|
|
741
|
+
.map(|s| s.to_string())
|
|
742
|
+
.map_err(|_| BuildError::Arg("name must be a String".into()))?;
|
|
743
|
+
let clauses = AnyObject::from(clauses)
|
|
744
|
+
.try_convert_to::<Array>()
|
|
745
|
+
.map_err(|_| BuildError::Arg("clauses must be an Array".into()))?;
|
|
746
|
+
let guarded = AnyObject::from(guarded)
|
|
747
|
+
.try_convert_to::<Array>()
|
|
748
|
+
.map_err(|_| BuildError::Arg("guard flags must be an Array".into()))?;
|
|
749
|
+
let limit = AnyObject::from(limit)
|
|
750
|
+
.try_convert_to::<Integer>()
|
|
751
|
+
.map(|i| i.to_i64())
|
|
752
|
+
.unwrap_or(20)
|
|
753
|
+
.max(1) as usize;
|
|
754
|
+
let scope = AnyObject::from(scope)
|
|
755
|
+
.try_convert_to::<Integer>()
|
|
756
|
+
.map(|i| i.to_i64())
|
|
757
|
+
.map_err(|_| BuildError::Arg("scope must be an Integer".into()))?;
|
|
758
|
+
if scope < 0 {
|
|
759
|
+
return Err(BuildError::Arg("scope must not be negative".into()));
|
|
760
|
+
}
|
|
761
|
+
let built = build(name, &clauses, &guarded, limit, scope as usize)?;
|
|
762
|
+
let obj: AnyObject = Class::from(klass).wrap_data(built.matcher, &*MATCHER_TYPE);
|
|
763
|
+
Ok((obj, built.missing, built.truncated, built.redundant))
|
|
764
|
+
},
|
|
765
|
+
));
|
|
766
|
+
match outcome {
|
|
767
|
+
Err(payload) => raise_panic(payload),
|
|
768
|
+
Ok(Err(BuildError::Arg(msg))) => raise_arg(&msg),
|
|
769
|
+
Ok(Err(BuildError::Core(e))) => raise_core(e),
|
|
770
|
+
Ok(Ok((mut obj, missing, truncated, redundant))) => {
|
|
771
|
+
obj.instance_variable_set("@missing", rstrings(&missing));
|
|
772
|
+
obj.instance_variable_set("@missing_truncated", Boolean::new(truncated));
|
|
773
|
+
let mut red = Array::with_capacity(redundant.len());
|
|
774
|
+
for r in redundant {
|
|
775
|
+
red.push(Integer::new(r as i64));
|
|
776
|
+
}
|
|
777
|
+
obj.instance_variable_set("@redundant", red);
|
|
778
|
+
obj.value()
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/// Outcome of the pure-Rust part of a call: the body to invoke and where its
|
|
784
|
+
/// arguments are.
|
|
785
|
+
struct Prepared {
|
|
786
|
+
body: Value,
|
|
787
|
+
bound: Bound,
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/// Run `select` and locate the body, without calling into Ruby.
|
|
791
|
+
unsafe fn prepare(
|
|
792
|
+
m: &Matcher,
|
|
793
|
+
args: &[Value],
|
|
794
|
+
bodies: Value,
|
|
795
|
+
guards: Value,
|
|
796
|
+
buf: &mut [Value; STACK_BINDS],
|
|
797
|
+
) -> Result<Prepared, RtError> {
|
|
798
|
+
let (idx, bound) = m.select(args, guards, buf)?;
|
|
799
|
+
let body = if bodies.ty() == ValueType::Array {
|
|
800
|
+
array::rb_ary_entry(bodies, idx as _)
|
|
801
|
+
} else {
|
|
802
|
+
Value::from(rutie::rubysys::value::RubySpecialConsts::Nil as usize)
|
|
803
|
+
};
|
|
804
|
+
Ok(Prepared { body, bound })
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
fn check_guards(guards: Value) -> Result<(), String> {
|
|
808
|
+
if guards.is_nil() || guards.ty() == ValueType::Array {
|
|
809
|
+
Ok(())
|
|
810
|
+
} else {
|
|
811
|
+
Err("guards must be an Array or nil".into())
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/// Continuations pending in a trampolined call, kept as a chain of small
|
|
816
|
+
/// Ruby arrays rather than one growing array. A full chunk is never written
|
|
817
|
+
/// again, so once Ruby's generational GC promotes it, minor collections skip
|
|
818
|
+
/// it; with a single array every push would re-mark the whole stack on each
|
|
819
|
+
/// minor GC, making GC cost grow with recursion depth.
|
|
820
|
+
const CONT_CHUNK: i64 = 256;
|
|
821
|
+
|
|
822
|
+
struct ContStack {
|
|
823
|
+
/// Current chunk: element 0 links to the previous chunk (or nil).
|
|
824
|
+
chunk: Value,
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
impl ContStack {
|
|
828
|
+
fn new() -> Self {
|
|
829
|
+
ContStack {
|
|
830
|
+
chunk: Value::from(rutie::rubysys::value::RubySpecialConsts::Nil as usize),
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
#[inline]
|
|
835
|
+
unsafe fn push(&mut self, k: Value) {
|
|
836
|
+
if self.chunk.is_nil() || array::rb_ary_len(self.chunk) as i64 > CONT_CHUNK {
|
|
837
|
+
let fresh = array::rb_ary_new_capa((CONT_CHUNK + 1) as _);
|
|
838
|
+
array::rb_ary_push(fresh, self.chunk);
|
|
839
|
+
self.chunk = fresh;
|
|
840
|
+
}
|
|
841
|
+
array::rb_ary_push(self.chunk, k);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/// Pop the most recent continuation, or nil when none is pending.
|
|
845
|
+
#[inline]
|
|
846
|
+
unsafe fn pop(&mut self) -> Value {
|
|
847
|
+
loop {
|
|
848
|
+
if self.chunk.is_nil() {
|
|
849
|
+
return self.chunk;
|
|
850
|
+
}
|
|
851
|
+
if array::rb_ary_len(self.chunk) <= 1 {
|
|
852
|
+
self.chunk = array::rb_ary_entry(self.chunk, 0);
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
return array::rb_ary_pop(self.chunk);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/// matcher.call(*args): select a clause and call its body (from `@bodies`,
|
|
861
|
+
/// with guards from `@guards`).
|
|
862
|
+
///
|
|
863
|
+
/// The call runs as a trampoline. A body may return a
|
|
864
|
+
/// `HaskellMatch::TailCall(function, args, continuation)`:
|
|
865
|
+
///
|
|
866
|
+
/// * with a nil continuation (`Function#tail`) the call simply continues
|
|
867
|
+
/// with that function and those arguments, in constant stack space;
|
|
868
|
+
/// * with a continuation (`Function#defer`) the continuation is pushed on a
|
|
869
|
+
/// chunked stack owned by this frame and the call continues; once a body
|
|
870
|
+
/// returns an ordinary value the pending continuations are applied to it
|
|
871
|
+
/// one after another (each may itself return a `TailCall`).
|
|
872
|
+
///
|
|
873
|
+
/// Recursion depth is therefore bounded by memory, as in GHC, rather than
|
|
874
|
+
/// by Ruby's VM stack.
|
|
875
|
+
unsafe extern "C" fn matcher_call(argc: c_int, argv: *const Value, rtself: Value) -> Value {
|
|
876
|
+
let mut buf = [Value::from(0); STACK_BINDS];
|
|
877
|
+
let mut target = rtself;
|
|
878
|
+
// Pending continuations (Ruby arrays, rooted by this frame).
|
|
879
|
+
let mut conts = ContStack::new();
|
|
880
|
+
// The current marker keeps the next arguments alive while they are matched.
|
|
881
|
+
let mut marker = Value::from(0);
|
|
882
|
+
let mut args_vec: Vec<Value> = Vec::new();
|
|
883
|
+
let mut first = true;
|
|
884
|
+
let tail_class = tail_call_class();
|
|
885
|
+
loop {
|
|
886
|
+
let args: &[Value] = if first {
|
|
887
|
+
std::slice::from_raw_parts(argv, argc.max(0) as usize)
|
|
888
|
+
} else {
|
|
889
|
+
&args_vec[..]
|
|
890
|
+
};
|
|
891
|
+
let outcome = catch_unwind(AssertUnwindSafe(|| -> Result<Prepared, RtError> {
|
|
892
|
+
let m = matcher_of(target);
|
|
893
|
+
let bodies = rclass::rb_ivar_get(target, ids().bodies);
|
|
894
|
+
let guards = rclass::rb_ivar_get(target, ids().guards);
|
|
895
|
+
prepare(m, args, bodies, guards, &mut buf)
|
|
896
|
+
}));
|
|
897
|
+
let p = match outcome {
|
|
898
|
+
Err(payload) => {
|
|
899
|
+
drop(args_vec);
|
|
900
|
+
raise_panic(payload)
|
|
901
|
+
}
|
|
902
|
+
Ok(Err(e)) => {
|
|
903
|
+
drop(args_vec);
|
|
904
|
+
raise_rt(&matcher_of(target).name, e)
|
|
905
|
+
}
|
|
906
|
+
Ok(Ok(p)) => p,
|
|
907
|
+
};
|
|
908
|
+
if p.body.is_nil() {
|
|
909
|
+
drop(args_vec);
|
|
910
|
+
raise_arg("this matcher has no clause bodies attached");
|
|
911
|
+
}
|
|
912
|
+
// Nothing needing Drop may be live across a call into Ruby, which
|
|
913
|
+
// may longjmp: `args_vec` is emptied first (its arguments are rooted
|
|
914
|
+
// by `marker` anyway).
|
|
915
|
+
drop(std::mem::take(&mut args_vec));
|
|
916
|
+
let mut result = match invoke_body(p.body, &p.bound, &buf) {
|
|
917
|
+
Ok(v) => v,
|
|
918
|
+
Err(state) => rb_jump_tag(state),
|
|
919
|
+
};
|
|
920
|
+
// Apply pending continuations until one asks for another call.
|
|
921
|
+
while rclass::rb_obj_class(result).value != tail_class {
|
|
922
|
+
let k = conts.pop();
|
|
923
|
+
if k.is_nil() {
|
|
924
|
+
std::hint::black_box(marker);
|
|
925
|
+
return result;
|
|
926
|
+
}
|
|
927
|
+
result = rutie::rubysys::rproc::rb_proc_call_with_block(
|
|
928
|
+
k,
|
|
929
|
+
1,
|
|
930
|
+
&result,
|
|
931
|
+
Value::from(rutie::rubysys::value::RubySpecialConsts::Nil as usize),
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
// TailCall(function, args, continuation): continue with the next call.
|
|
935
|
+
marker = result;
|
|
936
|
+
let function = rstruct::rb_struct_aref(result, Integer::new(0).value());
|
|
937
|
+
let next_args = rstruct::rb_struct_aref(result, Integer::new(1).value());
|
|
938
|
+
let continuation = rstruct::rb_struct_aref(result, Integer::new(2).value());
|
|
939
|
+
if next_args.ty() != ValueType::Array {
|
|
940
|
+
raise_arg("TailCall arguments must be an Array");
|
|
941
|
+
}
|
|
942
|
+
if !continuation.is_nil() {
|
|
943
|
+
conts.push(continuation);
|
|
944
|
+
}
|
|
945
|
+
// The matcher type is checked by `matcher_of` on the next iteration.
|
|
946
|
+
target = function;
|
|
947
|
+
let n = array::rb_ary_len(next_args) as usize;
|
|
948
|
+
args_vec = (0..n)
|
|
949
|
+
.map(|i| array::rb_ary_entry(next_args, i as _))
|
|
950
|
+
.collect();
|
|
951
|
+
first = false;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/// matcher.prepare(*args) -> [values..., body, depth]: the selected clause's
|
|
956
|
+
/// bound values, its body (from `@bodies`, guards from `@guards`) and the
|
|
957
|
+
/// nesting depth after counting this call, for callers that invoke the body
|
|
958
|
+
/// from Ruby code (see `Function` deep mode); they call `Native.depth_pop`
|
|
959
|
+
/// once the body has returned.
|
|
960
|
+
unsafe extern "C" fn matcher_prepare(argc: c_int, argv: *const Value, rtself: Value) -> Value {
|
|
961
|
+
let mut buf = [Value::from(0); STACK_BINDS];
|
|
962
|
+
let args = std::slice::from_raw_parts(argv, argc.max(0) as usize);
|
|
963
|
+
let outcome = catch_unwind(AssertUnwindSafe(|| -> Result<Prepared, RtError> {
|
|
964
|
+
let m = matcher_of(rtself);
|
|
965
|
+
let bodies = rclass::rb_ivar_get(rtself, ids().bodies);
|
|
966
|
+
let guards = rclass::rb_ivar_get(rtself, ids().guards);
|
|
967
|
+
prepare(m, args, bodies, guards, &mut buf)
|
|
968
|
+
}));
|
|
969
|
+
match outcome {
|
|
970
|
+
Err(payload) => raise_panic(payload),
|
|
971
|
+
Ok(Err(e)) => raise_rt(&matcher_of(rtself).name, e),
|
|
972
|
+
Ok(Ok(p)) => {
|
|
973
|
+
if p.body.is_nil() {
|
|
974
|
+
raise_arg("this matcher has no clause bodies attached");
|
|
975
|
+
}
|
|
976
|
+
let out = match p.bound {
|
|
977
|
+
Bound::Stack(n) => array::rb_ary_new_from_values(n as _, buf.as_ptr()),
|
|
978
|
+
Bound::Array(a) => a,
|
|
979
|
+
};
|
|
980
|
+
array::rb_ary_push(out, p.body);
|
|
981
|
+
// Count this level now; the Ruby caller pops it after the body.
|
|
982
|
+
let depth = native_depth_push(rtself);
|
|
983
|
+
array::rb_ary_push(out, depth);
|
|
984
|
+
out
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/// Native.depth_push -> the new nesting depth (raises StackOverflowError past
|
|
990
|
+
/// max_depth); Native.depth_pop undoes it. Used by deep-mode functions,
|
|
991
|
+
/// whose body invocation happens in Ruby.
|
|
992
|
+
unsafe extern "C" fn native_depth_push(_rtself: Value) -> Value {
|
|
993
|
+
let depth = DEPTH.with(|d| d.get());
|
|
994
|
+
let max = MAX_DEPTH.load(std::sync::atomic::Ordering::Relaxed);
|
|
995
|
+
if max > 0 && depth >= max {
|
|
996
|
+
VM::raise_ex(exception(
|
|
997
|
+
"StackOverflowError",
|
|
998
|
+
&format!(
|
|
999
|
+
"recursion deeper than HaskellMatch.max_depth ({}); use tail calls, defer, or raise the limit",
|
|
1000
|
+
max
|
|
1001
|
+
),
|
|
1002
|
+
));
|
|
1003
|
+
}
|
|
1004
|
+
DEPTH.with(|d| d.set(depth + 1));
|
|
1005
|
+
Integer::new((depth + 1) as i64).value()
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
unsafe extern "C" fn native_depth_pop(_rtself: Value) -> Value {
|
|
1009
|
+
DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
|
|
1010
|
+
Value::from(rutie::rubysys::value::RubySpecialConsts::Nil as usize)
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/// matcher.select(*args) -> [clause_index, [values]] or nil (guards from
|
|
1014
|
+
/// `@guards`).
|
|
1015
|
+
unsafe extern "C" fn matcher_select(argc: c_int, argv: *const Value, rtself: Value) -> Value {
|
|
1016
|
+
let mut buf = [Value::from(0); STACK_BINDS];
|
|
1017
|
+
let args = std::slice::from_raw_parts(argv, argc.max(0) as usize);
|
|
1018
|
+
let outcome = catch_unwind(AssertUnwindSafe(|| -> Result<(usize, Bound), RtError> {
|
|
1019
|
+
let m = matcher_of(rtself);
|
|
1020
|
+
let guards = rclass::rb_ivar_get(rtself, ids().guards);
|
|
1021
|
+
m.select(args, guards, &mut buf)
|
|
1022
|
+
}));
|
|
1023
|
+
select_result(outcome, rtself, &buf)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
unsafe fn select_result(
|
|
1027
|
+
outcome: Result<Result<(usize, Bound), RtError>, Box<dyn std::any::Any + Send>>,
|
|
1028
|
+
rtself: Value,
|
|
1029
|
+
buf: &[Value],
|
|
1030
|
+
) -> Value {
|
|
1031
|
+
match outcome {
|
|
1032
|
+
Err(payload) => raise_panic(payload),
|
|
1033
|
+
Ok(Err(RtError::NoMatch)) => {
|
|
1034
|
+
Value::from(rutie::rubysys::value::RubySpecialConsts::Nil as usize)
|
|
1035
|
+
}
|
|
1036
|
+
Ok(Err(e)) => raise_rt(&matcher_of(rtself).name, e),
|
|
1037
|
+
Ok(Ok((idx, bound))) => {
|
|
1038
|
+
let vals = runtime::bound_to_array(&bound, buf);
|
|
1039
|
+
let pair = [Integer::new(idx as i64).value(), vals];
|
|
1040
|
+
array::rb_ary_new_from_values(2, pair.as_ptr())
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/// Read an argument array into a Vec of values (rooted by the array itself).
|
|
1046
|
+
fn args_of(args: Value) -> Result<Vec<Value>, String> {
|
|
1047
|
+
if args.ty() != ValueType::Array {
|
|
1048
|
+
return Err("arguments must be an Array".into());
|
|
1049
|
+
}
|
|
1050
|
+
unsafe {
|
|
1051
|
+
let n = array::rb_ary_len(args) as usize;
|
|
1052
|
+
Ok((0..n).map(|i| array::rb_ary_entry(args, i as _)).collect())
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/// matcher.select_with(args_array, guards) -> [clause_index, [values]] or nil
|
|
1057
|
+
unsafe extern "C" fn matcher_select_with(rtself: Value, args: Value, guards: Value) -> Value {
|
|
1058
|
+
let mut buf = [Value::from(0); STACK_BINDS];
|
|
1059
|
+
let outcome = catch_unwind(AssertUnwindSafe(
|
|
1060
|
+
|| -> Result<Result<(usize, Bound), RtError>, String> {
|
|
1061
|
+
let m = matcher_of(rtself);
|
|
1062
|
+
let args = args_of(args)?;
|
|
1063
|
+
check_guards(guards)?;
|
|
1064
|
+
Ok(m.select(&args, guards, &mut buf))
|
|
1065
|
+
},
|
|
1066
|
+
));
|
|
1067
|
+
let outcome = match outcome {
|
|
1068
|
+
Ok(Err(msg)) => raise_arg(&msg),
|
|
1069
|
+
Ok(Ok(r)) => Ok(r),
|
|
1070
|
+
Err(p) => Err(p),
|
|
1071
|
+
};
|
|
1072
|
+
select_result(outcome, rtself, &buf)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/// matcher.run(args_array, bodies, guards) -> result of the selected body.
|
|
1076
|
+
unsafe extern "C" fn matcher_run(
|
|
1077
|
+
rtself: Value,
|
|
1078
|
+
args: Value,
|
|
1079
|
+
bodies: Value,
|
|
1080
|
+
guards: Value,
|
|
1081
|
+
) -> Value {
|
|
1082
|
+
let mut buf = [Value::from(0); STACK_BINDS];
|
|
1083
|
+
let outcome = catch_unwind(AssertUnwindSafe(
|
|
1084
|
+
|| -> Result<Result<Prepared, RtError>, String> {
|
|
1085
|
+
let m = matcher_of(rtself);
|
|
1086
|
+
let args = args_of(args)?;
|
|
1087
|
+
check_guards(guards)?;
|
|
1088
|
+
if bodies.ty() != ValueType::Array {
|
|
1089
|
+
return Err("bodies must be an Array".into());
|
|
1090
|
+
}
|
|
1091
|
+
Ok(prepare(m, &args, bodies, guards, &mut buf))
|
|
1092
|
+
},
|
|
1093
|
+
));
|
|
1094
|
+
match outcome {
|
|
1095
|
+
Err(payload) => raise_panic(payload),
|
|
1096
|
+
Ok(Err(msg)) => raise_arg(&msg),
|
|
1097
|
+
Ok(Ok(Err(e))) => raise_rt(&matcher_of(rtself).name, e),
|
|
1098
|
+
Ok(Ok(Ok(p))) => {
|
|
1099
|
+
if p.body.is_nil() {
|
|
1100
|
+
raise_arg("no body for the selected clause");
|
|
1101
|
+
}
|
|
1102
|
+
runtime::invoke(p.body, &p.bound, &buf)
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
unsafe extern "C" fn matcher_arity(rtself: Value) -> Value {
|
|
1108
|
+
Integer::new(matcher_of(rtself).arity as i64).value()
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
unsafe extern "C" fn matcher_name(rtself: Value) -> Value {
|
|
1112
|
+
RString::new_utf8(&matcher_of(rtself).name).value()
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
unsafe extern "C" fn matcher_names(rtself: Value) -> Value {
|
|
1116
|
+
let m = matcher_of(rtself);
|
|
1117
|
+
let mut out = Array::with_capacity(m.names.len());
|
|
1118
|
+
for ns in &m.names {
|
|
1119
|
+
out.push(rstrings(ns));
|
|
1120
|
+
}
|
|
1121
|
+
out.value()
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
unsafe extern "C" fn matcher_guarded(rtself: Value) -> Value {
|
|
1125
|
+
let m = matcher_of(rtself);
|
|
1126
|
+
let mut out = Array::with_capacity(m.guarded.len());
|
|
1127
|
+
for g in &m.guarded {
|
|
1128
|
+
out.push(Boolean::new(*g));
|
|
1129
|
+
}
|
|
1130
|
+
out.value()
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
unsafe extern "C" fn matcher_slots(rtself: Value) -> Value {
|
|
1134
|
+
Integer::new(matcher_of(rtself).n_slots as i64).value()
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
unsafe extern "C" fn matcher_tree(rtself: Value) -> Value {
|
|
1138
|
+
let m = matcher_of(rtself);
|
|
1139
|
+
RString::new_utf8(&runtime::dump(&m.env, &m.tree, &m.names, 0)).value()
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
unsafe fn def_method(klass: Value, name: &str, f: CallbackPtr, argc: c_int) {
|
|
1143
|
+
let cname = CString::new(name).unwrap();
|
|
1144
|
+
rclass::rb_define_method(klass, cname.as_ptr(), f, argc);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
pub fn init() {
|
|
1148
|
+
// Nothing here keeps per-Ractor state: the type registry is behind a
|
|
1149
|
+
// mutex and matchers are immutable once built.
|
|
1150
|
+
unsafe { VM::ext_ractor_safe(true) };
|
|
1151
|
+
let mut hm = Module::from_existing("HaskellMatch");
|
|
1152
|
+
let mut native = hm.define_nested_module("Native");
|
|
1153
|
+
unsafe {
|
|
1154
|
+
let n = native.value();
|
|
1155
|
+
let push = CString::new("depth_push").unwrap();
|
|
1156
|
+
let pop = CString::new("depth_pop").unwrap();
|
|
1157
|
+
rclass::rb_define_singleton_method(n, push.as_ptr(), native_depth_push as CallbackPtr, 0);
|
|
1158
|
+
rclass::rb_define_singleton_method(n, pop.as_ptr(), native_depth_pop as CallbackPtr, 0);
|
|
1159
|
+
}
|
|
1160
|
+
native.define(|m| {
|
|
1161
|
+
m.def_self("parse_data", native_parse_data);
|
|
1162
|
+
m.def_self("new_scope", native_new_scope);
|
|
1163
|
+
m.def_self("import_scope_in", native_import_scope);
|
|
1164
|
+
m.def_self("register_type_in", native_register_type);
|
|
1165
|
+
m.def_self("render_pattern_in", native_render_pattern);
|
|
1166
|
+
m.def_self("constructors_in", native_constructors);
|
|
1167
|
+
m.def_self("parse_haskell", native_parse_haskell);
|
|
1168
|
+
m.def_self("constructor_info_in", native_constructor_info);
|
|
1169
|
+
m.def_self("stack_segment", native_stack_segment);
|
|
1170
|
+
m.def_self("stack_segment=", native_set_stack_segment);
|
|
1171
|
+
m.def_self("max_depth", native_max_depth);
|
|
1172
|
+
m.def_self("max_depth=", native_set_max_depth);
|
|
1173
|
+
});
|
|
1174
|
+
let object = Class::from_existing("Object");
|
|
1175
|
+
let mut matcher = native.define_nested_class("Matcher", Some(&object));
|
|
1176
|
+
// Instances are only ever created through `new` with wrapped data.
|
|
1177
|
+
matcher.undef_alloc_func();
|
|
1178
|
+
unsafe {
|
|
1179
|
+
let k = matcher.value();
|
|
1180
|
+
let new_name = CString::new("new").unwrap();
|
|
1181
|
+
rclass::rb_define_singleton_method(k, new_name.as_ptr(), matcher_new as CallbackPtr, 5);
|
|
1182
|
+
def_method(k, "call", matcher_call as CallbackPtr, -1);
|
|
1183
|
+
def_method(k, "prepare", matcher_prepare as CallbackPtr, -1);
|
|
1184
|
+
def_method(k, "select", matcher_select as CallbackPtr, -1);
|
|
1185
|
+
def_method(k, "select_with", matcher_select_with as CallbackPtr, 2);
|
|
1186
|
+
def_method(k, "run", matcher_run as CallbackPtr, 3);
|
|
1187
|
+
def_method(k, "arity", matcher_arity as CallbackPtr, 0);
|
|
1188
|
+
def_method(k, "name", matcher_name as CallbackPtr, 0);
|
|
1189
|
+
def_method(k, "names", matcher_names as CallbackPtr, 0);
|
|
1190
|
+
def_method(k, "guarded", matcher_guarded as CallbackPtr, 0);
|
|
1191
|
+
def_method(k, "slots", matcher_slots as CallbackPtr, 0);
|
|
1192
|
+
def_method(k, "tree", matcher_tree as CallbackPtr, 0);
|
|
1193
|
+
}
|
|
1194
|
+
ids();
|
|
1195
|
+
}
|