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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb352d5517fab56493d4c3ad9e8bae04d579659d2e7e75d501738ff4ff9ca4fc
4
- data.tar.gz: 30f8f63b4c194ffffb8fd972a47e21cbd9a8c61755add53ff2ea700c1ee0ef24
3
+ metadata.gz: 4910f364d7296c14a45bb08c20b0129b74616d690d56f6cd19c598602946f535
4
+ data.tar.gz: 7a9f2c74794f08f3b2e2436e971ee5dd564819006220942e8adf8f302e26d48d
5
5
  SHA512:
6
- metadata.gz: 343fe3a4d9d4c67db7bd1d406055fd828db5a569bf2d3da71c2e929cd6e937815bb161476b43dda9cd52de69ee1ce8f870b891ff02d4721e5b2f21294caddf9a
7
- data.tar.gz: '09ae56f141727dc5ad9bdb4d1b77cd0c86abbaa3ad7fc1cb6f0571dc7547447b0c47f95c92bf087f5d1da23cb9fd62b9dcda283c946c215c9c806e2eab416bd3'
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] - unreleased
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]]) # => {:x=>9}
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]) # => {:x=>1, :xs=>[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, the trampoline that
1462
- handles `tail`/`defer` markers and the Fiber-segmented recursion.
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.1"
44
+ version = "0.13.3"
28
45
  source = "registry+https://github.com/rust-lang/crates.io-index"
29
- checksum = "30d1529470ad0b69b81a6d0224632d00c01b46ef671452e24407512165b7ed84"
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
- rutie = "0.13"
16
- lazy_static = "1.4"
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
@@ -6,8 +6,6 @@
6
6
 
7
7
  #[macro_use]
8
8
  extern crate rutie;
9
- #[macro_use]
10
- extern crate lazy_static;
11
9
 
12
10
  pub mod core;
13
11
  pub mod ruby;
@@ -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
+ }