Commit | Line | Data |
---|---|---|
3934e3f8 HT |
1 | import 'types.dart'; |
2 | ||
3 | final malRegExp = new RegExp( | |
4aa0ebdf | 4 | r"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)"""); |
3934e3f8 HT |
5 | |
6 | class Reader { | |
7 | final List<String> tokens; | |
8 | int _position = 0; | |
9 | ||
10 | Reader(this.tokens); | |
11 | ||
12 | String next() { | |
13 | var token = peek(); | |
14 | _position++; | |
15 | return token; | |
16 | } | |
17 | ||
18 | String peek() { | |
19 | if (_position >= tokens.length) return null; | |
20 | return tokens[_position]; | |
21 | } | |
22 | } | |
23 | ||
24 | class ParseException implements Exception { | |
25 | final String message; | |
26 | ||
27 | ParseException(this.message); | |
28 | } | |
29 | ||
30 | class NoInputException implements Exception {} | |
31 | ||
32 | MalType read_str(String code) { | |
33 | var tokens = tokenizer(code); | |
34 | if (tokens.isEmpty) { | |
35 | throw new NoInputException(); | |
36 | } | |
37 | var reader = new Reader(tokens); | |
38 | return read_form(reader); | |
39 | } | |
40 | ||
41 | List<String> tokenizer(String code) { | |
42 | var matches = malRegExp.allMatches(code); | |
43 | return matches | |
44 | .map((m) => m.group(1)) | |
45 | .where((token) => token.isNotEmpty && !token.startsWith(';')) | |
46 | .toList(); | |
47 | } | |
48 | ||
49 | MalType read_form(Reader reader) { | |
50 | const macros = const <String, String>{ | |
51 | "'": 'quote', | |
52 | '`': 'quasiquote', | |
53 | '~': 'unquote', | |
54 | '~@': 'splice-unquote', | |
55 | '@': 'deref', | |
56 | '^': 'with-meta', | |
57 | }; | |
58 | const sequenceStarters = const <String, String>{'(': ')', '[': ']', '{': '}'}; | |
59 | var token = reader.peek(); | |
60 | if (sequenceStarters.containsKey(token)) { | |
61 | var elements = read_sequence(reader, token, sequenceStarters[token]); | |
62 | if (token == '(') { | |
63 | return new MalList(elements); | |
64 | } | |
65 | if (token == '[') { | |
66 | return new MalVector(elements); | |
67 | } | |
68 | ||
69 | if (token == '{') { | |
70 | return new MalHashMap.fromSequence(elements); | |
71 | } | |
72 | ||
73 | throw new StateError("Impossible!"); | |
74 | } else if (macros.containsKey(token)) { | |
75 | var macro = new MalSymbol(macros[token]); | |
76 | reader.next(); | |
77 | var form = read_form(reader); | |
78 | if (token == '^') { | |
79 | var meta = read_form(reader); | |
80 | return new MalList([macro, meta, form]); | |
81 | } else { | |
82 | return new MalList([macro, form]); | |
83 | } | |
84 | } else { | |
85 | return read_atom(reader); | |
86 | } | |
87 | } | |
88 | ||
89 | List<MalType> read_sequence(Reader reader, String open, String close) { | |
90 | // Consume opening token | |
91 | var actualOpen = reader.next(); | |
92 | assert(actualOpen == open); | |
93 | ||
94 | var elements = <MalType>[]; | |
95 | for (var token = reader.peek();; token = reader.peek()) { | |
96 | if (token == null) { | |
97 | throw new ParseException("expected '$close', got EOF"); | |
98 | } | |
99 | if (token == close) break; | |
100 | elements.add(read_form(reader)); | |
101 | } | |
102 | ||
103 | var actualClose = reader.next(); | |
104 | assert(actualClose == close); | |
105 | ||
106 | return elements; | |
107 | } | |
108 | ||
109 | MalType read_atom(Reader reader) { | |
110 | var token = reader.next(); | |
111 | ||
112 | var intAtom = int.parse(token, onError: (_) => null); | |
113 | if (intAtom != null) { | |
114 | return new MalInt(intAtom); | |
115 | } | |
116 | ||
117 | if (token[0] == '"') { | |
4aa0ebdf JM |
118 | if (token[token.length -1 ] != '"') { |
119 | throw new ParseException("expected '\"', got EOF"); | |
120 | } | |
3934e3f8 HT |
121 | var sanitizedToken = token |
122 | // remove surrounding quotes | |
123 | .substring(1, token.length - 1) | |
a7d9ace3 JM |
124 | .replaceAllMapped(new RegExp("\\\\(.)"), |
125 | (Match m) => m[1] == 'n' ? '\n' : m[1]); | |
3934e3f8 HT |
126 | return new MalString(sanitizedToken); |
127 | } | |
128 | ||
129 | if (token[0] == ':') { | |
130 | return new MalKeyword(token.substring(1)); | |
131 | } | |
132 | ||
133 | if (token == 'nil') { | |
134 | return new MalNil(); | |
135 | } | |
136 | ||
137 | if (token == 'true') { | |
138 | return new MalBool(true); | |
139 | } | |
140 | ||
141 | if (token == 'false') { | |
142 | return new MalBool(false); | |
143 | } | |
144 | ||
145 | return new MalSymbol(token); | |
146 | } |