]> git.lizzy.rs Git - nothing.git/blobdiff - src/script/parser.c
(#408) use stdbool in camera unit
[nothing.git] / src / script / parser.c
index 630ba9c32c11dba830225fa2db23f7c1ca0aef71..0ca6cd89b0cb04d9e9c2ece47af16c0fb44b849d 100644 (file)
 #include <assert.h>
-#include <stdlib.h>
 #include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
 
+#include "script/builtins.h"
 #include "script/parser.h"
+#include "system/lt.h"
+#include "system/lt/lt_adapters.h"
+
+#define MAX_BUFFER_LENGTH (5 * 1000 * 1000)
 
-static void skip_whitespaces(const char *str, size_t *cursor, size_t n)
+static struct ParseResult parse_expr(Gc *gc, struct Token current_token);
+
+static struct ParseResult parse_cdr(Gc *gc, struct Token current_token)
 {
-    assert(str);
-    assert(cursor);
+    if (*current_token.begin != '.') {
+        return parse_failure("Expected .", current_token.begin);
+    }
 
-    while (*cursor < n && isspace(str[*cursor])) {
-        (*cursor)++;
+    struct ParseResult cdr = read_expr_from_string(gc, current_token.end);
+    if (cdr.is_error) {
+        return cdr;
     }
+
+    current_token = next_token(cdr.end);
+
+    if (*current_token.begin != ')') {
+        return parse_failure("Expected )", current_token.begin);
+    }
+
+    return parse_success(cdr.expr, current_token.end);
 }
 
-struct ParseResult create_expr_from_str(const char *str,
-                                        size_t *cursor,
-                                        size_t n)
+static struct ParseResult parse_list_end(Gc *gc, struct Token current_token)
 {
-    assert(str);
-    assert(cursor);
+    if (*current_token.begin != ')') {
+        return parse_failure("Expected )", current_token.begin);
+    }
+
+    return parse_success(atom_as_expr(create_symbol_atom(gc, "nil", NULL)),
+                         current_token.end);
+}
+
+static struct ParseResult parse_list(Gc *gc, struct Token current_token)
+{
+    if (*current_token.begin != '(') {
+        return parse_failure("Expected (", current_token.begin);
+    }
+
+    current_token = next_token(current_token.end);
 
-    /* TODO: create_expr_from_str doesn't no support comments */
+    if (*current_token.begin == ')') {
+        return parse_list_end(gc, current_token);
+    }
 
-    skip_whitespaces(str, cursor, n);
-    if (*cursor >= n) {
-        return parse_failure("EOF");
+    struct ParseResult car = parse_expr(gc, current_token);
+    if (car.is_error) {
+        return car;
     }
 
-    switch (str[*cursor]) {
-    case '(': {
-        (*cursor)++;
-        struct ParseResult car = create_expr_from_str(str, cursor, n);
+    struct Cons *list = create_cons(gc, car.expr, void_expr());
+    struct Cons *cons = list;
+    current_token = next_token(car.end);
+
+    while (*current_token.begin != '.' &&
+           *current_token.begin != ')' &&
+           *current_token.begin != 0) {
+        car = parse_expr(gc, current_token);
         if (car.is_error) {
             return car;
         }
 
-        skip_whitespaces(str, cursor, n);
-        if (*cursor >= n) {
-            return parse_failure("EOF");
-        }
+        cons->cdr = cons_as_expr(create_cons(gc, car.expr, void_expr()));
+        cons = cons->cdr.cons;
 
-        if (str[*cursor] != '.') {
-            return parse_failure("Expected .");
-        }
-        (*cursor)++;
+        current_token = next_token(car.end);
+    }
 
-        skip_whitespaces(str, cursor, n);
-        if (*cursor >= n) {
-            return parse_failure("EOF");
-        }
+    struct ParseResult cdr = *current_token.begin == '.'
+        ? parse_cdr(gc, current_token)
+        : parse_list_end(gc, current_token);
 
-        struct ParseResult cdr = create_expr_from_str(str, cursor, n);
-        if (cdr.is_error) {
-            return cdr;
-        }
+    if (cdr.is_error) {
+        return cdr;
+    }
+
+    cons->cdr = cdr.expr;
+
+    return parse_success(cons_as_expr(list), cdr.end);
+}
+
+static struct ParseResult parse_string(Gc *gc, struct Token current_token)
+{
+    if (*current_token.begin != '"') {
+        return parse_failure("Expected \"", current_token.begin);
+    }
+
+    if (*(current_token.end - 1) != '"') {
+        return parse_failure("Unclosed string", current_token.begin);
+    }
+
+    if (current_token.begin + 1 == current_token.end) {
+        return parse_success(atom_as_expr(create_string_atom(gc, "", NULL)),
+                             current_token.end);
+    }
+
+    return parse_success(
+        atom_as_expr(
+            create_string_atom(gc, current_token.begin + 1, current_token.end - 1)),
+        current_token.end);
+}
+
+static struct ParseResult parse_number(Gc *gc, struct Token current_token)
+{
+    char *endptr = 0;
+    const long int x = strtoimax(current_token.begin, &endptr, 10);
+
+    if (current_token.begin == endptr || current_token.end != endptr) {
+        return parse_failure("Expected number", current_token.begin);
+    }
 
-        skip_whitespaces(str, cursor, n);
-        if (*cursor >= n) {
-            return parse_failure("EOF");
+    return parse_success(
+        atom_as_expr(create_number_atom(gc, x)),
+        current_token.end);
+}
+
+static struct ParseResult parse_symbol(Gc *gc, struct Token current_token)
+{
+    if (*current_token.begin == 0) {
+        return parse_failure("EOF", current_token.begin);
+    }
+
+    return parse_success(
+        atom_as_expr(create_symbol_atom(gc, current_token.begin, current_token.end)),
+        current_token.end);
+}
+
+static struct ParseResult parse_expr(Gc *gc, struct Token current_token)
+{
+    if (*current_token.begin == 0) {
+        return parse_failure("EOF", current_token.begin);
+    }
+
+    switch (*current_token.begin) {
+    case '(': return parse_list(gc, current_token);
+    /* TODO(#292): parser does not support escaped string characters */
+    case '"': return parse_string(gc, current_token);
+    case '\'': {
+        struct ParseResult result = parse_expr(gc, next_token(current_token.end));
+
+        if (result.is_error) {
+            return result;
         }
 
-        if (str[*cursor] != ')') {
-            return parse_failure("Expected )");
+        result.expr = list(gc, 2, SYMBOL(gc, "quote"), result.expr);
+
+        return result;
+    } break;
+    default: {}
+    }
+
+    if (*current_token.begin == '-' || isdigit(*current_token.begin)) {
+        struct ParseResult result = parse_number(gc, current_token);
+        if (!result.is_error) {
+            return result;
         }
+    }
+
+    return parse_symbol(gc, current_token);
+}
 
-        (*cursor)++;
+struct ParseResult read_expr_from_string(Gc *gc, const char *str)
+{
+    assert(str);
+    return parse_expr(gc, next_token(str));
+}
 
-        return parse_success(cons_as_expr(create_cons(car.expr, cdr.expr)));
+struct ParseResult read_expr_from_file(Gc *gc, const char *filename)
+{
+    assert(filename);
+
+    Lt *lt = create_lt();
+    if (lt == NULL) {
+        return parse_failure("Could not create Lt object", NULL);
     }
 
-    case '"': {
-        /* TODO(#288): create_expr_from_str does not support strings */
-        return parse_failure("Strings are not supported");
+    FILE *stream = PUSH_LT(lt, fopen(filename, "rb"), fclose_lt);
+    if (!stream) {
+        /* TODO(#307): ParseResult should not be used for reporting IO failures */
+        RETURN_LT(lt, parse_failure(strerror(errno), NULL));
     }
 
-    default: {
-        if (isdigit(str[*cursor])) {
-            const char *nptr = str + *cursor;
-            char *endptr = 0;
-            const double x = strtod(nptr, &endptr);
+    if (fseek(stream, 0, SEEK_END) != 0) {
+        RETURN_LT(lt, parse_failure("Could not find the end of the file", NULL));
+    }
 
-            if (nptr == endptr) {
-                return parse_failure("Number expected");
-            }
+    const long int buffer_length = ftell(stream);
 
-            *cursor += (size_t) (endptr - nptr);
+    if (buffer_length < 0) {
+        RETURN_LT(lt, parse_failure("Couldn't get the size of file", NULL));
+    }
 
-            return parse_success(atom_as_expr(create_atom(ATOM_NUMBER, x)));
-        } else if (isalpha(str[*cursor])) {
-            /* TODO(#289): create_expr_from_str does not support symbols */
-            return parse_failure("Symbols are not supported");
-        }
+    if (buffer_length == 0) {
+        RETURN_LT(lt, parse_failure("File is empty", NULL));
+    }
+
+    if (buffer_length >= MAX_BUFFER_LENGTH) {
+        RETURN_LT(lt, parse_failure("File is too big", NULL));
+    }
+
+    if (fseek(stream, 0, SEEK_SET) != 0) {
+        RETURN_LT(lt, parse_failure("Could not find the beginning of the file", NULL));
+    }
+
+    char * const buffer = PUSH_LT(lt, malloc((size_t) buffer_length + 1), free);
+    if (buffer == NULL) {
+        RETURN_LT(lt, parse_failure(strerror(errno), NULL));
     }
+
+    if (fread(buffer, 1, (size_t) buffer_length, stream) != (size_t) buffer_length) {
+        RETURN_LT(lt, parse_failure("Could not read the file", NULL));
     }
 
-    return parse_failure("Unexpected sequence of characters");
+    struct ParseResult result = read_expr_from_string(gc, buffer);
+
+    RETURN_LT(lt, result);
 }
 
-struct ParseResult parse_success(struct Expr expr)
+struct ParseResult parse_success(struct Expr expr,
+                                 const char *end)
 {
     struct ParseResult result = {
         .is_error = false,
-        .expr = expr
+        .expr = expr,
+        .end = end
     };
 
     return result;
 }
 
-struct ParseResult parse_failure(const char *error)
+struct ParseResult parse_failure(const char *error_message,
+                                 const char *end)
 {
     struct ParseResult result = {
         .is_error = true,
-        .error = error
+        .error_message = error_message,
+        .end = end
     };
 
     return result;
 }
+
+void print_parse_error(FILE *stream,
+                       const char *str,
+                       struct ParseResult result)
+{
+    /* TODO(#294): print_parse_error doesn't support multiple lines */
+    if (!result.is_error) {
+        return;
+    }
+
+    if (result.end) {
+        fprintf(stream, "%s\n", str);
+        for (size_t i = 0; i < (size_t) (result.end - str); ++i) {
+            fprintf(stream, " ");
+        }
+        fprintf(stream, "^\n");
+    }
+
+    fprintf(stream, "%s\n", result.error_message);
+}