llm
Download
190 lines 6.8 KiBCModified 2026-09-09 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
// Parsing logic for type definitions

#include <stdbool.h>
#include <string.h>

#include "../ast.h"
#include "../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(ctx, &pos);
    int64_t indent = get_indent(ctx, pos);
    ast_list_t *statements = NULL;
    for (;;) {
        const char *next = pos;
        whitespace(ctx, &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_convert_def)) || (stmt = optional(ctx, &pos, parse_use))
            || (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(ctx, &pos); // TODO: check for newline
        } 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);

    bool secret = false, external = false, opaque = false, packed_bools = false;
    for (bool in_flags = false; match_flag_separator(ctx, &pos, in_flags); in_flags = true) {
        if (match_word(&pos, "secret")) {
            secret = true;
        } else if (match_word(&pos, "packed_bools")) {
            packed_bools = true;
        } else if (match_word(&pos, "external")) {
            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;
        }
    }
    whitespace(ctx, &pos);

    expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this struct");

    spaces(&pos);
    if (match(&pos, ":")) {
        parser_err(ctx, pos - 1, pos, "There should not be a colon here.");
    }

    ast_t *namespace = NULL;
    const char *ns_pos = pos;
    whitespace(ctx, &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, .packed_bools = packed_bools);
}

ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) {
    // tagged union: enum Foo(a, b{x:Int,y:Int}, ...) [: \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(ctx, &pos);
    for (;;) {
        spaces(&pos);
        const char *tag_start = pos;
        const char *tag_name = get_id(&pos);
        if (!tag_name) break;

        spaces(&pos);
        arg_ast_t *fields;
        bool secret = false, packed_bools = false;
        if (match(&pos, "{")) {
            whitespace(ctx, &pos);
            fields = parse_args(ctx, &pos);
            for (bool in_flags = false; match_flag_separator(ctx, &pos, in_flags); in_flags = true) {
                if (match_word(&pos, "secret")) secret = true;
                else if (match_word(&pos, "packed_bools")) packed_bools = true;
                else break;
            }
            whitespace(ctx, &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, .file = ctx->file, .start = tag_start, .end = pos, .name = tag_name, .fields = fields,
                    .secret = secret, .packed_bools = packed_bools, .next = tags);

        if (!match_separator(ctx, &pos)) break;
    }

    whitespace(ctx, &pos);
    expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this enum definition");

    spaces(&pos);
    if (match(&pos, ":")) {
        parser_err(ctx, pos - 1, pos, "There should not be a colon here.");
    }

    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(ctx, &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);

    if (match(&pos, ":")) {
        parser_err(ctx, pos - 1, pos, "There should not be a colon here.");
    }

    ast_t *namespace = NULL;
    const char *ns_pos = pos;
    whitespace(ctx, &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);
}