aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/language.md4
-rw-r--r--examples/constants.hdass15
-rwxr-xr-xscripts/test_examples.sh1
-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
-rw-r--r--tests/codegen_test.c2
-rw-r--r--tests/lexer_test.c9
-rw-r--r--tests/parser_test.c21
-rw-r--r--tests/sema_test.c18
13 files changed, 198 insertions, 11 deletions
diff --git a/docs/language.md b/docs/language.md
index 2429838..43b6246 100644
--- a/docs/language.md
+++ b/docs/language.md
@@ -50,10 +50,12 @@ Unknown keys, a `bits` value other than 32/64, and unknown extensions are errors
## Constants and data
-`const` names an integer (becomes a NASM `%define`). `data` puts a string in `.data`; the name is its address and `.len` is its length in bytes.
+`const` names a constant integer expression — integer literals, character literals, other constants, and `+` `-` `*` `/`. Integers are decimal, `0x` hex, or `0b` binary (these forms work anywhere an integer does). `data` puts a string in `.data`; the name is its address and `.len` is its length in bytes.
```hdass
const STDOUT = 1
+const MASK = 0xFF
+const AREA = 8 * 6 // 48
data message = "type shi.\n" // message -> address, message.len -> 10
```
diff --git a/examples/constants.hdass b/examples/constants.hdass
new file mode 100644
index 0000000..1bb85eb
--- /dev/null
+++ b/examples/constants.hdass
@@ -0,0 +1,15 @@
+[entry: main]
+
+// The exit status is folded from constant arithmetic, and SYS_EXIT is a hex
+// literal. STATUS = 6 * 7 = 42.
+const A = 6
+const B = 7
+const STATUS = A * B
+const SYS_EXIT = 0x3C
+
+proc main
+{
+ rdi = STATUS
+ rax = SYS_EXIT
+ syscall
+}
diff --git a/scripts/test_examples.sh b/scripts/test_examples.sh
index 1c677e7..3e04fde 100755
--- a/scripts/test_examples.sh
+++ b/scripts/test_examples.sh
@@ -83,6 +83,7 @@ check call "passes an argument through a proc" examples/call.hdass
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 load "stores then loads through a pointer" examples/load.hdass 7 ""
+check constants "hex literals and constant folding" examples/constants.hdass 42 ""
check fibonacci "prints the first ten Fibonacci numbers" examples/fibonacci.hdass 0 "0
1
1
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;
diff --git a/tests/codegen_test.c b/tests/codegen_test.c
index 1cb0071..c56ca7a 100644
--- a/tests/codegen_test.c
+++ b/tests/codegen_test.c
@@ -21,7 +21,7 @@ static void test_generate_consts_and_data(struct TestContext* context)
buffer[read] = '\0';
fclose(out);
- check(context, strstr(buffer, "%define N 5") != NULL);
+ check(context, strstr(buffer, "%define N (5)") != NULL);
check(context, strstr(buffer, "section .data") != NULL);
check(context, strstr(buffer, "msg: db `hi`") != NULL);
check(context, strstr(buffer, ".len equ $ - msg") != NULL);
diff --git a/tests/lexer_test.c b/tests/lexer_test.c
index f677793..7260d6c 100644
--- a/tests/lexer_test.c
+++ b/tests/lexer_test.c
@@ -20,6 +20,14 @@ static void test_identifiers_and_integers(struct TestContext* context)
check(context, scan_token(&lexer).type == TOKEN_EOF);
}
+static void test_number_bases(struct TestContext* context)
+{
+ struct Lexer lexer = create_lexer("42 0x1F 0b1010");
+ check(context, token_matches(scan_token(&lexer), TOKEN_INTEGER, "42"));
+ check(context, token_matches(scan_token(&lexer), TOKEN_INTEGER, "0x1F"));
+ check(context, token_matches(scan_token(&lexer), TOKEN_INTEGER, "0b1010"));
+}
+
static void test_operators(struct TestContext* context)
{
struct Lexer lexer = create_lexer("= == += != /");
@@ -76,6 +84,7 @@ static void test_comments(struct TestContext* context)
void run_lexer_tests(struct TestContext* context)
{
test_identifiers_and_integers(context);
+ test_number_bases(context);
test_operators(context);
test_literals(context);
test_keywords(context);
diff --git a/tests/parser_test.c b/tests/parser_test.c
index 3fa3342..5a45e9e 100644
--- a/tests/parser_test.c
+++ b/tests/parser_test.c
@@ -23,9 +23,25 @@ static void test_parse_consts(struct TestContext* context)
check(context, parse_program(&lexer, &program));
check(context, program.const_count == 2);
check(context, text_is(program.consts[0].name, "A"));
- check(context, text_is(program.consts[0].value, "1"));
+ check(context, primary_is(program.consts[0].value, "1"));
check(context, text_is(program.consts[1].name, "B"));
- check(context, text_is(program.consts[1].value, "60"));
+ check(context, primary_is(program.consts[1].value, "60"));
+
+ free_program(&program);
+}
+
+static void test_parse_const_expr(struct TestContext* context)
+{
+ struct Lexer lexer = create_lexer("const A = 1\nconst B = A + 2 * 3\n");
+ struct Program program;
+
+ check(context, parse_program(&lexer, &program));
+
+ 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"));
+ check(context, value->binary.right->kind == EXPR_BINARY);
free_program(&program);
}
@@ -271,6 +287,7 @@ static void test_parse_errors(struct TestContext* context)
void run_parser_tests(struct TestContext* context)
{
test_parse_consts(context);
+ test_parse_const_expr(context);
test_parse_data(context);
test_parse_proc_params(context);
test_parse_proc_body(context);
diff --git a/tests/sema_test.c b/tests/sema_test.c
index 2757e6c..2d6e187 100644
--- a/tests/sema_test.c
+++ b/tests/sema_test.c
@@ -76,6 +76,21 @@ static void test_assign_to_const(struct TestContext* context)
check(context, !analyze_source("const K = 5\nproc main\n{\nK = 1\n}\n"));
}
+static void test_const_expr_ok(struct TestContext* context)
+{
+ check(context, analyze_source("const A = 1\nconst B = A + 2 * 3\nproc main\n{\nsyscall\n}\n"));
+}
+
+static void test_const_expr_rejects_register(struct TestContext* context)
+{
+ check(context, !analyze_source("const X = rax + 1\nproc main\n{\nsyscall\n}\n"));
+}
+
+static void test_const_expr_rejects_data(struct TestContext* context)
+{
+ check(context, !analyze_source("data d = \"x\"\nconst X = d\nproc main\n{\nsyscall\n}\n"));
+}
+
static void test_deref_needs_register(struct TestContext* context)
{
check(context, !analyze_source("proc main\n{\nrax = ^MISSING\n}\n"));
@@ -114,6 +129,9 @@ void run_sema_tests(struct TestContext* context)
test_undefined_call(context);
test_call_arg_count(context);
test_assign_to_const(context);
+ test_const_expr_ok(context);
+ test_const_expr_rejects_register(context);
+ test_const_expr_rejects_data(context);
test_deref_needs_register(context);
test_references_resolve(context);
}