diff options
| -rw-r--r-- | docs/language.md | 4 | ||||
| -rw-r--r-- | examples/constants.hdass | 15 | ||||
| -rwxr-xr-x | scripts/test_examples.sh | 1 | ||||
| -rw-r--r-- | src/codegen/nasm.c | 44 | ||||
| -rw-r--r-- | src/lexer/lexer.c | 24 | ||||
| -rw-r--r-- | src/parser/ast.c | 2 | ||||
| -rw-r--r-- | src/parser/ast.h | 2 | ||||
| -rw-r--r-- | src/parser/parser.c | 6 | ||||
| -rw-r--r-- | src/sema/sema.c | 61 | ||||
| -rw-r--r-- | tests/codegen_test.c | 2 | ||||
| -rw-r--r-- | tests/lexer_test.c | 9 | ||||
| -rw-r--r-- | tests/parser_test.c | 21 | ||||
| -rw-r--r-- | tests/sema_test.c | 18 |
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); } |
