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