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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -1
- data/README.md +7 -4
- data/ext/haskell_match/Cargo.lock +49 -3
- data/ext/haskell_match/Cargo.toml +3 -3
- data/ext/haskell_match/src/lib.rs +0 -2
- data/ext/haskell_match/src/ruby/error.rs +123 -0
- data/ext/haskell_match/src/ruby/mod.rs +165 -551
- data/ext/haskell_match/src/ruby/native.rs +227 -0
- data/ext/haskell_match/src/ruby/state.rs +91 -0
- data/lib/haskell_match/version.rb +1 -1
- metadata +18 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4910f364d7296c14a45bb08c20b0129b74616d690d56f6cd19c598602946f535
|
|
4
|
+
data.tar.gz: 7a9f2c74794f08f3b2e2436e971ee5dd564819006220942e8adf8f302e26d48d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c55aebeb2dd4a51181112d05a958e41c6e76a7318289557979946cb23a9e93131d780cb463e09249e291a58b95c83a790b175e187fa8a6e443ac84a0aa23d25e
|
|
7
|
+
data.tar.gz: 8eb6749c25422f70c7e8178061f22ad55a8f83aa53fdc5453389671ad90e0750012ba733dc084dc807fbe3f520443419c6d6820f2f1a7a6872429eb7bc7ba422
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ All notable changes to haskell_match are recorded here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.0] - 2026-10-20
|
|
10
|
+
|
|
11
|
+
Installing still builds the Rust extension from source, so a Rust toolchain
|
|
12
|
+
(`cargo`) is required; `fiddle`, which loads the built library, is now a
|
|
13
|
+
declared dependency ahead of Ruby 3.5 dropping it from the default gems.
|
|
14
|
+
|
|
9
15
|
### Added
|
|
10
16
|
|
|
11
17
|
- **Haskell tier 1.** User-defined operators with `infixl`/`infixr`/`infix`
|
|
@@ -77,14 +83,32 @@ All notable changes to haskell_match are recorded here. The format follows
|
|
|
77
83
|
show the offending source line with a caret.
|
|
78
84
|
- `Native.parse_data` returns field types and the `deriving` list;
|
|
79
85
|
`Constructor.field_types`.
|
|
86
|
+
- `HaskellMatch::Native.rbs`: the RBS declaration of the module functions,
|
|
87
|
+
generated by Rutie.
|
|
80
88
|
|
|
81
89
|
### Changed
|
|
82
90
|
|
|
83
91
|
- `Native::Matcher.new` takes a fifth argument, the type scope;
|
|
84
92
|
`Native.register_type`, `render_pattern`, `constructors` and
|
|
85
93
|
`constructor_info` take an optional scope.
|
|
94
|
+
- **Rutie 0.13.3 with the `derive` feature.** The native layer now uses
|
|
95
|
+
Rutie's attributes: the `HaskellMatch::Native` module functions are
|
|
96
|
+
declared with `#[rubymodule]` on plain Rust functions that take Rust types
|
|
97
|
+
and return `Result`s, and the `HaskellMatch` exceptions the extension
|
|
98
|
+
raises are declared with `#[rubyerror]`, so an error is raised after the
|
|
99
|
+
method's Rust values have been dropped instead of `longjmp`ing past them.
|
|
100
|
+
`Native::Matcher`'s construction and accessor methods return `Result`s
|
|
101
|
+
through `methods!` the same way; its hot-path methods (`call`, `prepare`,
|
|
102
|
+
`select`, `select_with`, `run`) and its typed-data definition, which sets
|
|
103
|
+
`RUBY_TYPED_FROZEN_SHAREABLE` for `Ractor.make_shareable`, are unchanged.
|
|
104
|
+
The crate no longer depends on `lazy_static`.
|
|
105
|
+
- At the internal `Native` boundary an argument of the wrong type now raises
|
|
106
|
+
`TypeError` (Ruby's convention, and what the attributes generate) rather
|
|
107
|
+
than `ArgumentError`, and an Integer out of range raises `RangeError`;
|
|
108
|
+
well-typed arguments with bad values still raise `ArgumentError`, as does
|
|
109
|
+
`HaskellMatch.max_depth = -1`. The public API is unaffected.
|
|
86
110
|
|
|
87
|
-
## [0.1.0] -
|
|
111
|
+
## [0.1.0] - 2026-10-07
|
|
88
112
|
|
|
89
113
|
### Added
|
|
90
114
|
|
data/README.md
CHANGED
|
@@ -248,7 +248,7 @@ end # => "just 3"
|
|
|
248
248
|
|
|
249
249
|
# a pattern on its own, usable in case/when
|
|
250
250
|
head = HaskellMatch.pattern { Just([x, *_]) }
|
|
251
|
-
head.match(Just[[9, 8]]) # => {:
|
|
251
|
+
head.match(Just[[9, 8]]) # => {x: 9}
|
|
252
252
|
head === Just[[]] # => false
|
|
253
253
|
|
|
254
254
|
# methods defined by clauses, with access to self
|
|
@@ -282,7 +282,7 @@ end
|
|
|
282
282
|
f.([1, 2, 3]) # => [2, 3]
|
|
283
283
|
|
|
284
284
|
g = HaskellMatch.fn(:g) { on([x, *xs]) { |x:, xs:| { x: x, xs: xs } }; on([]) { {} } }
|
|
285
|
-
g.([1, 2]) # => {:
|
|
285
|
+
g.([1, 2]) # => {x: 1, xs: [2]}
|
|
286
286
|
```
|
|
287
287
|
|
|
288
288
|
## Recursion without limits, and laziness
|
|
@@ -1458,8 +1458,11 @@ Array per `(x:xs)` tail that is bound.
|
|
|
1458
1458
|
of {0, 1}`).
|
|
1459
1459
|
* `core::tree` — compilation to a decision tree over numbered value slots.
|
|
1460
1460
|
* `ruby::runtime` — evaluation over Ruby values, including Strings and lazy
|
|
1461
|
-
lists viewed as lists; `ruby` — the `Native` module,
|
|
1462
|
-
|
|
1461
|
+
lists viewed as lists; `ruby::native` — the `Native` module functions,
|
|
1462
|
+
declared with Rutie's `#[rubymodule]`; `ruby::error` — the exceptions the
|
|
1463
|
+
extension raises, declared with `#[rubyerror]`; `ruby` — the `Matcher`
|
|
1464
|
+
class, the trampoline that handles `tail`/`defer` markers and the
|
|
1465
|
+
Fiber-segmented recursion.
|
|
1463
1466
|
* `lib/haskell_match/pattern_ast.rb` — the in-place pattern syntax, rendered
|
|
1464
1467
|
to Haskell text; `lazy_list.rb` — memoised lazy lists.
|
|
1465
1468
|
|
|
@@ -6,7 +6,6 @@ version = 4
|
|
|
6
6
|
name = "haskell_match"
|
|
7
7
|
version = "0.1.0"
|
|
8
8
|
dependencies = [
|
|
9
|
-
"lazy_static",
|
|
10
9
|
"rutie",
|
|
11
10
|
]
|
|
12
11
|
|
|
@@ -22,12 +21,59 @@ version = "0.2.190"
|
|
|
22
21
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
23
22
|
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
|
24
23
|
|
|
24
|
+
[[package]]
|
|
25
|
+
name = "proc-macro2"
|
|
26
|
+
version = "1.0.107"
|
|
27
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
28
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
29
|
+
dependencies = [
|
|
30
|
+
"unicode-ident",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[[package]]
|
|
34
|
+
name = "quote"
|
|
35
|
+
version = "1.0.47"
|
|
36
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
37
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
38
|
+
dependencies = [
|
|
39
|
+
"proc-macro2",
|
|
40
|
+
]
|
|
41
|
+
|
|
25
42
|
[[package]]
|
|
26
43
|
name = "rutie"
|
|
27
|
-
version = "0.13.
|
|
44
|
+
version = "0.13.3"
|
|
28
45
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
29
|
-
checksum = "
|
|
46
|
+
checksum = "6517f7cec549a68e81525252a4088420659c7e46109a5f1f9d3204b656e3ee65"
|
|
30
47
|
dependencies = [
|
|
31
48
|
"lazy_static",
|
|
32
49
|
"libc",
|
|
50
|
+
"rutie-derive",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[[package]]
|
|
54
|
+
name = "rutie-derive"
|
|
55
|
+
version = "0.13.3"
|
|
56
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
57
|
+
checksum = "a2639fa56c00a98f1f4010bd779a12ee4c3c8348ce6ee2ab705c76009bb0f724"
|
|
58
|
+
dependencies = [
|
|
59
|
+
"proc-macro2",
|
|
60
|
+
"quote",
|
|
61
|
+
"syn",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[[package]]
|
|
65
|
+
name = "syn"
|
|
66
|
+
version = "2.0.119"
|
|
67
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
68
|
+
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
|
69
|
+
dependencies = [
|
|
70
|
+
"proc-macro2",
|
|
71
|
+
"quote",
|
|
72
|
+
"unicode-ident",
|
|
33
73
|
]
|
|
74
|
+
|
|
75
|
+
[[package]]
|
|
76
|
+
name = "unicode-ident"
|
|
77
|
+
version = "1.0.27"
|
|
78
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
79
|
+
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
|
@@ -11,9 +11,9 @@ name = "haskell_match"
|
|
|
11
11
|
crate-type = ["cdylib"]
|
|
12
12
|
|
|
13
13
|
[dependencies]
|
|
14
|
-
# Rutie 0.13 targets Ruby 3.x; Rutie 0.14 targets Ruby 4.0.
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
# Rutie 0.13 targets Ruby 3.x; Rutie 0.14 targets Ruby 4.0. The `derive`
|
|
15
|
+
# feature gives the `#[rubymodule]`, `#[rubyerror]` and related attributes.
|
|
16
|
+
rutie = { version = "0.13.3", features = ["derive"] }
|
|
17
17
|
|
|
18
18
|
[profile.release]
|
|
19
19
|
opt-level = 3
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//! The exceptions the native layer raises, declared with `#[rubyerror]`.
|
|
2
|
+
//!
|
|
3
|
+
//! The Ruby classes themselves, with their `CompileError`/`MatchError` tiers
|
|
4
|
+
//! and `PatternSyntaxError.with_caret`, are defined in
|
|
5
|
+
//! `lib/haskell_match/errors.rb` before the extension loads; `#[rubyerror]`
|
|
6
|
+
//! finds each class by its path. A method that returns `Err(HmError)` raises
|
|
7
|
+
//! the matching exception once its Rust values have been dropped, instead of
|
|
8
|
+
//! `longjmp`ing past them with `VM::raise_ex`.
|
|
9
|
+
|
|
10
|
+
use std::fmt;
|
|
11
|
+
|
|
12
|
+
use rutie::derive::IntoException;
|
|
13
|
+
use rutie::{rubyerror, AnyException, Exception};
|
|
14
|
+
|
|
15
|
+
use crate::core::error::{CoreError, ErrorKind};
|
|
16
|
+
|
|
17
|
+
/// Every `HaskellMatch` exception the extension raises, one variant per
|
|
18
|
+
/// Ruby class, each carrying its message.
|
|
19
|
+
#[rubyerror(path = "HaskellMatch::Error", superclass = "StandardError")]
|
|
20
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
21
|
+
pub enum HmError {
|
|
22
|
+
/// Malformed pattern text.
|
|
23
|
+
#[ruby(path = "HaskellMatch::PatternSyntaxError")]
|
|
24
|
+
Syntax(String),
|
|
25
|
+
/// A constructor name that no registered data type declares.
|
|
26
|
+
#[ruby(path = "HaskellMatch::UnknownConstructorError")]
|
|
27
|
+
UnknownConstructor(String),
|
|
28
|
+
/// A constructor applied to the wrong number of arguments.
|
|
29
|
+
#[ruby(path = "HaskellMatch::ArityError")]
|
|
30
|
+
Arity(String),
|
|
31
|
+
/// Patterns of different types in the same position.
|
|
32
|
+
#[ruby(path = "HaskellMatch::PatternTypeError")]
|
|
33
|
+
Type(String),
|
|
34
|
+
/// The same variable bound twice in one clause.
|
|
35
|
+
#[ruby(path = "HaskellMatch::DuplicateVariableError")]
|
|
36
|
+
DuplicateVariable(String),
|
|
37
|
+
/// Record pattern problems (unknown field, positional constructor, ...).
|
|
38
|
+
#[ruby(path = "HaskellMatch::FieldError")]
|
|
39
|
+
Field(String),
|
|
40
|
+
/// Malformed `data` declaration.
|
|
41
|
+
#[ruby(path = "HaskellMatch::DataDeclarationError")]
|
|
42
|
+
DataDeclaration(String),
|
|
43
|
+
/// Clauses of one function with different numbers of arguments.
|
|
44
|
+
#[ruby(path = "HaskellMatch::ClauseArityError")]
|
|
45
|
+
ClauseArity(String),
|
|
46
|
+
/// No clause matched at call time.
|
|
47
|
+
#[ruby(path = "HaskellMatch::MatchError")]
|
|
48
|
+
NoMatch(String),
|
|
49
|
+
/// A value is not of the type the patterns expect.
|
|
50
|
+
#[ruby(path = "HaskellMatch::TypeMismatchError")]
|
|
51
|
+
TypeMismatch(String),
|
|
52
|
+
/// An irrefutable (`~`) pattern failed to destructure.
|
|
53
|
+
#[ruby(path = "HaskellMatch::IrrefutablePatternError")]
|
|
54
|
+
IrrefutablePattern(String),
|
|
55
|
+
/// Recursion through compiled functions exceeded `HaskellMatch.max_depth`.
|
|
56
|
+
#[ruby(path = "HaskellMatch::StackOverflowError")]
|
|
57
|
+
StackOverflow(String),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl HmError {
|
|
61
|
+
/// The exception's message.
|
|
62
|
+
pub fn message(&self) -> &str {
|
|
63
|
+
match self {
|
|
64
|
+
HmError::Syntax(m)
|
|
65
|
+
| HmError::UnknownConstructor(m)
|
|
66
|
+
| HmError::Arity(m)
|
|
67
|
+
| HmError::Type(m)
|
|
68
|
+
| HmError::DuplicateVariable(m)
|
|
69
|
+
| HmError::Field(m)
|
|
70
|
+
| HmError::DataDeclaration(m)
|
|
71
|
+
| HmError::ClauseArity(m)
|
|
72
|
+
| HmError::NoMatch(m)
|
|
73
|
+
| HmError::TypeMismatch(m)
|
|
74
|
+
| HmError::IrrefutablePattern(m)
|
|
75
|
+
| HmError::StackOverflow(m) => m,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl fmt::Display for HmError {
|
|
81
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
82
|
+
f.write_str(self.message())
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
impl std::error::Error for HmError {}
|
|
87
|
+
|
|
88
|
+
/// A compile-time error of the pure core, by kind.
|
|
89
|
+
impl From<CoreError> for HmError {
|
|
90
|
+
fn from(e: CoreError) -> Self {
|
|
91
|
+
let m = e.message;
|
|
92
|
+
match e.kind {
|
|
93
|
+
ErrorKind::Syntax => HmError::Syntax(m),
|
|
94
|
+
ErrorKind::UnknownConstructor => HmError::UnknownConstructor(m),
|
|
95
|
+
ErrorKind::Arity => HmError::Arity(m),
|
|
96
|
+
ErrorKind::Type => HmError::Type(m),
|
|
97
|
+
ErrorKind::DuplicateVariable => HmError::DuplicateVariable(m),
|
|
98
|
+
ErrorKind::Field => HmError::Field(m),
|
|
99
|
+
ErrorKind::DataDeclaration => HmError::DataDeclaration(m),
|
|
100
|
+
ErrorKind::ClauseArity => HmError::ClauseArity(m),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// So `?` can mix `HmError`s and `ArgumentError`s in a method returning
|
|
106
|
+
/// `Result<T, AnyException>`.
|
|
107
|
+
impl From<HmError> for AnyException {
|
|
108
|
+
fn from(e: HmError) -> Self {
|
|
109
|
+
e.into_exception()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
impl From<CoreError> for AnyException {
|
|
114
|
+
fn from(e: CoreError) -> Self {
|
|
115
|
+
HmError::from(e).into_exception()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// An `ArgumentError`: a well-typed argument with a value that makes no
|
|
120
|
+
/// sense (a wrong type is the `TypeError` the argument conversion raises).
|
|
121
|
+
pub fn argument_error(message: &str) -> AnyException {
|
|
122
|
+
AnyException::new("ArgumentError", Some(message))
|
|
123
|
+
}
|