aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md6
-rw-r--r--docs/language.md13
-rw-r--r--examples/modulo.hdass15
-rwxr-xr-xscripts/run.sh3
-rwxr-xr-xscripts/test_examples.sh1
-rw-r--r--src/codegen/nasm.c44
-rw-r--r--src/lexer/lexer.c5
-rw-r--r--src/lexer/lexer.h2
-rw-r--r--src/parser/ast.c18
-rw-r--r--src/parser/parser.c25
-rw-r--r--src/sema/sema.c26
-rw-r--r--tests/codegen_test.c227
-rw-r--r--tests/parser_test.c10
13 files changed, 164 insertions, 231 deletions
diff --git a/README.md b/README.md
index 5201424..7c3847c 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ hachem's dumb assembly super-set, pronounced "HD Ass".
The entire point of this project is to provide a middle ground between C and assembly. If we think about why we still write assembly today, it's usually because we need direct control over what the CPU is doing. We want control over the exact instructions being executed, the memory, the stack and everything else that higher-level programming languages normally abstract away. The thing is, not every program written in assembly actually needs that control. Sometimes you want to write something close to the machine without having to manuall deal with every tiny detail yourself. You still want registers, explicit control over memory and a good understanding of what your program is doing, but you don't necessarily need to manually express everything as individual assembly instructions.
-This is where hdass comes in. It's not quite high-level enough to be a C-like language, but it's also not low-level enough to be as annoying to write as raw assembly. The goal is to sit somewhere in between, keeping the parts of assembly that make it useful while making the parts that don't need to be painful a little nicer to work with. hdass transpiles into multiple flavours of assembly, such as NASM and MASM, rather than directly producing machine code. The idea is to provide a single language for writing low-level programs while allowing the backend to translate code into the assembler syntax you want to target. You're still ultimately producing assembly, and you're never particularly far away from the code that gets assembled. The goal isn't to hide the machine from you or turn assembly into C. There are already plenty of high-level languages that do that. hdass just fills the gap between the two, where you might want some more convenience whilst writing assembly without taking away the reason you wanted to work close to the machine in the first place.
+This is where this piece of shit comes in. It's not quite high-level enough to be a C-like language, but it's also not low-level enough to be as annoying to write as raw assembly. The goal is to sit somewhere in between, keeping the parts of assembly that make it useful while making the parts that don't need to be painful a little nicer to work with. hdass transpiles into multiple flavours of assembly, such as NASM and MASM, rather than directly producing machine code. The idea is to provide a single language for writing low-level programs while allowing the backend to translate code into the assembler syntax you want to target. You're still ultimately producing assembly, and you're never particularly far away from the code that gets assembled. The goal isn't to hide the machine from you or turn assembly into C. There are already plenty of high-level languages that do that. hdass just fills the gap between the two, where you might want some more convenience whilst writing assembly without taking away the reason you wanted to work close to the machine in the first place.
## Examples
Here's a simple "Hello, World!" world program written using hdass' syntax:
@@ -15,7 +15,7 @@ const SYS_EXIT = 60
const STDOUT = 1
-data message = "Hello, World!\n"
+data message = "type shi\n"
proc main
{
@@ -34,7 +34,7 @@ proc main
The program still directly controls the registers used for the system calls. Nothing is hiding what the program is doing. More examples can be found in [examples/](examples/).
## Documentation
-The full language reference — directives, declarations, statements, expressions, registers and extensions — lives in [docs/language.md](docs/language.md).
+The full language reference, shit like directives, declarations, statements, expressions, registers and extensions, is explained in [docs/language.md](docs/language.md).
## Building
The build is driven by [premake5](https://premake.github.io/). Generate the makefiles and build the compiler:
diff --git a/docs/language.md b/docs/language.md
index d67cbf0..d77ca72 100644
--- a/docs/language.md
+++ b/docs/language.md
@@ -113,8 +113,8 @@ Written by their architecture names — `rax`–`rdi`, `rbp`, `rsp`, `r8`–`r15
```hdass
rax = SYS_WRITE // mov
-rcx -= 1 // += -= *= /= -> add sub imul idiv
-rax = rbx * rcx // + - * / in a value; / and /= use rax:rdx (see Gotchas)
+rcx -= 1 // += -= *= /= %= -> add sub imul idiv (idiv)
+rax = rbx * rcx // + - * / % in a value; / % and their = forms use rax:rdx
rdx = buffer + 31 // address math
loop: // label
goto loop
@@ -156,7 +156,7 @@ rdx = ^rsi + 4 // load, then add 4
## Expressions
-Assignment values and `if` operands: registers, integers, chars (`'0'`), constants, data names, member access (`data.len`), and `+` `-` `*` `/`. Operators are left-associative and each right-hand operand must be a single term, so `a * b + c` works but `a + b * c` (a nested right operand) doesn't yet.
+Assignment values and `if` operands: registers, integers, chars (`'0'`), constants, data names, member access (`data.len`), and `+` `-` `*` `/` `%`. Operators are left-associative and each right-hand operand must be a single term, so `a * b + c` works but `a + b * c` (a nested right operand) doesn't yet.
## Extensions
@@ -231,9 +231,6 @@ ld -e main program.o -o program
The [README](../README.md) has a Docker setup with these tools.
-## Gotchas
+## Some stinkies
+Clobbering is your responsibility: `syscall` trashes `rcx` and `r11`, while a callee can trash any registers it touches, so nothing is saved automatically. `examples/fibonacci.hdass`, for example, keeps its counter in `r15` for this reason. Register widths must also match, meaning something like `rax = r1.8` would become `mov rax, al`, which will not assemble. Division has its own restrictions: `/` and `/=` use `idiv` through `rax:rdx`, clobbering both registers regardless of the destination, and the divisor cannot be `rax`, `rdx`, or an immediate, so it must first be placed in another register. Finally, the entry procedure has no `ret`; it should end with an exit syscall.
-- **Clobbering is yours.** `syscall` trashes `rcx`/`r11`; a callee trashes what it touches. Nothing is saved for you — `examples/fibonacci.hdass` keeps its counter in `r15` for this reason.
-- **Widths must match.** `rax = r1.8` becomes `mov rax, al`, which won't assemble.
-- **Division uses `rax:rdx`.** `/` and `/=` go through `idiv`, so they clobber `rax` and `rdx` regardless of the target, and the divisor can't be `rax`, `rdx`, or an immediate — put it in another register first.
-- **The entry procedure has no `ret`** — end it with an exit syscall. \ No newline at end of file
diff --git a/examples/modulo.hdass b/examples/modulo.hdass
new file mode 100644
index 0000000..44c290d
--- /dev/null
+++ b/examples/modulo.hdass
@@ -0,0 +1,15 @@
+[entry: main]
+
+// Modulo: 2024 % 100 = 24, returned as the exit status.
+const SYS_EXIT = 60
+
+proc main
+{
+ rax = 2024
+ rbx = 100
+ rax %= rbx
+
+ rdi = rax
+ rax = SYS_EXIT
+ syscall
+}
diff --git a/scripts/run.sh b/scripts/run.sh
index 6d71521..01d3882 100755
--- a/scripts/run.sh
+++ b/scripts/run.sh
@@ -20,6 +20,9 @@ docker compose exec -T -e SRC="$file" -e NAME="$name" hdass bash -c '
premake5 gmake >/dev/null
make config=debug >/dev/null
./bin/debug-linux/hdass "$SRC" -o "/tmp/$NAME.asm"
+ echo
+ cat "/tmp/$NAME.asm"
+ echo
nasm -f elf64 "/tmp/$NAME.asm" -o "/tmp/$NAME.o"
ld -e main "/tmp/$NAME.o" -o "/tmp/$NAME"
set +e
diff --git a/scripts/test_examples.sh b/scripts/test_examples.sh
index 5290f09..c13ee1d 100755
--- a/scripts/test_examples.sh
+++ b/scripts/test_examples.sh
@@ -82,6 +82,7 @@ check branch "selects the larger of two values" examples/branch.hdass
check call "passes an argument through a proc" examples/call.hdass 21 ""
check logical "logical register names (r1..r14)" examples/logical.hdass 21 ""
check mul_div "multiply and non-rax division" examples/mul_div.hdass 8 ""
+check modulo "the modulo operator (%)" examples/modulo.hdass 24 ""
check load "stores then loads through a pointer" examples/load.hdass 7 ""
check constants "hex literals and constant folding" examples/constants.hdass 42 ""
check records "enum values and struct field offsets" examples/records.hdass 42 ""
diff --git a/src/codegen/nasm.c b/src/codegen/nasm.c
index 31bcee0..6df22a4 100644
--- a/src/codegen/nasm.c
+++ b/src/codegen/nasm.c
@@ -93,7 +93,7 @@ static bool is_float_register(struct Token token)
return true;
}
-static size_t float_index(struct FloatTable* floats, struct Token literal)
+static size_t float_index(const struct FloatTable* floats, struct Token literal)
{
for (size_t i = 0; i < floats->count; i += 1)
if (floats->items[i].length == literal.length
@@ -310,7 +310,7 @@ static bool fold_member(struct Program* program, struct Expr* object, struct Tok
return false;
struct Token name = object->primary.token;
- struct EnumDecl* enumeration = find_enum(program, name);
+ const struct EnumDecl* enumeration = find_enum(program, name);
if (enumeration != NULL)
{
for (size_t i = 0; i < enumeration->member_count; i += 1)
@@ -322,7 +322,7 @@ static bool fold_member(struct Program* program, struct Expr* object, struct Tok
return false;
}
- struct StructDecl* layout = find_struct(program, name);
+ const struct StructDecl* layout = find_struct(program, name);
if (layout != NULL)
{
uint64_t offset = 0;
@@ -400,7 +400,7 @@ static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out
// offset (or the total size for `.size`)
static bool emit_named_member(struct Emitter* emitter, struct Token object, struct Token member)
{
- struct EnumDecl* enumeration = find_enum(emitter->program, object);
+ const struct EnumDecl* enumeration = find_enum(emitter->program, object);
if (enumeration != NULL)
{
for (size_t i = 0; i < enumeration->member_count; i += 1)
@@ -411,7 +411,7 @@ static bool emit_named_member(struct Emitter* emitter, struct Token object, stru
}
}
- struct StructDecl* layout = find_struct(emitter->program, object);
+ const struct StructDecl* layout = find_struct(emitter->program, object);
if (layout != NULL)
{
uint64_t offset = 0;
@@ -494,6 +494,19 @@ static void emit_division(struct Emitter* emitter, const char* dst, struct Expr*
fprintf(emitter->out, "\tmov %s, rax\n", dst);
}
+// idiv leaves the remainder in rdx, so a modulo takes its result from there
+static void emit_modulo(struct Emitter* emitter, const char* dst, struct Expr* divisor)
+{
+ if (strcmp(dst, "rax") != 0)
+ fprintf(emitter->out, "\tmov rax, %s\n", dst);
+ fprintf(emitter->out, "\tcqo\n");
+ fprintf(emitter->out, "\tidiv ");
+ emit_operand(emitter, divisor);
+ fprintf(emitter->out, "\n");
+ if (strcmp(dst, "rdx") != 0)
+ fprintf(emitter->out, "\tmov %s, rdx\n", dst);
+}
+
static void emit_divide(struct Emitter* emitter, struct AssignStatement* assign)
{
if (assign->target_deref)
@@ -505,7 +518,11 @@ static void emit_divide(struct Emitter* emitter, struct AssignStatement* assign)
struct Token target = resolve_register(emitter, assign->target);
char dst[32];
snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start);
- emit_division(emitter, dst, assign->value);
+
+ if (assign->op.type == TOKEN_PERCENT_EQUAL)
+ emit_modulo(emitter, dst, assign->value);
+ else
+ emit_division(emitter, dst, assign->value);
}
// an expression can be evaluated into a register when it is a single term
@@ -576,6 +593,11 @@ static void emit_expr_into(struct Emitter* emitter, const char* dst, struct Expr
emit_division(emitter, dst, expr->binary.right);
return;
}
+ if (expr->binary.op.type == TOKEN_PERCENT)
+ {
+ emit_modulo(emitter, dst, expr->binary.right);
+ return;
+ }
const char* mnemonic =
expr->binary.op.type == TOKEN_PLUS ? "add" :
@@ -677,7 +699,7 @@ static const char* float_mnemonic(enum TokenType op)
}
}
-static bool value_is_float(struct Emitter* emitter, struct Expr* expr)
+static bool value_is_float(struct Emitter* emitter, const struct Expr* expr)
{
if (expr->kind != EXPR_PRIMARY)
return false;
@@ -766,7 +788,7 @@ static void emit_assign(struct Emitter* emitter, struct AssignStatement* assign)
return;
}
- if (assign->op.type == TOKEN_SLASH_EQUAL)
+ if (assign->op.type == TOKEN_SLASH_EQUAL || assign->op.type == TOKEN_PERCENT_EQUAL)
{
emit_divide(emitter, assign);
return;
@@ -860,7 +882,7 @@ static struct ProcDecl* find_proc(struct Program* program, struct Token name)
static void emit_call(struct Emitter* emitter, struct CallStatement* call)
{
- struct ProcDecl* callee = find_proc(emitter->program, call->name);
+ const struct ProcDecl* callee = find_proc(emitter->program, call->name);
if (callee == NULL || callee->param_count != call->arg_count)
{
fprintf(emitter->out, "\t; TODO: unsupported call\n");
@@ -902,7 +924,7 @@ static const char* float_jump_if_false(enum TokenType comparison)
}
}
-static void emit_float_operand(struct Emitter* emitter, struct Expr* expr)
+static void emit_float_operand(struct Emitter* emitter, const struct Expr* expr)
{
if (expr->kind == EXPR_PRIMARY && expr->primary.token.type == TOKEN_FLOAT)
{
@@ -1082,7 +1104,7 @@ static struct FloatTable collect_floats(struct Program* program)
return floats;
}
-static void emit_float_data(struct FloatTable* floats, FILE* out)
+static void emit_float_data(const struct FloatTable* floats, FILE* out)
{
for (size_t i = 0; i < floats->count; i += 1)
fprintf(out, "__float%zu: dq %.*s\n", i,
diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c
index b4fb7d7..01bbd41 100644
--- a/src/lexer/lexer.c
+++ b/src/lexer/lexer.c
@@ -114,7 +114,7 @@ static void skip_whitespace(struct Lexer* lexer)
}
}
-static struct Token make_token(struct Lexer* lexer, enum TokenType type, const char* start)
+static struct Token make_token(const struct Lexer* lexer, enum TokenType type, const char* start)
{
struct Token token;
token.type = type;
@@ -231,6 +231,7 @@ struct Token scan_token(struct Lexer* lexer)
case '-': return make_token(lexer, match(lexer, '=') ? TOKEN_MINUS_EQUAL : TOKEN_MINUS, start);
case '*': return make_token(lexer, match(lexer, '=') ? TOKEN_STAR_EQUAL : TOKEN_STAR, start);
case '/': return make_token(lexer, match(lexer, '=') ? TOKEN_SLASH_EQUAL : TOKEN_SLASH, start);
+ case '%': return make_token(lexer, match(lexer, '=') ? TOKEN_PERCENT_EQUAL : TOKEN_PERCENT, start);
case '^': return make_token(lexer, TOKEN_CARET, start);
case '.': return make_token(lexer, TOKEN_DOT, start);
@@ -275,6 +276,7 @@ const char* token_type_name(enum TokenType type)
case TOKEN_MINUS: return "minus";
case TOKEN_STAR: return "star";
case TOKEN_SLASH: return "slash";
+ case TOKEN_PERCENT: return "percent";
case TOKEN_CARET: return "caret";
case TOKEN_BANG: return "bang";
case TOKEN_LESS: return "less";
@@ -294,6 +296,7 @@ const char* token_type_name(enum TokenType type)
case TOKEN_MINUS_EQUAL: return "minus_equal";
case TOKEN_STAR_EQUAL: return "star_equal";
case TOKEN_SLASH_EQUAL: return "slash_equal";
+ case TOKEN_PERCENT_EQUAL: return "percent_equal";
case TOKEN_LESS_EQUAL: return "less_equal";
case TOKEN_GREATER_EQUAL: return "greater_equal";
case TOKEN_UNKNOWN: return "unknown";
diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h
index 5c1e868..45e9b3d 100644
--- a/src/lexer/lexer.h
+++ b/src/lexer/lexer.h
@@ -31,6 +31,7 @@ enum TokenType
TOKEN_MINUS,
TOKEN_STAR,
TOKEN_SLASH,
+ TOKEN_PERCENT,
TOKEN_CARET,
TOKEN_BANG,
TOKEN_LESS,
@@ -51,6 +52,7 @@ enum TokenType
TOKEN_MINUS_EQUAL,
TOKEN_STAR_EQUAL,
TOKEN_SLASH_EQUAL,
+ TOKEN_PERCENT_EQUAL,
TOKEN_LESS_EQUAL,
TOKEN_GREATER_EQUAL,
diff --git a/src/parser/ast.c b/src/parser/ast.c
index 788341b..a387533 100644
--- a/src/parser/ast.c
+++ b/src/parser/ast.c
@@ -150,10 +150,7 @@ void add_data(struct Program* program, struct DataDecl decl)
struct EnumDecl create_enum(void)
{
- struct EnumDecl decl;
- decl.members = NULL;
- decl.member_count = 0;
- decl.member_capacity = 0;
+ struct EnumDecl decl = { 0 };
return decl;
}
@@ -185,10 +182,7 @@ void add_enum(struct Program* program, struct EnumDecl decl)
struct StructDecl create_struct(void)
{
- struct StructDecl decl;
- decl.fields = NULL;
- decl.field_count = 0;
- decl.field_capacity = 0;
+ struct StructDecl decl = { 0 };
return decl;
}
@@ -220,13 +214,7 @@ void add_struct(struct Program* program, struct StructDecl decl)
struct ProcDecl create_proc(void)
{
- struct ProcDecl proc;
- proc.params = NULL;
- proc.param_count = 0;
- proc.param_capacity = 0;
- proc.body = NULL;
- proc.body_count = 0;
- proc.body_capacity = 0;
+ struct ProcDecl proc = { 0 };
return proc;
}
diff --git a/src/parser/parser.c b/src/parser/parser.c
index 3ed9fff..a83c3c4 100644
--- a/src/parser/parser.c
+++ b/src/parser/parser.c
@@ -209,13 +209,15 @@ static bool is_assign_op(enum TokenType type)
|| type == TOKEN_PLUS_EQUAL
|| type == TOKEN_MINUS_EQUAL
|| type == TOKEN_STAR_EQUAL
- || type == TOKEN_SLASH_EQUAL;
+ || type == TOKEN_SLASH_EQUAL
+ || type == TOKEN_PERCENT_EQUAL;
}
static struct Expr* alloc_expr(enum ExprKind kind)
{
struct Expr* expr = malloc(sizeof(struct Expr));
- expr->kind = kind;
+ if (expr != NULL)
+ expr->kind = kind;
return expr;
}
@@ -289,13 +291,14 @@ static struct Expr* parse_postfix(struct Parser* parser)
return expr;
}
-static struct Expr* parse_binary(struct Parser* parser, struct Expr* (*operand)(struct Parser*), enum TokenType a, enum TokenType b)
+static struct Expr* parse_binary(struct Parser* parser, struct Expr* (*operand)(struct Parser*),
+ enum TokenType a, enum TokenType b, enum TokenType c)
{
struct Expr* left = operand(parser);
if (left == NULL)
return NULL;
- while (check(parser, a) || check(parser, b))
+ while (check(parser, a) || check(parser, b) || check(parser, c))
{
advance_parser(parser);
struct Token op = parser->previous;
@@ -319,12 +322,12 @@ static struct Expr* parse_binary(struct Parser* parser, struct Expr* (*operand)(
static struct Expr* parse_multiplicative(struct Parser* parser)
{
- return parse_binary(parser, parse_postfix, TOKEN_STAR, TOKEN_SLASH);
+ return parse_binary(parser, parse_postfix, TOKEN_STAR, TOKEN_SLASH, TOKEN_PERCENT);
}
static struct Expr* parse_expression(struct Parser* parser)
{
- return parse_binary(parser, parse_multiplicative, TOKEN_PLUS, TOKEN_MINUS);
+ return parse_binary(parser, parse_multiplicative, TOKEN_PLUS, TOKEN_MINUS, TOKEN_MINUS);
}
static bool is_compare_op(enum TokenType type)
@@ -354,7 +357,13 @@ static bool parse_call(struct Parser* parser, struct Token name, struct Statemen
if (count == capacity)
{
capacity = capacity < 4 ? 4 : capacity * 2;
- args = realloc(args, capacity * sizeof(struct Expr*));
+ struct Expr** grown = realloc(args, capacity * sizeof(struct Expr*));
+ if (grown == NULL)
+ {
+ free_expr(arg);
+ goto error;
+ }
+ args = grown;
}
args[count] = arg;
count += 1;
@@ -614,7 +623,7 @@ static bool parse_directive(struct Parser* parser, struct Program* program)
bool parse_program(struct Lexer* lexer, struct Program* out)
{
- struct Parser parser;
+ struct Parser parser = { 0 };
parser.lexer = lexer;
parser.source.name = lexer->name;
parser.source.text = lexer->source;
diff --git a/src/sema/sema.c b/src/sema/sema.c
index 12fd53f..4b4bc26 100644
--- a/src/sema/sema.c
+++ b/src/sema/sema.c
@@ -33,6 +33,8 @@ static bool check_duplicate_names(struct Source source, struct Program* program)
return true;
struct Token* names = malloc(count * sizeof(struct Token));
+ if (names == NULL)
+ return true;
size_t n = 0;
for (size_t i = 0; i < program->const_count; i += 1)
{
@@ -92,7 +94,7 @@ static bool check_entry_point(struct Source source, struct Program* program)
return false;
}
-static bool is_program_const(struct Program* program, struct Token name)
+static bool is_program_const(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))
@@ -227,7 +229,7 @@ static bool is_register(struct RefCheck* check, struct Token token)
return check->program->config.logical_registers && is_logical_register(token);
}
-static bool is_param(struct RefCheck* check, struct Token token)
+static bool is_param(const struct RefCheck* check, struct Token token)
{
for (size_t i = 0; i < check->proc->param_count; i += 1)
if (names_equal(check->proc->params[i].name, token))
@@ -236,7 +238,7 @@ static bool is_param(struct RefCheck* check, struct Token token)
return false;
}
-static bool is_const(struct RefCheck* check, struct Token token)
+static bool is_const(const struct RefCheck* check, struct Token token)
{
for (size_t i = 0; i < check->program->const_count; i += 1)
if (names_equal(check->program->consts[i].name, token))
@@ -245,7 +247,7 @@ static bool is_const(struct RefCheck* check, struct Token token)
return false;
}
-static bool is_data(struct RefCheck* check, struct Token token)
+static bool is_data(const struct RefCheck* check, struct Token token)
{
for (size_t i = 0; i < check->program->data_count; i += 1)
if (names_equal(check->program->data_decls[i].name, token))
@@ -276,7 +278,7 @@ static bool is_stack_buffer(struct RefCheck* check, struct Token token)
{
for (size_t i = 0; i < check->proc->body_count; i += 1)
{
- struct Statement* statement = &check->proc->body[i];
+ const struct Statement* statement = &check->proc->body[i];
if (statement->kind == STATEMENT_STACK && names_equal(statement->stack.name, token))
return true;
}
@@ -288,7 +290,7 @@ static bool is_label(struct RefCheck* check, struct Token token)
{
for (size_t i = 0; i < check->proc->body_count; i += 1)
{
- struct Statement* statement = &check->proc->body[i];
+ const struct Statement* statement = &check->proc->body[i];
if (statement->kind == STATEMENT_LABEL && names_equal(statement->label.name, token))
return true;
}
@@ -305,7 +307,7 @@ static void check_value_name(struct RefCheck* check, struct Token name)
ref_error(check, name, "undefined name '%.*s'", (int)name.length, name.start);
}
-static void check_expr(struct RefCheck* check, struct Expr* expr)
+static void check_expr(struct RefCheck* check, const struct Expr* expr)
{
switch (expr->kind)
{
@@ -319,7 +321,7 @@ static void check_expr(struct RefCheck* check, struct Expr* expr)
break;
case EXPR_DEREF:
{
- struct Expr* address = expr->deref.address;
+ const struct Expr* address = expr->deref.address;
if (address->kind == EXPR_PRIMARY
&& (is_register(check, address->primary.token) || is_param(check, address->primary.token)))
break;
@@ -332,7 +334,7 @@ static void check_expr(struct RefCheck* check, struct Expr* expr)
}
case EXPR_MEMBER:
{
- struct Expr* object = expr->member.object;
+ const struct Expr* object = expr->member.object;
struct Token member = expr->member.member;
if (member.type == TOKEN_INTEGER)
@@ -344,7 +346,7 @@ static void check_expr(struct RefCheck* check, struct Expr* expr)
if (object->kind == EXPR_PRIMARY)
{
- struct EnumDecl* enumeration = find_enum(check, object->primary.token);
+ const struct EnumDecl* enumeration = find_enum(check, object->primary.token);
if (enumeration != NULL)
{
bool found = false;
@@ -358,7 +360,7 @@ static void check_expr(struct RefCheck* check, struct Expr* expr)
break;
}
- struct StructDecl* layout = find_struct(check, object->primary.token);
+ const struct StructDecl* layout = find_struct(check, object->primary.token);
if (layout != NULL)
{
bool found = token_is(member, "size");
@@ -406,7 +408,7 @@ static void check_stack_size(struct RefCheck* check, struct Expr* expr)
break;
case EXPR_MEMBER:
{
- struct Expr* object = expr->member.object;
+ const struct Expr* object = expr->member.object;
if (object->kind == EXPR_PRIMARY
&& (find_enum(check, object->primary.token) != NULL
|| find_struct(check, object->primary.token) != NULL))
diff --git a/tests/codegen_test.c b/tests/codegen_test.c
index 377ddeb..8fe1465 100644
--- a/tests/codegen_test.c
+++ b/tests/codegen_test.c
@@ -5,21 +5,32 @@
#include "codegen/nasm.h"
#include "tests.h"
-static void test_generate_consts_and_data(struct TestContext* context)
+static void generate_to_buffer(struct Program* program, char* buffer, size_t size)
{
- struct Lexer lexer = create_lexer("const N = 5\ndata msg = \"hi\"\n");
- struct Program program;
- check(context, parse_program(&lexer, &program));
-
FILE* out = tmpfile();
- generate_nasm(&program, out);
+ if (out == NULL)
+ {
+ buffer[0] = '\0';
+ return;
+ }
+
+ generate_nasm(program, out);
fflush(out);
rewind(out);
- char buffer[1024];
- size_t read = fread(buffer, 1, sizeof(buffer) - 1, out);
+ size_t read = fread(buffer, 1, size - 1, out);
buffer[read] = '\0';
fclose(out);
+}
+
+static void test_generate_consts_and_data(struct TestContext* context)
+{
+ struct Lexer lexer = create_lexer("const N = 5\ndata msg = \"hi\"\n");
+ struct Program program;
+ check(context, parse_program(&lexer, &program));
+
+ char buffer[1024];
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "%define N (5)") != NULL);
check(context, strstr(buffer, "section .data") != NULL);
@@ -37,15 +48,8 @@ static void test_generate_text(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "main:") != NULL);
check(context, strstr(buffer, "mov rax, SYS_WRITE") != NULL);
@@ -65,15 +69,8 @@ static void test_generate_if(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "cmp rcx, 0") != NULL);
check(context, strstr(buffer, "je .if_end_0") != NULL);
@@ -90,15 +87,8 @@ static void test_generate_call(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "mov rdi, r12") != NULL);
check(context, strstr(buffer, "call print_number") != NULL);
@@ -113,15 +103,8 @@ static void test_generate_param_substitution(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "mov rax, rdi") != NULL);
check(context, strstr(buffer, "value") == NULL);
@@ -135,15 +118,8 @@ static void test_generate_divide(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "cqo") != NULL);
check(context, strstr(buffer, "idiv rbx") != NULL);
@@ -157,15 +133,8 @@ static void test_generate_multiply(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "mov rax, rbx") != NULL);
check(context, strstr(buffer, "imul rax, rcx") != NULL);
@@ -176,6 +145,25 @@ static void test_generate_multiply(struct TestContext* context)
free_program(&program);
}
+static void test_generate_modulo(struct TestContext* context)
+{
+ struct Lexer lexer = create_lexer("proc main\n{\nrbx %= rcx\nrax = rsi % rdi\n}\n");
+ struct Program program;
+ check(context, parse_program(&lexer, &program));
+
+ char buffer[1024];
+ generate_to_buffer(&program, buffer, sizeof(buffer));
+
+ // modulo takes idiv's remainder from rdx
+ check(context, strstr(buffer, "mov rax, rbx") != NULL);
+ check(context, strstr(buffer, "idiv rcx") != NULL);
+ check(context, strstr(buffer, "mov rbx, rdx") != NULL);
+ check(context, strstr(buffer, "mov rax, rdx") != NULL);
+ check(context, strstr(buffer, "; TODO") == NULL);
+
+ free_program(&program);
+}
+
static void test_generate_divide_nonrax(struct TestContext* context)
{
// division into a register other than rax routes through rax:rdx
@@ -183,15 +171,8 @@ static void test_generate_divide_nonrax(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "mov rax, rbx") != NULL);
check(context, strstr(buffer, "idiv rcx") != NULL);
@@ -208,15 +189,8 @@ static void test_generate_stack_frame(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "push rbp") != NULL);
check(context, strstr(buffer, "mov rbp, rsp") != NULL);
@@ -233,15 +207,8 @@ static void test_generate_address_expr(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "lea rsi, [rbp - 32]") != NULL);
check(context, strstr(buffer, "add rsi, 31") != NULL);
@@ -260,15 +227,8 @@ static void test_generate_sized_store(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "mov byte [rsi], dl") != NULL);
check(context, strstr(buffer, "mov dword [rsi], eax") != NULL);
@@ -284,15 +244,8 @@ static void test_generate_entry_and_bits(struct TestContext* context)
struct Program program;
check(context, parse_program(&with_entry, &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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "bits 64") != NULL);
check(context, strstr(buffer, "global main") != NULL);
@@ -310,15 +263,8 @@ static void test_generate_no_entry(struct TestContext* context)
struct Program program;
check(context, parse_program(&no_entry, &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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "global") == NULL);
check(context, strstr(buffer, "main:") != NULL);
@@ -333,15 +279,8 @@ static void test_generate_logical_registers(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "mov rax, 5") != NULL); // r1 -> rax
check(context, strstr(buffer, "mov rdx, r11") != NULL); // r4 -> rdx, r10 -> r11
@@ -356,15 +295,8 @@ static void test_generate_logical_disabled(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
// without the extension, r1 is passed through untouched
check(context, strstr(buffer, "mov r1, 5") != NULL);
@@ -379,15 +311,8 @@ static void test_generate_load(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "mov rax, [rsi]") != NULL);
check(context, strstr(buffer, "movzx rbx, byte [rsi]") != NULL);
@@ -408,15 +333,8 @@ static void test_generate_enum_struct(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "mov rax, 2") != NULL); // Color.Blue -> 2
check(context, strstr(buffer, "mov rbx, 8") != NULL); // Point.y -> 8
@@ -433,15 +351,8 @@ static void test_generate_floats(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "__float0: dq 3.5") != NULL);
check(context, strstr(buffer, "movsd xmm0, [__float0]") != NULL);
@@ -459,15 +370,8 @@ static void test_generate_float_compare(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "ucomisd xmm0, [__float0]") != NULL);
check(context, strstr(buffer, "jbe .if_end") != NULL); // '>' skips when <=
@@ -483,15 +387,8 @@ static void test_generate_add_zero_peephole(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "add rax, 0") == NULL);
check(context, strstr(buffer, "sub rbx, 0") == NULL);
@@ -506,15 +403,8 @@ static void test_generate_float_memory(struct TestContext* context)
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);
+ generate_to_buffer(&program, buffer, sizeof(buffer));
check(context, strstr(buffer, "movsd [rsi], xmm0") != NULL); // float store
check(context, strstr(buffer, "movsd xmm1, [rsi]") != NULL); // float load
@@ -542,6 +432,7 @@ void run_codegen_tests(struct TestContext* context)
test_generate_param_substitution(context);
test_generate_divide(context);
test_generate_multiply(context);
+ test_generate_modulo(context);
test_generate_divide_nonrax(context);
test_generate_stack_frame(context);
test_generate_address_expr(context);
diff --git a/tests/parser_test.c b/tests/parser_test.c
index dbb250f..ca1d345 100644
--- a/tests/parser_test.c
+++ b/tests/parser_test.c
@@ -10,7 +10,7 @@ static bool text_is(struct Token token, const char* text)
return token.length == length && memcmp(token.start, text, length) == 0;
}
-static bool primary_is(struct Expr* expr, const char* text)
+static bool primary_is(const struct Expr* expr, const char* text)
{
return expr->kind == EXPR_PRIMARY && text_is(expr->primary.token, text);
}
@@ -37,7 +37,7 @@ static void test_parse_const_expr(struct TestContext* context)
check(context, parse_program(&lexer, &program));
- struct Expr* value = program.consts[1].value;
+ const struct Expr* value = program.consts[1].value;
check(context, value->kind == EXPR_BINARY);
check(context, text_is(value->binary.op, "+"));
check(context, primary_is(value->binary.left, "A"));
@@ -133,13 +133,13 @@ static void test_parse_expressions(struct TestContext* context)
check(context, parse_program(&lexer, &program));
check(context, program.procs[0].body_count == 2);
- struct Expr* sum = program.procs[0].body[0].assign.value;
+ const struct Expr* sum = program.procs[0].body[0].assign.value;
check(context, sum->kind == EXPR_BINARY);
check(context, text_is(sum->binary.op, "+"));
check(context, primary_is(sum->binary.left, "buffer"));
check(context, primary_is(sum->binary.right, "31"));
- struct Expr* member = program.procs[0].body[1].assign.value;
+ const struct Expr* member = program.procs[0].body[1].assign.value;
check(context, member->kind == EXPR_MEMBER);
check(context, primary_is(member->member.object, "message"));
check(context, text_is(member->member.member, "len"));
@@ -257,7 +257,7 @@ static void test_parse_register_size_suffix(struct TestContext* context)
check(context, parse_program(&lexer, &program));
- struct Expr* value = program.procs[0].body[0].assign.value;
+ const struct Expr* value = program.procs[0].body[0].assign.value;
check(context, value->kind == EXPR_MEMBER);
check(context, value->member.member.type == TOKEN_INTEGER);
check(context, text_is(value->member.member, "8"));