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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE-APACHE +202 -0
  4. data/LICENSE-MIT +21 -0
  5. data/README.md +1484 -0
  6. data/ext/haskell_match/Cargo.lock +33 -0
  7. data/ext/haskell_match/Cargo.toml +22 -0
  8. data/ext/haskell_match/extconf.rb +41 -0
  9. data/ext/haskell_match/src/core/ast.rs +190 -0
  10. data/ext/haskell_match/src/core/error.rs +52 -0
  11. data/ext/haskell_match/src/core/exhaust.rs +699 -0
  12. data/ext/haskell_match/src/core/hs/ast.rs +256 -0
  13. data/ext/haskell_match/src/core/hs/json.rs +225 -0
  14. data/ext/haskell_match/src/core/hs/layout.rs +346 -0
  15. data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
  16. data/ext/haskell_match/src/core/hs/mod.rs +14 -0
  17. data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
  18. data/ext/haskell_match/src/core/lexer.rs +590 -0
  19. data/ext/haskell_match/src/core/mod.rs +19 -0
  20. data/ext/haskell_match/src/core/parser.rs +1116 -0
  21. data/ext/haskell_match/src/core/pretty.rs +373 -0
  22. data/ext/haskell_match/src/core/resolve.rs +336 -0
  23. data/ext/haskell_match/src/core/tree.rs +921 -0
  24. data/ext/haskell_match/src/core/typecheck.rs +226 -0
  25. data/ext/haskell_match/src/core/types.rs +404 -0
  26. data/ext/haskell_match/src/lib.rs +19 -0
  27. data/ext/haskell_match/src/ruby/mod.rs +1195 -0
  28. data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
  29. data/lib/haskell_match/binding_plan.rb +84 -0
  30. data/lib/haskell_match/case_of.rb +71 -0
  31. data/lib/haskell_match/clauses.rb +354 -0
  32. data/lib/haskell_match/data.rb +417 -0
  33. data/lib/haskell_match/deep_call.rb +98 -0
  34. data/lib/haskell_match/deriving.rb +130 -0
  35. data/lib/haskell_match/dsl.rb +71 -0
  36. data/lib/haskell_match/errors.rb +85 -0
  37. data/lib/haskell_match/field_types.rb +140 -0
  38. data/lib/haskell_match/function.rb +240 -0
  39. data/lib/haskell_match/haskell/compiler.rb +961 -0
  40. data/lib/haskell_match/haskell.rb +326 -0
  41. data/lib/haskell_match/inspect.rb +45 -0
  42. data/lib/haskell_match/lazy_list.rb +210 -0
  43. data/lib/haskell_match/native_loader.rb +64 -0
  44. data/lib/haskell_match/pattern.rb +75 -0
  45. data/lib/haskell_match/pattern_ast.rb +394 -0
  46. data/lib/haskell_match/prelude.rb +448 -0
  47. data/lib/haskell_match/scope.rb +44 -0
  48. data/lib/haskell_match/version.rb +5 -0
  49. data/lib/haskell_match.rb +41 -0
  50. metadata +124 -0
