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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +98 -0
- data/LICENSE-APACHE +202 -0
- data/LICENSE-MIT +21 -0
- data/README.md +1484 -0
- data/ext/haskell_match/Cargo.lock +33 -0
- data/ext/haskell_match/Cargo.toml +22 -0
- data/ext/haskell_match/extconf.rb +41 -0
- data/ext/haskell_match/src/core/ast.rs +190 -0
- data/ext/haskell_match/src/core/error.rs +52 -0
- data/ext/haskell_match/src/core/exhaust.rs +699 -0
- data/ext/haskell_match/src/core/hs/ast.rs +256 -0
- data/ext/haskell_match/src/core/hs/json.rs +225 -0
- data/ext/haskell_match/src/core/hs/layout.rs +346 -0
- data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
- data/ext/haskell_match/src/core/hs/mod.rs +14 -0
- data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
- data/ext/haskell_match/src/core/lexer.rs +590 -0
- data/ext/haskell_match/src/core/mod.rs +19 -0
- data/ext/haskell_match/src/core/parser.rs +1116 -0
- data/ext/haskell_match/src/core/pretty.rs +373 -0
- data/ext/haskell_match/src/core/resolve.rs +336 -0
- data/ext/haskell_match/src/core/tree.rs +921 -0
- data/ext/haskell_match/src/core/typecheck.rs +226 -0
- data/ext/haskell_match/src/core/types.rs +404 -0
- data/ext/haskell_match/src/lib.rs +19 -0
- data/ext/haskell_match/src/ruby/mod.rs +1195 -0
- data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
- data/lib/haskell_match/binding_plan.rb +84 -0
- data/lib/haskell_match/case_of.rb +71 -0
- data/lib/haskell_match/clauses.rb +354 -0
- data/lib/haskell_match/data.rb +417 -0
- data/lib/haskell_match/deep_call.rb +98 -0
- data/lib/haskell_match/deriving.rb +130 -0
- data/lib/haskell_match/dsl.rb +71 -0
- data/lib/haskell_match/errors.rb +85 -0
- data/lib/haskell_match/field_types.rb +140 -0
- data/lib/haskell_match/function.rb +240 -0
- data/lib/haskell_match/haskell/compiler.rb +961 -0
- data/lib/haskell_match/haskell.rb +326 -0
- data/lib/haskell_match/inspect.rb +45 -0
- data/lib/haskell_match/lazy_list.rb +210 -0
- data/lib/haskell_match/native_loader.rb +64 -0
- data/lib/haskell_match/pattern.rb +75 -0
- data/lib/haskell_match/pattern_ast.rb +394 -0
- data/lib/haskell_match/prelude.rb +448 -0
- data/lib/haskell_match/scope.rb +44 -0
- data/lib/haskell_match/version.rb +5 -0
- data/lib/haskell_match.rb +41 -0
- 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
|
+
}
|