diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/environment.c | 4 | ||||
| -rw-r--r-- | src/parse/controlflow.c | 278 | ||||
| -rw-r--r-- | src/parse/controlflow.h | 18 | ||||
| -rw-r--r-- | src/parse/files.c | 70 | ||||
| -rw-r--r-- | src/parse/files.h | 4 | ||||
| -rw-r--r-- | src/parse/functions.c | 1 | ||||
| -rw-r--r-- | src/parse/parse.c | 647 | ||||
| -rw-r--r-- | src/parse/parse.h | 31 | ||||
| -rw-r--r-- | src/parse/statements.c | 159 | ||||
| -rw-r--r-- | src/parse/statements.h | 12 | ||||
| -rw-r--r-- | src/parse/typedefs.c | 204 | ||||
| -rw-r--r-- | src/parse/typedefs.h | 11 |
12 files changed, 762 insertions, 677 deletions
diff --git a/src/environment.c b/src/environment.c index 620d93c4..4a6407a6 100644 --- a/src/environment.c +++ b/src/environment.c @@ -5,7 +5,7 @@ #include "environment.h" #include "naming.h" -#include "parse/parse.h" +#include "parse/files.h" #include "stdlib/datatypes.h" #include "stdlib/tables.h" #include "stdlib/text.h" @@ -19,7 +19,7 @@ public type_t *PATH_TYPE_TYPE = NULL; static type_t *declare_type(env_t *env, const char *def_str) { - ast_t *ast = parse(def_str); + ast_t *ast = parse_file_str(def_str); if (!ast) errx(1, "Couldn't not parse struct def: %s", def_str); if (ast->tag != Block) errx(1, "Couldn't not parse struct def: %s", def_str); ast_list_t *statements = Match(ast, Block)->statements; diff --git a/src/parse/controlflow.c b/src/parse/controlflow.c new file mode 100644 index 00000000..e2e8dbc3 --- /dev/null +++ b/src/parse/controlflow.c @@ -0,0 +1,278 @@ +// Logic for parsing control flow + +#include <stdbool.h> +#include <string.h> + +#include "../ast.h" +#include "../stdlib/util.h" +#include "context.h" +#include "controlflow.h" +#include "errors.h" +#include "parse.h" +#include "statements.h" +#include "suffixes.h" +#include "utils.h" + +public +ast_t *parse_block(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + spaces(&pos); + + ast_list_t *statements = NULL; + if (!indent(ctx, &pos)) { + // Inline block + spaces(&pos); + while (*pos) { + spaces(&pos); + ast_t *stmt = optional(ctx, &pos, parse_statement); + if (!stmt) break; + statements = new (ast_list_t, .ast = stmt, .next = statements); + spaces(&pos); + if (!match(&pos, ";")) break; + } + } else { + goto indented; + } + + if (indent(ctx, &pos)) { + indented:; + int64_t block_indent = get_indent(ctx, pos); + whitespace(&pos); + while (*pos) { + ast_t *stmt = optional(ctx, &pos, parse_statement); + if (!stmt) { + const char *line_start = pos; + if (match_word(&pos, "struct")) + parser_err(ctx, line_start, eol(pos), "Struct definitions are only allowed at the top level"); + else if (match_word(&pos, "enum")) + parser_err(ctx, line_start, eol(pos), "Enum definitions are only allowed at the top level"); + else if (match_word(&pos, "func")) + parser_err(ctx, line_start, eol(pos), "Function definitions are only allowed at the top level"); + else if (match_word(&pos, "use")) + parser_err(ctx, line_start, eol(pos), "'use' statements are only allowed at the top level"); + + spaces(&pos); + if (*pos && *pos != '\r' && *pos != '\n') parser_err(ctx, pos, eol(pos), "I couldn't parse this line"); + break; + } + statements = new (ast_list_t, .ast = stmt, .next = statements); + whitespace(&pos); + + // Guard against having two valid statements on the same line, separated by spaces (but no newlines): + if (!memchr(stmt->end, '\n', (size_t)(pos - stmt->end))) { + if (*pos) parser_err(ctx, pos, eol(pos), "I don't know how to parse the rest of this line"); + pos = stmt->end; + break; + } + + if (get_indent(ctx, pos) != block_indent) { + pos = stmt->end; // backtrack + break; + } + } + } + REVERSE_LIST(statements); + return NewAST(ctx->file, start, pos, Block, .statements = statements); +} + +public +ast_t *parse_pass(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + return match_word(&pos, "pass") ? NewAST(ctx->file, start, pos, Pass) : NULL; +} + +public +ast_t *parse_defer(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + if (!match_word(&pos, "defer")) return NULL; + ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a block to be deferred here"); + return NewAST(ctx->file, start, pos, Defer, .body = body); +} + +public +ast_t *parse_skip(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + if (!match_word(&pos, "continue") && !match_word(&pos, "skip")) return NULL; + const char *target; + if (match_word(&pos, "for")) target = "for"; + else if (match_word(&pos, "while")) target = "while"; + else target = get_id(&pos); + ast_t *skip = NewAST(ctx->file, start, pos, Skip, .target = target); + skip = parse_optional_conditional_suffix(ctx, skip); + return skip; +} + +public +ast_t *parse_stop(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + if (!match_word(&pos, "stop") && !match_word(&pos, "break")) return NULL; + const char *target; + if (match_word(&pos, "for")) target = "for"; + else if (match_word(&pos, "while")) target = "while"; + else target = get_id(&pos); + ast_t *stop = NewAST(ctx->file, start, pos, Stop, .target = target); + stop = parse_optional_conditional_suffix(ctx, stop); + return stop; +} + +public +ast_t *parse_return(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + if (!match_word(&pos, "return")) return NULL; + ast_t *value = optional(ctx, &pos, parse_expr); + ast_t *ret = NewAST(ctx->file, start, pos, Return, .value = value); + ret = parse_optional_conditional_suffix(ctx, ret); + return ret; +} + +public +ast_t *parse_do(parse_ctx_t *ctx, const char *pos) { + // do [<indent>] body + const char *start = pos; + if (!match_word(&pos, "do")) return NULL; + ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'do'"); + return NewAST(ctx->file, start, pos, Block, .statements = Match(body, Block)->statements); +} + +public +ast_t *parse_while(parse_ctx_t *ctx, const char *pos) { + // while condition ["do"] [<indent>] body + const char *start = pos; + if (!match_word(&pos, "while")) return NULL; + + const char *tmp = pos; + // Shorthand form: `while when ...` + if (match_word(&tmp, "when")) { + ast_t *when = expect(ctx, start, &pos, parse_when, "I expected a 'when' block after this"); + if (!when->__data.When.else_body) when->__data.When.else_body = NewAST(ctx->file, pos, pos, Stop); + return NewAST(ctx->file, start, pos, While, .body = when); + } + + (void)match_word(&pos, "do"); // Optional 'do' + + ast_t *condition = expect(ctx, start, &pos, parse_expr, "I don't see a viable condition for this 'while'"); + ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'while'"); + return NewAST(ctx->file, start, pos, While, .condition = condition, .body = body); +} + +public +ast_t *parse_repeat(parse_ctx_t *ctx, const char *pos) { + // repeat [<indent>] body + const char *start = pos; + if (!match_word(&pos, "repeat")) return NULL; + ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'repeat'"); + return NewAST(ctx->file, start, pos, Repeat, .body = body); +} + +public +ast_t *parse_if(parse_ctx_t *ctx, const char *pos) { + // "if" <condition> ["then"] <body> ["else" <body>] | "unless" <condition> <body> ["else" <body>] + const char *start = pos; + int64_t starting_indent = get_indent(ctx, pos); + + bool unless; + if (match_word(&pos, "if")) unless = false; + else if (match_word(&pos, "unless")) unless = true; + else return NULL; + + ast_t *condition = unless ? NULL : optional(ctx, &pos, parse_declaration); + if (!condition) condition = expect(ctx, start, &pos, parse_expr, "I expected to find a condition for this 'if'"); + + if (unless) condition = WrapAST(condition, Not, condition); + + (void)match_word(&pos, "then"); // Optional 'then' + ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'if' statement"); + + const char *tmp = pos; + whitespace(&tmp); + ast_t *else_body = NULL; + const char *else_start = pos; + if (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "else")) { + pos = tmp; + spaces(&pos); + else_body = optional(ctx, &pos, parse_if); + if (!else_body) else_body = expect(ctx, else_start, &pos, parse_block, "I expected a body for this 'else'"); + } + return NewAST(ctx->file, start, pos, If, .condition = condition, .body = body, .else_body = else_body); +} + +public +ast_t *parse_when(parse_ctx_t *ctx, const char *pos) { + // when <expr> (is var : Tag <body>)* [else <body>] + const char *start = pos; + int64_t starting_indent = get_indent(ctx, pos); + + if (!match_word(&pos, "when")) return NULL; + + ast_t *subject = optional(ctx, &pos, parse_declaration); + if (!subject) subject = expect(ctx, start, &pos, parse_expr, "I expected to find an expression for this 'when'"); + + when_clause_t *clauses = NULL; + const char *tmp = pos; + whitespace(&tmp); + while (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "is")) { + pos = tmp; + spaces(&pos); + ast_t *pattern = expect(ctx, start, &pos, parse_expr, "I expected a pattern to match here"); + spaces(&pos); + when_clause_t *new_clauses = new (when_clause_t, .pattern = pattern, .next = clauses); + while (match(&pos, ",")) { + pattern = expect(ctx, start, &pos, parse_expr, "I expected a pattern to match here"); + new_clauses = new (when_clause_t, .pattern = pattern, .next = new_clauses); + spaces(&pos); + } + (void)match_word(&pos, "then"); // Optional 'then' + ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'when' clause"); + for (when_clause_t *c = new_clauses; c && c != clauses; c = c->next) { + c->body = body; + } + clauses = new_clauses; + tmp = pos; + whitespace(&tmp); + } + REVERSE_LIST(clauses); + + ast_t *else_body = NULL; + const char *else_start = pos; + if (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "else")) { + pos = tmp; + else_body = expect(ctx, else_start, &pos, parse_block, "I expected a body for this 'else'"); + } + return NewAST(ctx->file, start, pos, When, .subject = subject, .clauses = clauses, .else_body = else_body); +} + +public +ast_t *parse_for(parse_ctx_t *ctx, const char *pos) { + // for [k,] v in iter [<indent>] body + const char *start = pos; + if (!match_word(&pos, "for")) return NULL; + int64_t starting_indent = get_indent(ctx, pos); + spaces(&pos); + ast_list_t *vars = NULL; + for (;;) { + ast_t *var = optional(ctx, &pos, parse_var); + if (var) vars = new (ast_list_t, .ast = var, .next = vars); + + spaces(&pos); + if (!match(&pos, ",")) break; + } + + spaces(&pos); + expect_str(ctx, start, &pos, "in", "I expected an 'in' for this 'for'"); + + ast_t *iter = expect(ctx, start, &pos, parse_expr, "I expected an iterable value for this 'for'"); + + (void)match_word(&pos, "do"); // Optional 'do' + + ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'for'"); + + const char *else_start = pos; + whitespace(&else_start); + ast_t *empty = NULL; + if (match_word(&else_start, "else") && get_indent(ctx, else_start) == starting_indent) { + pos = else_start; + empty = expect(ctx, pos, &pos, parse_block, "I expected a body for this 'else'"); + } + REVERSE_LIST(vars); + return NewAST(ctx->file, start, pos, For, .vars = vars, .iter = iter, .body = body, .empty = empty); +} diff --git a/src/parse/controlflow.h b/src/parse/controlflow.h new file mode 100644 index 00000000..2ef093d4 --- /dev/null +++ b/src/parse/controlflow.h @@ -0,0 +1,18 @@ +// Logic for parsing control flow +#pragma once + +#include "../ast.h" +#include "context.h" + +ast_t *parse_block(parse_ctx_t *ctx, const char *pos); +ast_t *parse_defer(parse_ctx_t *ctx, const char *pos); +ast_t *parse_do(parse_ctx_t *ctx, const char *pos); +ast_t *parse_for(parse_ctx_t *ctx, const char *pos); +ast_t *parse_if(parse_ctx_t *ctx, const char *pos); +ast_t *parse_pass(parse_ctx_t *ctx, const char *pos); +ast_t *parse_repeat(parse_ctx_t *ctx, const char *pos); +ast_t *parse_return(parse_ctx_t *ctx, const char *pos); +ast_t *parse_skip(parse_ctx_t *ctx, const char *pos); +ast_t *parse_stop(parse_ctx_t *ctx, const char *pos); +ast_t *parse_when(parse_ctx_t *ctx, const char *pos); +ast_t *parse_while(parse_ctx_t *ctx, const char *pos); diff --git a/src/parse/files.c b/src/parse/files.c index 416adc92..6ff01af1 100644 --- a/src/parse/files.c +++ b/src/parse/files.c @@ -14,7 +14,10 @@ #include "files.h" #include "functions.h" #include "parse.h" +#include "statements.h" #include "text.h" +#include "typedefs.h" +#include "types.h" #include "utils.h" // The cache of {filename -> parsed AST} will hold at most this many entries: @@ -28,7 +31,6 @@ static ast_t *parse_top_declaration(parse_ctx_t *ctx, const char *pos) { return declaration; } -public ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) { const char *start = pos; whitespace(&pos); @@ -111,3 +113,69 @@ ast_t *parse_file(const char *path, jmp_buf *on_err) { Table$str_set(&cached, path, ast); return ast; } + +ast_t *parse_use(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + + ast_t *var = parse_var(ctx, pos); + if (var) { + pos = var->end; + spaces(&pos); + if (!match(&pos, ":=")) return NULL; + spaces(&pos); + } + + if (!match_word(&pos, "use")) return NULL; + spaces(&pos); + size_t name_len = strcspn(pos, " \t\r\n;"); + if (name_len < 1) parser_err(ctx, start, pos, "There is no module name here to use"); + char *name = GC_strndup(pos, name_len); + pos += name_len; + while (match(&pos, ";")) + continue; + int what; + if (name[0] == '<' || ends_with(name, ".h")) { + what = USE_HEADER; + } else if (starts_with(name, "-l")) { + what = USE_SHARED_OBJECT; + } else if (ends_with(name, ".c")) { + what = USE_C_CODE; + } else if (ends_with(name, ".S") || ends_with(name, ".s")) { + what = USE_ASM; + } else if (starts_with(name, "./") || starts_with(name, "/") || starts_with(name, "../") + || starts_with(name, "~/")) { + what = USE_LOCAL; + } else { + what = USE_MODULE; + } + return NewAST(ctx->file, start, pos, Use, .var = var, .path = name, .what = what); +} + +ast_t *parse_extern(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + if (!match_word(&pos, "extern")) return NULL; + spaces(&pos); + const char *name = get_id(&pos); + spaces(&pos); + if (!match(&pos, ":")) parser_err(ctx, start, pos, "I couldn't get a type for this extern"); + type_ast_t *type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this extern"); + return NewAST(ctx->file, start, pos, Extern, .name = name, .type = type); +} + +public +ast_t *parse_file_str(const char *str) { + file_t *file = spoof_file("<string>", str); + parse_ctx_t ctx = { + .file = file, + .on_err = NULL, + }; + + const char *pos = file->text; + whitespace(&pos); + ast_t *ast = parse_file_body(&ctx, pos); + pos = ast->end; + whitespace(&pos); + if (pos < file->text + file->len && *pos != '\0') + parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the string"); + return ast; +} diff --git a/src/parse/files.h b/src/parse/files.h index b8dc8726..3073449e 100644 --- a/src/parse/files.h +++ b/src/parse/files.h @@ -7,5 +7,9 @@ #include "../ast.h" #include "context.h" +ast_t *parse_file_str(const char *str); ast_t *parse_file(const char *path, jmp_buf *on_err); + ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos); +ast_t *parse_use(parse_ctx_t *ctx, const char *pos); +ast_t *parse_extern(parse_ctx_t *ctx, const char *pos); diff --git a/src/parse/functions.c b/src/parse/functions.c index 7786da7a..4293b43b 100644 --- a/src/parse/functions.c +++ b/src/parse/functions.c @@ -11,6 +11,7 @@ #include "../ast.h" #include "../stdlib/util.h" #include "context.h" +#include "controlflow.h" #include "errors.h" #include "functions.h" #include "parse.h" diff --git a/src/parse/parse.c b/src/parse/parse.c index a4462cd6..d4e51f1f 100644 --- a/src/parse/parse.c +++ b/src/parse/parse.c @@ -1,6 +1,5 @@ // Recursive descent parser for parsing code -#include <gc.h> #include <stdbool.h> #include <string.h> @@ -9,6 +8,7 @@ #include "binops.h" #include "containers.h" #include "context.h" +#include "controlflow.h" #include "errors.h" #include "files.h" #include "functions.h" @@ -79,152 +79,6 @@ ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos) { return NewAST(ctx->file, start, pos, Reduction, .iter = iter, .op = op, .key = key); } -ast_t *parse_if(parse_ctx_t *ctx, const char *pos) { - // "if" <condition> ["then"] <body> ["else" <body>] | "unless" <condition> <body> ["else" <body>] - const char *start = pos; - int64_t starting_indent = get_indent(ctx, pos); - - bool unless; - if (match_word(&pos, "if")) unless = false; - else if (match_word(&pos, "unless")) unless = true; - else return NULL; - - ast_t *condition = unless ? NULL : optional(ctx, &pos, parse_declaration); - if (!condition) condition = expect(ctx, start, &pos, parse_expr, "I expected to find a condition for this 'if'"); - - if (unless) condition = WrapAST(condition, Not, condition); - - (void)match_word(&pos, "then"); // Optional 'then' - ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'if' statement"); - - const char *tmp = pos; - whitespace(&tmp); - ast_t *else_body = NULL; - const char *else_start = pos; - if (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "else")) { - pos = tmp; - spaces(&pos); - else_body = optional(ctx, &pos, parse_if); - if (!else_body) else_body = expect(ctx, else_start, &pos, parse_block, "I expected a body for this 'else'"); - } - return NewAST(ctx->file, start, pos, If, .condition = condition, .body = body, .else_body = else_body); -} - -ast_t *parse_when(parse_ctx_t *ctx, const char *pos) { - // when <expr> (is var : Tag <body>)* [else <body>] - const char *start = pos; - int64_t starting_indent = get_indent(ctx, pos); - - if (!match_word(&pos, "when")) return NULL; - - ast_t *subject = optional(ctx, &pos, parse_declaration); - if (!subject) subject = expect(ctx, start, &pos, parse_expr, "I expected to find an expression for this 'when'"); - - when_clause_t *clauses = NULL; - const char *tmp = pos; - whitespace(&tmp); - while (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "is")) { - pos = tmp; - spaces(&pos); - ast_t *pattern = expect(ctx, start, &pos, parse_expr, "I expected a pattern to match here"); - spaces(&pos); - when_clause_t *new_clauses = new (when_clause_t, .pattern = pattern, .next = clauses); - while (match(&pos, ",")) { - pattern = expect(ctx, start, &pos, parse_expr, "I expected a pattern to match here"); - new_clauses = new (when_clause_t, .pattern = pattern, .next = new_clauses); - spaces(&pos); - } - (void)match_word(&pos, "then"); // Optional 'then' - ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'when' clause"); - for (when_clause_t *c = new_clauses; c && c != clauses; c = c->next) { - c->body = body; - } - clauses = new_clauses; - tmp = pos; - whitespace(&tmp); - } - REVERSE_LIST(clauses); - - ast_t *else_body = NULL; - const char *else_start = pos; - if (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "else")) { - pos = tmp; - else_body = expect(ctx, else_start, &pos, parse_block, "I expected a body for this 'else'"); - } - return NewAST(ctx->file, start, pos, When, .subject = subject, .clauses = clauses, .else_body = else_body); -} - -ast_t *parse_for(parse_ctx_t *ctx, const char *pos) { - // for [k,] v in iter [<indent>] body - const char *start = pos; - if (!match_word(&pos, "for")) return NULL; - int64_t starting_indent = get_indent(ctx, pos); - spaces(&pos); - ast_list_t *vars = NULL; - for (;;) { - ast_t *var = optional(ctx, &pos, parse_var); - if (var) vars = new (ast_list_t, .ast = var, .next = vars); - - spaces(&pos); - if (!match(&pos, ",")) break; - } - - spaces(&pos); - expect_str(ctx, start, &pos, "in", "I expected an 'in' for this 'for'"); - - ast_t *iter = expect(ctx, start, &pos, parse_expr, "I expected an iterable value for this 'for'"); - - (void)match_word(&pos, "do"); // Optional 'do' - - ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'for'"); - - const char *else_start = pos; - whitespace(&else_start); - ast_t *empty = NULL; - if (match_word(&else_start, "else") && get_indent(ctx, else_start) == starting_indent) { - pos = else_start; - empty = expect(ctx, pos, &pos, parse_block, "I expected a body for this 'else'"); - } - REVERSE_LIST(vars); - return NewAST(ctx->file, start, pos, For, .vars = vars, .iter = iter, .body = body, .empty = empty); -} - -ast_t *parse_do(parse_ctx_t *ctx, const char *pos) { - // do [<indent>] body - const char *start = pos; - if (!match_word(&pos, "do")) return NULL; - ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'do'"); - return NewAST(ctx->file, start, pos, Block, .statements = Match(body, Block)->statements); -} - -ast_t *parse_while(parse_ctx_t *ctx, const char *pos) { - // while condition ["do"] [<indent>] body - const char *start = pos; - if (!match_word(&pos, "while")) return NULL; - - const char *tmp = pos; - // Shorthand form: `while when ...` - if (match_word(&tmp, "when")) { - ast_t *when = expect(ctx, start, &pos, parse_when, "I expected a 'when' block after this"); - if (!when->__data.When.else_body) when->__data.When.else_body = NewAST(ctx->file, pos, pos, Stop); - return NewAST(ctx->file, start, pos, While, .body = when); - } - - (void)match_word(&pos, "do"); // Optional 'do' - - ast_t *condition = expect(ctx, start, &pos, parse_expr, "I don't see a viable condition for this 'while'"); - ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'while'"); - return NewAST(ctx->file, start, pos, While, .condition = condition, .body = body); -} - -ast_t *parse_repeat(parse_ctx_t *ctx, const char *pos) { - // repeat [<indent>] body - const char *start = pos; - if (!match_word(&pos, "repeat")) return NULL; - ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'repeat'"); - return NewAST(ctx->file, start, pos, Repeat, .body = body); -} - ast_t *parse_heap_alloc(parse_ctx_t *ctx, const char *pos) { const char *start = pos; if (!match(&pos, "@")) return NULL; @@ -298,51 +152,6 @@ ast_t *parse_bool(parse_ctx_t *ctx, const char *pos) { else return NULL; } -ast_t *parse_pass(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - return match_word(&pos, "pass") ? NewAST(ctx->file, start, pos, Pass) : NULL; -} - -ast_t *parse_defer(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - if (!match_word(&pos, "defer")) return NULL; - ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a block to be deferred here"); - return NewAST(ctx->file, start, pos, Defer, .body = body); -} - -ast_t *parse_skip(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - if (!match_word(&pos, "continue") && !match_word(&pos, "skip")) return NULL; - const char *target; - if (match_word(&pos, "for")) target = "for"; - else if (match_word(&pos, "while")) target = "while"; - else target = get_id(&pos); - ast_t *skip = NewAST(ctx->file, start, pos, Skip, .target = target); - skip = parse_optional_conditional_suffix(ctx, skip); - return skip; -} - -ast_t *parse_stop(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - if (!match_word(&pos, "stop") && !match_word(&pos, "break")) return NULL; - const char *target; - if (match_word(&pos, "for")) target = "for"; - else if (match_word(&pos, "while")) target = "while"; - else target = get_id(&pos); - ast_t *stop = NewAST(ctx->file, start, pos, Stop, .target = target); - stop = parse_optional_conditional_suffix(ctx, stop); - return stop; -} - -ast_t *parse_return(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - if (!match_word(&pos, "return")) return NULL; - ast_t *value = optional(ctx, &pos, parse_expr); - ast_t *ret = NewAST(ctx->file, start, pos, Return, .value = value); - ret = parse_optional_conditional_suffix(ctx, ret); - return ret; -} - ast_t *parse_none(parse_ctx_t *ctx, const char *pos) { const char *start = pos; if (!match_word(&pos, "none")) return NULL; @@ -410,111 +219,6 @@ ast_t *parse_term(parse_ctx_t *ctx, const char *pos) { ast_t *parse_expr(parse_ctx_t *ctx, const char *pos) { return parse_infix_expr(ctx, pos, 0); } -ast_t *parse_declaration(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - ast_t *var = parse_var(ctx, pos); - if (!var) return NULL; - pos = var->end; - spaces(&pos); - if (!match(&pos, ":")) return NULL; - spaces(&pos); - type_ast_t *type = optional(ctx, &pos, parse_type); - spaces(&pos); - ast_t *val = NULL; - if (match(&pos, "=")) { - val = optional(ctx, &pos, parse_extended_expr); - if (!val) { - if (optional(ctx, &pos, parse_use)) - parser_err(ctx, start, pos, "'use' statements are only allowed at the top level of a file"); - else parser_err(ctx, pos, eol(pos), "This is not a valid expression"); - } - } - return NewAST(ctx->file, start, pos, Declare, .var = var, .type = type, .value = val); -} - -ast_t *parse_update(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - ast_t *lhs = optional(ctx, &pos, parse_expr); - if (!lhs) return NULL; - spaces(&pos); - ast_e op; - if (match(&pos, "+=")) op = PlusUpdate; - else if (match(&pos, "++=")) op = ConcatUpdate; - else if (match(&pos, "-=")) op = MinusUpdate; - else if (match(&pos, "*=")) op = MultiplyUpdate; - else if (match(&pos, "/=")) op = DivideUpdate; - else if (match(&pos, "^=")) op = PowerUpdate; - else if (match(&pos, "<<=")) op = LeftShiftUpdate; - else if (match(&pos, "<<<=")) op = UnsignedLeftShiftUpdate; - else if (match(&pos, ">>=")) op = RightShiftUpdate; - else if (match(&pos, ">>>=")) op = UnsignedRightShiftUpdate; - else if (match(&pos, "and=")) op = AndUpdate; - else if (match(&pos, "or=")) op = OrUpdate; - else if (match(&pos, "xor=")) op = XorUpdate; - else return NULL; - ast_t *rhs = expect(ctx, start, &pos, parse_extended_expr, "I expected an expression here"); - return new (ast_t, .file = ctx->file, .start = start, .end = pos, .tag = op, .__data.PlusUpdate.lhs = lhs, - .__data.PlusUpdate.rhs = rhs); -} - -ast_t *parse_assignment(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - ast_list_t *targets = NULL; - for (;;) { - ast_t *lhs = optional(ctx, &pos, parse_term); - if (!lhs) break; - targets = new (ast_list_t, .ast = lhs, .next = targets); - spaces(&pos); - if (!match(&pos, ",")) break; - whitespace(&pos); - } - - if (!targets) return NULL; - - spaces(&pos); - if (!match(&pos, "=")) return NULL; - if (match(&pos, "=")) return NULL; // == comparison - - ast_list_t *values = NULL; - for (;;) { - ast_t *rhs = optional(ctx, &pos, parse_extended_expr); - if (!rhs) break; - values = new (ast_list_t, .ast = rhs, .next = values); - spaces(&pos); - if (!match(&pos, ",")) break; - whitespace(&pos); - } - - REVERSE_LIST(targets); - REVERSE_LIST(values); - - return NewAST(ctx->file, start, pos, Assign, .targets = targets, .values = values); -} - -ast_t *parse_statement(parse_ctx_t *ctx, const char *pos) { - ast_t *stmt = NULL; - if ((stmt = parse_declaration(ctx, pos)) || (stmt = parse_doctest(ctx, pos)) || (stmt = parse_assert(ctx, pos))) - return stmt; - - if (!(false || (stmt = parse_update(ctx, pos)) || (stmt = parse_assignment(ctx, pos)))) - stmt = parse_extended_expr(ctx, pos); - - for (bool progress = (stmt != NULL); progress;) { - ast_t *new_stmt; - progress = false; - if (stmt->tag == Var) { - progress = (false || (new_stmt = parse_method_call_suffix(ctx, stmt)) - || (new_stmt = parse_fncall_suffix(ctx, stmt))); - } else if (stmt->tag == FunctionCall) { - new_stmt = parse_optional_conditional_suffix(ctx, stmt); - progress = (new_stmt != stmt); - } - - if (progress) stmt = new_stmt; - } - return stmt; -} - ast_t *parse_extended_expr(parse_ctx_t *ctx, const char *pos) { ast_t *expr = NULL; @@ -526,354 +230,7 @@ ast_t *parse_extended_expr(parse_ctx_t *ctx, const char *pos) { return parse_expr(ctx, pos); } -ast_t *parse_block(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - spaces(&pos); - - ast_list_t *statements = NULL; - if (!indent(ctx, &pos)) { - // Inline block - spaces(&pos); - while (*pos) { - spaces(&pos); - ast_t *stmt = optional(ctx, &pos, parse_statement); - if (!stmt) break; - statements = new (ast_list_t, .ast = stmt, .next = statements); - spaces(&pos); - if (!match(&pos, ";")) break; - } - } else { - goto indented; - } - - if (indent(ctx, &pos)) { - indented:; - int64_t block_indent = get_indent(ctx, pos); - whitespace(&pos); - while (*pos) { - ast_t *stmt = optional(ctx, &pos, parse_statement); - if (!stmt) { - const char *line_start = pos; - if (match_word(&pos, "struct")) - parser_err(ctx, line_start, eol(pos), "Struct definitions are only allowed at the top level"); - else if (match_word(&pos, "enum")) - parser_err(ctx, line_start, eol(pos), "Enum definitions are only allowed at the top level"); - else if (match_word(&pos, "func")) - parser_err(ctx, line_start, eol(pos), "Function definitions are only allowed at the top level"); - else if (match_word(&pos, "use")) - parser_err(ctx, line_start, eol(pos), "'use' statements are only allowed at the top level"); - - spaces(&pos); - if (*pos && *pos != '\r' && *pos != '\n') parser_err(ctx, pos, eol(pos), "I couldn't parse this line"); - break; - } - statements = new (ast_list_t, .ast = stmt, .next = statements); - whitespace(&pos); - - // Guard against having two valid statements on the same line, separated by spaces (but no newlines): - if (!memchr(stmt->end, '\n', (size_t)(pos - stmt->end))) { - if (*pos) parser_err(ctx, pos, eol(pos), "I don't know how to parse the rest of this line"); - pos = stmt->end; - break; - } - - if (get_indent(ctx, pos) != block_indent) { - pos = stmt->end; // backtrack - break; - } - } - } - REVERSE_LIST(statements); - return NewAST(ctx->file, start, pos, Block, .statements = statements); -} - -ast_t *parse_namespace(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - whitespace(&pos); - int64_t indent = get_indent(ctx, pos); - ast_list_t *statements = NULL; - for (;;) { - const char *next = pos; - whitespace(&next); - if (get_indent(ctx, next) != indent) break; - ast_t *stmt; - if ((stmt = optional(ctx, &pos, parse_struct_def)) || (stmt = optional(ctx, &pos, parse_func_def)) - || (stmt = optional(ctx, &pos, parse_enum_def)) || (stmt = optional(ctx, &pos, parse_lang_def)) - || (stmt = optional(ctx, &pos, parse_extend)) || (stmt = optional(ctx, &pos, parse_convert_def)) - || (stmt = optional(ctx, &pos, parse_use)) || (stmt = optional(ctx, &pos, parse_extern)) - || (stmt = optional(ctx, &pos, parse_inline_c)) || (stmt = optional(ctx, &pos, parse_declaration))) { - statements = new (ast_list_t, .ast = stmt, .next = statements); - pos = stmt->end; - whitespace(&pos); // TODO: check for newline - // if (!(space_types & WHITESPACE_NEWLINES)) { - // pos = stmt->end; - // break; - // } - } else { - if (get_indent(ctx, next) > indent && next < eol(next)) - parser_err(ctx, next, eol(next), "I couldn't parse this namespace declaration"); - break; - } - } - REVERSE_LIST(statements); - return NewAST(ctx->file, start, pos, Block, .statements = statements); -} - -ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos) { - // struct Foo(...) [: \n body] - const char *start = pos; - if (!match_word(&pos, "struct")) return NULL; - - int64_t starting_indent = get_indent(ctx, pos); - - spaces(&pos); - const char *name = get_id(&pos); - if (!name) parser_err(ctx, start, pos, "I expected a name for this struct"); - spaces(&pos); - - if (!match(&pos, "(")) parser_err(ctx, pos, pos, "I expected a '(' and a list of fields here"); - - arg_ast_t *fields = parse_args(ctx, &pos); - - whitespace(&pos); - bool secret = false, external = false, opaque = false; - if (match(&pos, ";")) { // Extra flags - whitespace(&pos); - for (;;) { - if (match_word(&pos, "secret")) { - secret = true; - } else if (match_word(&pos, "extern")) { - external = true; - } else if (match_word(&pos, "opaque")) { - if (fields) - parser_err(ctx, pos - strlen("opaque"), pos, "A struct can't be opaque if it has fields defined"); - opaque = true; - } else { - break; - } - - if (!match_separator(&pos)) break; - } - } - - expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this struct"); - - ast_t *namespace = NULL; - const char *ns_pos = pos; - whitespace(&ns_pos); - int64_t ns_indent = get_indent(ctx, ns_pos); - if (ns_indent > starting_indent) { - pos = ns_pos; - namespace = optional(ctx, &pos, parse_namespace); - } - if (!namespace) namespace = NewAST(ctx->file, pos, pos, Block, .statements = NULL); - return NewAST(ctx->file, start, pos, StructDef, .name = name, .fields = fields, .namespace = namespace, - .secret = secret, .external = external, .opaque = opaque); -} - -ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) { - // tagged union: enum Foo(a, b(x:Int,y:Int)=5, ...) [: \n namespace] - const char *start = pos; - if (!match_word(&pos, "enum")) return NULL; - int64_t starting_indent = get_indent(ctx, pos); - spaces(&pos); - const char *name = get_id(&pos); - if (!name) parser_err(ctx, start, pos, "I expected a name for this enum"); - spaces(&pos); - if (!match(&pos, "(")) return NULL; - - tag_ast_t *tags = NULL; - whitespace(&pos); - for (;;) { - spaces(&pos); - const char *tag_name = get_id(&pos); - if (!tag_name) break; - - spaces(&pos); - arg_ast_t *fields; - bool secret = false; - if (match(&pos, "(")) { - whitespace(&pos); - fields = parse_args(ctx, &pos); - whitespace(&pos); - if (match(&pos, ";")) { // Extra flags - whitespace(&pos); - secret = match_word(&pos, "secret"); - whitespace(&pos); - } - expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this tagged union member"); - } else { - fields = NULL; - } - - tags = new (tag_ast_t, .name = tag_name, .fields = fields, .secret = secret, .next = tags); - - if (!match_separator(&pos)) break; - } - - whitespace(&pos); - expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this enum definition"); - - REVERSE_LIST(tags); - - if (tags == NULL) parser_err(ctx, start, pos, "This enum does not have any tags!"); - - ast_t *namespace = NULL; - const char *ns_pos = pos; - whitespace(&ns_pos); - int64_t ns_indent = get_indent(ctx, ns_pos); - if (ns_indent > starting_indent) { - pos = ns_pos; - namespace = optional(ctx, &pos, parse_namespace); - } - if (!namespace) namespace = NewAST(ctx->file, pos, pos, Block, .statements = NULL); - - return NewAST(ctx->file, start, pos, EnumDef, .name = name, .tags = tags, .namespace = namespace); -} - -ast_t *parse_lang_def(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - // lang Name: [namespace...] - if (!match_word(&pos, "lang")) return NULL; - int64_t starting_indent = get_indent(ctx, pos); - spaces(&pos); - const char *name = get_id(&pos); - if (!name) parser_err(ctx, start, pos, "I expected a name for this lang"); - spaces(&pos); - - ast_t *namespace = NULL; - const char *ns_pos = pos; - whitespace(&ns_pos); - int64_t ns_indent = get_indent(ctx, ns_pos); - if (ns_indent > starting_indent) { - pos = ns_pos; - namespace = optional(ctx, &pos, parse_namespace); - } - if (!namespace) namespace = NewAST(ctx->file, pos, pos, Block, .statements = NULL); - - return NewAST(ctx->file, start, pos, LangDef, .name = name, .namespace = namespace); -} - -ast_t *parse_extend(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - // extend Name: body... - if (!match_word(&pos, "extend")) return NULL; - int64_t starting_indent = get_indent(ctx, pos); - spaces(&pos); - const char *name = get_id(&pos); - if (!name) parser_err(ctx, start, pos, "I expected a name for this lang"); - - ast_t *body = NULL; - const char *ns_pos = pos; - whitespace(&ns_pos); - int64_t ns_indent = get_indent(ctx, ns_pos); - if (ns_indent > starting_indent) { - pos = ns_pos; - body = optional(ctx, &pos, parse_namespace); - } - if (!body) body = NewAST(ctx->file, pos, pos, Block, .statements = NULL); - - return NewAST(ctx->file, start, pos, Extend, .name = name, .body = body); -} - -ast_t *parse_extern(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - if (!match_word(&pos, "extern")) return NULL; - spaces(&pos); - const char *name = get_id(&pos); - spaces(&pos); - if (!match(&pos, ":")) parser_err(ctx, start, pos, "I couldn't get a type for this extern"); - type_ast_t *type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this extern"); - return NewAST(ctx->file, start, pos, Extern, .name = name, .type = type); -} - -ast_t *parse_doctest(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - if (!match(&pos, ">>")) return NULL; - spaces(&pos); - ast_t *expr = expect(ctx, start, &pos, parse_statement, "I couldn't parse the expression for this doctest"); - whitespace(&pos); - ast_t *expected = NULL; - if (match(&pos, "=")) { - spaces(&pos); - expected = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the expected expression here"); - } else { - pos = expr->end; - } - return NewAST(ctx->file, start, pos, DocTest, .expr = expr, .expected = expected); -} - -ast_t *parse_assert(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - if (!match_word(&pos, "assert")) return NULL; - spaces(&pos); - ast_t *expr = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the expression for this assert"); - spaces(&pos); - ast_t *message = NULL; - if (match(&pos, ",")) { - whitespace(&pos); - message = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the error message for this assert"); - } else { - pos = expr->end; - } - return NewAST(ctx->file, start, pos, Assert, .expr = expr, .message = message); -} - -ast_t *parse_use(parse_ctx_t *ctx, const char *pos) { - const char *start = pos; - - ast_t *var = parse_var(ctx, pos); - if (var) { - pos = var->end; - spaces(&pos); - if (!match(&pos, ":=")) return NULL; - spaces(&pos); - } - - if (!match_word(&pos, "use")) return NULL; - spaces(&pos); - size_t name_len = strcspn(pos, " \t\r\n;"); - if (name_len < 1) parser_err(ctx, start, pos, "There is no module name here to use"); - char *name = GC_strndup(pos, name_len); - pos += name_len; - while (match(&pos, ";")) - continue; - int what; - if (name[0] == '<' || ends_with(name, ".h")) { - what = USE_HEADER; - } else if (starts_with(name, "-l")) { - what = USE_SHARED_OBJECT; - } else if (ends_with(name, ".c")) { - what = USE_C_CODE; - } else if (ends_with(name, ".S") || ends_with(name, ".s")) { - what = USE_ASM; - } else if (starts_with(name, "./") || starts_with(name, "/") || starts_with(name, "../") - || starts_with(name, "~/")) { - what = USE_LOCAL; - } else { - what = USE_MODULE; - } - return NewAST(ctx->file, start, pos, Use, .var = var, .path = name, .what = what); -} - -ast_t *parse(const char *str) { - file_t *file = spoof_file("<string>", str); - parse_ctx_t ctx = { - .file = file, - .on_err = NULL, - }; - - const char *pos = file->text; - whitespace(&pos); - ast_t *ast = parse_file_body(&ctx, pos); - pos = ast->end; - whitespace(&pos); - if (pos < file->text + file->len && *pos != '\0') - parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the string"); - return ast; -} - -ast_t *parse_expression(const char *str) { +ast_t *parse_expr_str(const char *str) { file_t *file = spoof_file("<string>", str); parse_ctx_t ctx = { .file = file, diff --git a/src/parse/parse.h b/src/parse/parse.h index 57d798ce..c7c97f24 100644 --- a/src/parse/parse.h +++ b/src/parse/parse.h @@ -1,49 +1,22 @@ -#pragma once - // Parsing logic +#pragma once #include "../ast.h" #include "context.h" -ast_t *parse(const char *str); -ast_t *parse_expression(const char *str); +ast_t *parse_expr_str(const char *str); -ast_t *parse_assignment(parse_ctx_t *ctx, const char *pos); -ast_t *parse_block(parse_ctx_t *ctx, const char *pos); ast_t *parse_bool(parse_ctx_t *ctx, const char *pos); -ast_t *parse_declaration(parse_ctx_t *ctx, const char *pos); -ast_t *parse_defer(parse_ctx_t *ctx, const char *pos); -ast_t *parse_do(parse_ctx_t *ctx, const char *pos); -ast_t *parse_doctest(parse_ctx_t *ctx, const char *pos); -ast_t *parse_assert(parse_ctx_t *ctx, const char *pos); -ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos); ast_t *parse_expr(parse_ctx_t *ctx, const char *pos); ast_t *parse_extended_expr(parse_ctx_t *ctx, const char *pos); -ast_t *parse_extern(parse_ctx_t *ctx, const char *pos); -ast_t *parse_for(parse_ctx_t *ctx, const char *pos); ast_t *parse_heap_alloc(parse_ctx_t *ctx, const char *pos); -ast_t *parse_if(parse_ctx_t *ctx, const char *pos); -ast_t *parse_lang_def(parse_ctx_t *ctx, const char *pos); -ast_t *parse_extend(parse_ctx_t *ctx, const char *pos); -ast_t *parse_namespace(parse_ctx_t *ctx, const char *pos); ast_t *parse_negative(parse_ctx_t *ctx, const char *pos); ast_t *parse_not(parse_ctx_t *ctx, const char *pos); ast_t *parse_none(parse_ctx_t *ctx, const char *pos); ast_t *parse_parens(parse_ctx_t *ctx, const char *pos); -ast_t *parse_pass(parse_ctx_t *ctx, const char *pos); ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos); -ast_t *parse_repeat(parse_ctx_t *ctx, const char *pos); -ast_t *parse_return(parse_ctx_t *ctx, const char *pos); -ast_t *parse_skip(parse_ctx_t *ctx, const char *pos); ast_t *parse_stack_reference(parse_ctx_t *ctx, const char *pos); -ast_t *parse_statement(parse_ctx_t *ctx, const char *pos); -ast_t *parse_stop(parse_ctx_t *ctx, const char *pos); -ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos); ast_t *parse_term(parse_ctx_t *ctx, const char *pos); ast_t *parse_term_no_suffix(parse_ctx_t *ctx, const char *pos); -ast_t *parse_update(parse_ctx_t *ctx, const char *pos); -ast_t *parse_use(parse_ctx_t *ctx, const char *pos); ast_t *parse_var(parse_ctx_t *ctx, const char *pos); -ast_t *parse_when(parse_ctx_t *ctx, const char *pos); -ast_t *parse_while(parse_ctx_t *ctx, const char *pos); ast_t *parse_deserialize(parse_ctx_t *ctx, const char *pos); diff --git a/src/parse/statements.c b/src/parse/statements.c new file mode 100644 index 00000000..c4ab5608 --- /dev/null +++ b/src/parse/statements.c @@ -0,0 +1,159 @@ +// Logic for parsing statements + +#include <gc.h> +#include <stdbool.h> +#include <string.h> + +#include "../ast.h" +#include "../stdlib/util.h" +#include "context.h" +#include "errors.h" +#include "files.h" +#include "parse.h" +#include "statements.h" +#include "suffixes.h" +#include "types.h" +#include "utils.h" + +public +ast_t *parse_declaration(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + ast_t *var = parse_var(ctx, pos); + if (!var) return NULL; + pos = var->end; + spaces(&pos); + if (!match(&pos, ":")) return NULL; + spaces(&pos); + type_ast_t *type = optional(ctx, &pos, parse_type); + spaces(&pos); + ast_t *val = NULL; + if (match(&pos, "=")) { + val = optional(ctx, &pos, parse_extended_expr); + if (!val) { + if (optional(ctx, &pos, parse_use)) + parser_err(ctx, start, pos, "'use' statements are only allowed at the top level of a file"); + else parser_err(ctx, pos, eol(pos), "This is not a valid expression"); + } + } + return NewAST(ctx->file, start, pos, Declare, .var = var, .type = type, .value = val); +} + +public +ast_t *parse_assignment(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + ast_list_t *targets = NULL; + for (;;) { + ast_t *lhs = optional(ctx, &pos, parse_term); + if (!lhs) break; + targets = new (ast_list_t, .ast = lhs, .next = targets); + spaces(&pos); + if (!match(&pos, ",")) break; + whitespace(&pos); + } + + if (!targets) return NULL; + + spaces(&pos); + if (!match(&pos, "=")) return NULL; + if (match(&pos, "=")) return NULL; // == comparison + + ast_list_t *values = NULL; + for (;;) { + ast_t *rhs = optional(ctx, &pos, parse_extended_expr); + if (!rhs) break; + values = new (ast_list_t, .ast = rhs, .next = values); + spaces(&pos); + if (!match(&pos, ",")) break; + whitespace(&pos); + } + + REVERSE_LIST(targets); + REVERSE_LIST(values); + + return NewAST(ctx->file, start, pos, Assign, .targets = targets, .values = values); +} + +public +ast_t *parse_update(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + ast_t *lhs = optional(ctx, &pos, parse_expr); + if (!lhs) return NULL; + spaces(&pos); + ast_e op; + if (match(&pos, "+=")) op = PlusUpdate; + else if (match(&pos, "++=")) op = ConcatUpdate; + else if (match(&pos, "-=")) op = MinusUpdate; + else if (match(&pos, "*=")) op = MultiplyUpdate; + else if (match(&pos, "/=")) op = DivideUpdate; + else if (match(&pos, "^=")) op = PowerUpdate; + else if (match(&pos, "<<=")) op = LeftShiftUpdate; + else if (match(&pos, "<<<=")) op = UnsignedLeftShiftUpdate; + else if (match(&pos, ">>=")) op = RightShiftUpdate; + else if (match(&pos, ">>>=")) op = UnsignedRightShiftUpdate; + else if (match(&pos, "and=")) op = AndUpdate; + else if (match(&pos, "or=")) op = OrUpdate; + else if (match(&pos, "xor=")) op = XorUpdate; + else return NULL; + ast_t *rhs = expect(ctx, start, &pos, parse_extended_expr, "I expected an expression here"); + return new (ast_t, .file = ctx->file, .start = start, .end = pos, .tag = op, .__data.PlusUpdate.lhs = lhs, + .__data.PlusUpdate.rhs = rhs); +} + +public +ast_t *parse_doctest(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + if (!match(&pos, ">>")) return NULL; + spaces(&pos); + ast_t *expr = expect(ctx, start, &pos, parse_statement, "I couldn't parse the expression for this doctest"); + whitespace(&pos); + ast_t *expected = NULL; + if (match(&pos, "=")) { + spaces(&pos); + expected = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the expected expression here"); + } else { + pos = expr->end; + } + return NewAST(ctx->file, start, pos, DocTest, .expr = expr, .expected = expected); +} + +public +ast_t *parse_assert(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + if (!match_word(&pos, "assert")) return NULL; + spaces(&pos); + ast_t *expr = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the expression for this assert"); + spaces(&pos); + ast_t *message = NULL; + if (match(&pos, ",")) { + whitespace(&pos); + message = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the error message for this assert"); + } else { + pos = expr->end; + } + return NewAST(ctx->file, start, pos, Assert, .expr = expr, .message = message); +} + +public +ast_t *parse_statement(parse_ctx_t *ctx, const char *pos) { + ast_t *stmt = NULL; + if ((stmt = parse_declaration(ctx, pos)) || (stmt = parse_doctest(ctx, pos)) || (stmt = parse_assert(ctx, pos))) + return stmt; + + if (!(false || (stmt = parse_update(ctx, pos)) || (stmt = parse_assignment(ctx, pos)))) + stmt = parse_extended_expr(ctx, pos); + + for (bool progress = (stmt != NULL); progress;) { + ast_t *new_stmt; + progress = false; + if (stmt->tag == Var) { + progress = (false || (new_stmt = parse_method_call_suffix(ctx, stmt)) + || (new_stmt = parse_fncall_suffix(ctx, stmt))); + } else if (stmt->tag == FunctionCall) { + new_stmt = parse_optional_conditional_suffix(ctx, stmt); + progress = (new_stmt != stmt); + } + + if (progress) stmt = new_stmt; + } + return stmt; +} diff --git a/src/parse/statements.h b/src/parse/statements.h new file mode 100644 index 00000000..fbc1af8f --- /dev/null +++ b/src/parse/statements.h @@ -0,0 +1,12 @@ +// Logic for parsing statements +#pragma once + +#include "../ast.h" +#include "context.h" + +ast_t *parse_assignment(parse_ctx_t *ctx, const char *pos); +ast_t *parse_declaration(parse_ctx_t *ctx, const char *pos); +ast_t *parse_doctest(parse_ctx_t *ctx, const char *pos); +ast_t *parse_assert(parse_ctx_t *ctx, const char *pos); +ast_t *parse_statement(parse_ctx_t *ctx, const char *pos); +ast_t *parse_update(parse_ctx_t *ctx, const char *pos); diff --git a/src/parse/typedefs.c b/src/parse/typedefs.c new file mode 100644 index 00000000..73fe9d7c --- /dev/null +++ b/src/parse/typedefs.c @@ -0,0 +1,204 @@ +// Parsing logic for type definitions + +#include <stdbool.h> +#include <string.h> + +#include "../ast.h" +#include "../stdlib/util.h" +#include "context.h" +#include "errors.h" +#include "files.h" +#include "functions.h" +#include "statements.h" +#include "text.h" +#include "typedefs.h" +#include "utils.h" + +ast_t *parse_namespace(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + whitespace(&pos); + int64_t indent = get_indent(ctx, pos); + ast_list_t *statements = NULL; + for (;;) { + const char *next = pos; + whitespace(&next); + if (get_indent(ctx, next) != indent) break; + ast_t *stmt; + if ((stmt = optional(ctx, &pos, parse_struct_def)) || (stmt = optional(ctx, &pos, parse_func_def)) + || (stmt = optional(ctx, &pos, parse_enum_def)) || (stmt = optional(ctx, &pos, parse_lang_def)) + || (stmt = optional(ctx, &pos, parse_extend)) || (stmt = optional(ctx, &pos, parse_convert_def)) + || (stmt = optional(ctx, &pos, parse_use)) || (stmt = optional(ctx, &pos, parse_extern)) + || (stmt = optional(ctx, &pos, parse_inline_c)) || (stmt = optional(ctx, &pos, parse_declaration))) { + statements = new (ast_list_t, .ast = stmt, .next = statements); + pos = stmt->end; + whitespace(&pos); // TODO: check for newline + // if (!(space_types & WHITESPACE_NEWLINES)) { + // pos = stmt->end; + // break; + // } + } else { + if (get_indent(ctx, next) > indent && next < eol(next)) + parser_err(ctx, next, eol(next), "I couldn't parse this namespace declaration"); + break; + } + } + REVERSE_LIST(statements); + return NewAST(ctx->file, start, pos, Block, .statements = statements); +} + +ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos) { + // struct Foo(...) [: \n body] + const char *start = pos; + if (!match_word(&pos, "struct")) return NULL; + + int64_t starting_indent = get_indent(ctx, pos); + + spaces(&pos); + const char *name = get_id(&pos); + if (!name) parser_err(ctx, start, pos, "I expected a name for this struct"); + spaces(&pos); + + if (!match(&pos, "(")) parser_err(ctx, pos, pos, "I expected a '(' and a list of fields here"); + + arg_ast_t *fields = parse_args(ctx, &pos); + + whitespace(&pos); + bool secret = false, external = false, opaque = false; + if (match(&pos, ";")) { // Extra flags + whitespace(&pos); + for (;;) { + if (match_word(&pos, "secret")) { + secret = true; + } else if (match_word(&pos, "extern")) { + external = true; + } else if (match_word(&pos, "opaque")) { + if (fields) + parser_err(ctx, pos - strlen("opaque"), pos, "A struct can't be opaque if it has fields defined"); + opaque = true; + } else { + break; + } + + if (!match_separator(&pos)) break; + } + } + + expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this struct"); + + ast_t *namespace = NULL; + const char *ns_pos = pos; + whitespace(&ns_pos); + int64_t ns_indent = get_indent(ctx, ns_pos); + if (ns_indent > starting_indent) { + pos = ns_pos; + namespace = optional(ctx, &pos, parse_namespace); + } + if (!namespace) namespace = NewAST(ctx->file, pos, pos, Block, .statements = NULL); + return NewAST(ctx->file, start, pos, StructDef, .name = name, .fields = fields, .namespace = namespace, + .secret = secret, .external = external, .opaque = opaque); +} + +ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) { + // tagged union: enum Foo(a, b(x:Int,y:Int)=5, ...) [: \n namespace] + const char *start = pos; + if (!match_word(&pos, "enum")) return NULL; + int64_t starting_indent = get_indent(ctx, pos); + spaces(&pos); + const char *name = get_id(&pos); + if (!name) parser_err(ctx, start, pos, "I expected a name for this enum"); + spaces(&pos); + if (!match(&pos, "(")) return NULL; + + tag_ast_t *tags = NULL; + whitespace(&pos); + for (;;) { + spaces(&pos); + const char *tag_name = get_id(&pos); + if (!tag_name) break; + + spaces(&pos); + arg_ast_t *fields; + bool secret = false; + if (match(&pos, "(")) { + whitespace(&pos); + fields = parse_args(ctx, &pos); + whitespace(&pos); + if (match(&pos, ";")) { // Extra flags + whitespace(&pos); + secret = match_word(&pos, "secret"); + whitespace(&pos); + } + expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this tagged union member"); + } else { + fields = NULL; + } + + tags = new (tag_ast_t, .name = tag_name, .fields = fields, .secret = secret, .next = tags); + + if (!match_separator(&pos)) break; + } + + whitespace(&pos); + expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this enum definition"); + + REVERSE_LIST(tags); + + if (tags == NULL) parser_err(ctx, start, pos, "This enum does not have any tags!"); + + ast_t *namespace = NULL; + const char *ns_pos = pos; + whitespace(&ns_pos); + int64_t ns_indent = get_indent(ctx, ns_pos); + if (ns_indent > starting_indent) { + pos = ns_pos; + namespace = optional(ctx, &pos, parse_namespace); + } + if (!namespace) namespace = NewAST(ctx->file, pos, pos, Block, .statements = NULL); + + return NewAST(ctx->file, start, pos, EnumDef, .name = name, .tags = tags, .namespace = namespace); +} + +ast_t *parse_lang_def(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + // lang Name: [namespace...] + if (!match_word(&pos, "lang")) return NULL; + int64_t starting_indent = get_indent(ctx, pos); + spaces(&pos); + const char *name = get_id(&pos); + if (!name) parser_err(ctx, start, pos, "I expected a name for this lang"); + spaces(&pos); + + ast_t *namespace = NULL; + const char *ns_pos = pos; + whitespace(&ns_pos); + int64_t ns_indent = get_indent(ctx, ns_pos); + if (ns_indent > starting_indent) { + pos = ns_pos; + namespace = optional(ctx, &pos, parse_namespace); + } + if (!namespace) namespace = NewAST(ctx->file, pos, pos, Block, .statements = NULL); + + return NewAST(ctx->file, start, pos, LangDef, .name = name, .namespace = namespace); +} + +ast_t *parse_extend(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + // extend Name: body... + if (!match_word(&pos, "extend")) return NULL; + int64_t starting_indent = get_indent(ctx, pos); + spaces(&pos); + const char *name = get_id(&pos); + if (!name) parser_err(ctx, start, pos, "I expected a name for this lang"); + + ast_t *body = NULL; + const char *ns_pos = pos; + whitespace(&ns_pos); + int64_t ns_indent = get_indent(ctx, ns_pos); + if (ns_indent > starting_indent) { + pos = ns_pos; + body = optional(ctx, &pos, parse_namespace); + } + if (!body) body = NewAST(ctx->file, pos, pos, Block, .statements = NULL); + + return NewAST(ctx->file, start, pos, Extend, .name = name, .body = body); +} diff --git a/src/parse/typedefs.h b/src/parse/typedefs.h new file mode 100644 index 00000000..1a746cff --- /dev/null +++ b/src/parse/typedefs.h @@ -0,0 +1,11 @@ +// Parsing logic for type definitions +#pragma once + +#include "../ast.h" +#include "context.h" + +ast_t *parse_lang_def(parse_ctx_t *ctx, const char *pos); +ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos); +ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos); +ast_t *parse_extend(parse_ctx_t *ctx, const char *pos); +ast_t *parse_namespace(parse_ctx_t *ctx, const char *pos); |
