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