commit 90edb5a86eebfe628e1d864e52f70b7046cb0b67 Author: sfja Date: Wed Sep 16 00:20:55 2026 +0200 init diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..93dd8d5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,16 @@ +Language: Cpp +BasedOnStyle: WebKit +IndentWidth: 4 +ColumnLimit: 80 +IndentCaseLabels: true +InsertNewlineAtEOF: true +AllowShortFunctionsOnASingleLine: None + +BinPackArguments: false +AllowAllArgumentsOnNextLine: true + +BinPackParameters: false +AllowAllParametersOfDeclarationOnNextLine: true + +# PointerAlignment: Right + diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..afde1f1 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,6 @@ +-std=c23 +-pedantic-errors +-Wall +-Wextra + + diff --git a/lex.c b/lex.c new file mode 100644 index 0000000..aeb0333 --- /dev/null +++ b/lex.c @@ -0,0 +1,111 @@ +#include "lex.h" +#include +#include + +static bool done(Lexer* l) +{ + return l->text[l->idx] != '\0'; +} + +static Tok tok(Lexer* l, TokTy ty, Loc loc) +{ + return (Tok) { + .ty = ty, + .loc = loc, + .len = l->idx - loc.idx, + }; +} + +static void step(Lexer* l) +{ + if (done(l)) { + return; + } + + if (l->ch == '\n') { + l->line += 1; + l->col = 1; + } else { + l->col += 1; + } + l->idx += 1; + l->ch = l->text[l->idx]; +} + +static TokTy match_keyword(const char* text, size_t len) +{ + if (strncmp("if", text, len)) + return Tok_If; + return Tok_Ident; +} + +Tok lexer_next(Lexer* l) +{ + Loc loc = { l->idx, l->line, l->col }; + if (done(l)) { + return tok(l, Tok_Eof, loc); + } + bool matched = false; + while (strchr(" \t\r", l->ch)) { + matched = true; + step(l); + } + if (matched) { + return lexer_next(l); + } + while ((l->ch >= 'a' && l->ch <= 'z') || (l->ch >= 'A' && l->ch <= 'Z') + || (matched && l->ch >= '0' && l->ch <= '9') || strchr("_", l->ch)) { + matched = true; + step(l); + } + if (matched) { + return tok(l, match_keyword(&l->text[loc.idx], l->idx - loc.idx), loc); + } + while ((l->ch >= '1' && l->ch <= '9') || (matched && l->ch == '0')) { + matched = true; + step(l); + } + if (matched) { + return tok(l, Tok_Int, loc); + } + if (l->ch == '"') { + step(l); + while (l->ch != '"') { + if (l->ch == '\\') { + step(l); + if (done(l)) + break; + } + step(l); + } + if (done(l) || l->ch != '"') { + fprintf(stderr, "error: malformed string literal\n"); + return lexer_next(l); + } + step(l); + return tok(l, Tok_Str, loc); + } + if (l->ch == '\'') { + step(l); + if (l->ch == '\\') { + step(l); + } + if (!done(l)) { + step(l); + } + if (done(l) && l->ch != '\'') { + fprintf(stderr, "error: malformed character literal\n"); + return lexer_next(l); + } + step(l); + return tok(l, Tok_Char, loc); + } + if (strchr("\n(){}", l->ch)) { + TokTy ty = (TokTy)l->ch; + step(l); + return tok(l, ty, loc); + } + fprintf(stderr, "error: illegal character '%c'\n", l->ch); + step(l); + return lexer_next(l); +} diff --git a/lex.h b/lex.h new file mode 100644 index 0000000..4c4c634 --- /dev/null +++ b/lex.h @@ -0,0 +1,42 @@ +#ifndef LEX_H +#define LEX_H + +#include + +typedef struct { + size_t idx; + int line; + int col; +} Loc; + +typedef enum { + Tok_Eof, + Tok_Ident, + Tok_Int, + Tok_Str, + Tok_Char, + Tok_If, + Tok_Nl = '\n', + Tok_LParen = '(', + Tok_RParen = ')', + Tok_LBrace = '{', + Tok_RBrace = '}', +} TokTy; + +typedef struct { + TokTy ty; + Loc loc; + size_t len; +} Tok; + +typedef struct { + const char* text; + size_t idx; + int line; + int col; + char ch; +} Lexer; + +Tok lexer_next(Lexer* l); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..8308ac8 --- /dev/null +++ b/main.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include + +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'; + +couldnt_read_file: + fprintf(stderr, + "error: couldn't read file '%s': %s\n", + filename, + strerror(errno)); + return nullptr; +} + +int main(int argc, char* argv[]) +{ + if (argc < 2) { + fprintf(stderr, "error: no filename\n"); + return EXIT_FAILURE; + } + char* filename = argv[1]; + char* text = read_textfile(filename); + if (!text) { + return EXIT_FAILURE; + } +} diff --git a/parse.c b/parse.c new file mode 100644 index 0000000..9e484c3 --- /dev/null +++ b/parse.c @@ -0,0 +1,64 @@ +#include "parse.h" +#include + +// Using this as a reference: +// https://en.cppreference.com/c/language/translation_phases + +typedef struct { + int line; + int col; +} Loc; + +// Phase 1 already happens when the file is read. + +typedef struct { + Loc loc; + char ch; +} CannonicalChar; + +typedef struct { + const char* text; + size_t idx; + Loc loc; +} NewlineCanonicalizer; + +CannonicalChar next_cannonical_char(NewlineCanonicalizer* iter) +{ + Loc loc = iter->loc; + char ch = iter->text[iter->idx]; + if (ch == '\0') + return (CannonicalChar) { loc, ch }; + do { + if (ch == '\n') { + + } else { + } + iter->loc.col += 1; + + } while (iter->text[iter->idx]); +} + +// Phase 2 is an iterator that handles escaped newlines. + +typedef struct { + /// when this is '\0', it means EOF + char ch; + int line; + int col; +} Char; + +typedef struct { + const char* text; + size_t idx; + int line; + int col; +} CharIter; + +static Char next_src_char(CharIter* iter) +{ + if (!iter->text[iter->idx]) + return (Char) { .ch = '\0', iter->line, iter->col }; + char ch = iter->text[iter->idx]; + iter->idx += 1; + while (ch == '\\' &&) +} diff --git a/parse.h b/parse.h new file mode 100644 index 0000000..30c06e4 --- /dev/null +++ b/parse.h @@ -0,0 +1,4 @@ +#ifndef PARSE_H +#define PARSE_H + +#endif