676 lines
15 KiB
C
676 lines
15 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);
|
|
} 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;
|
|
|
|
void condstack_push(CondStack* s, bool cond)
|
|
{
|
|
if (cond) {
|
|
s->conds |= 1ul << s->depth;
|
|
} else {
|
|
s->conds &= ~(1ul << s->depth);
|
|
}
|
|
s->depth += 1;
|
|
}
|
|
|
|
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, 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 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) {
|
|
ppsyms_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.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)));
|
|
|
|
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);
|
|
}
|
|
|
|
#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.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 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))) {
|
|
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) {
|
|
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.loc = tok.loc;
|
|
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.loc.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.loc.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->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);
|
|
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)
|
|
{
|
|
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;
|
|
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;
|
|
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, 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;
|
|
|
|
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;
|
|
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 != '#'))
|
|
step(p);
|
|
if (p->curr.ty == Tok_Eof)
|
|
break;
|
|
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)
|
|
{
|
|
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 == '#') {
|
|
step(p);
|
|
return interpret_directive(p);
|
|
}
|
|
return 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;
|
|
}
|