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()
{
printf("hello\n");
puts("hello");
}

View File

@ -4,11 +4,6 @@
#include <stdio.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)
{
*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];
int line = r->line;
@ -35,14 +30,12 @@ SourceChar source_iter_next(SourceCharIter* r)
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)
{
SourceChar curr = source_iter_next(source_char_iter);
Char curr = source_iter_next(source_char_iter);
*r = (LslCharIter) {
.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') {
r->curr = source_iter_next(r->source_char_iter);
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;
return lsl_char;
}
// phase 3 - preprocessor tokens
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) {
.lsl_char_iter = lsl_char_iter,
.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 };
}
static bool l_done(Lexer* l)
static bool 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)
static void step(Lexer* l, char text[], size_t* text_len)
{
assert(*text_len < max_token_text_len);
text[*text_len] = l->curr.ch;
@ -106,16 +99,19 @@ Tok lexer_next(Lexer* l)
char text[max_token_text_len] = "";
size_t text_len = 0;
if (l_done(l)) {
if (done(l)) {
return tok(Tok_Eof, text, line, col);
}
bool matched = false;
while (strchr(" \t\r", l->curr.ch)) {
matched = true;
l_step(l, text, &text_len);
step(l, text, &text_len);
}
if (matched) {
if (l->lex_whitespace) {
return tok(Tok_Whitespace, text, line, col);
}
return lexer_next(l);
}
@ -124,7 +120,7 @@ Tok lexer_next(Lexer* l)
|| (matched && l->curr.ch >= '0' && l->curr.ch <= '9')
|| strchr("_", l->curr.ch)) {
matched = true;
l_step(l, text, &text_len);
step(l, text, &text_len);
}
if (matched) {
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')
|| (matched && l->curr.ch == '0')) {
matched = true;
l_step(l, text, &text_len);
step(l, text, &text_len);
}
if (matched) {
return tok(Tok_Int, text, line, col);
}
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->lex_headername && (l->curr.ch == '"' || l->curr.ch == '<')) {
char ter = l->curr.ch == '<' ? '>' : '"';
while (l->curr.ch != '\0' && l->curr.ch != '\n' && l->curr.ch != ter) {
step(l, text, &text_len);
}
if (l_done(l) || l->curr.ch != '"') {
fprintf(stderr, "error: malformed string literal\n");
if (l->curr.ch != ter) {
fprintf(stderr,
"error: malformed header name, expected '%c' on line %d\n",
ter,
l->curr.line);
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);
}
if (l->curr.ch == '\'') {
l_step(l, text, &text_len);
step(l, text, &text_len);
if (l->curr.ch == '\\') {
l_step(l, text, &text_len);
step(l, text, &text_len);
}
if (!l_done(l)) {
l_step(l, text, &text_len);
if (!done(l)) {
step(l, text, &text_len);
}
if (l_done(l) && l->curr.ch != '\'') {
fprintf(stderr, "error: malformed character literal\n");
if (done(l) && l->curr.ch != '\'') {
fprintf(stderr,
"error: malformed character literal on line %d\n",
l->curr.line);
return lexer_next(l);
}
l_step(l, text, &text_len);
step(l, text, &text_len);
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;
l_step(l, text, &text_len);
step(l, text, &text_len);
return tok(ty, text, line, col);
}
fprintf(stderr, "error: illegal character '%c'\n", l->curr.ch);
l_step(l, text, &text_len);
fprintf(stderr,
"error: illegal character '%c' on line %d\n",
l->curr.ch,
l->curr.line);
step(l, text, &text_len);
return lexer_next(l);
}

49
parse.h
View File

@ -1,13 +1,19 @@
#ifndef PARSE_H
#define PARSE_H
#include "collection.h"
#include <stddef.h>
// phases:
// https://en.cppreference.com/c/language/translation_phases
// phase 1 - source chars
typedef struct {
char ch;
int line;
int col;
} SourceChar;
} Char;
typedef struct {
const char* text;
@ -17,36 +23,35 @@ typedef struct {
} SourceCharIter;
void source_char_iter_init(SourceCharIter* r, const char* text);
SourceChar source_iter_next(SourceCharIter* r);
Char source_iter_next(SourceCharIter* r);
typedef struct {
char ch;
int line;
int col;
} LslChar;
// phase 2 - logical source lines
typedef struct {
SourceCharIter* source_char_iter;
SourceChar curr;
Char curr;
} LslCharIter;
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 {
Tok_Eof,
Tok_Eol,
Tok_Ident,
Tok_Int,
Tok_Str,
Tok_Char,
Tok_Whitespace,
Tok_HeaderName,
Tok_Newline = '\n', // NOTE: has value 10
Tok_Eol = '\n', // NOTE: has value 10
Tok_Semicolon = ';',
Tok_LParen = '(',
Tok_RParen = ')',
Tok_LBrace = '{',
Tok_RBrace = '}',
Tok_Hashtag = '#',
} TokTy;
typedef struct {
@ -58,10 +63,30 @@ typedef struct {
typedef struct {
LslCharIter* lsl_char_iter;
LslChar curr;
Char curr;
bool lex_whitespace;
bool lex_headername;
} Lexer;
void lexer_init(Lexer* r, LslCharIter* lsl_char_iter);
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

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;
}