add preproc

This commit is contained in:
sfja 2026-09-17 00:27:20 +02:00
parent 1767d1682c
commit b19290f20e
6 changed files with 364 additions and 59 deletions

77
collection.c Normal file
View File

@ -0,0 +1,77 @@
#include "collection.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
void array_push(
void** data, size_t* capacity, size_t* size, void* elem, size_t elem_size)
{
if (!*capacity) {
*capacity = 4;
}
if (!*data) {
*data = malloc(elem_size * *capacity);
*size = 0;
}
if (*size >= *capacity) {
*capacity *= 2;
*data = realloc(*data, *capacity * elem_size);
}
memcpy((unsigned char*)*data + *size, elem, elem_size);
*size += 1;
}
static void push_kwpair(Hashmap* m, KwPair* pair)
{
array_push((void**)&m->entries,
&m->entries_capacity,
&m->entries_size,
pair,
sizeof(*pair));
}
void hashmap_init(Hashmap* m)
{
*m = (Hashmap) {
.entries = nullptr,
.entries_capacity = 8,
.entries_size = 0,
};
}
void hashmap_deinit(Hashmap* m)
{
if (m->entries) {
for (size_t i = 0; i < m->entries_size; ++i) {
free(m->entries[i].key);
}
free(m->entries);
}
}
static KwPair* find_entry(Hashmap* m, const char* key)
{
for (size_t i = 0; i < m->entries_size; ++i) {
if (strcmp(m->entries[i].key, key) == 0) {
return &m->entries[i];
}
}
return nullptr;
}
void hashmap_set(Hashmap* m, const char* key, void* val)
{
KwPair* existing = find_entry(m, key);
if (existing) {
existing->val = val;
return;
}
push_kwpair(m, &(KwPair) { strdup(key), val });
}
void* hashmap_get(Hashmap* m, const char* key)
{
KwPair* existing = find_entry(m, key);
return existing ? existing->val : nullptr;
}

26
collection.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef COLLECTION_H
#define COLLECTION_H
#include <stddef.h>
void array_push(
void** data, size_t* capacity, size_t* size, void* elem, size_t elem_size);
typedef struct {
char* key;
void* val;
} KwPair;
typedef struct {
KwPair* entries;
size_t entries_capacity;
size_t entries_size;
} Hashmap;
void hashmap_init(Hashmap* m);
void hashmap_deinit(Hashmap* m);
void hashmap_set(Hashmap* m, const char* key, void* val);
void* hashmap_get(Hashmap* m, const char* key);
#endif

View File

@ -1,6 +1,6 @@
#include <stdio.h> int puts(const char* __s);
int main() int main()
{ {
printf("hello\n"); puts("hello");
} }

View File

