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.
@@ -0,0 +1,227 @@
1
+ //! The functions of the `HaskellMatch::Native` module, declared with
2
+ //! `#[rubymodule]`: parsing `data` declarations and Haskell source, the type
3
+ //! scopes, and the recursion settings.
4
+ //!
5
+ //! Each function takes Rust types (a wrong argument raises `TypeError`, an
6
+ //! integer out of range `RangeError`, a well-typed argument with a bad value
7
+ //! `ArgumentError`) and returns a `Result`, whose `Err` is raised once the
8
+ //! function's values have been dropped. `HaskellMatch::Native.rbs` is the
9
+ //! module's RBS declaration.
10
+
11
+ use rutie::derive::FromArgument;
12
+ use rutie::{AnyException, Array, Object};
13
+
14
+ use super::error::argument_error;
15
+ use crate::core::types::ConSpec;
16
+
17
+ /// One constructor of a parsed `data` declaration: name, arity, record
18
+ /// field names (or `nil`) and field types.
19
+ pub type ParsedCon = (String, usize, Option<Vec<String>>, Vec<String>);
20
+
21
+ /// A parsed `data` declaration: type name, type variables, constructors and
22
+ /// the classes it derives.
23
+ pub type ParsedData = (String, Vec<String>, Vec<ParsedCon>, Vec<String>);
24
+
25
+ /// What `constructor_info_in` reports: type name, arity and record field
26
+ /// names (or `nil`).
27
+ pub type ConInfo = (String, usize, Option<Vec<String>>);
28
+
29
+ /// A `[name, arity, fields_or_nil, class]` row of `register_type_in`.
30
+ fn constructor_spec(row: &Array) -> Result<ConSpec, AnyException> {
31
+ if row.length() != 4 {
32
+ return Err(argument_error(
33
+ "constructor spec must be [name, arity, fields, class]",
34
+ ));
35
+ }
36
+ let name = String::hold(row.at(0))?;
37
+ let arity = usize::hold(row.at(1))?;
38
+ let fields = <Option<Vec<String>>>::hold(row.at(2))?;
39
+ let klass = row.at(3);
40
+ if klass.is_nil() {
41
+ return Err(argument_error("constructor class must not be nil"));
42
+ }
43
+ Ok(ConSpec {
44
+ name,
45
+ arity,
46
+ fields,
47
+ handle: klass.value().value,
48
+ })
49
+ }
50
+
51
+ #[rubymodule(path = "HaskellMatch::Native")]
52
+ pub mod functions {
53
+ use rutie::rubysys::gc::rb_gc_register_mark_object;
54
+ use rutie::types::Value;
55
+ use rutie::{AnyException, Array};
56
+
57
+ use super::{constructor_spec, ConInfo, ParsedCon, ParsedData};
58
+ use crate::core::parser::parse_pattern;
59
+ use crate::core::pretty::render_pat;
60
+ use crate::core::resolve::resolve_clause;
61
+ use crate::ruby::error::{argument_error, HmError};
62
+ use crate::ruby::state;
63
+
64
+ /// `Native.parse_data(decl)` ->
65
+ /// `[name, tyvars, [[con, arity, fields_or_nil, types], ...], deriving]`
66
+ pub fn parse_data(decl: &str) -> Result<ParsedData, HmError> {
67
+ let d = crate::core::parser::parse_data(decl)?;
68
+ let cons: Vec<ParsedCon> = d
69
+ .cons
70
+ .into_iter()
71
+ .map(|c| (c.name, c.arity, c.fields, c.types))
72
+ .collect();
73
+ Ok((d.name, d.tyvars, cons, d.deriving))
74
+ }
75
+
76
+ /// `Native.new_scope` -> a type scope seeded from the global registry.
77
+ pub fn new_scope() -> usize {
78
+ let mut scopes = state::scopes();
79
+ let seed = scopes[0].clone();
80
+ scopes.push(seed);
81
+ scopes.len() - 1
82
+ }
83
+
84
+ /// `Native.import_scope_in(dst, src, names_or_nil)` -> the type names
85
+ /// imported: every user type of `src` (or those named) that `dst` does
86
+ /// not already declare identically.
87
+ pub fn import_scope_in(
88
+ dst: usize,
89
+ src: usize,
90
+ names: Option<Vec<String>>,
91
+ ) -> Result<Vec<String>, AnyException> {
92
+ let mut scopes = state::scopes();
93
+ if src >= scopes.len() || dst >= scopes.len() {
94
+ return Err(argument_error("unknown type scope"));
95
+ }
96
+ let types = scopes[src].user_types();
97
+ let env = &mut scopes[dst];
98
+ let mut imported = Vec::new();
99
+ for (name, cons) in types {
100
+ if let Some(wanted) = &names {
101
+ if !wanted.contains(&name) {
102
+ continue;
103
+ }
104
+ }
105
+ if !env.has_same_type(&name, &cons) {
106
+ env.register(&name, &cons)?;
107
+ }
108
+ imported.push(name);
109
+ }
110
+ Ok(imported)
111
+ }
112
+
113
+ /// `Native.register_type_in(name, [[con, arity, fields_or_nil, class], ...], scope)`
114
+ pub fn register_type_in(
115
+ name: &str,
116
+ specs: Vec<Array>,
117
+ scope: usize,
118
+ ) -> Result<(), AnyException> {
119
+ let mut cons = Vec::with_capacity(specs.len());
120
+ for row in &specs {
121
+ cons.push(constructor_spec(row)?);
122
+ }
123
+ let mut scopes = state::scopes();
124
+ let env = state::scope_env(&mut scopes, scope)?;
125
+ env.register(name, &cons)?;
126
+ for c in &cons {
127
+ // Keep constructor classes alive for as long as the process
128
+ // runs: compiled matchers refer to them by VALUE.
129
+ unsafe { rb_gc_register_mark_object(Value::from(c.handle)) };
130
+ }
131
+ Ok(())
132
+ }
133
+
134
+ /// `Native.render_pattern_in(src, scope)` -> the pattern's canonical
135
+ /// Haskell rendering (checking it along the way).
136
+ pub fn render_pattern_in(src: &str, scope: usize) -> Result<String, AnyException> {
137
+ let raw = parse_pattern(src)?;
138
+ let mut scopes = state::scopes();
139
+ let env = state::scope_env(&mut scopes, scope)?;
140
+ let (pats, bindings) = resolve_clause(env, &[raw])?;
141
+ Ok(render_pat(env, &pats[0], &bindings.names))
142
+ }
143
+
144
+ /// `Native.constructors_in(type_name, scope)` -> the constructor names,
145
+ /// or `nil` for an unknown type.
146
+ pub fn constructors_in(name: &str, scope: usize) -> Result<Option<Vec<String>>, AnyException> {
147
+ let mut scopes = state::scopes();
148
+ let env = state::scope_env(&mut scopes, scope)?;
149
+ Ok(env.lookup_type(name).map(|t| {
150
+ env.ty(t)
151
+ .cons
152
+ .iter()
153
+ .map(|c| env.con(*c).name.clone())
154
+ .collect()
155
+ }))
156
+ }
157
+
158
+ /// `Native.constructor_info_in(name, scope)` ->
159
+ /// `[type_name, arity, fields_or_nil]`, or `nil` for an unknown
160
+ /// constructor.
161
+ pub fn constructor_info_in(name: &str, scope: usize) -> Result<Option<ConInfo>, AnyException> {
162
+ let mut scopes = state::scopes();
163
+ let env = state::scope_env(&mut scopes, scope)?;
164
+ Ok(env.lookup_con(name).map(|c| {
165
+ let con = env.con(c);
166
+ (env.ty(con.ty).name.clone(), con.arity, con.fields.clone())
167
+ }))
168
+ }
169
+
170
+ /// `Native.parse_haskell(source)` -> the module's AST as JSON.
171
+ pub fn parse_haskell(source: &str) -> Result<String, HmError> {
172
+ let module = crate::core::hs::parse_module(source)?;
173
+ Ok(crate::core::hs::json::module(&module))
174
+ }
175
+
176
+ /// `Native.stack_segment` -> levels between Fiber segments (0: disabled).
177
+ pub fn stack_segment() -> usize {
178
+ state::stack_segment()
179
+ }
180
+
181
+ /// `Native.stack_segment = levels`
182
+ #[ruby(name = "stack_segment=")]
183
+ pub fn set_stack_segment(levels: i64) -> Result<i64, AnyException> {
184
+ if levels < 0 {
185
+ return Err(argument_error(
186
+ "stack_segment must be a non-negative Integer (0 disables segmentation)",
187
+ ));
188
+ }
189
+ state::set_stack_segment(levels as usize);
190
+ Ok(levels)
191
+ }
192
+
193
+ /// `Native.max_depth` -> deepest allowed nesting (0: unlimited).
194
+ pub fn max_depth() -> usize {
195
+ state::max_depth()
196
+ }
197
+
198
+ /// `Native.max_depth = levels`
199
+ #[ruby(name = "max_depth=")]
200
+ pub fn set_max_depth(levels: i64) -> Result<i64, AnyException> {
201
+ if levels < 0 {
202
+ return Err(argument_error(
203
+ "max_depth must be a non-negative Integer (0 means unlimited)",
204
+ ));
205
+ }
206
+ state::set_max_depth(levels as usize);
207
+ Ok(levels)
208
+ }
209
+
210
+ /// `Native.depth_push` -> the new nesting depth (raises
211
+ /// `StackOverflowError` past `max_depth`); `Native.depth_pop` undoes it.
212
+ /// Used by deep-mode functions, whose body invocation happens in Ruby.
213
+ pub fn depth_push() -> Result<usize, HmError> {
214
+ state::depth_push()
215
+ }
216
+
217
+ /// `Native.depth_pop`
218
+ pub fn depth_pop() {
219
+ state::depth_pop();
220
+ }
221
+
222
+ /// `Native.rbs` -> the module's RBS declaration.
223
+ #[ruby(name = "rbs")]
224
+ pub fn signature() -> String {
225
+ rbs()
226
+ }
227
+ }
@@ -0,0 +1,91 @@
1
+ //! Process-wide state shared by the module functions and the matcher: the
2
+ //! type scopes, the recursion settings and the per-thread nesting depth.
3
+
4
+ use std::cell::Cell;
5
+ use std::sync::atomic::{AtomicUsize, Ordering};
6
+ use std::sync::{LazyLock, Mutex, MutexGuard};
7
+
8
+ use rutie::AnyException;
9
+
10
+ use super::error::{argument_error, HmError};
11
+ use crate::core::types::TypeEnv;
12
+
13
+ /// Type scopes: index 0 is the global registry; every other scope is a
14
+ /// snapshot of the global one at its creation plus its own declarations
15
+ /// (a compiled Haskell module gets one, like a Haskell module namespace).
16
+ static SCOPES: LazyLock<Mutex<Vec<TypeEnv>>> = LazyLock::new(|| Mutex::new(vec![TypeEnv::new()]));
17
+
18
+ /// The type scopes, locked.
19
+ pub fn scopes() -> MutexGuard<'static, Vec<TypeEnv>> {
20
+ SCOPES.lock().unwrap_or_else(|p| p.into_inner())
21
+ }
22
+
23
+ /// The type environment of scope `id`.
24
+ pub fn scope_env(scopes: &mut [TypeEnv], id: usize) -> Result<&mut TypeEnv, AnyException> {
25
+ scopes
26
+ .get_mut(id)
27
+ .ok_or_else(|| argument_error(&format!("unknown type scope {}", id)))
28
+ }
29
+
30
+ thread_local! {
31
+ /// Nesting depth of native calls on this thread.
32
+ static DEPTH: Cell<usize> = const { Cell::new(0) };
33
+ }
34
+
35
+ /// Every this many nested native calls, the next clause body runs in a fresh
36
+ /// Fiber. Each Fiber brings its own VM stack and machine stack, so chained
37
+ /// segments make the recursion depth a matter of memory rather than of
38
+ /// Ruby's fixed stack size. 0 disables segmentation.
39
+ static STACK_SEGMENT: AtomicUsize = AtomicUsize::new(DEFAULT_STACK_SEGMENT);
40
+ const DEFAULT_STACK_SEGMENT: usize = 100;
41
+
42
+ /// Maximum nesting depth of native calls before `StackOverflowError` is
43
+ /// raised, so a runaway recursion fails instead of consuming all memory
44
+ /// (GHC has the same kind of limit). 0 means unlimited.
45
+ static MAX_DEPTH: AtomicUsize = AtomicUsize::new(DEFAULT_MAX_DEPTH);
46
+ const DEFAULT_MAX_DEPTH: usize = 250_000;
47
+
48
+ pub fn stack_segment() -> usize {
49
+ STACK_SEGMENT.load(Ordering::Relaxed)
50
+ }
51
+
52
+ pub fn set_stack_segment(levels: usize) {
53
+ STACK_SEGMENT.store(levels, Ordering::Relaxed);
54
+ }
55
+
56
+ pub fn max_depth() -> usize {
57
+ MAX_DEPTH.load(Ordering::Relaxed)
58
+ }
59
+
60
+ pub fn set_max_depth(levels: usize) {
61
+ MAX_DEPTH.store(levels, Ordering::Relaxed);
62
+ }
63
+
64
+ /// The nesting depth of native calls on this thread.
65
+ pub fn depth() -> usize {
66
+ DEPTH.with(|d| d.get())
67
+ }
68
+
69
+ pub fn set_depth(depth: usize) {
70
+ DEPTH.with(|d| d.set(depth));
71
+ }
72
+
73
+ /// Count one more nesting level and return the new depth, or the
74
+ /// `StackOverflowError` for a recursion past `max_depth`.
75
+ pub fn depth_push() -> Result<usize, HmError> {
76
+ let depth = depth();
77
+ let max = max_depth();
78
+ if max > 0 && depth >= max {
79
+ return Err(HmError::StackOverflow(format!(
80
+ "recursion deeper than HaskellMatch.max_depth ({}); use tail calls, defer, or raise the limit",
81
+ max
82
+ )));
83
+ }
84
+ set_depth(depth + 1);
85
+ Ok(depth + 1)
86
+ }
87
+
88
+ /// Undo one `depth_push`.
89
+ pub fn depth_pop() {
90
+ DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
91
+ }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HaskellMatch
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haskell_match
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel P. Clark
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: fiddle
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.1'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: minitest
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -74,8 +88,11 @@ files:
74
88
  - ext/haskell_match/src/core/typecheck.rs
75
89
  - ext/haskell_match/src/core/types.rs
76
90
  - ext/haskell_match/src/lib.rs
91
+ - ext/haskell_match/src/ruby/error.rs
77
92
  - ext/haskell_match/src/ruby/mod.rs
93
+ - ext/haskell_match/src/ruby/native.rs
78
94
  - ext/haskell_match/src/ruby/runtime.rs
95
+ - ext/haskell_match/src/ruby/state.rs
79
96
  - lib/haskell_match.rb
80
97
  - lib/haskell_match/binding_plan.rb
81
98
  - lib/haskell_match/case_of.rb