haskell_match 0.1.0 → 0.2.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.
@@ -1,45 +1,50 @@
1
- //! Ruby-facing API: the `HaskellMatch::Native` module and
2
- //! `HaskellMatch::Native::Matcher` class.
1
+ //! Ruby-facing API: the `HaskellMatch::Native` module (`native`, declared
2
+ //! with `#[rubymodule]`), the exceptions it raises (`error`, declared with
3
+ //! `#[rubyerror]`) and the `HaskellMatch::Native::Matcher` class defined
4
+ //! here.
3
5
  //!
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
+ //! `Matcher` keeps a hand-written typed-data definition rather than
7
+ //! `#[rubyclass]`: it sets `RUBY_TYPED_FROZEN_SHAREABLE`, which a frozen
8
+ //! matcher (and the `Function` objects built on it) needs to pass
9
+ //! `Ractor.make_shareable`, and the derive data type has no way to add that
10
+ //! flag. Its hot-path methods (`call`, `prepare`, `select`, `select_with`,
11
+ //! `run`) are raw `extern "C"` functions (argc/argv style) so that a call
6
12
  //! allocates nothing on the Rust side and clause bodies are invoked from a
7
13
  //! frame that owns no values needing `Drop` (a body may leave by exception,
8
- //! `throw`, `break` or `return`).
14
+ //! `throw`, `break` or `return`). The remaining methods go through
15
+ //! `methods!` and return `Result`s, raised once their values are dropped.
9
16
 
17
+ pub mod error;
18
+ pub mod native;
10
19
  pub mod runtime;
20
+ pub mod state;
11
21
 
12
22
  use std::ffi::CString;
13
23
  use std::os::raw::c_int;
14
24
  use std::panic::{catch_unwind, AssertUnwindSafe};
15
- use std::sync::{Mutex, OnceLock};
25
+ use std::sync::{LazyLock, OnceLock};
16
26
 
27
+ use rutie::derive::IntoException;
17
28
  use rutie::rubysys::typed_data::{RUBY_TYPED_FREE_IMMEDIATELY, RUBY_TYPED_FROZEN_SHAREABLE};
18
29
  use rutie::rubysys::{array, class as rclass, rstruct, value::ValueType};
19
30
  use rutie::typed_data::DataTypeWrapper;
20
31
  use rutie::types::{CallbackPtr, DataType, DataTypeFunction, Id, Value};
21
32
  use rutie::{
22
- AnyException, AnyObject, Array, Boolean, Class, Exception, Integer, Module, NilClass, Object,
23
- RString, VM,
33
+ AnyException, AnyObject, Array, Boolean, Class, Exception, Integer, Module, Object, RString, VM,
24
34
  };
25
35
 
26
36
  use crate::core::error::{CoreError, ErrorKind};
27
37
  use crate::core::exhaust;
28
- use crate::core::parser::{parse_data, parse_pattern};
29
- use crate::core::pretty::{render_pat, render_witness};
38
+ use crate::core::parser::parse_pattern;
39
+ use crate::core::pretty::render_witness;
30
40
  use crate::core::resolve::resolve_clause;
31
41
  use crate::core::tree;
32
42
  use crate::core::typecheck;
33
- use crate::core::types::{ConSpec, TypeEnv};
43
+ use crate::core::types::TypeEnv;
44
+ use error::{argument_error, HmError};
34
45
  use runtime::{Bound, Matcher, RtError, STACK_BINDS};
35
46
 
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
- }
47
+ static MATCHER_TYPE: LazyLock<MatcherType> = LazyLock::new(MatcherType::new);
43
48
 
44
49
  /// Interned ivar names read on every call.
