larpcc/preproc.c
2026-10-20 01:27:50 +02:00

964 lines
22 KiB
C

#include "preproc.h"
#include "collection.h"
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* read_textfile(const char* filename)
{
FILE* fp = fopen(filename, "r");
if (!fp) {
goto couldnt_read_file;
}
if (fseek(fp, 0, SEEK_END) != 0) {
goto couldnt_read_file;
}
long file_size = ftell(fp);
if (file_size < 0) {
goto couldnt_read_file;
}
if (fseek(fp, 0, SEEK_SET) != 0) {
goto couldnt_read_file;
}
char* buffer = malloc(file_size + 1);
assert(buffer);
if (fread(buffer, sizeof(char), file_size, fp) != (size_t)file_size) {
goto couldnt_read_file;
}
buffer[file_size] = '\0';
return buffer;
couldnt_read_file:
fprintf(stderr,
"error: couldn't read file '%s': %s\n",
filename,
strerror(errno));
return nullptr;
}
const char* tokty_str(TokTy ty)
{
switch (ty) {
case Tok_Eof:
return "Eof";
case Tok_Ident:
return "Ident";
case Tok_Int:
return "Int";
case Tok_Str:
return "Str";
case Tok_Char:
return "Char";
case Tok_Whitespace:
return "Whitespace";
case Tok_HeaderName:
return "HeaderName";
case Tok_Eol:
return "Eol";
case Tok_Semicolon:
return "Semicolon";
case Tok_LParen:
return "LParen";
case Tok_RParen:
return "RParen";
case Tok_LBrace:
return "LBrace";
case Tok_RBrace:
return "RBrace";
case Tok_Asterisk:
return "Asterisk";
case Tok_Hashtag:
return "Hashtag";
case Tok_Comma:
return "Comma";
case Tok_KwInt:
return "KwInt";
case Tok_KwChar:
return "KwChar";
case Tok_KwConst:
return "KwConst";
case Tok_KwVoid:
return "KwVoid";
case Tok_EndOfMacroInvocation:
return "EndOfMacroInvocation";
}
return "<unknown>";
}
void source_iter_init(SourceIter* r, const char* filename, const char* text)
{
*r = (SourceIter) {
.filename = filename,
.text = text,
.i = 0,
.line = 1,
.col = 1,
};
}
Char source_iter_next(SourceIter* r)
{
char ch = r->text[r->i];
Loc loc = { r->line, r->col, r->filename };
if (r->text[r->i] != '\0') {
if (ch == '\n') {
r->line += 1;
r->col = 1;
} else {
r->col += 1;
}
r->i += 1;
}
return (Char) { ch, loc };
}
void lsl_iter_init(LslIter* r, SourceIter* source_char_iter)
{
Char curr = source_iter_next(source_char_iter);
*r = (LslIter) {
.source_char_iter = source_char_iter,
.curr = curr,
};
}
Char lsl_iter_next(LslIter* r)
{
Char next = source_iter_next(r->source_char_iter);
if (r->curr.ch == '\\' && next.ch == '\n') {
r->curr = source_iter_next(r->source_char_iter);
return lsl_iter_next(r);
}
Char curr = r->curr;
r->curr = next;
return curr;
}
void lexer_init(Lexer* r, LslIter* lsl_char_iter, Arena* arena)
{
Char curr = lsl_iter_next(lsl_char_iter);
*r = (Lexer) {
.lsl_iter = lsl_char_iter,
.arena = arena,
.curr = curr,
.lex_whitespace = false,
.lex_headername = false,
};
}
static Tok l_tok(Lexer* l, TokTy ty, const char* text, Loc loc)
{
char* text_dup = arena_alloc(l->arena, strlen(text) + 1);
text_dup[0] = '\0';
strcpy(text_dup, text);
return (Tok) { ty, text_dup, loc };
}
static bool l_done(Lexer* l)
{
return l->curr.ch == '\0';
}
constexpr size_t max_token_text_len = 64;
static void l_step(Lexer* l, char text[], size_t* text_len)
{
assert(*text_len < max_token_text_len);
text[*text_len] = l->curr.ch;
*text_len += 1;
l->curr = lsl_iter_next(l->lsl_iter);
}
Tok lexer_next(Lexer* l)
{
Loc loc = l->curr.loc;
char text[max_token_text_len] = "";
size_t text_len = 0;
if (l_done(l)) {
return l_tok(l, Tok_Eof, text, loc);
}
bool matched = false;
while (strchr(" \t\r", l->curr.ch)) {
matched = true;
l_step(l, text, &text_len);
}
if (matched) {
if (l->lex_whitespace) {
return l_tok(l, Tok_Whitespace, text, loc);
}
return lexer_next(l);
}
while ((l->curr.ch >= 'a' && l->curr.ch <= 'z')
|| (l->curr.ch >= 'A' && l->curr.ch <= 'Z')
|| (matched && l->curr.ch >= '0' && l->curr.ch <= '9')
|| strchr("_", l->curr.ch)) {
matched = true;
l_step(l, text, &text_len);
}
if (matched) {
return l_tok(l, Tok_Ident, text, loc);
}
while ((l->curr.ch >= '1' && l->curr.ch <= '9')
|| (matched && l->curr.ch == '0')) {
matched = true;
l_step(l, text, &text_len);
}
if (matched) {
return l_tok(l, Tok_Int, text, loc);
}
if (l->lex_headername && (l->curr.ch == '"' || l->curr.ch == '<')) {
char ter = l->curr.ch == '<' ? '>' : '"';
l_step(l, text, &text_len);
while (l->curr.ch != '\0' && l->curr.ch != '\n' && l->curr.ch != ter) {
l_step(l, text, &text_len);
}
if (l->curr.ch != ter) {
fprintf(stderr,
"error: malformed header name, expected '%c' on line %d\n",
ter,
l->curr.loc.line);
return lexer_next(l);
}
l_step(l, text, &text_len);
return l_tok(l, Tok_HeaderName, text, loc);
}
if (l->curr.ch == '"') {
l_step(l, text, &text_len);
while (l->curr.ch != '"') {
if (l->curr.ch == '\\') {
l_step(l, text, &text_len);
if (l_done(l))
break;
}
l_step(l, text, &text_len);
}
if (l_done(l) || l->curr.ch != '"') {
fprintf(stderr,
"error: malformed string literal on line %d\n",
l->curr.loc.line);
return lexer_next(l);
}
l_step(l, text, &text_len);
return l_tok(l, Tok_Str, text, loc);
}
if (l->curr.ch == '\'') {
l_step(l, text, &text_len);
if (l->curr.ch == '\\') {
l_step(l, text, &text_len);
}
if (!l_done(l)) {
l_step(l, text, &text_len);
}
if (l_done(l) && l->curr.ch != '\'') {
fprintf(stderr,
"error: malformed character literal on line %d\n",
l->curr.loc.line);
return lexer_next(l);
}
l_step(l, text, &text_len);
return l_tok(l, Tok_Char, text, loc);
}
if (strchr("\n;(){},*#", l->curr.ch)) {
TokTy ty = (TokTy)l->curr.ch;
l_step(l, text, &text_len);
return l_tok(l, ty, text, loc);
}
fprintf(stderr,
"error: illegal character '%c' on line %d\n",
l->curr.ch,
l->curr.loc.line);
l_step(l, text, &text_len);
return lexer_next(l);
}
typedef struct {
ARRAY_FIELD(Hashmap, maps);
} PPSyms;
static void ppsyms_push_layer(PPSyms* s);
static void ppsyms_init(PPSyms* s)
{
*s = (PPSyms) {
ARRAY_FIELD_INIT(maps),
};
ppsyms_push_layer(s);
}
static void ppsyms_deinit(PPSyms* s)
{
if (s->maps) {
for (size_t i = 0; i < s->maps_size; ++i) {
hashmap_deinit(&s->maps[i]);
}
free(s->maps);
}
}
static void ppsyms_push_layer(PPSyms* 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 ppsyms_pop_layer(PPSyms* s)
{
assert(s->maps_size > 0);
hashmap_deinit(&s->maps[s->maps_size - 1]);
s->maps_size -= 1;
}
static Hashmap* ppsyms_current(PPSyms* s)
{
assert(s->maps_size > 0);
return &s->maps[s->maps_size - 1];
}
static void ppsyms_define(PPSyms* s, const char* ident, size_t def_idx)
{
Hashmap* m = ppsyms_current(s);
hashmap_set(m, ident, &def_idx);
}
static size_t* ppsyms_get(PPSyms* 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 {
size_t conds;
size_t depth;
} CondStack;
static void condstack_push(CondStack* s, bool cond)
{
if (cond) {
s->conds |= 1ul << s->depth;
} else {
s->conds &= ~(1ul << s->depth);
}
s->depth += 1;
}
static bool condstack_pop(CondStack* s)
{
assert(s->depth > 0);
s->depth -= 1;
return (s->conds >> s->depth) & 1;
}
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* arena;
ARRAY_FIELD(File, files);
ARRAY_FIELD(Tok, tok_stack);
Tok curr;
ARRAY_FIELD(Def, defs);
PPSyms syms;
CondStack condstack;
bool next_can_be_directive;
};
Preproc* preproc_new(char* filename, char* text, Arena* arena)
{
Preproc* p = malloc(sizeof(Preproc));
*p = (Preproc) {
.arena = arena,
ARRAY_FIELD_INIT(files),
ARRAY_FIELD_INIT(tok_stack),
.curr = { },
ARRAY_FIELD_INIT(defs),
.syms = { },
.condstack = { },
.next_can_be_directive = true,
};
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, filename, text);
lsl_iter_init(&file->lsl_iter, &file->source_iter);
lexer_init(&file->lexer, &file->lsl_iter, p->arena);
p->curr = lexer_next(&file->lexer);
// null-definition at idx 0
ARRAY_FIELD_PUSH(p, defs, &(Def) { });
ppsyms_init(&p->syms);
return p;
}
void preproc_free(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);
}
ppsyms_deinit(&p->syms);
free(p);
}
static void p_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;
p_step(p);
}
return;
}
p->curr = p->tok_stack[p->tok_stack_size - 1];
p->tok_stack_size -= 1;
if (p->curr.ty == Tok_EndOfMacroInvocation) {
ppsyms_pop_layer(&p->syms);
p_step(p);
return;
}
}
static void p_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.loc.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->arena,
(void**)&toks,
&toks_cap,
&toks_size,
(&p->curr),
sizeof(*(&p->curr)));
p_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.loc.line);
exit(1);
}
ARRAY_FIELD_PUSH(p,
defs,
(&(Def) {
.toks = toks,
.toks_size = toks_size,
}));
ppsyms_define(&p->syms, p->defs[def_idx].params[arg_idx], p->defs_size - 1);
}
static void p_collect_args(Preproc* p, size_t def_idx)
{
assert(p->curr.ty == '(');
p_step(p);
size_t idx = 0;
if (p->curr.ty != Tok_Eof && p->curr.ty != ')') {
p_collect_arg(p, def_idx, idx);
idx += 1;
while (p->curr.ty == ',') {
p_step(p);
p_collect_arg(p, def_idx, idx);
idx += 1;
}
}
if (p->curr.ty != ')') {
fprintf(stderr, "error: expected ')' on line %d\n", p->curr.loc.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.loc.line);
exit(1);
}
}
static Tok p_expand_tok(Preproc* p)
{
Tok tok = p->curr;
size_t* def_idx;
if (tok.ty != Tok_Ident || !(def_idx = ppsyms_get(&p->syms, tok.text))) {
p_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.
ppsyms_push_layer(&p->syms);
ppsyms_define(&p->syms, tok.text, 0);
if (p->defs[*def_idx].params) {
p_step(p);
// function-like macros without arg-lists are ignored.
if (p->curr.ty != '(')
return tok;
p_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.loc = tok.loc;
ARRAY_FIELD_PUSH(p, tok_stack, &expanded_tok);
}
// step into the new invocation
p_step(p);
return p_expand_tok(p);
}
static Tok do_define(Preproc* p)
{
p_step(p);
if (p->curr.ty != Tok_Ident) {
fprintf(stderr,
"error: expected identifier on line %d\n",
p->curr.loc.line);
exit(1);
}
Tok ident_tok = p->curr;
p->files[p->files_size - 1].lexer.lex_whitespace = true;
p_step(p);
p->files[p->files_size - 1].lexer.lex_whitespace = false;
Def def = { };
if (p->curr.ty == '(') {
p_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);
p_step(p);
while (p->curr.ty == ',') {
p_step(p);
if (p->curr.ty != Tok_Ident)
goto error_expected_identifier;
ARRAY_VAR_PUSH(params, &p->curr.text);
p_step(p);
}
}
if (p->curr.ty != ')') {
fprintf(
stderr, "error: expected ')' on line %d\n", p->curr.loc.line);
exit(1);
}
p_step(p);
def.params = params;
def.params_size = params_size;
}
if (p->curr.ty == Tok_Whitespace)
p_step(p);
ARRAY_VAR(Tok, toks);
while (p->curr.ty != Tok_Eof && p->curr.ty != Tok_Eol) {
arena_array_push(p->arena,
(void**)&toks,
&toks_cap,
&toks_size,
(&p->curr),
sizeof(*(&p->curr)));
p_step(p);
}
def.toks = toks;
def.toks_size = toks_size;
ARRAY_FIELD_PUSH(p, defs, &def);
ppsyms_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.loc.line);
exit(1);
}
static Tok do_undef(Preproc* p)
{
p_step(p);
if (p->curr.ty != Tok_Ident) {
fprintf(stderr,
"error: expected identifier on line %d\n",
p->curr.loc.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.loc.line);
exit(1);
}
ppsyms_define(&p->syms, ident_tok.text, 0);
return p->curr;
}
Tok preproc_next(Preproc* p);
static Tok do_include(Preproc* p)
{
File* curr_file = &p->files[p->files_size - 1];
curr_file->lexer.lex_headername = true;
p_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.loc.line);
exit(1);
}
Tok headername_tok = p->curr;
p_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.loc.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, filename, text);
lsl_iter_init(&file->lsl_iter, &file->source_iter);
lexer_init(&file->lexer, &file->lsl_iter, p->arena);
p->curr = lexer_next(&file->lexer);
p->next_can_be_directive = true;
return preproc_next(p);
}
static Tok interpret_directive(Preproc* p);
static Tok do_ifndef(Preproc* p)
{
int original_line = p->curr.loc.line;
p_step(p);
if (p->curr.ty != Tok_Ident) {
fprintf(stderr,
"error: expected identifier on line %d\n",
p->curr.loc.line);
exit(1);
}
Tok ident = p->curr;
p_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.loc.line);
exit(1);
}
bool cond = ppsyms_get(&p->syms, ident.text) == nullptr;
condstack_push(&p->condstack, cond);
if (!cond) {
while (p->curr.ty != Tok_Eof) {
while (p->curr.ty != Tok_Eof
&& (!p->next_can_be_directive || p->curr.ty != '#'))
p_step(p);
if (p->curr.ty == Tok_Eof)
break;
p_step(p);
if (p->curr.ty != Tok_Ident)
continue;
if (strcmp(p->curr.text, "endif") == 0)
return interpret_directive(p);
}
if (p->curr.ty != Tok_Eof) {
fprintf(stderr,
"error: unclosed '#ifndef' on line %d\n",
original_line);
exit(1);
}
}
return p->curr;
}
static Tok do_endif(Preproc* p)
{
p_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.loc.line);
exit(1);
}
condstack_pop(&p->condstack);
return p->curr;
}
static Tok interpret_directive(Preproc* p)
{
if (p->curr.ty == Tok_Eol)
return p->curr;
if (p->curr.ty != Tok_Ident) {
fprintf(stderr,
"error: expected identifier on line %d\n",
p->curr.loc.line);
exit(1);
}
Tok kw = p->curr;
struct {
const char* keyword;
Tok (*fn)(Preproc* p);
} directives[] = {
{ "define", do_define },
{ "undef", do_undef },
{ "include", do_include },
{ "ifndef", do_ifndef },
{ "endif", do_endif },
{ "if", nullptr },
{ "ifdef", nullptr },
{ "else", nullptr },
{ "elif", nullptr },
{ "elifdef", nullptr },
{ "elifndef", nullptr },
{ "line", nullptr },
{ "embed", nullptr },
{ "error", nullptr },
{ "warning", nullptr },
{ "pragma", nullptr },
};
for (size_t i = 0; i < sizeof(directives) / sizeof(directives[0]); ++i) {
if (strcmp(kw.text, directives[i].keyword) == 0) {
assert(directives[i].fn && "not implemented");
return directives[i].fn(p);
}
}
fprintf(stderr,
"error: '%s' is not a preprocessor instruction on line %d\n",
kw.text,
kw.loc.line);
exit(1);
}
Tok preproc_next(Preproc* p)
{
if (p->next_can_be_directive && p->curr.ty == '#') {
p_step(p);
return interpret_directive(p);
}
return p_expand_tok(p);
}
static Tok tokstream_next_filtered(TokStream* s)
{
Tok tok;
while ((tok = preproc_next(s->preproc)).ty == Tok_Eol
|| tok.ty == Tok_Whitespace) { }
return tok;
}
void tokstream_init(TokStream* s, Preproc* preproc, Arena* arena)
{
*s = (TokStream) {
.preproc = preproc,
.arena = arena,
.curr = { },
};
s->curr = tokstream_next_filtered(s);
}
Tok tokstream_next(TokStream* s)
{
Tok curr = s->curr;
Tok next = tokstream_next_filtered(s);
if (curr.ty == Tok_Str && next.ty == Tok_Str) {
char* text
= arena_alloc(s->arena, strlen(curr.text) + strlen(next.text) + 1);
text[0] = '\0';
strncat(text, curr.text, strlen(curr.text) - 1);
strcat(text, next.text + 1);
s->curr = (Tok) { .ty = Tok_Str, text, curr.loc };
return tokstream_next(s);
}
s->curr = next;
return curr;
}