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,688 @@
|
|
|
1
|
+
//! Haskell lexer with line/column positions (needed for the layout rule).
|
|
2
|
+
|
|
3
|
+
use crate::core::error::{CoreError, ErrorKind, Result};
|
|
4
|
+
|
|
5
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
6
|
+
pub enum Tok {
|
|
7
|
+
// identifiers
|
|
8
|
+
VarId(String),
|
|
9
|
+
ConId(String),
|
|
10
|
+
/// Symbolic operator such as `+`, `++`, `>>=`, or a backticked identifier
|
|
11
|
+
/// (`` `div` `` becomes `VarSym("div")` with `backtick = true`).
|
|
12
|
+
VarSym(String),
|
|
13
|
+
ConSym(String), // `:`, `:+:` ...
|
|
14
|
+
// literals
|
|
15
|
+
Int(String),
|
|
16
|
+
Float(String),
|
|
17
|
+
Char(char),
|
|
18
|
+
Str(String),
|
|
19
|
+
// reserved words
|
|
20
|
+
Case,
|
|
21
|
+
Class,
|
|
22
|
+
Data,
|
|
23
|
+
Deriving,
|
|
24
|
+
Do,
|
|
25
|
+
Else,
|
|
26
|
+
If,
|
|
27
|
+
Import,
|
|
28
|
+
In,
|
|
29
|
+
Infix,
|
|
30
|
+
Infixl,
|
|
31
|
+
Infixr,
|
|
32
|
+
Instance,
|
|
33
|
+
Let,
|
|
34
|
+
Module,
|
|
35
|
+
Newtype,
|
|
36
|
+
Of,
|
|
37
|
+
Then,
|
|
38
|
+
Type,
|
|
39
|
+
Where,
|
|
40
|
+
// reserved operators / punctuation
|
|
41
|
+
DotDot,
|
|
42
|
+
DoubleColon,
|
|
43
|
+
Equals,
|
|
44
|
+
Backslash,
|
|
45
|
+
Pipe,
|
|
46
|
+
LArrow,
|
|
47
|
+
RArrow,
|
|
48
|
+
At,
|
|
49
|
+
Tilde,
|
|
50
|
+
DArrow,
|
|
51
|
+
Bang,
|
|
52
|
+
LParen,
|
|
53
|
+
RParen,
|
|
54
|
+
LBracket,
|
|
55
|
+
RBracket,
|
|
56
|
+
LBrace,
|
|
57
|
+
RBrace,
|
|
58
|
+
Comma,
|
|
59
|
+
Semi,
|
|
60
|
+
Underscore,
|
|
61
|
+
Backtick,
|
|
62
|
+
// layout-inserted
|
|
63
|
+
VLBrace,
|
|
64
|
+
VRBrace,
|
|
65
|
+
VSemi,
|
|
66
|
+
Eof,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
70
|
+
pub struct Token {
|
|
71
|
+
pub tok: Tok,
|
|
72
|
+
pub line: usize,
|
|
73
|
+
pub col: usize,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub fn lex_error(line: usize, col: usize, msg: impl std::fmt::Display) -> CoreError {
|
|
77
|
+
CoreError::new(ErrorKind::Syntax, format!("{}:{}: {}", line, col, msg))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn is_symbol_char(c: char) -> bool {
|
|
81
|
+
"!#$%&*+./<=>?@\\^|-~:".contains(c)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn is_ident_char(c: char) -> bool {
|
|
85
|
+
c.is_alphanumeric() || c == '_' || c == '\''
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub fn tokenize(src: &str) -> Result<Vec<Token>> {
|
|
89
|
+
let chars: Vec<char> = src.chars().collect();
|
|
90
|
+
let mut out = Vec::new();
|
|
91
|
+
let mut i = 0;
|
|
92
|
+
let mut line = 1usize;
|
|
93
|
+
let mut col = 1usize;
|
|
94
|
+
let advance = |i: &mut usize, line: &mut usize, col: &mut usize, chars: &[char]| {
|
|
95
|
+
if chars[*i] == '\n' {
|
|
96
|
+
*line += 1;
|
|
97
|
+
*col = 1;
|
|
98
|
+
} else if chars[*i] == '\t' {
|
|
99
|
+
*col += 8 - ((*col - 1) % 8);
|
|
100
|
+
} else {
|
|
101
|
+
*col += 1;
|
|
102
|
+
}
|
|
103
|
+
*i += 1;
|
|
104
|
+
};
|
|
105
|
+
while i < chars.len() {
|
|
106
|
+
let c = chars[i];
|
|
107
|
+
let (tl, tc) = (line, col);
|
|
108
|
+
// whitespace
|
|
109
|
+
if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
|
|
110
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// line comment: `--` followed by non-symbol
|
|
114
|
+
if c == '-' && i + 1 < chars.len() && chars[i + 1] == '-' {
|
|
115
|
+
let mut j = i;
|
|
116
|
+
while j < chars.len() && chars[j] == '-' {
|
|
117
|
+
j += 1;
|
|
118
|
+
}
|
|
119
|
+
if j >= chars.len() || !is_symbol_char(chars[j]) {
|
|
120
|
+
while i < chars.len() && chars[i] != '\n' {
|
|
121
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// block comment (nested)
|
|
127
|
+
if c == '{' && i + 1 < chars.len() && chars[i + 1] == '-' {
|
|
128
|
+
let mut depth = 0;
|
|
129
|
+
loop {
|
|
130
|
+
if i + 1 >= chars.len() {
|
|
131
|
+
return Err(lex_error(tl, tc, "unterminated block comment"));
|
|
132
|
+
}
|
|
133
|
+
if chars[i] == '{' && chars[i + 1] == '-' {
|
|
134
|
+
depth += 1;
|
|
135
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
136
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
137
|
+
} else if chars[i] == '-' && chars[i + 1] == '}' {
|
|
138
|
+
depth -= 1;
|
|
139
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
140
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
141
|
+
if depth == 0 {
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
let mut push = |tok: Tok| {
|
|
151
|
+
out.push(Token {
|
|
152
|
+
tok,
|
|
153
|
+
line: tl,
|
|
154
|
+
col: tc,
|
|
155
|
+
})
|
|
156
|
+
};
|
|
157
|
+
match c {
|
|
158
|
+
'(' | ')' | '[' | ']' | '{' | '}' | ',' | ';' | '`' => {
|
|
159
|
+
push(match c {
|
|
160
|
+
'(' => Tok::LParen,
|
|
161
|
+
')' => Tok::RParen,
|
|
162
|
+
'[' => Tok::LBracket,
|
|
163
|
+
']' => Tok::RBracket,
|
|
164
|
+
'{' => Tok::LBrace,
|
|
165
|
+
'}' => Tok::RBrace,
|
|
166
|
+
',' => Tok::Comma,
|
|
167
|
+
';' => Tok::Semi,
|
|
168
|
+
_ => Tok::Backtick,
|
|
169
|
+
});
|
|
170
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
171
|
+
}
|
|
172
|
+
'"' => {
|
|
173
|
+
let (s, next) = lex_string(&chars, i, '"', tl, tc)?;
|
|
174
|
+
push(Tok::Str(s));
|
|
175
|
+
while i < next {
|
|
176
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
'\'' if is_char_literal_start(&chars, i) => {
|
|
180
|
+
let (s, next) = lex_string(&chars, i, '\'', tl, tc)?;
|
|
181
|
+
let mut it = s.chars();
|
|
182
|
+
let ch = it.next();
|
|
183
|
+
if ch.is_none() || it.next().is_some() {
|
|
184
|
+
return Err(lex_error(
|
|
185
|
+
tl,
|
|
186
|
+
tc,
|
|
187
|
+
"character literal must contain exactly one character",
|
|
188
|
+
));
|
|
189
|
+
}
|
|
190
|
+
push(Tok::Char(ch.unwrap()));
|
|
191
|
+
while i < next {
|
|
192
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
c if c.is_ascii_digit() => {
|
|
196
|
+
let (tok, next) = lex_number(&chars, i, tl, tc)?;
|
|
197
|
+
push(tok);
|
|
198
|
+
while i < next {
|
|
199
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
'_' if !(i + 1 < chars.len() && is_ident_char(chars[i + 1])) => {
|
|
203
|
+
push(Tok::Underscore);
|
|
204
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
205
|
+
}
|
|
206
|
+
c if c.is_alphabetic() || c == '_' => {
|
|
207
|
+
let start = i;
|
|
208
|
+
let mut j = i;
|
|
209
|
+
while j < chars.len() && is_ident_char(chars[j]) {
|
|
210
|
+
j += 1;
|
|
211
|
+
}
|
|
212
|
+
let mut name: String = chars[start..j].iter().collect();
|
|
213
|
+
let upper = c.is_uppercase();
|
|
214
|
+
// qualified names: Mod.Sub.name / Mod.Con / Mod.+
|
|
215
|
+
if upper {
|
|
216
|
+
while j + 1 < chars.len() && chars[j] == '.' {
|
|
217
|
+
let n = chars[j + 1];
|
|
218
|
+
if n.is_alphabetic() || n == '_' {
|
|
219
|
+
let seg_start = j + 1;
|
|
220
|
+
let mut k = seg_start;
|
|
221
|
+
while k < chars.len() && is_ident_char(chars[k]) {
|
|
222
|
+
k += 1;
|
|
223
|
+
}
|
|
224
|
+
let seg: String = chars[seg_start..k].iter().collect();
|
|
225
|
+
if seg.starts_with(|ch: char| ch.is_uppercase()) {
|
|
226
|
+
// Mod.Con keeps its qualifier (module headers need it);
|
|
227
|
+
// the parser drops it where a constructor is meant
|
|
228
|
+
name.push('.');
|
|
229
|
+
name.push_str(&seg);
|
|
230
|
+
} else {
|
|
231
|
+
name = seg;
|
|
232
|
+
}
|
|
233
|
+
j = k;
|
|
234
|
+
} else if is_symbol_char(n) {
|
|
235
|
+
let seg_start = j + 1;
|
|
236
|
+
let mut k = seg_start;
|
|
237
|
+
while k < chars.len() && is_symbol_char(chars[k]) {
|
|
238
|
+
k += 1;
|
|
239
|
+
}
|
|
240
|
+
let sym: String = chars[seg_start..k].iter().collect();
|
|
241
|
+
push(if sym.starts_with(':') {
|
|
242
|
+
Tok::ConSym(sym)
|
|
243
|
+
} else {
|
|
244
|
+
Tok::VarSym(sym)
|
|
245
|
+
});
|
|
246
|
+
while i < k {
|
|
247
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
248
|
+
}
|
|
249
|
+
name.clear();
|
|
250
|
+
break;
|
|
251
|
+
} else {
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if name.is_empty() {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
let tok = match name.as_str() {
|
|
260
|
+
"case" => Tok::Case,
|
|
261
|
+
"class" => Tok::Class,
|
|
262
|
+
"data" => Tok::Data,
|
|
263
|
+
"deriving" => Tok::Deriving,
|
|
264
|
+
"do" => Tok::Do,
|
|
265
|
+
"else" => Tok::Else,
|
|
266
|
+
"if" => Tok::If,
|
|
267
|
+
"import" => Tok::Import,
|
|
268
|
+
"in" => Tok::In,
|
|
269
|
+
"infix" => Tok::Infix,
|
|
270
|
+
"infixl" => Tok::Infixl,
|
|
271
|
+
"infixr" => Tok::Infixr,
|
|
272
|
+
"instance" => Tok::Instance,
|
|
273
|
+
"let" => Tok::Let,
|
|
274
|
+
"module" => Tok::Module,
|
|
275
|
+
"newtype" => Tok::Newtype,
|
|
276
|
+
"of" => Tok::Of,
|
|
277
|
+
"then" => Tok::Then,
|
|
278
|
+
"type" => Tok::Type,
|
|
279
|
+
"where" => Tok::Where,
|
|
280
|
+
_ => {
|
|
281
|
+
if name
|
|
282
|
+
.chars()
|
|
283
|
+
.next()
|
|
284
|
+
.map(|c| c.is_uppercase())
|
|
285
|
+
.unwrap_or(false)
|
|
286
|
+
{
|
|
287
|
+
Tok::ConId(name)
|
|
288
|
+
} else {
|
|
289
|
+
Tok::VarId(name)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
push(tok);
|
|
294
|
+
while i < j {
|
|
295
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
c if is_symbol_char(c) => {
|
|
299
|
+
let start = i;
|
|
300
|
+
let mut j = i;
|
|
301
|
+
while j < chars.len() && is_symbol_char(chars[j]) {
|
|
302
|
+
j += 1;
|
|
303
|
+
}
|
|
304
|
+
let sym: String = chars[start..j].iter().collect();
|
|
305
|
+
let tok = match sym.as_str() {
|
|
306
|
+
".." => Tok::DotDot,
|
|
307
|
+
"::" => Tok::DoubleColon,
|
|
308
|
+
"=" => Tok::Equals,
|
|
309
|
+
"\\" => Tok::Backslash,
|
|
310
|
+
"|" => Tok::Pipe,
|
|
311
|
+
"<-" => Tok::LArrow,
|
|
312
|
+
"->" => Tok::RArrow,
|
|
313
|
+
"@" => Tok::At,
|
|
314
|
+
"~" => Tok::Tilde,
|
|
315
|
+
"=>" => Tok::DArrow,
|
|
316
|
+
"!" => Tok::Bang,
|
|
317
|
+
_ => {
|
|
318
|
+
if sym.starts_with(':') {
|
|
319
|
+
Tok::ConSym(sym)
|
|
320
|
+
} else {
|
|
321
|
+
Tok::VarSym(sym)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
push(tok);
|
|
326
|
+
while i < j {
|
|
327
|
+
advance(&mut i, &mut line, &mut col, &chars);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
other => {
|
|
331
|
+
return Err(lex_error(
|
|
332
|
+
tl,
|
|
333
|
+
tc,
|
|
334
|
+
format!("unexpected character {:?}", other),
|
|
335
|
+
))
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
out.push(Token {
|
|
340
|
+
tok: Tok::Eof,
|
|
341
|
+
line,
|
|
342
|
+
col: 0,
|
|
343
|
+
});
|
|
344
|
+
Ok(out)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/// A `'` starts a char literal unless it is part of an identifier (`x'`);
|
|
348
|
+
/// identifiers are lexed before we get here, so any `'` seen is a literal.
|
|
349
|
+
fn is_char_literal_start(chars: &[char], i: usize) -> bool {
|
|
350
|
+
// 'x' or '\n' etc.
|
|
351
|
+
if i + 2 < chars.len() && chars[i + 1] != '\\' && chars[i + 2] == '\'' {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
if i + 1 < chars.len() && chars[i + 1] == '\\' {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
false
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
fn lex_number(chars: &[char], start: usize, line: usize, col: usize) -> Result<(Tok, usize)> {
|
|
361
|
+
let mut i = start;
|
|
362
|
+
if chars[i] == '0'
|
|
363
|
+
&& i + 1 < chars.len()
|
|
364
|
+
&& matches!(chars[i + 1], 'x' | 'X' | 'o' | 'O' | 'b' | 'B')
|
|
365
|
+
{
|
|
366
|
+
let radix = match chars[i + 1] {
|
|
367
|
+
'x' | 'X' => 16,
|
|
368
|
+
'o' | 'O' => 8,
|
|
369
|
+
_ => 2,
|
|
370
|
+
};
|
|
371
|
+
let mut j = i + 2;
|
|
372
|
+
let ds = j;
|
|
373
|
+
while j < chars.len() && (chars[j].is_digit(radix) || chars[j] == '_') {
|
|
374
|
+
j += 1;
|
|
375
|
+
}
|
|
376
|
+
let digits: String = chars[ds..j].iter().filter(|c| **c != '_').collect();
|
|
377
|
+
if digits.is_empty() {
|
|
378
|
+
return Err(lex_error(line, col, "malformed numeric literal"));
|
|
379
|
+
}
|
|
380
|
+
let v = u128::from_str_radix(&digits, radix)
|
|
381
|
+
.map_err(|_| lex_error(line, col, "numeric literal too large"))?;
|
|
382
|
+
return Ok((Tok::Int(v.to_string()), j));
|
|
383
|
+
}
|
|
384
|
+
while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '_') {
|
|
385
|
+
i += 1;
|
|
386
|
+
}
|
|
387
|
+
let mut is_float = false;
|
|
388
|
+
if i + 1 < chars.len() && chars[i] == '.' && chars[i + 1].is_ascii_digit() {
|
|
389
|
+
is_float = true;
|
|
390
|
+
i += 1;
|
|
391
|
+
while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '_') {
|
|
392
|
+
i += 1;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if i < chars.len() && (chars[i] == 'e' || chars[i] == 'E') {
|
|
396
|
+
let mut j = i + 1;
|
|
397
|
+
if j < chars.len() && (chars[j] == '+' || chars[j] == '-') {
|
|
398
|
+
j += 1;
|
|
399
|
+
}
|
|
400
|
+
if j < chars.len() && chars[j].is_ascii_digit() {
|
|
401
|
+
is_float = true;
|
|
402
|
+
while j < chars.len() && chars[j].is_ascii_digit() {
|
|
403
|
+
j += 1;
|
|
404
|
+
}
|
|
405
|
+
i = j;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
let text: String = chars[start..i].iter().filter(|c| **c != '_').collect();
|
|
409
|
+
Ok((
|
|
410
|
+
if is_float {
|
|
411
|
+
Tok::Float(text)
|
|
412
|
+
} else {
|
|
413
|
+
Tok::Int(text)
|
|
414
|
+
},
|
|
415
|
+
i,
|
|
416
|
+
))
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
fn lex_string(
|
|
420
|
+
chars: &[char],
|
|
421
|
+
start: usize,
|
|
422
|
+
quote: char,
|
|
423
|
+
line: usize,
|
|
424
|
+
col: usize,
|
|
425
|
+
) -> Result<(String, usize)> {
|
|
426
|
+
let mut i = start + 1;
|
|
427
|
+
let mut out = String::new();
|
|
428
|
+
loop {
|
|
429
|
+
if i >= chars.len() || chars[i] == '\n' {
|
|
430
|
+
return Err(lex_error(
|
|
431
|
+
line,
|
|
432
|
+
col,
|
|
433
|
+
"unterminated string or character literal",
|
|
434
|
+
));
|
|
435
|
+
}
|
|
436
|
+
let c = chars[i];
|
|
437
|
+
if c == quote {
|
|
438
|
+
return Ok((out, i + 1));
|
|
439
|
+
}
|
|
440
|
+
if c == '\\' {
|
|
441
|
+
i += 1;
|
|
442
|
+
if i >= chars.len() {
|
|
443
|
+
return Err(lex_error(line, col, "unterminated escape sequence"));
|
|
444
|
+
}
|
|
445
|
+
let e = chars[i];
|
|
446
|
+
match e {
|
|
447
|
+
'n' => out.push('\n'),
|
|
448
|
+
't' => out.push('\t'),
|
|
449
|
+
'r' => out.push('\r'),
|
|
450
|
+
'0' => out.push('\0'),
|
|
451
|
+
'a' => out.push('\u{7}'),
|
|
452
|
+
'b' => out.push('\u{8}'),
|
|
453
|
+
'f' => out.push('\u{c}'),
|
|
454
|
+
'v' => out.push('\u{b}'),
|
|
455
|
+
'\\' => out.push('\\'),
|
|
456
|
+
'"' => out.push('"'),
|
|
457
|
+
'\'' => out.push('\''),
|
|
458
|
+
'&' => {}
|
|
459
|
+
// string gap: backslash, whitespace (newlines allowed), backslash
|
|
460
|
+
' ' | '\t' | '\n' | '\r' => {
|
|
461
|
+
let mut j = i;
|
|
462
|
+
while j < chars.len() && matches!(chars[j], ' ' | '\t' | '\n' | '\r') {
|
|
463
|
+
j += 1;
|
|
464
|
+
}
|
|
465
|
+
if j >= chars.len() || chars[j] != '\\' {
|
|
466
|
+
return Err(lex_error(
|
|
467
|
+
line,
|
|
468
|
+
col,
|
|
469
|
+
"a string gap must end with a backslash",
|
|
470
|
+
));
|
|
471
|
+
}
|
|
472
|
+
i = j + 1;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
'^' => {
|
|
476
|
+
// control character: \^A .. \^Z, \^@, \^[, \^\\, \^], \^^, \^_
|
|
477
|
+
let c2 = chars.get(i + 1).copied().unwrap_or(' ');
|
|
478
|
+
let code = match c2 {
|
|
479
|
+
'@' => 0,
|
|
480
|
+
'A'..='Z' => (c2 as u32) - ('A' as u32) + 1,
|
|
481
|
+
'[' => 27,
|
|
482
|
+
'\\' => 28,
|
|
483
|
+
']' => 29,
|
|
484
|
+
'^' => 30,
|
|
485
|
+
'_' => 31,
|
|
486
|
+
_ => return Err(lex_error(line, col, "invalid control escape")),
|
|
487
|
+
};
|
|
488
|
+
out.push(char::from_u32(code).expect("control code"));
|
|
489
|
+
i += 2;
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
'o' => {
|
|
493
|
+
let mut j = i + 1;
|
|
494
|
+
let os = j;
|
|
495
|
+
while j < chars.len() && chars[j].is_digit(8) {
|
|
496
|
+
j += 1;
|
|
497
|
+
}
|
|
498
|
+
let oct: String = chars[os..j].iter().collect();
|
|
499
|
+
let cp = u32::from_str_radix(&oct, 8)
|
|
500
|
+
.ok()
|
|
501
|
+
.and_then(char::from_u32)
|
|
502
|
+
.ok_or_else(|| lex_error(line, col, "invalid octal escape"))?;
|
|
503
|
+
out.push(cp);
|
|
504
|
+
i = j;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
c2 if c2.is_ascii_uppercase() => {
|
|
508
|
+
// ASCII control names: \NUL, \SOH, ..., \DEL (longest match: \SOH before \SO)
|
|
509
|
+
let rest: String = chars[i..chars.len().min(i + 3)].iter().collect();
|
|
510
|
+
let (name, code) = ASCII_ESCAPES
|
|
511
|
+
.iter()
|
|
512
|
+
.filter(|(n, _)| rest.starts_with(n))
|
|
513
|
+
.max_by_key(|(n, _)| n.len())
|
|
514
|
+
.copied()
|
|
515
|
+
.ok_or_else(|| {
|
|
516
|
+
lex_error(line, col, format!("unknown escape sequence \\{}", c2))
|
|
517
|
+
})?;
|
|
518
|
+
out.push(char::from_u32(code).expect("ascii code"));
|
|
519
|
+
i += name.len();
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
'x' => {
|
|
523
|
+
let mut j = i + 1;
|
|
524
|
+
let hs = j;
|
|
525
|
+
while j < chars.len() && chars[j].is_ascii_hexdigit() {
|
|
526
|
+
j += 1;
|
|
527
|
+
}
|
|
528
|
+
let hex: String = chars[hs..j].iter().collect();
|
|
529
|
+
let cp = u32::from_str_radix(&hex, 16)
|
|
530
|
+
.ok()
|
|
531
|
+
.and_then(char::from_u32)
|
|
532
|
+
.ok_or_else(|| lex_error(line, col, "invalid hexadecimal escape"))?;
|
|
533
|
+
out.push(cp);
|
|
534
|
+
i = j;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
d if d.is_ascii_digit() => {
|
|
538
|
+
let mut j = i;
|
|
539
|
+
while j < chars.len() && chars[j].is_ascii_digit() {
|
|
540
|
+
j += 1;
|
|
541
|
+
}
|
|
542
|
+
let dec: String = chars[i..j].iter().collect();
|
|
543
|
+
let cp = dec
|
|
544
|
+
.parse::<u32>()
|
|
545
|
+
.ok()
|
|
546
|
+
.and_then(char::from_u32)
|
|
547
|
+
.ok_or_else(|| lex_error(line, col, "invalid decimal escape"))?;
|
|
548
|
+
out.push(cp);
|
|
549
|
+
i = j;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
other => {
|
|
553
|
+
return Err(lex_error(
|
|
554
|
+
line,
|
|
555
|
+
col,
|
|
556
|
+
format!("unknown escape sequence \\{}", other),
|
|
557
|
+
))
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
i += 1;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
out.push(c);
|
|
564
|
+
i += 1;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/// Haskell's named ASCII escapes.
|
|
569
|
+
const ASCII_ESCAPES: &[(&str, u32)] = &[
|
|
570
|
+
("NUL", 0),
|
|
571
|
+
("SOH", 1),
|
|
572
|
+
("STX", 2),
|
|
573
|
+
("ETX", 3),
|
|
574
|
+
("EOT", 4),
|
|
575
|
+
("ENQ", 5),
|
|
576
|
+
("ACK", 6),
|
|
577
|
+
("BEL", 7),
|
|
578
|
+
("BS", 8),
|
|
579
|
+
("HT", 9),
|
|
580
|
+
("LF", 10),
|
|
581
|
+
("VT", 11),
|
|
582
|
+
("FF", 12),
|
|
583
|
+
("CR", 13),
|
|
584
|
+
("SO", 14),
|
|
585
|
+
("SI", 15),
|
|
586
|
+
("DLE", 16),
|
|
587
|
+
("DC1", 17),
|
|
588
|
+
("DC2", 18),
|
|
589
|
+
("DC3", 19),
|
|
590
|
+
("DC4", 20),
|
|
591
|
+
("NAK", 21),
|
|
592
|
+
("SYN", 22),
|
|
593
|
+
("ETB", 23),
|
|
594
|
+
("CAN", 24),
|
|
595
|
+
("EM", 25),
|
|
596
|
+
("SUB", 26),
|
|
597
|
+
("ESC", 27),
|
|
598
|
+
("FS", 28),
|
|
599
|
+
("GS", 29),
|
|
600
|
+
("RS", 30),
|
|
601
|
+
("US", 31),
|
|
602
|
+
("SP", 32),
|
|
603
|
+
("DEL", 127),
|
|
604
|
+
];
|
|
605
|
+
|
|
606
|
+
#[cfg(test)]
|
|
607
|
+
mod tests {
|
|
608
|
+
use super::*;
|
|
609
|
+
|
|
610
|
+
#[test]
|
|
611
|
+
fn escapes_and_gaps() {
|
|
612
|
+
let t = tokenize(r#""a\o101\ESC\^A\SOH\&b\ \c" 'x' '\'' '\DEL'"#).unwrap();
|
|
613
|
+
assert_eq!(t[0].tok, Tok::Str("aA\u{1b}\u{1}\u{1}bc".to_string()));
|
|
614
|
+
assert_eq!(t[1].tok, Tok::Char('x'));
|
|
615
|
+
assert_eq!(t[2].tok, Tok::Char('\''));
|
|
616
|
+
assert_eq!(t[3].tok, Tok::Char('\u{7f}'));
|
|
617
|
+
assert!(tokenize("\"\\q\"").is_err());
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
fn toks(s: &str) -> Vec<Tok> {
|
|
621
|
+
tokenize(s).unwrap().into_iter().map(|t| t.tok).collect()
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
#[test]
|
|
625
|
+
fn lexes_declarations() {
|
|
626
|
+
assert_eq!(
|
|
627
|
+
toks("f (x:xs) = x + sum xs `div` 2 -- c\n"),
|
|
628
|
+
vec![
|
|
629
|
+
Tok::VarId("f".into()),
|
|
630
|
+
Tok::LParen,
|
|
631
|
+
Tok::VarId("x".into()),
|
|
632
|
+
Tok::ConSym(":".into()),
|
|
633
|
+
Tok::VarId("xs".into()),
|
|
634
|
+
Tok::RParen,
|
|
635
|
+
Tok::Equals,
|
|
636
|
+
Tok::VarId("x".into()),
|
|
637
|
+
Tok::VarSym("+".into()),
|
|
638
|
+
Tok::VarId("sum".into()),
|
|
639
|
+
Tok::VarId("xs".into()),
|
|
640
|
+
Tok::Backtick,
|
|
641
|
+
Tok::VarId("div".into()),
|
|
642
|
+
Tok::Backtick,
|
|
643
|
+
Tok::Int("2".into()),
|
|
644
|
+
Tok::Eof,
|
|
645
|
+
]
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
#[test]
|
|
650
|
+
fn positions_and_literals() {
|
|
651
|
+
let ts = tokenize("x = 'a'\n y = \"s\\n\" 1.5").unwrap();
|
|
652
|
+
assert_eq!((ts[0].line, ts[0].col), (1, 1));
|
|
653
|
+
assert_eq!(ts[2].tok, Tok::Char('a'));
|
|
654
|
+
assert_eq!((ts[3].line, ts[3].col), (2, 3));
|
|
655
|
+
assert_eq!(ts[5].tok, Tok::Str("s\n".into()));
|
|
656
|
+
assert_eq!(ts[6].tok, Tok::Float("1.5".into()));
|
|
657
|
+
assert_eq!(
|
|
658
|
+
toks("x' y_1 Just Data.Map.lookup -> <- :: .. => \\ | @ ~ !")[..],
|
|
659
|
+
[
|
|
660
|
+
Tok::VarId("x'".into()),
|
|
661
|
+
Tok::VarId("y_1".into()),
|
|
662
|
+
Tok::ConId("Just".into()),
|
|
663
|
+
Tok::VarId("lookup".into()),
|
|
664
|
+
Tok::RArrow,
|
|
665
|
+
Tok::LArrow,
|
|
666
|
+
Tok::DoubleColon,
|
|
667
|
+
Tok::DotDot,
|
|
668
|
+
Tok::DArrow,
|
|
669
|
+
Tok::Backslash,
|
|
670
|
+
Tok::Pipe,
|
|
671
|
+
Tok::At,
|
|
672
|
+
Tok::Tilde,
|
|
673
|
+
Tok::Bang,
|
|
674
|
+
Tok::Eof
|
|
675
|
+
]
|
|
676
|
+
);
|
|
677
|
+
assert_eq!(toks("a --> b")[1], Tok::VarSym("-->".into())); // not a comment
|
|
678
|
+
assert_eq!(toks("{- {- nested -} -} z")[0], Tok::VarId("z".into()));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
#[test]
|
|
682
|
+
fn errors() {
|
|
683
|
+
assert!(tokenize("\"abc").is_err());
|
|
684
|
+
assert!(tokenize("'ab'").is_err());
|
|
685
|
+
assert!(tokenize("{- open").is_err());
|
|
686
|
+
assert!(tokenize("§").is_err());
|
|
687
|
+
}
|
|
688
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//! A Haskell front end: a positioned lexer, the layout algorithm, and a
|
|
2
|
+
//! parser for the subset of Haskell 2010 that haskell_match compiles to Ruby
|
|
3
|
+
//! (data declarations, type signatures, function equations with guards and
|
|
4
|
+
//! `where`, and expressions). The parser emits a JSON AST consumed by the
|
|
5
|
+
//! Ruby code generator in `lib/haskell_match/haskell/`.
|
|
6
|
+
|
|
7
|
+
pub mod ast;
|
|
8
|
+
pub mod json;
|
|
9
|
+
pub mod layout;
|
|
10
|
+
pub mod lexer;
|
|
11
|
+
pub mod parser;
|
|
12
|
+
|
|
13
|
+
pub use ast::*;
|
|
14
|
+
pub use parser::parse_module;
|