45
50
  struct Ids {
@@ -55,7 +60,7 @@ static TAIL_CALL: OnceLock<usize> = OnceLock::new();
55
60
 
56
61
  fn tail_call_class() -> usize {
57
62
  *TAIL_CALL.get_or_init(|| {
58
- let k = hm_class("TailCall");
63
+ let k = Module::from_existing("HaskellMatch").get_nested_class("TailCall");
59
64
  unsafe { rutie::rubysys::gc::rb_gc_register_mark_object(k.value()) };
60
65
  k.value().value
61
66
  })
@@ -79,26 +84,6 @@ extern "C" {
79
84
  fn rb_fiber_resume(fiber: Value, argc: c_int, argv: *const Value) -> Value;
80
85
  }
81
86
 
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
87
  rutie_callback! {
103
88
  /// Body of a stack-segment Fiber: `data` is `[body, values]`.
104
89
  fn segment_fiber_body(
@@ -116,24 +101,20 @@ rutie_callback! {
116
101
  }
117
102
  }
118
103
 
119
- /// Invoke a clause body, either directly or (every `STACK_SEGMENT` levels)
104
+ /// Invoke a clause body, either directly or (every `stack_segment` levels)
120
105
  /// in a fresh Fiber. Exceptions and non-local jumps are returned as the
121
106
  /// `rb_protect` state for the caller to resume once it has cleaned up.
107
+ ///
108
+ /// # Safety
109
+ /// Must run on a Ruby thread holding the GVL with no Rust values needing
110
+ /// `Drop` live in the caller (a `StackOverflowError` is raised from here).
122
111
  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) {
112
+ let depth = match state::depth_push() {
113
+ Ok(depth) => depth,
114
+ Err(e) => raise(e),
115
+ };
116
+ let segment = state::stack_segment();
117
+ let result = if segment > 0 && depth.is_multiple_of(segment) {
137
118
  let vals = runtime::bound_to_array(bound, buf);
138
119
  let pair = [body, vals];
139
120
  let data = array::rb_ary_new_from_values(2, pair.as_ptr());
@@ -145,13 +126,14 @@ unsafe fn invoke_body(body: Value, bound: &Bound, buf: &[Value]) -> Result<Value
145
126
  } else {
146
127
  runtime::call_protected(|| runtime::invoke(body, bound, buf))
147
128
  };
148
- DEPTH.with(|d| d.set(depth));
129
+ state::set_depth(depth - 1);
149
130
  result
150
131
  }
151
132
 
152
133
  /// 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`.
134
+ /// and `#[rubyclass]` it sets `RUBY_TYPED_FROZEN_SHAREABLE`, so a frozen
135
+ /// matcher (and the `Function` objects built on it) can be passed to
136
+ /// `Ractor.make_shareable`.
155
137
  pub struct MatcherType {
156
138
  data_type: DataType,
157
139
  }
@@ -185,60 +167,21 @@ impl DataTypeWrapper<Matcher> for MatcherType {
185
167
  }
186
168
  }
187
169
 
188
- class!(RMatcher);
189
- module!(Native);
170
+ class!(RMatcher, "HaskellMatch::Native::Matcher");
190
171
 
191
172
  // ---------------------------------------------------------------------------
192
- // error helpers
173
+ // error helpers for the raw methods
193
174
  // ---------------------------------------------------------------------------
194
175
 
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));
176
+ /// Raise `e` from a raw method. Callers hold no Rust values needing `Drop`
177
+ /// at this point: `VM::raise_ex` `longjmp`s past the frame.
178
+ fn raise(e: impl IntoException) -> ! {
179
+ VM::raise_ex(e.into_exception());
236
180
  unreachable!()
237
181
  }
238
182
 
239
183
  fn raise_arg(msg: &str) -> ! {
240
- VM::raise_ex(AnyException::new("ArgumentError", Some(msg)));
241
- unreachable!()
184
+ raise(argument_error(msg))
242
185
  }
243
186
 
244
187
  fn inspect(v: Value) -> String {
@@ -254,41 +197,33 @@ fn class_name(v: Value) -> String {
254
197
  .unwrap_or_else(|| "?".into())
255
198
  }
256
199
 
200
+ /// Report a failed match from a raw method: a `HaskellMatch` exception, an
201
+ /// `ArgumentError`, or the resumption of a pending Ruby exception or jump.
257
202
  fn raise_rt(name: &str, e: RtError) -> ! {
258
203
  match e {
259
204
  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
- )),
205
+ RtError::NoMatch => raise(HmError::NoMatch(format!(
206
+ "Non-exhaustive patterns in {}",
207
+ name
208
+ ))),
209
+ RtError::TypeMismatch { expected, got } => raise(HmError::TypeMismatch(format!(
210
+ "{}: expected a value of type {} but got {} ({})",
211
+ name,
212
+ expected,
213
+ inspect(got),
214
+ class_name(got)
215
+ ))),
216
+ RtError::LazyFailed { clause, got } => raise(HmError::IrrefutablePattern(format!(
217
+ "{}: irrefutable pattern failed for clause {}: {} does not match",
218
+ name,
219
+ clause + 1,
220
+ inspect(got)
221
+ ))),
222
+ RtError::Arity { expected, got } => raise_arg(&format!(
223
+ "wrong number of arguments (given {}, expected {})",
224
+ got, expected
289
225
  )),
290
226
  }
