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,590 @@
1
+ //! Tokeniser shared by the pattern parser and the `data` declaration parser.
2
+
3
+ use super::error::{CoreError, Result};
4
+
5
+ #[derive(Clone, Debug, PartialEq)]
6
+ pub enum Tok {
7
+ /// An infix constructor operator such as `:+:` (always starts with `:`).
8
+ ConSym(String),
9
+ LParen,
10
+ RParen,
11
+ LBracket,
12
+ RBracket,
13
+ LBrace,
14
+ RBrace,
15
+ Comma,
16
+ Colon,
17
+ DoubleColon,
18
+ Equals,
19
+ At,
20
+ Tilde,
21
+ Bang,
22
+ Minus,
23
+ Pipe,
24
+ Arrow,
25
+ DotDot,
26
+ Underscore,
27
+ /// Lower-case identifier (variable / field / type variable).
28
+ VarId(String),
29
+ /// Upper-case identifier (constructor / type), possibly qualified
30
+ /// (`Data.Maybe.Just` is reported as `Just`; qualification is ignored).
31
+ ConId(String),
32
+ Int(String),
33
+ Float(String),
34
+ Str(String),
35
+ Char(String),
36
+ Sym(String),
37
+ }
38
+
39
+ #[derive(Clone, Debug, PartialEq)]
40
+ pub struct Token {
41
+ pub tok: Tok,
42
+ pub pos: usize,
43
+ }
44
+
45
+ pub fn tokenize(src: &str) -> Result<Vec<Token>> {
46
+ let chars: Vec<char> = src.chars().collect();
47
+ let mut out = Vec::new();
48
+ let mut i = 0;
49
+ while i < chars.len() {
50
+ let c = chars[i];
51
+ let pos = i;
52
+ let push = |out: &mut Vec<Token>, tok: Tok| out.push(Token { tok, pos });
53
+ match c {
54
+ ' ' | '\t' | '\n' | '\r' => {
55
+ i += 1;
56
+ }
57
+ '-' if i + 1 < chars.len() && chars[i + 1] == '-' => {
58
+ // line comment
59
+ while i < chars.len() && chars[i] != '\n' {
60
+ i += 1;
61
+ }
62
+ }
63
+ '{' if i + 1 < chars.len() && chars[i + 1] == '-' => {
64
+ // block comment (nested)
65
+ let mut depth = 0;
66
+ loop {
67
+ if i + 1 >= chars.len() {
68
+ return Err(CoreError::syntax(format!(
69
+ "unterminated block comment starting at column {}",
70
+ pos + 1
71
+ )));
72
+ }
73
+ if chars[i] == '{' && chars[i + 1] == '-' {
74
+ depth += 1;
75
+ i += 2;
76
+ } else if chars[i] == '-' && chars[i + 1] == '}' {
77
+ depth -= 1;
78
+ i += 2;
79
+ if depth == 0 {
80
+ break;
81
+ }
82
+ } else {
83
+ i += 1;
84
+ }
85
+ }
86
+ }
87
+ '(' => {
88
+ push(&mut out, Tok::LParen);
89
+ i += 1;
90
+ }
91
+ ')' => {
92
+ push(&mut out, Tok::RParen);
93
+ i += 1;
94
+ }
95
+ '[' => {
96
+ push(&mut out, Tok::LBracket);
97
+ i += 1;
98
+ }
99
+ ']' => {
100
+ push(&mut out, Tok::RBracket);
101
+ i += 1;
102
+ }
103
+ '{' => {
104
+ push(&mut out, Tok::LBrace);
105
+ i += 1;
106
+ }
107
+ '}' => {
108
+ push(&mut out, Tok::RBrace);
109
+ i += 1;
110
+ }
111
+ ',' => {
112
+ push(&mut out, Tok::Comma);
113
+ i += 1;
114
+ }
115
+ '@' => {
116
+ push(&mut out, Tok::At);
117
+ i += 1;
118
+ }
119
+ '~' => {
120
+ push(&mut out, Tok::Tilde);
121
+ i += 1;
122
+ }
123
+ '!' => {
124
+ push(&mut out, Tok::Bang);
125
+ i += 1;
126
+ }
127
+ '|' => {
128
+ push(&mut out, Tok::Pipe);
129
+ i += 1;
130
+ }
131
+ '=' => {
132
+ push(&mut out, Tok::Equals);
133
+ i += 1;
134
+ }
135
+ '-' if i + 1 < chars.len() && chars[i + 1] == '>' => {
136
+ push(&mut out, Tok::Arrow);
137
+ i += 2;
138
+ }
139
+ '-' => {
140
+ push(&mut out, Tok::Minus);
141
+ i += 1;
142
+ }
143
+ '.' if i + 1 < chars.len() && chars[i + 1] == '.' => {
144
+ push(&mut out, Tok::DotDot);
145
+ i += 2;
146
+ }
147
+ ':' if i + 1 < chars.len() && chars[i + 1] == ':' => {
148
+ push(&mut out, Tok::DoubleColon);
149
+ i += 2;
150
+ }
151
+ ':' if i + 1 < chars.len()
152
+ && (chars[i + 1].is_alphabetic() || chars[i + 1] == '_')
153
+ && symbol_position(&chars, i) =>
154
+ {
155
+ // Ruby symbol literal extension: `:foo`, `:foo?`, `:foo!`, `:Foo`
156
+ let start = i + 1;
157
+ i += 1;
158
+ while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
159
+ i += 1;
160
+ }
161
+ if i < chars.len() && (chars[i] == '?' || chars[i] == '!' || chars[i] == '=') {
162
+ // `=` is part of a symbol only when not followed by another `=`
163
+ if chars[i] != '=' || i + 1 >= chars.len() || chars[i + 1] != '=' {
164
+ i += 1;
165
+ }
166
+ }
167
+ let name: String = chars[start..i].iter().collect();
168
+ push(&mut out, Tok::Sym(name));
169
+ }
170
+ ':' if i + 1 < chars.len() && chars[i + 1] == '"' && symbol_position(&chars, i) => {
171
+ // `:"quoted symbol"`
172
+ let (s, next) = lex_string(&chars, i + 1, '"')?;
173
+ push(&mut out, Tok::Sym(s));
174
+ i = next;
175
+ }
176
+ ':' if i + 1 < chars.len() && is_consym_char(chars[i + 1]) => {
177
+ // an infix constructor such as `:+:` or `:|`
178
+ let start = i;
179
+ i += 1;
180
+ while i < chars.len() && is_consym_char(chars[i]) {
181
+ i += 1;
182
+ }
183
+ let sym: String = chars[start..i].iter().collect();
184
+ push(&mut out, Tok::ConSym(sym));
185
+ }
186
+ ':' => {
187
+ push(&mut out, Tok::Colon);
188
+ i += 1;
189
+ }
190
+ '"' => {
191
+ let (s, next) = lex_string(&chars, i, '"')?;
192
+ push(&mut out, Tok::Str(s));
193
+ i = next;
194
+ }
195
+ '\'' => {
196
+ let (s, next) = lex_string(&chars, i, '\'')?;
197
+ if s.chars().count() != 1 {
198
+ return Err(CoreError::syntax(format!(
199
+ "character literal at column {} must contain exactly one character",
200
+ pos + 1
201
+ )));
202
+ }
203
+ push(&mut out, Tok::Char(s));
204
+ i = next;
205
+ }
206
+ '_' if !(i + 1 < chars.len() && is_ident_char(chars[i + 1])) => {
207
+ push(&mut out, Tok::Underscore);
208
+ i += 1;
209
+ }
210
+ c if c.is_ascii_digit() => {
211
+ let (tok, next) = lex_number(&chars, i)?;
212
+ push(&mut out, tok);
213
+ i = next;
214
+ }
215
+ c if c.is_alphabetic() || c == '_' => {
216
+ let start = i;
217
+ while i < chars.len() && (is_ident_char(chars[i]) || chars[i] == '\'') {
218
+ i += 1;
219
+ }
220
+ let mut name: String = chars[start..i].iter().collect();
221
+ if c.is_uppercase() {
222
+ // qualified name: `Mod.Sub.Con` — consume dotted segments
223
+ while i + 1 < chars.len()
224
+ && chars[i] == '.'
225
+ && (chars[i + 1].is_alphabetic() || chars[i + 1] == '_')
226
+ {
227
+ i += 1;
228
+ let seg_start = i;
229
+ while i < chars.len() && (is_ident_char(chars[i]) || chars[i] == '\'') {
230
+ i += 1;
231
+ }
232
+ let seg: String = chars[seg_start..i].iter().collect();
233
+ if seg
234
+ .chars()
235
+ .next()
236
+ .map(|c| c.is_uppercase())
237
+ .unwrap_or(false)
238
+ {
239
+ name = seg;
240
+ } else {
241
+ return Err(CoreError::syntax(format!(
242
+ "qualified name at column {} must end in a constructor",
243
+ pos + 1
244
+ )));
245
+ }
246
+ }
247
+ push(&mut out, Tok::ConId(name));
248
+ } else {
249
+ push(&mut out, Tok::VarId(name));
250
+ }
251
+ }
252
+ other => {
253
+ return Err(CoreError::syntax(format!(
254
+ "unexpected character {:?} at column {}",
255
+ other,
256
+ pos + 1
257
+ )));
258
+ }
259
+ }
260
+ }
261
+ Ok(out)
262
+ }
263
+
264
+ /// A `:` starts a Ruby symbol literal (an extension) only when it is not
265
+ /// directly attached to a preceding operand: `(x:xs)` and `x : xs` are cons,
266
+ /// `:ok`, `[:a, :b]`, `Just :ok` and `(Pair :a :b)` are symbols.
267
+ /// Characters that may follow the leading `:` of an infix constructor.
268
+ fn is_consym_char(c: char) -> bool {
269
+ "!#$%&*+./<=>?@\\^|-~:".contains(c)
270
+ }
271
+
272
+ fn symbol_position(chars: &[char], i: usize) -> bool {
273
+ if i == 0 {
274
+ return true;
275
+ }
276
+ matches!(
277
+ chars[i - 1],
278
+ ' ' | '\t' | '\n' | '\r' | '(' | '[' | ',' | '=' | '|' | '{'
279
+ )
280
+ }
281
+
282
+ fn is_ident_char(c: char) -> bool {
283
+ c.is_alphanumeric() || c == '_'
284
+ }
285
+
286
+ fn lex_number(chars: &[char], start: usize) -> Result<(Tok, usize)> {
287
+ let mut i = start;
288
+ // hexadecimal / octal / binary integers
289
+ if chars[i] == '0' && i + 1 < chars.len() {
290
+ let radix = match chars[i + 1] {
291
+ 'x' | 'X' => Some(16),
292
+ 'o' | 'O' => Some(8),
293
+ 'b' | 'B' => Some(2),
294
+ _ => None,
295
+ };
296
+ if let Some(radix) = radix {
297
+ let mut j = i + 2;
298
+ let digits_start = j;
299
+ while j < chars.len() && (chars[j].is_digit(radix) || chars[j] == '_') {
300
+ j += 1;
301
+ }
302
+ let digits: String = chars[digits_start..j]
303
+ .iter()
304
+ .filter(|c| **c != '_')
305
+ .collect();
306
+ if digits.is_empty() {
307
+ return Err(CoreError::syntax(format!(
308
+ "malformed numeric literal at column {}",
309
+ start + 1
310
+ )));
311
+ }
312
+ let value = u128::from_str_radix(&digits, radix).map_err(|_| {
313
+ CoreError::syntax(format!(
314
+ "numeric literal at column {} is too large",
315
+ start + 1
316
+ ))
317
+ })?;
318
+ return Ok((Tok::Int(value.to_string()), j));
319
+ }
320
+ }
321
+ while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '_') {
322
+ i += 1;
323
+ }
324
+ let mut is_float = false;
325
+ if i + 1 < chars.len() && chars[i] == '.' && chars[i + 1].is_ascii_digit() {
326
+ is_float = true;
327
+ i += 1;
328
+ while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '_') {
329
+ i += 1;
330
+ }
331
+ }
332
+ if i < chars.len() && (chars[i] == 'e' || chars[i] == 'E') {
333
+ let mut j = i + 1;
334
+ if j < chars.len() && (chars[j] == '+' || chars[j] == '-') {
335
+ j += 1;
336
+ }
337
+ if j < chars.len() && chars[j].is_ascii_digit() {
338
+ is_float = true;
339
+ while j < chars.len() && chars[j].is_ascii_digit() {
340
+ j += 1;
341
+ }
342
+ i = j;
343
+ }
344
+ }
345
+ let text: String = chars[start..i].iter().filter(|c| **c != '_').collect();
346
+ if is_float {
347
+ Ok((Tok::Float(text), i))
348
+ } else {
349
+ Ok((Tok::Int(text), i))
350
+ }
351
+ }
352
+
353
+ /// Lex a quoted literal starting at `start` (which holds the opening quote).
354
+ /// Returns the unescaped contents and the index just past the closing quote.
355
+ fn lex_string(chars: &[char], start: usize, quote: char) -> Result<(String, usize)> {
356
+ let mut i = start + 1;
357
+ let mut out = String::new();
358
+ loop {
359
+ if i >= chars.len() {
360
+ return Err(CoreError::syntax(format!(
361
+ "unterminated {} literal starting at column {}",
362
+ if quote == '"' { "string" } else { "character" },
363
+ start + 1
364
+ )));
365
+ }
366
+ let c = chars[i];
367
+ if c == quote {
368
+ return Ok((out, i + 1));
369
+ }
370
+ if c == '\\' {
371
+ i += 1;
372
+ if i >= chars.len() {
373
+ return Err(CoreError::syntax("unterminated escape sequence"));
374
+ }
375
+ let e = chars[i];
376
+ match e {
377
+ 'n' => out.push('\n'),
378
+ 't' => out.push('\t'),
379
+ 'r' => out.push('\r'),
380
+ '0' => out.push('\0'),
381
+ 'a' => out.push('\u{7}'),
382
+ 'b' => out.push('\u{8}'),
383
+ 'f' => out.push('\u{c}'),
384
+ 'v' => out.push('\u{b}'),
385
+ '\\' => out.push('\\'),
386
+ '"' => out.push('"'),
387
+ '\'' => out.push('\''),
388
+ '&' => {} // Haskell empty escape
389
+ 'x' | 'u' => {
390
+ // hex escape: \xHH.. or \uHHHH / \u{...}
391
+ let mut j = i + 1;
392
+ let braces = j < chars.len() && chars[j] == '{';
393
+ if braces {
394
+ j += 1;
395
+ }
396
+ let hs = j;
397
+ while j < chars.len() && chars[j].is_ascii_hexdigit() {
398
+ j += 1;
399
+ }
400
+ let hex: String = chars[hs..j].iter().collect();
401
+ if braces {
402
+ if j < chars.len() && chars[j] == '}' {
403
+ j += 1;
404
+ } else {
405
+ return Err(CoreError::syntax("unterminated \\u{...} escape"));
406
+ }
407
+ }
408
+ let cp = u32::from_str_radix(&hex, 16)
409
+ .ok()
410
+ .and_then(char::from_u32)
411
+ .ok_or_else(|| CoreError::syntax("invalid hexadecimal escape"))?;
412
+ out.push(cp);
413
+ i = j;
414
+ continue;
415
+ }
416
+ d if d.is_ascii_digit() => {
417
+ let mut j = i;
418
+ while j < chars.len() && chars[j].is_ascii_digit() {
419
+ j += 1;
420
+ }
421
+ let dec: String = chars[i..j].iter().collect();
422
+ let cp = dec
423
+ .parse::<u32>()
424
+ .ok()
425
+ .and_then(char::from_u32)
426
+ .ok_or_else(|| CoreError::syntax("invalid decimal escape"))?;
427
+ out.push(cp);
428
+ i = j;
429
+ continue;
430
+ }
431
+ other => {
432
+ return Err(CoreError::syntax(format!(
433
+ "unknown escape sequence \\{}",
434
+ other
435
+ )))
436
+ }
437
+ }
438
+ i += 1;
439
+ continue;
440
+ }
441
+ out.push(c);
442
+ i += 1;
443
+ }
444
+ }
445
+
446
+ #[cfg(test)]
447
+ mod tests {
448
+ use super::*;
449
+
450
+ fn toks(s: &str) -> Vec<Tok> {
451
+ tokenize(s).unwrap().into_iter().map(|t| t.tok).collect()
452
+ }
453
+
454
+ #[test]
455
+ fn punctuation_and_identifiers() {
456
+ assert_eq!(
457
+ toks("(x:xs) _ Just a@(y, _) [] ~p !q"),
458
+ vec![
459
+ Tok::LParen,
460
+ Tok::VarId("x".into()),
461
+ Tok::Colon,
462
+ Tok::VarId("xs".into()),
463
+ Tok::RParen,
464
+ Tok::Underscore,
465
+ Tok::ConId("Just".into()),
466
+ Tok::VarId("a".into()),
467
+ Tok::At,
468
+ Tok::LParen,
469
+ Tok::VarId("y".into()),
470
+ Tok::Comma,
471
+ Tok::Underscore,
472
+ Tok::RParen,
473
+ Tok::LBracket,
474
+ Tok::RBracket,
475
+ Tok::Tilde,
476
+ Tok::VarId("p".into()),
477
+ Tok::Bang,
478
+ Tok::VarId("q".into()),
479
+ ]
480
+ );
481
+ }
482
+
483
+ #[test]
484
+ fn literals() {
485
+ assert_eq!(
486
+ toks(r#"0 42 1.5 2e3 0x1F "hi\n" 'c' :sym :"quoted sym" -7 _x x'"#),
487
+ vec![
488
+ Tok::Int("0".into()),
489
+ Tok::Int("42".into()),
490
+ Tok::Float("1.5".into()),
491
+ Tok::Float("2e3".into()),
492
+ Tok::Int("31".into()),
493
+ Tok::Str("hi\n".into()),
494
+ Tok::Char("c".into()),
495
+ Tok::Sym("sym".into()),
496
+ Tok::Sym("quoted sym".into()),
497
+ Tok::Minus,
498
+ Tok::Int("7".into()),
499
+ Tok::VarId("_x".into()),
500
+ Tok::VarId("x'".into()),
501
+ ]
502
+ );
503
+ }
504
+
505
+ #[test]
506
+ fn data_decl_tokens() {
507
+ assert_eq!(
508
+ toks("data Maybe a = Nothing | Just a -- comment\n{- block {- nested -} -} deriving (Show)"),
509
+ vec![
510
+ Tok::VarId("data".into()),
511
+ Tok::ConId("Maybe".into()),
512
+ Tok::VarId("a".into()),
513
+ Tok::Equals,
514
+ Tok::ConId("Nothing".into()),
515
+ Tok::Pipe,
516
+ Tok::ConId("Just".into()),
517
+ Tok::VarId("a".into()),
518
+ Tok::VarId("deriving".into()),
519
+ Tok::LParen,
520
+ Tok::ConId("Show".into()),
521
+ Tok::RParen,
522
+ ]
523
+ );
524
+ assert_eq!(
525
+ toks("P { name :: String, age :: Int } -> ..")[..],
526
+ [
527
+ Tok::ConId("P".into()),
528
+ Tok::LBrace,
529
+ Tok::VarId("name".into()),
530
+ Tok::DoubleColon,
531
+ Tok::ConId("String".into()),
532
+ Tok::Comma,
533
+ Tok::VarId("age".into()),
534
+ Tok::DoubleColon,
535
+ Tok::ConId("Int".into()),
536
+ Tok::RBrace,
537
+ Tok::Arrow,
538
+ Tok::DotDot,
539
+ ]
540
+ );
541
+ }
542
+
543
+ #[test]
544
+ fn colon_versus_symbol() {
545
+ assert_eq!(
546
+ toks("x:xs"),
547
+ vec![Tok::VarId("x".into()), Tok::Colon, Tok::VarId("xs".into())]
548
+ );
549
+ assert_eq!(
550
+ toks("x : xs"),
551
+ vec![Tok::VarId("x".into()), Tok::Colon, Tok::VarId("xs".into())]
552
+ );
553
+ assert_eq!(
554
+ toks("Just :ok"),
555
+ vec![Tok::ConId("Just".into()), Tok::Sym("ok".into())]
556
+ );
557
+ assert_eq!(
558
+ toks("[:a,:b]"),
559
+ vec![
560
+ Tok::LBracket,
561
+ Tok::Sym("a".into()),
562
+ Tok::Comma,
563
+ Tok::Sym("b".into()),
564
+ Tok::RBracket
565
+ ]
566
+ );
567
+ assert_eq!(
568
+ toks("x:_"),
569
+ vec![Tok::VarId("x".into()), Tok::Colon, Tok::Underscore]
570
+ );
571
+ }
572
+
573
+ #[test]
574
+ fn qualified_constructor() {
575
+ assert_eq!(
576
+ toks("Data.Maybe.Just x"),
577
+ vec![Tok::ConId("Just".into()), Tok::VarId("x".into())]
578
+ );
579
+ }
580
+
581
+ #[test]
582
+ fn errors() {
583
+ assert!(tokenize("\"abc").is_err());
584
+ assert!(tokenize("'ab'").is_err());
585
+ assert!(tokenize("x $ y").is_err());
586
+ assert!(tokenize("{- never closed").is_err());
587
+ assert!(tokenize("\"\\q\"").is_err());
588
+ assert!(tokenize("0x").is_err());
589
+ }
590
+ }
@@ -0,0 +1,19 @@
1
+ //! Pure-Rust core: no Ruby dependencies. Everything in this module is unit
2
+ //! tested with plain `cargo test` and is shared between the Ruby bindings and
3
+ //! the test suite.
4
+
5
+ pub mod ast;
6
+ pub mod error;
7
+ pub mod exhaust;
8
+ pub mod hs;
9
+ pub mod lexer;
10
+ pub mod parser;
11
+ pub mod pretty;
12
+ pub mod resolve;
13
+ pub mod tree;
14
+ pub mod typecheck;
15
+ pub mod types;
16
+
17
+ pub use ast::{Lit, LitKey, LitKind, Pat, RawPat, VarId};
18
+ pub use error::{CoreError, ErrorKind};
19
+ pub use types::{ConId, Constructor, DataType, TypeEnv, TypeId, TypeKind};