]> git.lizzy.rs Git - nothing.git/blob - src/script/parser.c
TODO(#391)
[nothing.git] / src / script / parser.c
1 #include <assert.h>
2 #include <ctype.h>
3 #include <errno.h>
4 #include <stdint.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <inttypes.h>
9
10 #include "script/parser.h"
11 #include "system/lt.h"
12 #include "system/lt/lt_adapters.h"
13
14 #define MAX_BUFFER_LENGTH (5 * 1000 * 1000)
15
16 static struct ParseResult parse_expr(Gc *gc, struct Token current_token);
17
18 static struct ParseResult parse_cdr(Gc *gc, struct Token current_token)
19 {
20     if (*current_token.begin != '.') {
21         return parse_failure("Expected .", current_token.begin);
22     }
23
24     struct ParseResult cdr = read_expr_from_string(gc, current_token.end);
25     if (cdr.is_error) {
26         return cdr;
27     }
28
29     current_token = next_token(cdr.end);
30
31     if (*current_token.begin != ')') {
32         return parse_failure("Expected )", current_token.begin);
33     }
34
35     return parse_success(cdr.expr, current_token.end);
36 }
37
38 static struct ParseResult parse_list_end(Gc *gc, struct Token current_token)
39 {
40     if (*current_token.begin != ')') {
41         return parse_failure("Expected )", current_token.begin);
42     }
43
44     return parse_success(atom_as_expr(create_symbol_atom(gc, "nil", NULL)),
45                          current_token.end);
46 }
47
48 static struct ParseResult parse_list(Gc *gc, struct Token current_token)
49 {
50     if (*current_token.begin != '(') {
51         return parse_failure("Expected (", current_token.begin);
52     }
53
54     current_token = next_token(current_token.end);
55
56     if (*current_token.begin == ')') {
57         return parse_list_end(gc, current_token);
58     }
59
60     struct ParseResult car = parse_expr(gc, current_token);
61     if (car.is_error) {
62         return car;
63     }
64
65     struct Cons *list = create_cons(gc, car.expr, void_expr());
66     struct Cons *cons = list;
67     current_token = next_token(car.end);
68
69     while (*current_token.begin != '.' &&
70            *current_token.begin != ')' &&
71            *current_token.begin != 0) {
72         car = parse_expr(gc, current_token);
73         if (car.is_error) {
74             return car;
75         }
76
77         cons->cdr = cons_as_expr(create_cons(gc, car.expr, void_expr()));
78         cons = cons->cdr.cons;
79
80         current_token = next_token(car.end);
81     }
82
83     struct ParseResult cdr = *current_token.begin == '.'
84         ? parse_cdr(gc, current_token)
85         : parse_list_end(gc, current_token);
86
87     if (cdr.is_error) {
88         return cdr;
89     }
90
91     cons->cdr = cdr.expr;
92
93     return parse_success(cons_as_expr(list), cdr.end);
94 }
95
96 static struct ParseResult parse_string(Gc *gc, struct Token current_token)
97 {
98     if (*current_token.begin != '"') {
99         return parse_failure("Expected \"", current_token.begin);
100     }
101
102     if (*(current_token.end - 1) != '"') {
103         return parse_failure("Unclosed string", current_token.begin);
104     }
105
106     if (current_token.begin + 1 == current_token.end) {
107         return parse_success(atom_as_expr(create_string_atom(gc, "", NULL)),
108                              current_token.end);
109     }
110
111     return parse_success(
112         atom_as_expr(
113             create_string_atom(gc, current_token.begin + 1, current_token.end - 1)),
114         current_token.end);
115 }
116
117 static struct ParseResult parse_number(Gc *gc, struct Token current_token)
118 {
119     char *endptr = 0;
120     const long int x = strtoimax(current_token.begin, &endptr, 10);
121
122     if (current_token.begin == endptr || current_token.end != endptr) {
123         return parse_failure("Expected number", current_token.begin);
124     }
125
126     return parse_success(
127         atom_as_expr(create_number_atom(gc, x)),
128         current_token.end);
129 }
130
131 static struct ParseResult parse_symbol(Gc *gc, struct Token current_token)
132 {
133     if (*current_token.begin == 0) {
134         return parse_failure("EOF", current_token.begin);
135     }
136
137     return parse_success(
138         atom_as_expr(create_symbol_atom(gc, current_token.begin, current_token.end)),
139         current_token.end);
140 }
141
142 static struct ParseResult parse_expr(Gc *gc, struct Token current_token)
143 {
144     if (*current_token.begin == 0) {
145         return parse_failure("EOF", current_token.begin);
146     }
147
148     switch (*current_token.begin) {
149     case '(': return parse_list(gc, current_token);
150     /* TODO(#292): parser does not support escaped string characters */
151     case '"': return parse_string(gc, current_token);
152     default: {}
153     }
154
155     if (*current_token.begin == '-' || isdigit(*current_token.begin)) {
156         struct ParseResult result = parse_number(gc, current_token);
157         if (!result.is_error) {
158             return result;
159         }
160     }
161
162     return parse_symbol(gc, current_token);
163 }
164
165 struct ParseResult read_expr_from_string(Gc *gc, const char *str)
166 {
167     assert(str);
168     return parse_expr(gc, next_token(str));
169 }
170
171 struct ParseResult read_expr_from_file(Gc *gc, const char *filename)
172 {
173     assert(filename);
174
175     Lt *lt = create_lt();
176     if (lt == NULL) {
177         return parse_failure("Could not create Lt object", NULL);
178     }
179
180     FILE *stream = PUSH_LT(lt, fopen(filename, "rb"), fclose_lt);
181     if (!stream) {
182         /* TODO(#307): ParseResult should not be used for reporting IO failures */
183         RETURN_LT(lt, parse_failure(strerror(errno), NULL));
184     }
185
186     if (fseek(stream, 0, SEEK_END) != 0) {
187         RETURN_LT(lt, parse_failure("Could not find the end of the file", NULL));
188     }
189
190     const long int buffer_length = ftell(stream);
191
192     if (buffer_length < 0) {
193         RETURN_LT(lt, parse_failure("Couldn't get the size of file", NULL));
194     }
195
196     if (buffer_length == 0) {
197         RETURN_LT(lt, parse_failure("File is empty", NULL));
198     }
199
200     if (buffer_length >= MAX_BUFFER_LENGTH) {
201         RETURN_LT(lt, parse_failure("File is too big", NULL));
202     }
203
204     if (fseek(stream, 0, SEEK_SET) != 0) {
205         RETURN_LT(lt, parse_failure("Could not find the beginning of the file", NULL));
206     }
207
208     char * const buffer = PUSH_LT(lt, malloc((size_t) buffer_length + 1), free);
209     if (buffer == NULL) {
210         RETURN_LT(lt, parse_failure(strerror(errno), NULL));
211     }
212
213     if (fread(buffer, 1, (size_t) buffer_length, stream) != (size_t) buffer_length) {
214         RETURN_LT(lt, parse_failure("Could not read the file", NULL));
215     }
216
217     struct ParseResult result = read_expr_from_string(gc, buffer);
218
219     RETURN_LT(lt, result);
220 }
221
222 struct ParseResult parse_success(struct Expr expr,
223                                  const char *end)
224 {
225     struct ParseResult result = {
226         .is_error = false,
227         .expr = expr,
228         .end = end
229     };
230
231     return result;
232 }
233
234 struct ParseResult parse_failure(const char *error_message,
235                                  const char *end)
236 {
237     struct ParseResult result = {
238         .is_error = true,
239         .error_message = error_message,
240         .end = end
241     };
242
243     return result;
244 }
245
246 void print_parse_error(FILE *stream,
247                        const char *str,
248                        struct ParseResult result)
249 {
250     /* TODO(#294): print_parse_error doesn't support multiple lines */
251     if (!result.is_error) {
252         return;
253     }
254
255     if (result.end) {
256         fprintf(stream, "%s\n", str);
257         for (size_t i = 0; i < (size_t) (result.end - str); ++i) {
258             fprintf(stream, " ");
259         }
260         fprintf(stream, "^\n");
261     }
262
263     fprintf(stream, "%s\n", result.error_message);
264 }