@ -4,11 +4,6 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
// phases:
// https://en.cppreference.com/c/language/translation_phases
// phase 1 - source chars
void source_char_iter_init(SourceCharIter* r, const char* text) void source_char_iter_init(SourceCharIter* r, const char* text)
{ {
*r = (SourceCharIter) { *r = (SourceCharIter) {
@ -19,7 +14,7 @@ void source_char_iter_init(SourceCharIter* r, const char* text)
}; };
} }
SourceChar source_iter_next(SourceCharIter* r) Char source_iter_next(SourceCharIter* r)
{ {
char ch = r->text[r->i]; char ch = r->text[r->i];
int line = r->line; int line = r->line;
@ -35,14 +30,12 @@ SourceChar source_iter_next(SourceCharIter* r)
r->i += 1; r->i += 1;
} }
return (SourceChar) { ch, line, col }; return (Char) { ch, line, col };
} }
// phase 2 - logical source lines
void lsl_iter_init(LslCharIter* r, SourceCharIter* source_char_iter) void lsl_iter_init(LslCharIter* r, SourceCharIter* source_char_iter)
{ {
SourceChar curr = source_iter_next(source_char_iter); Char curr = source_iter_next(source_char_iter);
*r = (LslCharIter) { *r = (LslCharIter) {
.source_char_iter = source_char_iter, .source_char_iter = source_char_iter,
@ -50,30 +43,30 @@ void lsl_iter_init(LslCharIter* r, SourceCharIter* source_char_iter)
}; };
} }
LslChar lsl_iter_next(LslCharIter* r) Char lsl_iter_next(LslCharIter* r)
{ {
SourceChar next = source_iter_next(r->source_char_iter); Char next = source_iter_next(r->source_char_iter);
if (r->curr.ch == '\\' && next.ch == '\n') { if (r->curr.ch == '\\' && next.ch == '\n') {
r->curr = source_iter_next(r->source_char_iter); r->curr = source_iter_next(r->source_char_iter);
return lsl_iter_next(r); return lsl_iter_next(r);
} }
LslChar lsl_char = { r->curr.ch, r->curr.line, r->curr.col }; Char lsl_char = { r->curr.ch, r->curr.line, r->curr.col };
r->curr = next; r->curr = next;
return lsl_char; return lsl_char;
} }
// phase 3 - preprocessor tokens
void lexer_init(Lexer* r, LslCharIter* lsl_char_iter) void lexer_init(Lexer* r, LslCharIter* lsl_char_iter)
{ {
LslChar curr = lsl_iter_next(lsl_char_iter); Char curr = lsl_iter_next(lsl_char_iter);
*r = (Lexer) { *r = (Lexer) {
.lsl_char_iter = lsl_char_iter, .lsl_char_iter = lsl_char_iter,
.curr = curr, .curr = curr,
.lex_whitespace = false,
.lex_headername = false,
}; };
} }
@ -82,14 +75,14 @@ static Tok tok(TokTy ty, const char* text, int line, int col)
return (Tok) { ty, strdup(text), line, col }; return (Tok) { ty, strdup(text), line, col };
} }
static bool l_done(Lexer* l) static bool done(Lexer* l)
{ {
return l->curr.ch == '\0'; return l->curr.ch == '\0';
} }
constexpr size_t max_token_text_len = 64; constexpr size_t max_token_text_len = 64;
static void l_step(Lexer* l, char text[], size_t* text_len) static void step(Lexer* l, char text[], size_t* text_len)
{ {
assert(*text_len < max_token_text_len); assert(*text_len < max_token_text_len);
text[*text_len] = l->curr.ch; text[*text_len] = l->curr.ch;
@ -106,16 +99,19 @@ Tok lexer_next(Lexer* l)
char text[max_token_text_len] = ""; char text[max_token_text_len] = "";
size_t text_len = 0; size_t text_len = 0;
if (l_done(l)) { if (done(l)) {
return tok(Tok_Eof, text, line, col); return tok(Tok_Eof, text, line, col);
} }
bool matched = false; bool matched = false;
while (strchr(" \t\r", l->curr.ch)) { while (strchr(" \t\r", l->curr.ch)) {
matched = true; matched = true;
l_step(l, text, &text_len); step(l, text, &text_len);
} }
if (matched) { if (matched) {
if (l->lex_whitespace) {
return tok(Tok_Whitespace, text, line, col);
}
return lexer_next(l); return lexer_next(l);
} }
@ -124,7 +120,7 @@ Tok lexer_next(Lexer* l)
|| (matched && l->curr.ch >= '0' && l->curr.ch <= '9') || (matched && l->curr.ch >= '0' && l->curr.ch <= '9')
|| strchr("_", l->curr.ch)) { || strchr("_", l->curr.ch)) {
matched = true; matched = true;
l_step(l, text, &text_len); step(l, text, &text_len);
} }
if (matched) { if (matched) {
return tok(Tok_Ident, text, line, col); return tok(Tok_Ident, text, line, col);
@ -133,52 +129,75 @@ Tok lexer_next(Lexer* l)
while ((l->curr.ch >= '1' && l->curr.ch <= '9') while ((l->curr.ch >= '1' && l->curr.ch <= '9')
|| (matched && l->curr.ch == '0')) { || (matched && l->curr.ch == '0')) {
matched = true; matched = true;
l_step(l, text, &text_len); step(l, text, &text_len);
} }
if (matched) { if (matched) {
return tok(Tok_Int, text, line, col); return tok(Tok_Int, text, line, col);
} }
if (l->curr.ch == '"') { if (l->lex_headername && (l->curr.ch == '"' || l->curr.ch == '<')) {
l_step(l, text, &text_len); char ter = l->curr.ch == '<' ? '>' : '"';
while (l->curr.ch != '"') { while (l->curr.ch != '\0' && l->curr.ch != '\n' && l->curr.ch != ter) {
if (l->curr.ch == '\\') { step(l, text, &text_len);
l_step(l, text, &text_len);
if (l_done(l))
break;
}
l_step(l, text, &text_len);
} }
if (l_done(l) || l->curr.ch != '"') { if (l->curr.ch != ter) {
fprintf(stderr, "error: malformed string literal\n"); fprintf(stderr,
"error: malformed header name, expected '%c' on line %d\n",
ter,
l->curr.line);
return lexer_next(l); return lexer_next(l);
} }
l_step(l, text, &text_len); step(l, text, &text_len);
return tok(Tok_HeaderName, text, line, col);
}
if (l->curr.ch == '"') {
step(l, text, &text_len);
while (l->curr.ch != '"') {
if (l->curr.ch == '\\') {
step(l, text, &text_len);
if (done(l))
break;
}
step(l, text, &text_len);
}
if (done(l) || l->curr.ch != '"') {
fprintf(stderr,
"error: malformed string literal on line %d\n",
l->curr.line);
return lexer_next(l);
}
step(l, text, &text_len);
return tok(Tok_Str, text, line, col); return tok(Tok_Str, text, line, col);
} }
if (l->curr.ch == '\'') { if (l->curr.ch == '\'') {
l_step(l, text, &text_len); step(l, text, &text_len);
if (l->curr.ch == '\\') { if (l->curr.ch == '\\') {
l_step(l, text, &text_len); step(l, text, &text_len);
} }
if (!l_done(l)) { if (!done(l)) {
l_step(l, text, &text_len); step(l, text, &text_len);
} }
if (l_done(l) && l->curr.ch != '\'') { if (done(l) && l->curr.ch != '\'') {
fprintf(stderr, "error: malformed character literal\n"); fprintf(stderr,
"error: malformed character literal on line %d\n",
l->curr.line);
return lexer_next(l); return lexer_next(l);
} }
l_step(l, text, &text_len); step(l, text, &text_len);
return tok(Tok_Char, text, line, col); return tok(Tok_Char, text, line, col);
} }
if (strchr("\n;(){}", l->curr.ch)) { if (strchr("\n;(){}#", l->curr.ch)) {
TokTy ty = (TokTy)l->curr.ch; TokTy ty = (TokTy)l->curr.ch;
l_step(l, text, &text_len); step(l, text, &text_len);
return tok(ty, text, line, col); return tok(ty, text, line, col);
} }
fprintf(stderr, "error: illegal character '%c'\n", l->curr.ch); fprintf(stderr,
l_step(l, text, &text_len); "error: illegal character '%c' on line %d\n",
l->curr.ch,
l->curr.line);
step(l, text, &text_len);
return lexer_next(l); return lexer_next(l);
} }

