larpcc/preproc.c
2026-09-23 18:39:10 +02:00

537 lines
13 KiB
C

#include "collection.h"
#include "parse.h"
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
ARRAY_FIELD(Hashmap, maps);
} Syms;
static void syms_push_layer(Syms* s);
static void syms_init(Syms* s)
{
*s = (Syms) {
ARRAY_FIELD_INIT(maps),
};
syms_push_layer(s);
}
static void syms_deinit(Syms* s)
{
if (s->maps) {
for (size_t i = 0; i < s->maps_size; ++i) {
hashmap_deinit(&s->maps[i]);
}
free(s->maps);
}
}
static void syms_push_layer(Syms* s)
{
Hashmap m = { };
hashmap_init(&m, sizeof(size_t));
array_push((void**)&s->maps, &s->maps_cap, &s->maps_size, &m, sizeof(m));
}
static void syms_pop_layer(Syms* s)
{
assert(s->maps_size > 0);
hashmap_deinit(&s->maps[s->maps_size - 1]);
s->maps_size -= 1;
}
static Hashmap* syms_current(Syms* s)
{
assert(s->maps_size > 0);
return &s->maps[s->maps_size - 1];
}
static void syms_define(Syms* s, const char* ident, size_t def_idx)
{
Hashmap* m = syms_current(s);
hashmap_set(m, ident, &def_idx);
}
static size_t* syms_get(Syms* s, const char* ident)
{
for (size_t i = 0; i < s->maps_size; ++i) {
Hashmap* m = &s->maps[s->maps_size - i - 1];
size_t* def_idx = hashmap_get(m, ident);
if (def_idx)
return def_idx;
}
return nullptr;
}
typedef struct {
char* filename;
char* text;
SourceIter source_iter;
LslIter lsl_iter;
Lexer lexer;
} File;
typedef struct {
Tok* toks;
size_t toks_size;
char** params;
size_t params_size;
} Def;
struct Preproc {
Arena tok_arena;
ARRAY_FIELD(File, files);
ARRAY_FIELD(Tok, tok_stack);
Tok curr;
ARRAY_FIELD(Def, defs);
Syms syms;
bool next_can_be_directive;
};
void preproc_init(Preproc** p, char* filename, char* text)
{
*p = malloc(sizeof(Preproc));
**p = (Preproc) {
ARRAY_FIELD_INIT(files),
ARRAY_FIELD_INIT(tok_stack),
.curr = { },
ARRAY_FIELD_INIT(defs),
.syms = { },
.tok_arena = { },
.next_can_be_directive = true,
};
arena_init(&(*p)->tok_arena, 512);
ARRAY_FIELD_PUSH(*p,
files,
(&(File) {
.filename = filename,
.text = text,
.source_iter = { },
.lsl_iter = { },
.lexer = { },
}));
File* file = &(*p)->files[0];
source_iter_init(&file->source_iter, text);
lsl_iter_init(&file->lsl_iter, &file->source_iter);
lexer_init(&file->lexer, &file->lsl_iter, &(*p)->tok_arena);
(*p)->curr = lexer_next(&file->lexer);
// null-definition at idx 0
ARRAY_FIELD_PUSH(*p, defs, &(Def) { });
syms_init(&(*p)->syms);
}
void preproc_deinit(Preproc* p)
{
for (size_t i = 0; i < p->files_size; ++i) {
free(p->files[i].filename);
free(p->files[i].text);
}
free(p->files);
if (p->tok_stack)
free(p->tok_stack);
if (p->defs) {
for (size_t i = 0; i < p->defs_size; ++i) {
if (p->defs[i].params)
free(p->defs[i].params);
}
free(p->defs);
}
syms_deinit(&p->syms);
arena_deinit(&p->tok_arena);
free(p);
}
static void step(Preproc* p)
{
p->next_can_be_directive = p->curr.ty == Tok_Eol;
if (p->tok_stack_size == 0) {
p->curr = lexer_next(&p->files[p->files_size - 1].lexer);
if (p->curr.ty == Tok_Eof && p->files_size > 1) {
File* curr_file = &p->files[p->files_size - 1];
free(curr_file->filename);
free(curr_file->text);
p->files_size -= 1;
step(p);
}
return;
}
p->curr = p->tok_stack[p->tok_stack_size - 1];
p->tok_stack_size -= 1;
if (p->curr.ty == Tok_EndOfMacroInvocation) {
syms_pop_layer(&p->syms);
step(p);
return;
}
}
static void collect_arg(Preproc* p, size_t def_idx, size_t arg_idx)
{
if (arg_idx >= p->defs[def_idx].params_size) {
fprintf(stderr,
"error: too many arguments for macro on line %d\n",
p->curr.line);
exit(1);
}
ARRAY_VAR(Tok, toks);
int depth = 0;
while (p->curr.ty != Tok_Eof
&& (depth != 0 || (p->curr.ty != ',' && p->curr.ty != ')'))) {
if (p->curr.ty == '(')
depth += 1;
if (p->curr.ty == ')') {
if (depth == 0)
continue;
depth -= 1;
}
arena_array_push(&p->tok_arena,
(void**)&toks,
&toks_cap,
&toks_size,
(&p->curr),
sizeof(*(&p->curr)));
step(p);
}
if (p->curr.ty != ',' && p->curr.ty != ')') {
assert(p->curr.ty == Tok_Eof);
fprintf(
stderr, "error: expected ',' or ')' on line %d\n", p->curr.line);
exit(1);
}
ARRAY_FIELD_PUSH(p,
defs,
(&(Def) {
.toks = toks,
.toks_size = toks_size,
}));
syms_define(&p->syms, p->defs[def_idx].params[arg_idx], p->defs_size - 1);
}
#define A(B, C) B C
static void collect_args(Preproc* p, size_t def_idx)
{
assert(p->curr.ty == '(');
step(p);
size_t idx = 0;
if (p->curr.ty != Tok_Eof && p->curr.ty != ')') {
collect_arg(p, def_idx, idx);
idx += 1;
while (p->curr.ty == ',') {
step(p);
collect_arg(p, def_idx, idx);
idx += 1;
}
}
if (p->curr.ty != ')') {
fprintf(stderr, "error: expected ')' on line %d\n", p->curr.line);
exit(1);
}
assert(idx >= p->defs[def_idx].params_size);
if (idx > p->defs[def_idx].params_size) {
fprintf(stderr,
"error: too few arguments for macro on line %d\n",
p->curr.line);
exit(1);
}
}
static Tok expand_tok(Preproc* p)
{
Tok tok = p->curr;
size_t* def_idx;
if (tok.ty != Tok_Ident || !(def_idx = syms_get(&p->syms, tok.text))) {
step(p);
return tok;
}
// macro expension doesn't support recursion.
// when seeing the same macro a second time
// in the invocation tree, treat it as a normal token.
// undefine it at this layer.
syms_push_layer(&p->syms);
syms_define(&p->syms, tok.text, 0);
if (p->defs[*def_idx].params) {
step(p);
// function-like macros without arg-lists are ignored.
if (p->curr.ty != '(')
return tok;
collect_args(p, *def_idx);
}
ARRAY_FIELD_PUSH(p, tok_stack, (&(Tok) { .ty = Tok_EndOfMacroInvocation }));
for (size_t i = 0; i < p->defs[*def_idx].toks_size; ++i) {
Def* def = &p->defs[*def_idx];
Tok expanded_tok = def->toks[def->toks_size - i - 1];
expanded_tok.line = tok.line;
expanded_tok.col = tok.col;
ARRAY_FIELD_PUSH(p, tok_stack, &expanded_tok);
}
// step into the new invocation
step(p);
return expand_tok(p);
}
static Tok do_define(Preproc* p)
{
step(p);
if (p->curr.ty != Tok_Ident) {
fprintf(
stderr, "error: expected identifier on line %d\n", p->curr.line);
exit(1);
}
Tok ident_tok = p->curr;
p->files[p->files_size - 1].lexer.lex_whitespace = true;
step(p);
p->files[p->files_size - 1].lexer.lex_whitespace = false;
Def def = { };
if (p->curr.ty == '(') {
step(p);
ARRAY_VAR(char*, params);
if (p->curr.ty != Tok_Eof && p->curr.ty != ')') {
if (p->curr.ty != Tok_Ident)
goto error_expected_identifier;
ARRAY_VAR_PUSH(params, &p->curr.text);
step(p);
while (p->curr.ty == ',') {
step(p);
if (p->curr.ty != Tok_Ident)
goto error_expected_identifier;
ARRAY_VAR_PUSH(params, &p->curr.text);
step(p);
}
}
if (p->curr.ty != ')') {
fprintf(stderr, "error: expected ')' on line %d\n", p->curr.line);
exit(1);
}
step(p);
def.params = params;
def.params_size = params_size;
}
if (p->curr.ty == Tok_Whitespace)
step(p);
ARRAY_VAR(Tok, toks);
while (p->curr.ty != Tok_Eof && p->curr.ty != Tok_Eol) {
arena_array_push(&p->tok_arena,
(void**)&toks,
&toks_cap,
&toks_size,
(&p->curr),
sizeof(*(&p->curr)));
step(p);
}
def.toks = toks;
def.toks_size = toks_size;
ARRAY_FIELD_PUSH(p, defs, &def);
syms_define(&p->syms, ident_tok.text, p->defs_size - 1);
return p->curr;
error_expected_identifier:
fprintf(stderr, "error: expected identifier on line %d\n", p->curr.line);
exit(1);
}
static Tok do_undef(Preproc* p)
{
step(p);
if (p->curr.ty != Tok_Ident) {
fprintf(
stderr, "error: expected identifier on line %d\n", p->curr.line);
exit(1);
}
Tok ident_tok = p->curr;
if (p->curr.ty != Tok_Eof && p->curr.ty != Tok_Eol) {
fprintf(stderr,
"error: unexpected extra tokens on line %d\n",
p->curr.line);
exit(1);
}
syms_define(&p->syms, ident_tok.text, 0);
return p->curr;
}
static Tok do_include(Preproc* p)
{
File* curr_file = &p->files[p->files_size - 1];
curr_file->lexer.lex_headername = true;
step(p);
curr_file->lexer.lex_headername = false;
if (p->curr.ty != Tok_HeaderName) {
fprintf(
stderr, "error: expected header name on line %d\n", p->curr.line);
exit(1);
}
Tok headername_tok = p->curr;
step(p);
if (p->curr.ty != Tok_Eof && p->curr.ty != Tok_Eol) {
fprintf(stderr,
"error: unexpected extra tokens on line %d\n",
p->curr.line);
exit(1);
}
char* rel_path
= strndup(headername_tok.text + 1, strlen(headername_tok.text) - 2);
char* last_delim = strrchr(curr_file->filename, '/');
char* filename;
if (last_delim) {
size_t path_len = last_delim - curr_file->filename;
filename = calloc(path_len + 1 + strlen(rel_path) + 1, 1);
strncat(filename, curr_file->filename, path_len);
strcat(filename, "/");
strcat(filename, rel_path);
} else {
filename = strdup(rel_path);
}
free(rel_path);
char* text = read_textfile(filename);
ARRAY_FIELD_PUSH(p,
files,
(&(File) {
.filename = filename,
.text = text,
.source_iter = { },
.lsl_iter = { },
.lexer = { },
}));
File* file = &p->files[p->files_size - 1];
source_iter_init(&file->source_iter, text);
lsl_iter_init(&file->lsl_iter, &file->source_iter);
lexer_init(&file->lexer, &file->lsl_iter, &p->tok_arena);
p->curr = lexer_next(&file->lexer);
return p->curr;
}
static Tok interpret_directive(Preproc* p)
{
assert(p->curr.ty == '#');
step(p);
Tok kw = p->curr;
if (strcmp(kw.text, "define") == 0) {
return do_define(p);
} else if (strcmp(kw.text, "undef") == 0) {
return do_undef(p);
} else if (strcmp(kw.text, "include") == 0) {
return do_include(p);
} else if (strcmp(kw.text, "if") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "ifdef") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "ifndef") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "else") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "elif") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "elifdef") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "elifndef") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "endif") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "line") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "embed") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "error") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "warning") == 0) {
assert(false && "not implemented");
} else if (strcmp(kw.text, "pragma") == 0) {
fprintf(stderr,
"error: '%s' is not a preprocessor instruction on line %d\n",
kw.text,
kw.line);
exit(1);
}
assert(false && "unreachable");
}
Tok preproc_next_with_newline(Preproc* p)
{
if (p->next_can_be_directive && p->curr.ty == '#')
return interpret_directive(p);
return expand_tok(p);
}
Tok preproc_next(Preproc* p)
{
Tok tok;
while ((tok = preproc_next_with_newline(p)).ty == Tok_Eol
|| tok.ty == Tok_Whitespace) { }
return tok;
}