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,346 @@
|
|
|
1
|
+
//! The Haskell 2010 layout algorithm (report §10.3), simplified: implicit
|
|
2
|
+
//! blocks after `where`, `let`, `do` and `of`, closed by indentation, by
|
|
3
|
+
//! `in`, or when a closing bracket of an enclosing explicit construct is met.
|
|
4
|
+
|
|
5
|
+
use super::lexer::{Tok, Token};
|
|
6
|
+
|
|
7
|
+
enum Ctx {
|
|
8
|
+
Explicit,
|
|
9
|
+
/// column of the block, whether `let` opened it (so `in` may close it),
|
|
10
|
+
/// and whether the current item of a let block has already seen its `=`
|
|
11
|
+
Implicit(usize, bool, bool),
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fn opens_block(t: &Tok) -> bool {
|
|
15
|
+
matches!(t, Tok::Where | Tok::Let | Tok::Do | Tok::Of)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn layout(tokens: Vec<Token>) -> Vec<Token> {
|
|
19
|
+
let mut out: Vec<Token> = Vec::with_capacity(tokens.len() * 2);
|
|
20
|
+
let mut ctx: Vec<Ctx> = Vec::new();
|
|
21
|
+
let mut i = 0;
|
|
22
|
+
let n = tokens.len();
|
|
23
|
+
let mut pending_open: Option<bool> = None; // Some(is_let) after a layout keyword
|
|
24
|
+
let mut last_line = 0usize;
|
|
25
|
+
// the whole module is an implicit block unless it starts with `module` or `{`
|
|
26
|
+
if !matches!(
|
|
27
|
+
tokens.first().map(|t| &t.tok),
|
|
28
|
+
Some(Tok::Module) | Some(Tok::LBrace)
|
|
29
|
+
) {
|
|
30
|
+
if let Some(first) = tokens.first() {
|
|
31
|
+
if first.tok != Tok::Eof {
|
|
32
|
+
ctx.push(Ctx::Implicit(first.col, false, false));
|
|
33
|
+
out.push(Token {
|
|
34
|
+
tok: Tok::VLBrace,
|
|
35
|
+
line: first.line,
|
|
36
|
+
col: first.col,
|
|
37
|
+
});
|
|
38
|
+
last_line = first.line;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// explicit-bracket depth tracking so a `)` / `]` / `,` can close implicit blocks
|
|
43
|
+
let mut bracket_stack: Vec<usize> = Vec::new(); // ctx depth at each open bracket
|
|
44
|
+
while i < n {
|
|
45
|
+
let t = &tokens[i];
|
|
46
|
+
if t.tok == Tok::Eof {
|
|
47
|
+
// close everything
|
|
48
|
+
while let Some(c) = ctx.pop() {
|
|
49
|
+
if let Ctx::Implicit(..) = c {
|
|
50
|
+
out.push(Token {
|
|
51
|
+
tok: Tok::VRBrace,
|
|
52
|
+
line: t.line,
|
|
53
|
+
col: t.col,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
out.push(t.clone());
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
if let Some(is_let) = pending_open.take() {
|
|
61
|
+
if t.tok == Tok::LBrace {
|
|
62
|
+
ctx.push(Ctx::Explicit);
|
|
63
|
+
out.push(t.clone());
|
|
64
|
+
last_line = t.line;
|
|
65
|
+
i += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// open an implicit block at this token's column
|
|
69
|
+
let enclosing = ctx.iter().rev().find_map(|c| {
|
|
70
|
+
if let Ctx::Implicit(m, _, _) = c {
|
|
71
|
+
Some(*m)
|
|
72
|
+
} else {
|
|
73
|
+
None
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if enclosing.map(|m| t.col > m).unwrap_or(true) {
|
|
77
|
+
ctx.push(Ctx::Implicit(t.col, is_let, false));
|
|
78
|
+
out.push(Token {
|
|
79
|
+
tok: Tok::VLBrace,
|
|
80
|
+
line: t.line,
|
|
81
|
+
col: t.col,
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
// empty block: `{}` then fall through to normal processing
|
|
85
|
+
out.push(Token {
|
|
86
|
+
tok: Tok::VLBrace,
|
|
87
|
+
line: t.line,
|
|
88
|
+
col: t.col,
|
|
89
|
+
});
|
|
90
|
+
out.push(Token {
|
|
91
|
+
tok: Tok::VRBrace,
|
|
92
|
+
line: t.line,
|
|
93
|
+
col: t.col,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
last_line = t.line;
|
|
97
|
+
// the token itself is processed below without the new-line rule
|
|
98
|
+
emit(&mut out, &mut ctx, &mut bracket_stack, t, &mut pending_open);
|
|
99
|
+
i += 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// new line: compare indentation with the innermost implicit context
|
|
103
|
+
if t.line > last_line {
|
|
104
|
+
loop {
|
|
105
|
+
match ctx.last() {
|
|
106
|
+
Some(Ctx::Implicit(m, _, _)) if t.col < *m => {
|
|
107
|
+
ctx.pop();
|
|
108
|
+
out.push(Token {
|
|
109
|
+
tok: Tok::VRBrace,
|
|
110
|
+
line: t.line,
|
|
111
|
+
col: t.col,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
Some(Ctx::Implicit(m, _, _)) if t.col == *m => {
|
|
115
|
+
// a new item in the block, unless the token continues an expression
|
|
116
|
+
if !matches!(t.tok, Tok::Then | Tok::Else | Tok::Of | Tok::In) {
|
|
117
|
+
out.push(Token {
|
|
118
|
+
tok: Tok::VSemi,
|
|
119
|
+
line: t.line,
|
|
120
|
+
col: t.col,
|
|
121
|
+
});
|
|
122
|
+
if let Some(Ctx::Implicit(_, _, eq)) = ctx.last_mut() {
|
|
123
|
+
*eq = false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
_ => break,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
last_line = t.line;
|
|
132
|
+
}
|
|
133
|
+
// `\case` (LambdaCase) opens an alternatives block like `of`
|
|
134
|
+
let lambda_case =
|
|
135
|
+
t.tok == Tok::Case && out.last().map(|x| x.tok == Tok::Backslash).unwrap_or(false);
|
|
136
|
+
emit(&mut out, &mut ctx, &mut bracket_stack, t, &mut pending_open);
|
|
137
|
+
if lambda_case {
|
|
138
|
+
pending_open = Some(false);
|
|
139
|
+
}
|
|
140
|
+
i += 1;
|
|
141
|
+
}
|
|
142
|
+
out
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn emit(
|
|
146
|
+
out: &mut Vec<Token>,
|
|
147
|
+
ctx: &mut Vec<Ctx>,
|
|
148
|
+
brackets: &mut Vec<usize>,
|
|
149
|
+
t: &Token,
|
|
150
|
+
pending_open: &mut Option<bool>,
|
|
151
|
+
) {
|
|
152
|
+
match &t.tok {
|
|
153
|
+
Tok::Equals => {
|
|
154
|
+
// Inside a `let` block each binding has one `=`; a second one
|
|
155
|
+
// before the next binding means the block was a guard qualifier
|
|
156
|
+
// (`| let y = f x = y`) and the block ends here (the report's
|
|
157
|
+
// parse-error(t) rule, for this one case).
|
|
158
|
+
let inside_block = brackets.last().map(|d| ctx.len() > *d).unwrap_or(true);
|
|
159
|
+
if let Some(Ctx::Implicit(_, true, eq_seen)) = ctx.last_mut() {
|
|
160
|
+
if *eq_seen && inside_block {
|
|
161
|
+
ctx.pop();
|
|
162
|
+
out.push(Token {
|
|
163
|
+
tok: Tok::VRBrace,
|
|
164
|
+
line: t.line,
|
|
165
|
+
col: t.col,
|
|
166
|
+
});
|
|
167
|
+
} else {
|
|
168
|
+
*eq_seen = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
out.push(t.clone());
|
|
172
|
+
}
|
|
173
|
+
Tok::Semi => {
|
|
174
|
+
if let Some(Ctx::Implicit(_, _, eq)) = ctx.last_mut() {
|
|
175
|
+
*eq = false;
|
|
176
|
+
}
|
|
177
|
+
out.push(t.clone());
|
|
178
|
+
}
|
|
179
|
+
Tok::In => {
|
|
180
|
+
// `let ... in`: close the implicit let block if it is still open
|
|
181
|
+
// (the indentation rule may already have closed it)
|
|
182
|
+
if let Some(Ctx::Implicit(_, true, _)) = ctx.last() {
|
|
183
|
+
if brackets.last().map(|d| ctx.len() > *d).unwrap_or(true) {
|
|
184
|
+
ctx.pop();
|
|
185
|
+
out.push(Token {
|
|
186
|
+
tok: Tok::VRBrace,
|
|
187
|
+
line: t.line,
|
|
188
|
+
col: t.col,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
out.push(t.clone());
|
|
193
|
+
}
|
|
194
|
+
Tok::LParen | Tok::LBracket => {
|
|
195
|
+
brackets.push(ctx.len());
|
|
196
|
+
out.push(t.clone());
|
|
197
|
+
}
|
|
198
|
+
Tok::RParen | Tok::RBracket => {
|
|
199
|
+
if let Some(depth) = brackets.pop() {
|
|
200
|
+
while ctx.len() > depth {
|
|
201
|
+
if let Some(Ctx::Implicit(..)) = ctx.pop() {
|
|
202
|
+
out.push(Token {
|
|
203
|
+
tok: Tok::VRBrace,
|
|
204
|
+
line: t.line,
|
|
205
|
+
col: t.col,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
out.push(t.clone());
|
|
211
|
+
}
|
|
212
|
+
Tok::Comma => {
|
|
213
|
+
// a comma belonging to an enclosing bracket closes implicit blocks opened inside it
|
|
214
|
+
if let Some(depth) = brackets.last() {
|
|
215
|
+
while ctx.len() > *depth {
|
|
216
|
+
if let Some(Ctx::Implicit(..)) = ctx.pop() {
|
|
217
|
+
out.push(Token {
|
|
218
|
+
tok: Tok::VRBrace,
|
|
219
|
+
line: t.line,
|
|
220
|
+
col: t.col,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} else if let Some(Ctx::Implicit(_, true, _)) = ctx.last() {
|
|
225
|
+
// `| let y = e, cond`: a comma ends a let block used as a guard
|
|
226
|
+
ctx.pop();
|
|
227
|
+
out.push(Token {
|
|
228
|
+
tok: Tok::VRBrace,
|
|
229
|
+
line: t.line,
|
|
230
|
+
col: t.col,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
out.push(t.clone());
|
|
234
|
+
}
|
|
235
|
+
Tok::LBrace => {
|
|
236
|
+
ctx.push(Ctx::Explicit);
|
|
237
|
+
out.push(t.clone());
|
|
238
|
+
}
|
|
239
|
+
Tok::RBrace => {
|
|
240
|
+
// close implicit blocks opened inside the explicit one
|
|
241
|
+
while let Some(Ctx::Implicit(..)) = ctx.last() {
|
|
242
|
+
ctx.pop();
|
|
243
|
+
out.push(Token {
|
|
244
|
+
tok: Tok::VRBrace,
|
|
245
|
+
line: t.line,
|
|
246
|
+
col: t.col,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if let Some(Ctx::Explicit) = ctx.last() {
|
|
250
|
+
ctx.pop();
|
|
251
|
+
}
|
|
252
|
+
out.push(t.clone());
|
|
253
|
+
}
|
|
254
|
+
Tok::Then | Tok::Else | Tok::Of => {
|
|
255
|
+
// `case e of` inside a `let`/`where` item on the same line is fine;
|
|
256
|
+
// `then`/`else` never close blocks here (handled by indentation)
|
|
257
|
+
out.push(t.clone());
|
|
258
|
+
if t.tok == Tok::Of {
|
|
259
|
+
*pending_open = Some(false);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
tok => {
|
|
263
|
+
out.push(t.clone());
|
|
264
|
+
if opens_block(tok) {
|
|
265
|
+
*pending_open = Some(matches!(tok, Tok::Let));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#[cfg(test)]
|
|
272
|
+
mod tests {
|
|
273
|
+
use super::*;
|
|
274
|
+
use crate::core::hs::lexer::tokenize;
|
|
275
|
+
|
|
276
|
+
fn show(src: &str) -> String {
|
|
277
|
+
layout(tokenize(src).unwrap())
|
|
278
|
+
.into_iter()
|
|
279
|
+
.map(|t| match t.tok {
|
|
280
|
+
Tok::VLBrace => "{".to_string(),
|
|
281
|
+
Tok::VRBrace => "}".to_string(),
|
|
282
|
+
Tok::VSemi => ";".to_string(),
|
|
283
|
+
Tok::Eof => "".to_string(),
|
|
284
|
+
Tok::VarId(v) => v,
|
|
285
|
+
Tok::ConId(c) => c,
|
|
286
|
+
Tok::VarSym(s) | Tok::ConSym(s) => s,
|
|
287
|
+
Tok::Int(s) => s,
|
|
288
|
+
Tok::Equals => "=".into(),
|
|
289
|
+
Tok::Where => "where".into(),
|
|
290
|
+
Tok::Let => "let".into(),
|
|
291
|
+
Tok::In => "in".into(),
|
|
292
|
+
Tok::Case => "case".into(),
|
|
293
|
+
Tok::Of => "of".into(),
|
|
294
|
+
Tok::RArrow => "->".into(),
|
|
295
|
+
Tok::LParen => "(".into(),
|
|
296
|
+
Tok::RParen => ")".into(),
|
|
297
|
+
Tok::Pipe => "|".into(),
|
|
298
|
+
other => format!("{:?}", other),
|
|
299
|
+
})
|
|
300
|
+
.filter(|s| !s.is_empty())
|
|
301
|
+
.collect::<Vec<_>>()
|
|
302
|
+
.join(" ")
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#[test]
|
|
306
|
+
fn top_level_items_and_where() {
|
|
307
|
+
let src = "f x = y\n where y = x\n z = 1\ng = 2\n";
|
|
308
|
+
assert_eq!(show(src), "{ f x = y where { y = x ; z = 1 } ; g = 2 }");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#[test]
|
|
312
|
+
fn let_in_and_case() {
|
|
313
|
+
assert_eq!(show("f = let a = 1 in a"), "{ f = let { a = 1 } in a }");
|
|
314
|
+
assert_eq!(
|
|
315
|
+
show("f x = case x of\n 0 -> 1\n n -> n\ng = 3"),
|
|
316
|
+
"{ f x = case x of { 0 -> 1 ; n -> n } ; g = 3 }"
|
|
317
|
+
);
|
|
318
|
+
assert_eq!(
|
|
319
|
+
show("f = (let a = 1 in a) + 1"),
|
|
320
|
+
"{ f = ( let { a = 1 } in a ) + 1 }"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
#[test]
|
|
325
|
+
fn multi_line_let_with_in_on_its_own_line() {
|
|
326
|
+
let src = "f x = let y = x\n z = y\n in y + z\ng = 1\n";
|
|
327
|
+
assert_eq!(
|
|
328
|
+
show(src),
|
|
329
|
+
"{ f x = let { y = x ; z = y } in y + z ; g = 1 }"
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#[test]
|
|
334
|
+
fn let_guards_close_at_comma_or_second_equals() {
|
|
335
|
+
assert_eq!(
|
|
336
|
+
show("f x\n | let y = x, y > 1 = y\n | let z = x = z\n"),
|
|
337
|
+
"{ f x | let { y = x } Comma y > 1 = y | let { z = x } = z }"
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#[test]
|
|
342
|
+
fn guards_continue_an_equation() {
|
|
343
|
+
let src = "sign n\n | n > 0 = 1\n | otherwise = 0\n";
|
|
344
|
+
assert_eq!(show(src), "{ sign n | n > 0 = 1 | otherwise = 0 }");
|
|
345
|
+
}
|
|
346
|
+
}
|