aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-31 17:55:16 +0200
committerhachem <im@hachem.wtf>2026-08-31 17:55:16 +0200
commitcbdb9073480c3e4d8a088b306c23406cf42e25c0 (patch)
tree39f3093654cc8f27ea701ed1338cac84d93f42eb /src
parent53aed3fadf1f7ad66dea9a8a0b46bf42eda921be (diff)
feat: hex/binary integer literals and constant-folding const expressions
Diffstat (limited to 'src')
-rw-r--r--src/codegen/nasm.c44
-rw-r--r--src/lexer/lexer.c24
-rw-r--r--src/parser/ast.c2
-rw-r--r--src/parser/ast.h2
-rw-r--r--src/parser/parser.c6
-rw-r--r--src/sema/sema.c61
6 files changed, 132 insertions, 7 deletions
diff --git a/src/codegen/nasm.c b/src/codegen/nasm.c
index 2f8de80..fef68a8 100644
--- a/src/codegen/nasm.c
+++ b/src/codegen/nasm.c
@@ -6,14 +6,31 @@
#include "codegen/nasm.h"
+static void emit_const_expr(struct Expr* expr, FILE* out)
+{
+ switch (expr->kind)
+ {
+ case EXPR_PRIMARY:
+ fprintf(out, "%.*s", (int)expr->primary.token.length, expr->primary.token.start);
+ break;
+ case EXPR_BINARY:
+ emit_const_expr(expr->binary.left, out);
+ fprintf(out, " %.*s ", (int)expr->binary.op.length, expr->binary.op.start);
+ emit_const_expr(expr->binary.right, out);
+ break;
+ default:
+ break;
+ }
+}
+
static void emit_consts(struct Program* program, FILE* out)
{
for (size_t i = 0; i < program->const_count; i += 1)
{
struct ConstDecl decl = program->consts[i];
- fprintf(out, "%%define %.*s %.*s\n",
- (int)decl.name.length, decl.name.start,
- (int)decl.value.length, decl.value.start);
+ fprintf(out, "%%define %.*s (", (int)decl.name.length, decl.name.start);
+ emit_const_expr(decl.value, out);
+ fprintf(out, ")\n");
}
}
@@ -125,6 +142,27 @@ static struct Token resolve_register(struct Emitter* emitter, struct Token token
static uint64_t token_to_u64(struct Token token)
{
+ if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'x' || token.start[1] == 'X'))
+ {
+ uint64_t value = 0;
+ for (size_t i = 2; i < token.length; i += 1)
+ {
+ char digit = token.start[i];
+ uint64_t nibble = digit <= '9' ? (uint64_t)(digit - '0')
+ : (uint64_t)((digit | 0x20) - 'a' + 10);
+ value = value * 16 + nibble;
+ }
+ return value;
+ }
+
+ if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'b' || token.start[1] == 'B'))
+ {
+ uint64_t value = 0;
+ for (size_t i = 2; i < token.length; i += 1)
+ value = value * 2 + (uint64_t)(token.start[i] - '0');
+ return value;
+ }
+
uint64_t value = 0;
for (size_t i = 0; i < token.length; i += 1)
value = value * 10 + (uint64_t)(token.start[i] - '0');
diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c
index fb3fce7..23d0a82 100644
--- a/src/lexer/lexer.c
+++ b/src/lexer/lexer.c
@@ -15,6 +15,13 @@ static bool is_digit(char character)
return character >= '0' && character <= '9';
}
+static bool is_hex_digit(char character)
+{
+ return is_digit(character)
+ || (character >= 'a' && character <= 'f')
+ || (character >= 'A' && character <= 'F');
+}
+
static enum TokenType identifier_type(const char* start, size_t length)
{
static const struct Keyword
@@ -177,8 +184,23 @@ struct Token scan_token(struct Lexer* lexer)
if (is_digit(character))
{
- while (is_digit(peek(lexer)))
+ if (character == '0' && (peek(lexer) == 'x' || peek(lexer) == 'X'))
+ {
+ advance(lexer);
+ while (is_hex_digit(peek(lexer)))
+ advance(lexer);
+ }
+ else if (character == '0' && (peek(lexer) == 'b' || peek(lexer) == 'B'))
+ {
advance(lexer);
+ while (peek(lexer) == '0' || peek(lexer) == '1')
+ advance(lexer);
+ }
+ else
+ {
+ while (is_digit(peek(lexer)))
+ advance(lexer);
+ }
return make_token(lexer, TOKEN_INTEGER, start);
}
diff --git a/src/parser/ast.c b/src/parser/ast.c
index f9f857d..704e9a4 100644
--- a/src/parser/ast.c
+++ b/src/parser/ast.c
@@ -78,6 +78,8 @@ struct Program create_program(void)
void free_program(struct Program* program)
{
+ for (size_t i = 0; i < program->const_count; i += 1)
+ free_expr(program->consts[i].value);
free(program->consts);
free(program->data_decls);
diff --git a/src/parser/ast.h b/src/parser/ast.h
index fc7af2b..98e8dea 100644
--- a/src/parser/ast.h
+++ b/src/parser/ast.h
@@ -9,7 +9,7 @@
struct ConstDecl
{
struct Token name;
- struct Token value;
+ struct Expr* value;
};
struct DataDecl
diff --git a/src/parser/parser.c b/src/parser/parser.c
index 969ed86..87dedbd 100644
--- a/src/parser/parser.c
+++ b/src/parser/parser.c
@@ -52,6 +52,8 @@ static bool consume(struct Parser* parser, enum TokenType type, const char* mess
return false;
}
+static struct Expr* parse_expression(struct Parser* parser);
+
static bool parse_const(struct Parser* parser, struct Program* program)
{
struct ConstDecl decl;
@@ -63,9 +65,9 @@ static bool parse_const(struct Parser* parser, struct Program* program)
if (!consume(parser, TOKEN_EQUAL, "expected '=' after constant name"))
return false;
- if (!consume(parser, TOKEN_INTEGER, "expected integer value after '='"))
+ decl.value = parse_expression(parser);
+ if (decl.value == NULL)
return false;
- decl.value = parser->previous;
add_const(program, decl);
return true;
diff --git a/src/sema/sema.c b/src/sema/sema.c
index fb53bbf..85907e9 100644
--- a/src/sema/sema.c
+++ b/src/sema/sema.c
@@ -81,6 +81,65 @@ static bool check_entry_point(struct Source source, struct Program* program)
return false;
}
+static bool is_program_const(struct Program* program, struct Token name)
+{
+ for (size_t i = 0; i < program->const_count; i += 1)
+ if (names_equal(program->consts[i].name, name))
+ return true;
+
+ return false;
+}
+
+static struct Token first_token(struct Expr* expr)
+{
+ switch (expr->kind)
+ {
+ case EXPR_BINARY: return first_token(expr->binary.left);
+ case EXPR_MEMBER: return first_token(expr->member.object);
+ case EXPR_DEREF: return first_token(expr->deref.address);
+ default: return expr->primary.token;
+ }
+}
+
+static bool check_const_value(struct Source source, struct Program* program, struct Expr* expr)
+{
+ if (expr->kind == EXPR_BINARY)
+ {
+ bool left = check_const_value(source, program, expr->binary.left);
+ bool right = check_const_value(source, program, expr->binary.right);
+ return left && right;
+ }
+
+ if (expr->kind == EXPR_PRIMARY)
+ {
+ struct Token token = expr->primary.token;
+ if (token.type == TOKEN_INTEGER || token.type == TOKEN_CHAR)
+ return true;
+
+ if (token.type == TOKEN_IDENTIFIER && is_program_const(program, token))
+ return true;
+
+ char message[128];
+ snprintf(message, sizeof(message), "'%.*s' is not a constant",
+ (int)token.length, token.start);
+ report_error(source, token, message);
+ return false;
+ }
+
+ report_error(source, first_token(expr), "constant must be an integer expression");
+ return false;
+}
+
+static bool check_const_values(struct Source source, struct Program* program)
+{
+ bool ok = true;
+ for (size_t i = 0; i < program->const_count; i += 1)
+ if (!check_const_value(source, program, program->consts[i].value))
+ ok = false;
+
+ return ok;
+}
+
struct RefCheck
{
struct Source source;
@@ -335,6 +394,8 @@ bool analyze_program(struct Source source, struct Program* program)
ok = false;
if (!check_entry_point(source, program))
ok = false;
+ if (!check_const_values(source, program))
+ ok = false;
if (!check_references(source, program))
ok = false;