@@ -0,0 +1,226 @@
1
+ //! Structural type checking of a clause matrix.
2
+ //!
3
+ //! Ruby values are untyped, so we infer a "shape type" for every position of
4
+ //! the patterns and require all clauses to agree: a position matched against
5
+ //! `Just _` in one clause cannot be matched against `(x:xs)` or `0` in
6
+ //! another. This mirrors the Haskell type checker's rejection of such a
7
+ //! function and is what makes the exhaustiveness check meaningful.
8
+
9
+ use std::collections::HashMap;
10
+
11
+ use super::ast::{HKey, LitKind, Pat};
12
+ use super::error::{CoreError, ErrorKind, Result};
13
+ use super::types::{ConId, TypeEnv, TypeId};
14
+
15
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
16
+ pub enum ColType {
17
+ Con(TypeId),
18
+ Lit(LitKind),
19
+ Hash,
20
+ }
21
+
22
+ fn describe(env: &TypeEnv, t: ColType) -> String {
23
+ match t {
24
+ ColType::Con(ty) => env.ty(ty).name.clone(),
25
+ ColType::Lit(k) => k.name().to_string(),
26
+ ColType::Hash => "Hash".to_string(),
27
+ }
28
+ }
29
+
30
+ /// One step into a sub-pattern: a constructor field or a Hash key.
31
+ #[derive(Clone, Debug, PartialEq, Eq, Hash)]
32
+ enum Step {
33
+ Field(ConId, usize),
34
+ Key(HKey),
35
+ }
36
+
37
+ /// Position of a sub-pattern: the argument index followed by the chain of
38
+ /// steps taken to reach it.
39
+ type Path = (usize, Vec<Step>);
40
+
41
+ fn describe_path(env: &TypeEnv, path: &Path) -> String {
42
+ let mut s = format!("argument {}", path.0 + 1);
43
+ for step in &path.1 {
44
+ match step {
45
+ Step::Field(con, field) => {
46
+ let c = env.con(*con);
47
+ let fname = match &c.fields {
48
+ Some(fs) => format!("field '{}'", fs[*field]),
49
+ None => format!("field {}", field + 1),
50
+ };
51
+ s.push_str(&format!(", {} of '{}'", fname, c.name));
52
+ }
53
+ Step::Key(k) => s.push_str(&format!(", key {} of the Hash", k)),
54
+ }
55
+ }
56
+ s
57
+ }
58
+
59
+ /// Check that every position has a single type across all clauses.
60
+ pub fn check(env: &TypeEnv, clauses: &[Vec<Pat>]) -> Result<()> {
61
+ let mut seen: HashMap<Path, (ColType, usize)> = HashMap::new();
62
+ for (ci, clause) in clauses.iter().enumerate() {
63
+ for (ai, pat) in clause.iter().enumerate() {
64
+ let mut path: Path = (ai, Vec::new());
65
+ walk(env, pat, &mut path, ci, &mut seen)?;
66
+ }
67
+ }
68
+ Ok(())
69
+ }
70
+
71
+ fn walk(
72
+ env: &TypeEnv,
73
+ pat: &Pat,
74
+ path: &mut Path,
75
+ clause: usize,
76
+ seen: &mut HashMap<Path, (ColType, usize)>,
77
+ ) -> Result<()> {
78
+ match pat {
79
+ Pat::Wild | Pat::Var(_) => Ok(()),
80
+ Pat::As(_, p) | Pat::Lazy(p) => walk(env, p, path, clause, seen),
81
+ Pat::Lit(l) => unify(env, path, ColType::Lit(l.kind()), clause, seen),
82
+ Pat::Con(c, args) => {
83
+ unify(env, path, ColType::Con(env.type_of_con(*c)), clause, seen)?;
84
+ for (i, a) in args.iter().enumerate() {
85
+ path.1.push(Step::Field(*c, i));
86
+ let r = walk(env, a, path, clause, seen);
87
+ path.1.pop();
88
+ r?;
89
+ }
90
+ Ok(())
91
+ }
92
+ Pat::Hash(fields) => {
93
+ unify(env, path, ColType::Hash, clause, seen)?;
94
+ for (k, a) in fields {
95
+ path.1.push(Step::Key(k.clone()));
96
+ let r = walk(env, a, path, clause, seen);
97
+ path.1.pop();
98
+ r?;
99
+ }
100
+ Ok(())
101
+ }
102
+ }
103
+ }
104
+
105
+ fn unify(
106
+ env: &TypeEnv,
107
+ path: &Path,
108
+ t: ColType,
109
+ clause: usize,
110
+ seen: &mut HashMap<Path, (ColType, usize)>,
111
+ ) -> Result<()> {
112
+ match seen.get(path) {
113
+ None => {
114
+ seen.insert(path.clone(), (t, clause));
115
+ Ok(())
116
+ }
117
+ Some(&(prev, prev_clause)) if prev == t => {
118
+ let _ = prev_clause;
119
+ Ok(())
120
+ }
121
+ Some(&(prev, prev_clause)) => Err(CoreError::new(
122
+ ErrorKind::Type,
123
+ format!(
124
+ "couldn't match type '{}' (clause {}) with '{}' (clause {}) at {}",
125
+ describe(env, prev),
126
+ prev_clause + 1,
127
+ describe(env, t),
128
+ clause + 1,
129
+ describe_path(env, path)
130
+ ),
131
+ )),
132
+ }
133
+ }
134
+
135
+ #[cfg(test)]
136
+ mod tests {
137
+ use super::*;
138
+ use crate::core::parser::parse_pattern;
139
+ use crate::core::resolve::resolve_clause;
140
+ use crate::core::types::ConSpec;
141
+
142
+ fn env() -> TypeEnv {
143
+ let mut env = TypeEnv::new();
144
+ env.register(
145
+ "Maybe",
146
+ &[
147
+ ConSpec {
148
+ name: "Nothing".into(),
149
+ arity: 0,
150
+ fields: None,
151
+ handle: 0,
152
+ },
153
+ ConSpec {
154
+ name: "Just".into(),
155
+ arity: 1,
156
+ fields: None,
157
+ handle: 0,
158
+ },
159
+ ],
160
+ )
161
+ .unwrap();
162
+ env
163
+ }
164
+
165
+ fn clauses(env: &mut TypeEnv, srcs: &[&[&str]]) -> Vec<Vec<Pat>> {
166
+ srcs.iter()
167
+ .map(|c| {
168
+ let raws: Vec<_> = c.iter().map(|s| parse_pattern(s).unwrap()).collect();
169
+ resolve_clause(env, &raws).unwrap().0
170
+ })
171
+ .collect()
172
+ }
173
+
174
+ #[test]
175
+ fn accepts_consistent_columns() {
176
+ let mut env = env();
177
+ let cs = clauses(
178
+ &mut env,
179
+ &[&["Just 0", "[]"], &["Just n", "(x:xs)"], &["Nothing", "_"]],
180
+ );
181
+ check(&env, &cs).unwrap();
182
+ let cs = clauses(
183
+ &mut env,
184
+ &[&["(Just \"a\", True)"], &["(_, False)"], &["(Nothing, _)"]],
185
+ );
186
+ check(&env, &cs).unwrap();
187
+ let cs = clauses(&mut env, &[&["0"], &["1.5"], &["-3"], &["n"]]);
188
+ check(&env, &cs).unwrap();
189
+ }
190
+
191
+ #[test]
192
+ fn rejects_mixed_columns() {
193
+ let mut env = env();
194
+ let cs = clauses(&mut env, &[&["Just x"], &["(x:xs)"]]);
195
+ let e = check(&env, &cs).unwrap_err();
196
+ assert_eq!(e.kind, ErrorKind::Type);
197
+ assert!(
198
+ e.message
199
+ .contains("'Maybe' (clause 1) with '[]' (clause 2) at argument 1"),
200
+ "{}",
201
+ e.message
202
+ );
203
+
204
+ let cs = clauses(&mut env, &[&["Just 0"], &["Just 's'"]]);
205
+ let e = check(&env, &cs).unwrap_err();
206
+ assert!(e.message.contains("field 1 of 'Just'"), "{}", e.message);
207
+ // Char and String ([Char]) are different types, as in Haskell
208
+ let cs = clauses(&mut env, &[&["'a'"], &["\"a\""]]);
209
+ assert!(check(&env, &cs).is_err());
210
+ // but string literals unify with list patterns
211
+ let cs = clauses(&mut env, &[&["\"\""], &["(c:cs)"]]);
212
+ check(&env, &cs).unwrap();
213
+ let cs = clauses(&mut env, &[&["\"yes\""], &["['n', _]"], &["_"]]);
214
+ check(&env, &cs).unwrap();
215
+
216
+ let cs = clauses(&mut env, &[&["(a, b)"], &["[]"]]);
217
+ assert!(check(&env, &cs).is_err());
218
+ let cs = clauses(&mut env, &[&["(a, b)"], &["(a, b, c)"]]);
219
+ assert!(check(&env, &cs).is_err());
220
+ let cs = clauses(&mut env, &[&["True"], &["0"]]);
221
+ assert!(check(&env, &cs).is_err());
222
+ let cs = clauses(&mut env, &[&["x", "Just y"], &["Nothing", "[]"]]);
223
+ let e = check(&env, &cs).unwrap_err();
224
+ assert!(e.message.contains("argument 2"), "{}", e.message);
225
+ }
226
+ }
@@ -0,0 +1,404 @@
1
+ //! The type environment: algebraic data types and their constructors.
2
+ //!
3
+ //! Built-in types `Bool`, lists and tuples are registered up front so the
4
+ //! exhaustiveness checker knows their complete constructor signatures.
5
+ //! User types are added through `register`.
6
+
7
+ use std::collections::HashMap;
8
+
9
+ use super::error::{CoreError, ErrorKind, Result};
10
+
11
+ pub type TypeId = usize;
12
+ pub type ConId = usize;
13
+
14
+ pub const BOOL: TypeId = 0;
15
+ pub const LIST: TypeId = 1;
16
+
17
+ pub const CON_FALSE: ConId = 0;
18
+ pub const CON_TRUE: ConId = 1;
19
+ pub const CON_NIL: ConId = 2;
20
+ pub const CON_CONS: ConId = 3;
21
+
22
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
23
+ pub enum TypeKind {
24
+ /// User-defined algebraic data type. Values are instances of a Ruby class
25
+ /// per constructor.
26
+ Adt,
27
+ /// `true` / `false`.
28
+ Bool,
29
+ /// Ruby `Array` viewed as a cons list.
30
+ List,
31
+ /// Ruby `Array` of exactly `n` elements (`Tuple(0)` is unit `()`).
32
+ Tuple(usize),
33
+ }
34
+
35
+ #[derive(Clone, Debug)]
36
+ pub struct DataType {
37
+ pub name: String,
38
+ pub kind: TypeKind,
39
+ pub cons: Vec<ConId>,
40
+ /// `false` once the type has been replaced by a redefinition.
41
+ pub live: bool,
42
+ }
43
+
44
+ #[derive(Clone, Debug)]
45
+ pub struct Constructor {
46
+ pub name: String,
47
+ pub ty: TypeId,
48
+ /// Index within the owning type's constructor list.
49
+ pub tag: usize,
50
+ pub arity: usize,
51
+ /// Field names for record constructors.
52
+ pub fields: Option<Vec<String>>,
53
+ /// Opaque host handle (the Ruby class, as a raw `VALUE`) for ADT
54
+ /// constructors; `0` for built-ins.
55
+ pub handle: usize,
56
+ }
57
+
58
+ #[derive(Clone, Debug)]
59
+ pub struct ConSpec {
60
+ pub name: String,
61
+ pub arity: usize,
62
+ pub fields: Option<Vec<String>>,
63
+ pub handle: usize,
64
+ }
65
+
66
+ #[derive(Clone, Debug)]
67
+ pub struct TypeEnv {
68
+ types: Vec<DataType>,
69
+ cons: Vec<Constructor>,
70
+ by_con_name: HashMap<String, ConId>,
71
+ by_type_name: HashMap<String, TypeId>,
72
+ tuples: HashMap<usize, TypeId>,
73
+ }
74
+
75
+ impl Default for TypeEnv {
76
+ fn default() -> Self {
77
+ Self::new()
78
+ }
79
+ }
80
+
81
+ impl TypeEnv {
82
+ pub fn new() -> Self {
83
+ let mut env = TypeEnv {
84
+ types: Vec::new(),
85
+ cons: Vec::new(),
86
+ by_con_name: HashMap::new(),
87
+ by_type_name: HashMap::new(),
88
+ tuples: HashMap::new(),
89
+ };
90
+ let b = env.add_type("Bool", TypeKind::Bool);
91
+ debug_assert_eq!(b, BOOL);
92
+ let f = env.add_con(b, "False", 0, None, 0);
93
+ let t = env.add_con(b, "True", 0, None, 0);
94
+ debug_assert_eq!((f, t), (CON_FALSE, CON_TRUE));
95
+ let l = env.add_type("[]", TypeKind::List);
96
+ debug_assert_eq!(l, LIST);
97
+ let n = env.add_con(l, "[]", 0, None, 0);
98
+ let c = env.add_con(l, ":", 2, None, 0);
99
+ debug_assert_eq!((n, c), (CON_NIL, CON_CONS));
100
+ env
101
+ }
102
+
103
+ fn add_type(&mut self, name: &str, kind: TypeKind) -> TypeId {
104
+ let id = self.types.len();
105
+ self.types.push(DataType {
106
+ name: name.to_string(),
107
+ kind,
108
+ cons: Vec::new(),
109
+ live: true,
110
+ });
111
+ self.by_type_name.insert(name.to_string(), id);
112
+ id
113
+ }
114
+
115
+ fn add_con(
116
+ &mut self,
117
+ ty: TypeId,
118
+ name: &str,
119
+ arity: usize,
120
+ fields: Option<Vec<String>>,
121
+ handle: usize,
122
+ ) -> ConId {
123
+ let id = self.cons.len();
124
+ let tag = self.types[ty].cons.len();
125
+ self.cons.push(Constructor {
126
+ name: name.to_string(),
127
+ ty,
128
+ tag,
129
+ arity,
130
+ fields,
131
+ handle,
132
+ });
133
+ self.types[ty].cons.push(id);
134
+ self.by_con_name.insert(name.to_string(), id);
135
+ id
136
+ }
137
+
138
+ /// Register (or re-register) a user data type. Re-registering a type with
139
+ /// the same name replaces its constructors; constructor names must not be
140
+ /// in use by another live type.
141
+ pub fn register(&mut self, name: &str, cons: &[ConSpec]) -> Result<TypeId> {
142
+ if cons.is_empty() {
143
+ return Err(CoreError::new(
144
+ ErrorKind::DataDeclaration,
145
+ format!("type '{}' must have at least one constructor", name),
146
+ ));
147
+ }
148
+ if name == "Bool" || name == "[]" {
149
+ return Err(CoreError::new(
150
+ ErrorKind::DataDeclaration,
151
+ format!("cannot redefine built-in type '{}'", name),
152
+ ));
153
+ }
154
+ for (i, c) in cons.iter().enumerate() {
155
+ if !c
156
+ .name
157
+ .chars()
158
+ .next()
159
+ .map(|ch| ch.is_uppercase() || ch == ':')
160
+ .unwrap_or(false)
161
+ {
162
+ return Err(CoreError::new(
163
+ ErrorKind::DataDeclaration,
164
+ format!(
165
+ "constructor name '{}' must start with an upper-case letter (or ':' for an infix constructor)",
166
+ c.name
167
+ ),
168
+ ));
169
+ }
170
+ if cons[..i].iter().any(|d| d.name == c.name) {
171
+ return Err(CoreError::new(
172
+ ErrorKind::DataDeclaration,
173
+ format!(
174
+ "constructor '{}' is declared twice in type '{}'",
175
+ c.name, name
176
+ ),
177
+ ));
178
+ }
179
+ if let Some(fields) = &c.fields {
180
+ if fields.len() != c.arity {
181
+ return Err(CoreError::new(
182
+ ErrorKind::DataDeclaration,
183
+ format!(
184
+ "constructor '{}' has {} fields but arity {}",
185
+ c.name,
186
+ fields.len(),
187
+ c.arity
188
+ ),
189
+ ));
190
+ }
191
+ }
192
+ if let Some(&existing) = self.by_con_name.get(&c.name) {
193
+ let owner = self.cons[existing].ty;
194
+ if self.types[owner].name != name && self.types[owner].live {
195
+ return Err(CoreError::new(
196
+ ErrorKind::DataDeclaration,
197
+ format!(
198
+ "constructor '{}' is already defined by type '{}'",
199
+ c.name, self.types[owner].name
200
+ ),
201
+ ));
202
+ }
203
+ }
204
+ }
205
+ // retire a previous definition with this name
206
+ if let Some(&old) = self.by_type_name.get(name) {
207
+ let old_cons = self.types[old].cons.clone();
208
+ for cid in old_cons {
209
+ if self.by_con_name.get(&self.cons[cid].name) == Some(&cid) {
210
+ self.by_con_name.remove(&self.cons[cid].name);
211
+ }
212
+ }
213
+ self.types[old].live = false;
214
+ }
215
+ let ty = self.add_type(name, TypeKind::Adt);
216
+ for c in cons {
217
+ self.add_con(ty, &c.name, c.arity, c.fields.clone(), c.handle);
218
+ }
219
+ Ok(ty)
220
+ }
221
+
222
+ pub fn tuple(&mut self, n: usize) -> TypeId {
223
+ if let Some(&t) = self.tuples.get(&n) {
224
+ return t;
225
+ }
226
+ let name = if n == 0 {
227
+ "()".to_string()
228
+ } else {
229
+ format!("({})", ",".repeat(n - 1))
230
+ };
231
+ let ty = self.add_type(&name, TypeKind::Tuple(n));
232
+ self.add_con(ty, &name, n, None, 0);
233
+ self.tuples.insert(n, ty);
234
+ ty
235
+ }
236
+
237
+ /// The single constructor of the `n`-tuple type (creating the type if
238
+ /// needed).
239
+ pub fn tuple_con(&mut self, n: usize) -> ConId {
240
+ let ty = self.tuple(n);
241
+ self.types[ty].cons[0]
242
+ }
243
+
244
+ pub fn lookup_con(&self, name: &str) -> Option<ConId> {
245
+ self.by_con_name.get(name).copied()
246
+ }
247
+
248
+ pub fn lookup_type(&self, name: &str) -> Option<TypeId> {
249
+ self.by_type_name
250
+ .get(name)
251
+ .copied()
252
+ .filter(|&t| self.types[t].live)
253
+ }
254
+
255
+ pub fn con(&self, id: ConId) -> &Constructor {
256
+ &self.cons[id]
257
+ }
258
+
259
+ pub fn ty(&self, id: TypeId) -> &DataType {
260
+ &self.types[id]
261
+ }
262
+
263
+ pub fn type_of_con(&self, id: ConId) -> TypeId {
264
+ self.cons[id].ty
265
+ }
266
+
267
+ /// Constructor with `tag` within type `ty`.
268
+ pub fn con_by_tag(&self, ty: TypeId, tag: usize) -> ConId {
269
+ self.types[ty].cons[tag]
270
+ }
271
+
272
+ pub fn is_complete(&self, ty: TypeId, present: &[ConId]) -> bool {
273
+ self.types[ty].cons.iter().all(|c| present.contains(c))
274
+ }
275
+
276
+ pub fn types_len(&self) -> usize {
277
+ self.types.len()
278
+ }
279
+
280
+ /// Live user-declared types (not Bool, lists or tuples) with their
281
+ /// constructor specs, for importing into another type scope.
282
+ pub fn user_types(&self) -> Vec<(String, Vec<ConSpec>)> {
283
+ self.types
284
+ .iter()
285
+ .filter(|t| t.live && t.kind == TypeKind::Adt)
286
+ .map(|t| {
287
+ let cons = t
288
+ .cons
289
+ .iter()
290
+ .map(|&c| {
291
+ let c = &self.cons[c];
292
+ ConSpec {
293
+ name: c.name.clone(),
294
+ arity: c.arity,
295
+ fields: c.fields.clone(),
296
+ handle: c.handle,
297
+ }
298
+ })
299
+ .collect();
300
+ (t.name.clone(), cons)
301
+ })
302
+ .collect()
303
+ }
304
+
305
+ /// Whether `name` is a live type whose constructors are exactly `cons`
306
+ /// (same names and host handles).
307
+ pub fn has_same_type(&self, name: &str, cons: &[ConSpec]) -> bool {
308
+ match self.lookup_type(name) {
309
+ Some(t) => {
310
+ let mine = &self.types[t].cons;
311
+ mine.len() == cons.len()
312
+ && mine.iter().zip(cons.iter()).all(|(&m, c)| {
313
+ let m = &self.cons[m];
314
+ m.name == c.name && m.handle == c.handle
315
+ })
316
+ }
317
+ None => false,
318
+ }
319
+ }
320
+ }
321
+
322
+ #[cfg(test)]
323
+ mod tests {
324
+ use super::*;
325
+
326
+ fn spec(name: &str, arity: usize) -> ConSpec {
327
+ ConSpec {
328
+ name: name.into(),
329
+ arity,
330
+ fields: None,
331
+ handle: 0,
332
+ }
333
+ }
334
+
335
+ #[test]
336
+ fn builtins() {
337
+ let mut env = TypeEnv::new();
338
+ assert_eq!(env.lookup_con("True"), Some(CON_TRUE));
339
+ assert_eq!(env.lookup_con("[]"), Some(CON_NIL));
340
+ assert_eq!(env.con(CON_CONS).arity, 2);
341
+ let t2 = env.tuple(2);
342
+ assert_eq!(env.ty(t2).name, "(,)");
343
+ assert_eq!(env.tuple(2), t2);
344
+ let t0 = env.tuple(0);
345
+ assert_eq!(env.ty(t0).name, "()");
346
+ let t3 = env.tuple_con(3);
347
+ assert_eq!(env.con(t3).arity, 3);
348
+ }
349
+
350
+ #[test]
351
+ fn register_and_redefine() {
352
+ let mut env = TypeEnv::new();
353
+ let maybe = env
354
+ .register("Maybe", &[spec("Nothing", 0), spec("Just", 1)])
355
+ .unwrap();
356
+ let just = env.lookup_con("Just").unwrap();
357
+ assert_eq!(env.type_of_con(just), maybe);
358
+ assert_eq!(env.con(just).tag, 1);
359
+ assert!(env.is_complete(maybe, &[just, env.lookup_con("Nothing").unwrap()]));
360
+ assert!(!env.is_complete(maybe, &[just]));
361
+
362
+ // clash with another type
363
+ let e = env.register("Other", &[spec("Just", 2)]).unwrap_err();
364
+ assert_eq!(e.kind, ErrorKind::DataDeclaration);
365
+ assert!(e.message.contains("already defined by type 'Maybe'"));
366
+
367
+ // redefinition replaces constructors
368
+ let maybe2 = env
369
+ .register(
370
+ "Maybe",
371
+ &[spec("Nothing", 0), spec("Just", 1), spec("Unknown", 0)],
372
+ )
373
+ .unwrap();
374
+ assert_ne!(maybe, maybe2);
375
+ assert_eq!(env.lookup_type("Maybe"), Some(maybe2));
376
+ assert!(!env.ty(maybe).live);
377
+ assert_eq!(env.type_of_con(env.lookup_con("Unknown").unwrap()), maybe2);
378
+ // and now "Just" can move to another type after Maybe is redefined without it
379
+ env.register("Maybe", &[spec("Nothing", 0)]).unwrap();
380
+ assert_eq!(env.lookup_con("Just"), None);
381
+ env.register("Other", &[spec("Just", 2)]).unwrap();
382
+ }
383
+
384
+ #[test]
385
+ fn register_errors() {
386
+ let mut env = TypeEnv::new();
387
+ assert!(env.register("T", &[]).is_err());
388
+ assert!(env.register("Bool", &[spec("X", 0)]).is_err());
389
+ assert!(env.register("T", &[spec("lower", 0)]).is_err());
390
+ assert!(env.register("T", &[spec("A", 0), spec("A", 1)]).is_err());
391
+ assert!(env
392
+ .register(
393
+ "T",
394
+ &[ConSpec {
395
+ name: "R".into(),
396
+ arity: 2,
397
+ fields: Some(vec!["a".into()]),
398
+ handle: 0
399
+ }]
400
+ )
401
+ .is_err());
402
+ assert!(env.register("T", &[spec("True", 0)]).is_err());
403
+ }
404
+ }
@@ -0,0 +1,19 @@
1
+ //! haskell_match: Haskell-style pattern matching for Ruby.
2
+ //!
3
+ //! The `core` module is pure Rust (parser, type checking, exhaustiveness
4
+ //! analysis, decision-tree compilation) and is unit tested on its own. The
5
+ //! `ruby` module binds it to Ruby through Rutie.
6
+
7
+ #[macro_use]
8
+ extern crate rutie;
9
+ #[macro_use]
10
+ extern crate lazy_static;
11
+
12
+ pub mod core;
13
+ pub mod ruby;
14
+
15
+ #[allow(non_snake_case)]
16
+ #[no_mangle]
17
+ pub extern "C" fn Init_haskell_match() {
18
+ ruby::init();
19
+ }