aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-30 18:26:15 +0200
committerhachem <im@hachem.wtf>2026-08-30 18:26:15 +0200
commit287b075294610e224c67eb7a746c9fd7ae318275 (patch)
tree3e1a7dd3f4a1bede22f5f4d67a9283459fc3f485
parentea2ebf063cc6a87c73068a3e8b06fb744655159f (diff)
add: start nasm code gen
-rw-r--r--src/codegen/nasm.c91
-rw-r--r--tests/codegen_test.c30
2 files changed, 121 insertions, 0 deletions
diff --git a/src/codegen/nasm.c b/src/codegen/nasm.c
index 0782ee1..37d13a7 100644
--- a/src/codegen/nasm.c
+++ b/src/codegen/nasm.c
@@ -1,5 +1,7 @@
#include <stdio.h>
#include <stddef.h>
+#include <string.h>
+#include <stdbool.h>
#include "codegen/nasm.h"
@@ -32,6 +34,89 @@ static void emit_data(struct Program* program, FILE* out)
}
}
+static const char* assign_mnemonic(enum TokenType op)
+{
+ switch (op)
+ {
+ case TOKEN_EQUAL: return "mov";
+ case TOKEN_PLUS_EQUAL: return "add";
+ case TOKEN_MINUS_EQUAL: return "sub";
+ case TOKEN_STAR_EQUAL: return "imul";
+ default: return NULL;
+ }
+}
+
+static bool emit_operand(struct Expr* expr, FILE* out)
+{
+ switch (expr->kind)
+ {
+ case EXPR_PRIMARY:
+ fprintf(out, "%.*s", (int)expr->primary.token.length, expr->primary.token.start);
+ return true;
+ case EXPR_MEMBER:
+ if (!emit_operand(expr->member.object, out))
+ return false;
+ fprintf(out, ".%.*s", (int)expr->member.member.length, expr->member.member.start);
+ return true;
+ case EXPR_BINARY:
+ return false;
+ }
+
+ return false;
+}
+
+static void emit_assign(struct AssignStatement* assign, FILE* out)
+{
+ const char* mnemonic = assign_mnemonic(assign->op.type);
+ if (mnemonic == NULL || assign->value->kind == EXPR_BINARY)
+ {
+ fprintf(out, "\t; TODO: unsupported assignment\n");
+ return;
+ }
+
+ if (assign->target_deref)
+ fprintf(out, "\t%s [%.*s], ", mnemonic, (int)assign->target.length, assign->target.start);
+ else
+ fprintf(out, "\t%s %.*s, ", mnemonic, (int)assign->target.length, assign->target.start);
+
+ emit_operand(assign->value, out);
+ fprintf(out, "\n");
+}
+
+static void emit_statement(struct Statement* statement, FILE* out)
+{
+ switch (statement->kind)
+ {
+ case STATEMENT_ASSIGN:
+ emit_assign(&statement->assign, out);
+ break;
+ case STATEMENT_LABEL:
+ fprintf(out, "%.*s:\n", (int)statement->label.name.length, statement->label.name.start);
+ break;
+ case STATEMENT_GOTO:
+ fprintf(out, "\tjmp %.*s\n", (int)statement->jump.label.length, statement->jump.label.start);
+ break;
+ case STATEMENT_SYSCALL:
+ fprintf(out, "\tsyscall\n");
+ break;
+ default:
+ fprintf(out, "\t; TODO: unsupported statement\n");
+ break;
+ }
+}
+
+static void emit_proc(struct ProcDecl* proc, FILE* out)
+{
+ bool is_entry = proc->name.length == 4 && memcmp(proc->name.start, "main", 4) == 0;
+ if (is_entry)
+ fprintf(out, "_start:\n");
+ else
+ fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start);
+
+ for (size_t i = 0; i < proc->body_count; i += 1)
+ emit_statement(&proc->body[i], out);
+}
+
void generate_nasm(struct Program* program, FILE* out)
{
if (program->const_count > 0)
@@ -45,4 +130,10 @@ void generate_nasm(struct Program* program, FILE* out)
fprintf(out, "section .text\n");
fprintf(out, "global _start\n");
+
+ for (size_t i = 0; i < program->proc_count; i += 1)
+ {
+ fprintf(out, "\n");
+ emit_proc(&program->procs[i], out);
+ }
}
diff --git a/tests/codegen_test.c b/tests/codegen_test.c
index 64421ea..0d8c5e8 100644
--- a/tests/codegen_test.c
+++ b/tests/codegen_test.c
@@ -31,7 +31,37 @@ static void test_generate_consts_and_data(struct TestContext* context)
free_program(&program);
}
+static void test_generate_text(struct TestContext* context)
+{
+ struct Lexer lexer = create_lexer(
+ "proc main\n{\nrax = SYS_WRITE\nrsi = message\nrdx = message.len\n^rdi = rax\nloop:\nsyscall\ngoto loop\n}\n");
+ struct Program program;
+ check(context, parse_program(&lexer, &program));
+
+ FILE* out = tmpfile();
+ generate_nasm(&program, out);
+ fflush(out);
+ rewind(out);
+
+ char buffer[1024];
+ size_t read = fread(buffer, 1, sizeof(buffer) - 1, out);
+ buffer[read] = '\0';
+ fclose(out);
+
+ check(context, strstr(buffer, "_start:") != NULL);
+ check(context, strstr(buffer, "mov rax, SYS_WRITE") != NULL);
+ check(context, strstr(buffer, "mov rsi, message") != NULL);
+ check(context, strstr(buffer, "mov rdx, message.len") != NULL);
+ check(context, strstr(buffer, "mov [rdi], rax") != NULL);
+ check(context, strstr(buffer, "loop:") != NULL);
+ check(context, strstr(buffer, "syscall") != NULL);
+ check(context, strstr(buffer, "jmp loop") != NULL);
+
+ free_program(&program);
+}
+
void run_codegen_tests(struct TestContext* context)
{
test_generate_consts_and_data(context);
+ test_generate_text(context);
}