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,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;