49
parse.h
View File

@ -1,13 +1,19 @@
#ifndef PARSE_H #ifndef PARSE_H
#define PARSE_H #define PARSE_H
#include "collection.h"
#include <stddef.h> #include <stddef.h>
// phases:
// https://en.cppreference.com/c/language/translation_phases
// phase 1 - source chars
typedef struct { typedef struct {
char ch; char ch;
int line; int line;
int col; int col;
} SourceChar; } Char;
typedef struct { typedef struct {
const char* text; const char* text;
@ -17,36 +23,35 @@ typedef struct {
} SourceCharIter; } SourceCharIter;
void source_char_iter_init(SourceCharIter* r, const char* text); void source_char_iter_init(SourceCharIter* r, const char* text);
SourceChar source_iter_next(SourceCharIter* r); Char source_iter_next(SourceCharIter* r);
typedef struct { // phase 2 - logical source lines
char ch;
int line;
int col;
} LslChar;
typedef struct { typedef struct {
SourceCharIter* source_char_iter; SourceCharIter* source_char_iter;
SourceChar curr; Char curr;
} LslCharIter; } LslCharIter;
void lsl_iter_init(LslCharIter* r, SourceCharIter* source_char_iter); void lsl_iter_init(LslCharIter* r, SourceCharIter* source_char_iter);
LslChar lsl_iter_next(LslCharIter* r); Char lsl_iter_next(LslCharIter* r);
// phase 3 - preprocessor tokens
typedef enum { typedef enum {
Tok_Eof, Tok_Eof,
Tok_Eol,
Tok_Ident, Tok_Ident,
Tok_Int, Tok_Int,
Tok_Str, Tok_Str,
Tok_Char, Tok_Char,
Tok_Whitespace,
Tok_HeaderName, Tok_HeaderName,
Tok_Newline = '\n', // NOTE: has value 10 Tok_Eol = '\n', // NOTE: has value 10
Tok_Semicolon = ';', Tok_Semicolon = ';',
Tok_LParen = '(', Tok_LParen = '(',
Tok_RParen = ')', Tok_RParen = ')',
Tok_LBrace = '{', Tok_LBrace = '{',
Tok_RBrace = '}', Tok_RBrace = '}',
Tok_Hashtag = '#',
} TokTy; } TokTy;
typedef struct { typedef struct {
@ -58,10 +63,30 @@ typedef struct {
typedef struct { typedef struct {
LslCharIter* lsl_char_iter; LslCharIter* lsl_char_iter;
LslChar curr; Char curr;
bool lex_whitespace;
bool lex_headername;
} Lexer; } Lexer;
void lexer_init(Lexer* r, LslCharIter* lsl_char_iter); void lexer_init(Lexer* r, LslCharIter* lsl_char_iter);
Tok lexer_next(Lexer* l); Tok lexer_next(Lexer* l);
// phase 4 - preprocessor
typedef struct DefToks DefToks;
typedef struct {
Lexer* lexer;
Tok curr;
DefToks* deftoks;
size_t deftoks_cap;
size_t deftoks_size;
Hashmap defs;
} Preproc;
void preproc_init(Preproc* p, Lexer* lexer);
void preproc_deinit(Preproc* p);
Tok preproc_next(Preproc* p);
#endif #endif

158
preproc.c Normal file
View File

@ -0,0 +1,158 @@
#include "collection.h"
#include "parse.h"
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct DefToks {
Tok* toks;
size_t toks_cap;
size_t toks_size;
};
void preproc_init(Preproc* p, Lexer* lexer)
{
Tok curr = lexer_next(p->lexer);
*p = (Preproc) {
.lexer = lexer,
.curr = curr,
.deftoks = nullptr,
.deftoks_cap = 0,
.deftoks_size = 0,
.defs = { },
};
hashmap_init(&p->defs);
}
void preproc_deinit(Preproc* p)
{
if (p->deftoks) {
free(p->deftoks);
}
hashmap_deinit(&p->defs);
}
static void step(Preproc* p)
{
p->curr = lexer_next(p->lexer);
}
Tok preproc_next(Preproc* p)
{
if (p->curr.ty == '#') {
step(p);
Tok kw = p->curr;
if (strcmp(kw.text, "define") == 0) {
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->lexer->lex_whitespace = true;
step(p);
p->lexer->lex_whitespace = false;
if (p->curr.ty == '(') {
// function-like macro
assert(false && "not implemented");
}
DefToks toks = { };
while (p->curr.ty != Tok_Eof && p->curr.ty != Tok_Eol) {
array_push((void**)&toks.toks,
&toks.toks_cap,
&toks.toks_size,
&p->curr,
sizeof(p->curr));
step(p);
}
array_push((void**)&p->deftoks,
&p->deftoks_cap,
&p->deftoks_size,
&toks,
sizeof(toks));
DefToks* deftoks = &p->deftoks[p->deftoks_size - 1];
hashmap_set(&p->defs, ident_tok.text, deftoks);
return p->curr;
} else if (strcmp(kw.text, "undef") == 0) {
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);
}
hashmap_set(&p->defs, ident_tok.text, nullptr);
return p->curr;
} else if (strcmp(kw.text, "include") == 0) {
p->lexer->lex_headername = true;
step(p);
p->lexer->lex_headername = false;
if (p->curr.ty != Tok_HeaderName) {
fprintf(stderr,
"error: expected header name line %d\n",
p->curr.line);
exit(1);
}
assert(false && "not implemented");
} 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);
}
}
return p->curr;
}