This commit is contained in:
sfja 2026-09-16 00:20:55 +02:00
commit 90edb5a86e
7 changed files with 299 additions and 0 deletions

16
.clang-format Normal file
View File

@ -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

6
compile_flags.txt Normal file
View File

@ -0,0 +1,6 @@
-std=c23
-pedantic-errors
-Wall
-Wextra

111
lex.c Normal file
View File

@ -0,0 +1,111 @@
#include "lex.h"
#include <stdio.h>
#include <string.h>
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);
}

42
lex.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef LEX_H
#define LEX_H
#include <stddef.h>
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

56
main.c Normal file
View File

@ -0,0 +1,56 @@
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}
}

64
parse.c Normal file
View File

@ -0,0 +1,64 @@
#include "parse.h"
#include <stddef.h>
// 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 == '\\' &&)
}

4
parse.h Normal file
View File

@ -0,0 +1,4 @@
#ifndef PARSE_H
#define PARSE_H
#endif