291
- unreachable!()
292
227
  }
293
228
 
294
229
  fn raise_panic(payload: Box<dyn std::any::Any + Send>) -> ! {
@@ -300,22 +235,10 @@ fn raise_panic(payload: Box<dyn std::any::Any + Send>) -> ! {
300
235
  "unknown panic".to_string()
301
236
  };
302
237
  drop(payload);
303
- VM::raise_ex(AnyException::new(
238
+ raise(AnyException::new(
304
239
  "RuntimeError",
305
240
  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())))
241
+ ))
319
242
  }
320
243
 
321
244
  fn rstrings(items: &[String]) -> Array {
@@ -326,247 +249,6 @@ fn rstrings(items: &[String]) -> Array {
326
249
  a
327
250
  }
328
251
 
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
252
  // ---------------------------------------------------------------------------
571
253
  // Matcher construction
572
254
  // ---------------------------------------------------------------------------
@@ -578,35 +260,22 @@ struct Built {
578
260
  redundant: Vec<usize>,
579
261
  }
580
262
 
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
263
  fn build(
593
264
  name: String,
594
265
  clauses: &Array,
595
266
  guarded: &Array,
596
267
  limit: usize,
597
268
  scope: usize,
598
- ) -> Result<Built, BuildError> {
269
+ ) -> Result<Built, AnyException> {
599
270
  let n = clauses.length();
600
271
  if n == 0 {
601
- return Err(BuildError::Arg("at least one clause is required".into()));
272
+ return Err(argument_error("at least one clause is required"));
602
273
  }
603
274
  if guarded.length() != n {
604
- return Err(BuildError::Arg(
605
- "guard flags must have one entry per clause".into(),
606
- ));
275
+ return Err(argument_error("guard flags must have one entry per clause"));
607
276
  }
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)?;
277
+ let mut scopes = state::scopes();
278
+ let env = state::scope_env(&mut scopes, scope)?;
610
279
  let mut resolved: Vec<(Vec<crate::core::ast::Pat>, bool)> = Vec::with_capacity(n);
611
280
  let mut names: Vec<Vec<String>> = Vec::with_capacity(n);
612
281
  let mut guard_flags: Vec<bool> = Vec::with_capacity(n);
@@ -722,63 +391,84 @@ fn matcher_of(obj: Value) -> &'static Matcher {
722
391
  }
723
392
 
724
393
  // ---------------------------------------------------------------------------
725
- // Matcher methods (raw extern "C")
394
+ // Matcher methods (`methods!`, returning `Result`s)
726
395
  // ---------------------------------------------------------------------------
727
396
 
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()
397
+ methods!(
398
+ RMatcher,
399
+ rtself,
400
+ // Matcher.new(name, clauses, guarded_flags, witness_limit, scope):
401
+ // compiles the clauses and wraps the matcher in an instance of the
402
+ // receiver (`Matcher` or a subclass such as `Function`), with the
403
+ // exhaustiveness report in `@missing`, `@missing_truncated` and
404
+ // `@redundant`.
405
+ fn matcher_new(
406
+ name: RString,
407
+ clauses: Array,
408
+ guarded: Array,
409
+ limit: Integer,
410
+ scope: Integer,
411
+ ) -> Result<AnyObject, AnyException> {
412
+ let name = name?.to_string();
413
+ let clauses = clauses?;
414
+ let guarded = guarded?;
415
+ let limit = limit.map(|i| i.to_i64()).unwrap_or(20).max(1) as usize;
416
+ let scope = scope?.to_i64();
417
+ if scope < 0 {
418
+ return Err(argument_error("scope must not be negative"));
419
+ }
420
+ let built = build(name, &clauses, &guarded, limit, scope as usize)?;
421
+ let klass = Class::from(rtself.value());
422
+ let mut obj: AnyObject = klass.wrap_data(built.matcher, &*MATCHER_TYPE);
423
+ obj.instance_variable_set("@missing", rstrings(&built.missing));
424
+ obj.instance_variable_set("@missing_truncated", Boolean::new(built.truncated));
425
+ let mut redundant = Array::with_capacity(built.redundant.len());
426
+ for r in built.redundant {
427
+ redundant.push(Integer::new(r as i64));
428
+ }
429
+ obj.instance_variable_set("@redundant", redundant);
430
+ Ok(obj)
431
+ },
432
+ // matcher.arity -> number of arguments
433
+ fn matcher_arity() -> Integer {
434
+ Integer::new(matcher_of(rtself.value()).arity as i64)
435
+ },
436
+ // matcher.name -> the function's name
437
+ fn matcher_name() -> RString {
438
+ RString::new_utf8(&matcher_of(rtself.value()).name)
439
+ },
440
+ // matcher.names -> variables bound by each clause, in order
441
+ fn matcher_names() -> Array {
442
+ let m = matcher_of(rtself.value());
443
+ let mut out = Array::with_capacity(m.names.len());
444
+ for ns in &m.names {
445
+ out.push(rstrings(ns));
446
+ }
447
+ out
448
+ },
449
+ // matcher.guarded -> whether each clause has a guard
450
+ fn matcher_guarded() -> Array {
451
+ let m = matcher_of(rtself.value());
452
+ let mut out = Array::with_capacity(m.guarded.len());
453
+ for g in &m.guarded {
454
+ out.push(Boolean::new(*g));
779
455
  }
456
+ out
457
+ },
458
+ // matcher.slots -> value slots of the decision tree
459
+ fn matcher_slots() -> Integer {
460
+ Integer::new(matcher_of(rtself.value()).n_slots as i64)
461
+ },
462
+ // matcher.tree -> human-readable dump of the decision tree
463
+ fn matcher_tree() -> RString {
464
+ let m = matcher_of(rtself.value());
465
+ RString::new_utf8(&runtime::dump(&m.env, &m.tree, &m.names, 0))
780
466
  }
781
- }
467
+ );
468
+
469
+ // ---------------------------------------------------------------------------
470
+ // Matcher hot-path methods (raw extern "C")
471
+ // ---------------------------------------------------------------------------
782
472
 
