diff --git a/collection.c b/collection.c new file mode 100644 index 0000000..3919902 --- /dev/null +++ b/collection.c @@ -0,0 +1,77 @@ +#include "collection.h" +#include +#include +#include + +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; +} diff --git a/collection.h b/collection.h new file mode 100644 index 0000000..bf92c0a --- /dev/null +++ b/collection.h @@ -0,0 +1,26 @@ +#ifndef COLLECTION_H +#define COLLECTION_H + +#include + +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 diff --git a/examples/hello.c b/examples/hello.c index 7583bb3..64d917b 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -1,6 +1,6 @@ -#include +int puts(const char* __s); int main() { - printf("hello\n"); + puts("hello"); } diff --git a/parse.c b/lex.c similarity index 56% rename from parse.c rename to lex.c index 6119f43..35b5608 100644 --- a/parse.c +++ b/lex.c @@ -4,11 +4,6 @@ #include #include -// 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); } diff --git a/parse.h b/parse.h index b77b871..1daadac 100644 --- a/parse.h +++ b/parse.h @@ -1,13 +1,19 @@ #ifndef PARSE_H #define PARSE_H +#include "collection.h" #include +// 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 diff --git a/preproc.c b/preproc.c new file mode 100644 index 0000000..40aac23 --- /dev/null +++ b/preproc.c @@ -0,0 +1,158 @@ +#include "collection.h" +#include "parse.h" +#include +#include +#include +#include +#include + +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; +}