783
473
  /// Outcome of the pure-Rust part of a call: the body to invoke and where its
784
474
  /// arguments are.
@@ -979,37 +669,16 @@ unsafe extern "C" fn matcher_prepare(argc: c_int, argv: *const Value, rtself: Va
979
669
  };
980
670
  array::rb_ary_push(out, p.body);
981
671
  // 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);
672
+ let depth = match state::depth_push() {
673
+ Ok(depth) => depth,
674
+ Err(e) => raise(e),
675
+ };
676
+ array::rb_ary_push(out, Integer::new(depth as i64).value());
984
677
  out
985
678
  }
986
679
  }
987
680
  }
988
681
 
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
682
  /// matcher.select(*args) -> [clause_index, [values]] or nil (guards from
1014
683
  /// `@guards`).
1015
684
  unsafe extern "C" fn matcher_select(argc: c_int, argv: *const Value, rtself: Value) -> Value {
@@ -1104,41 +773,6 @@ unsafe extern "C" fn matcher_run(
1104
773
  }
1105
774
  }
1106
775
 
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
776
  unsafe fn def_method(klass: Value, name: &str, f: CallbackPtr, argc: c_int) {
1143
777
  let cname = CString::new(name).unwrap();
1144
778
  rclass::rb_define_method(klass, cname.as_ptr(), f, argc);
@@ -1148,48 +782,28 @@ pub fn init() {
1148
782
  // Nothing here keeps per-Ractor state: the type registry is behind a
1149
783
  // mutex and matchers are immutable once built.
1150
784
  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
- });
785
+ let mut native = native::functions::define_ruby();
786
+ HmError::define_ruby();
1174
787
  let object = Class::from_existing("Object");
1175
788
  let mut matcher = native.define_nested_class("Matcher", Some(&object));
1176
789
  // Instances are only ever created through `new` with wrapped data.
1177
790
  matcher.undef_alloc_func();
791
+ matcher.define(|k| {
792
+ k.def_self("new", matcher_new);
793
+ k.def("arity", matcher_arity);
794
+ k.def("name", matcher_name);
795
+ k.def("names", matcher_names);
796
+ k.def("guarded", matcher_guarded);
797
+ k.def("slots", matcher_slots);
798
+ k.def("tree", matcher_tree);
799
+ });
1178
800
  unsafe {
1179
801
  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
802
  def_method(k, "call", matcher_call as CallbackPtr, -1);
1183
803
  def_method(k, "prepare", matcher_prepare as CallbackPtr, -1);
1184
804
  def_method(k, "select", matcher_select as CallbackPtr, -1);
1185
805
  def_method(k, "select_with", matcher_select_with as CallbackPtr, 2);
1186
806
  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
807
  }
1194
808
  ids();
1195
809
  }