From b233755d751db7de0deea1eeb24a30070a38524c Mon Sep 17 00:00:00 2001 From: TheDevConnor Date: Thu, 16 Oct 2025 22:24:43 -0400 Subject: [PATCH 1/3] Fixed issue with str data type in the typechecker --- src/typechecker/lookup.c | 2 +- src/typechecker/type.c | 8 ++++---- tests/test.lx | 38 +++++++++++++++++++++++++------------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/typechecker/lookup.c b/src/typechecker/lookup.c index 6cb06006..042d0270 100644 --- a/src/typechecker/lookup.c +++ b/src/typechecker/lookup.c @@ -84,7 +84,7 @@ AstNode *typecheck_expression(AstNode *expr, Scope *scope, case LITERAL_FLOAT: return create_basic_type(arena, "double", expr->line, expr->column); case LITERAL_STRING: - return create_basic_type(arena, "string", expr->line, expr->column); + return create_basic_type(arena, "str", expr->line, expr->column); case LITERAL_BOOL: return create_basic_type(arena, "bool", expr->line, expr->column); case LITERAL_CHAR: diff --git a/src/typechecker/type.c b/src/typechecker/type.c index 703dcebd..d1213c60 100644 --- a/src/typechecker/type.c +++ b/src/typechecker/type.c @@ -56,12 +56,12 @@ TypeMatchResult types_match(AstNode *type1, AstNode *type2) { bool type1_is_builtin = (strcmp(name1, "int") == 0 || strcmp(name1, "float") == 0 || strcmp(name1, "double") == 0 || strcmp(name1, "bool") == 0 || - strcmp(name1, "string") == 0 || strcmp(name1, "char") == 0 || + strcmp(name1, "str") == 0 || strcmp(name1, "char") == 0 || strcmp(name1, "void") == 0); bool type2_is_builtin = (strcmp(name2, "int") == 0 || strcmp(name2, "float") == 0 || strcmp(name2, "double") == 0 || strcmp(name2, "bool") == 0 || - strcmp(name2, "string") == 0 || strcmp(name2, "char") == 0 || + strcmp(name2, "str") == 0 || strcmp(name2, "char") == 0 || strcmp(name2, "void") == 0); // Allow enum to int conversion (one is enum, other is int) @@ -85,7 +85,7 @@ TypeMatchResult types_match(AstNode *type1, AstNode *type2) { AstNode *pointee = type2->type_data.pointer.pointee_type; // string is compatible with char* - if (strcmp(basic_name, "string") == 0 && pointee && + if (strcmp(basic_name, "str") == 0 && pointee && pointee->category == Node_Category_TYPE && pointee->type == AST_TYPE_BASIC && strcmp(pointee->type_data.basic.name, "char") == 0) { @@ -101,7 +101,7 @@ TypeMatchResult types_match(AstNode *type1, AstNode *type2) { if (pointee && pointee->category == Node_Category_TYPE && pointee->type == AST_TYPE_BASIC && strcmp(pointee->type_data.basic.name, "char") == 0 && - strcmp(basic_name, "string") == 0) { + strcmp(basic_name, "str") == 0) { return TYPE_MATCH_COMPATIBLE; } } diff --git a/tests/test.lx b/tests/test.lx index 81dcfe03..4eb489f4 100644 --- a/tests/test.lx +++ b/tests/test.lx @@ -1,21 +1,33 @@ -@module "main" +// @module "main" -@use "string" as string -@use "terminal" as term +// @use "string" as string +// @use "terminal" as term -pub const main = fn () int { - output("Press any key (Press 'q' to quit): \n"); +// pub const main = fn () int { +// output("Press any key (Press 'q' to quit): \n"); - loop { - let c: char = term::getch(); +// loop { +// let c: char = term::getch(); - if (c == 'q') { - output("\nQuiting...\n"); - break; - } +// if (c == 'q') { +// output("\nQuiting...\n"); +// break; +// } - output("\nYou pressed: ", string::from_char(c), "\n"); - } +// output("\nYou pressed: ", string::from_char(c), "\n"); +// } + +// return 0; +// } +@module "main" + +pub const main = fn () int { + let person: [str; 7] = ["Software Dev", "Compiler Dev", + "Low-Level Dev", "C Dev", + "Rust Dev", "Zig Dev", "Zura Dev"]; + loop [i: int = 0](i < 7) : (++i) { + output("Hello there, ", person[i], "!\n"); + } return 0; } From c7d3cd397aebb7f75e556277c6aaa88043d2990e Mon Sep 17 00:00:00 2001 From: TheDevConnor Date: Fri, 17 Oct 2025 00:03:09 -0400 Subject: [PATCH 2/3] Fixed issues with defer and chars in the lexer. Also added a putchar function --- c_test_files/putchar.c | 23 ++++++++++++++++++ src/lexer/lexer.c | 55 ++++++++++++++++++++++++++++++++++++------ src/llvm/expr.c | 22 ++++++----------- src/typechecker/expr.c | 2 +- std/string.lx | 29 ++++++++++++++++++++++ tests/test.lx | 36 ++++++--------------------- 6 files changed, 115 insertions(+), 52 deletions(-) create mode 100644 c_test_files/putchar.c diff --git a/c_test_files/putchar.c b/c_test_files/putchar.c new file mode 100644 index 00000000..f94276fa --- /dev/null +++ b/c_test_files/putchar.c @@ -0,0 +1,23 @@ +#include +#include + +int putchar_custom(int c) { + char cmd[16]; + if (c == '\n') { + system("echo"); + } else { + // Print single character without newline using printf -n + // Some shells don't support -n, so fallback is printf '%c' + snprintf(cmd, sizeof(cmd), "printf '%c'", c); + system(cmd); + } + return c; +} + +int main(void) { + putchar_custom('H'); + putchar_custom('i'); + putchar_custom('!'); + putchar_custom('\n'); + return 0; +} diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 18c9f4f1..8e63b430 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -451,6 +451,8 @@ Token next_token(Lexer *lx) { if (c == '\'') { const char *char_start = lx->current; // Start after opening quote + char actual_char = 0; // The actual character value we'll store + int char_count = 0; // How many characters we consumed if (is_at_end(lx)) { // Unclosed character literal @@ -466,6 +468,8 @@ Token next_token(Lexer *lx) { // Handle escape sequences if (ch == '\\') { advance(lx); // consume backslash + char_count++; + if (is_at_end(lx)) { report_lexer_error(lx, "LexerError", "unknown_file", "Incomplete escape sequence in character literal", @@ -475,10 +479,31 @@ Token next_token(Lexer *lx) { } char escaped = peek(lx, 0); - // Validate common escape sequences - if (escaped != 'n' && escaped != 't' && escaped != 'r' && - escaped != '\\' && escaped != '\'' && escaped != '\"' && - escaped != '0') { + + // Convert escape sequence to actual character value + switch (escaped) { + case 'n': + actual_char = '\n'; + break; + case 't': + actual_char = '\t'; + break; + case 'r': + actual_char = '\r'; + break; + case '\\': + actual_char = '\\'; + break; + case '\'': + actual_char = '\''; + break; + case '\"': + actual_char = '\"'; + break; + case '0': + actual_char = '\0'; + break; + default: { static char error_msg[64]; snprintf(error_msg, sizeof(error_msg), "Invalid escape sequence '\\%c' in character literal", @@ -488,7 +513,11 @@ Token next_token(Lexer *lx) { lx->line, lx->col - 2, 3); return MAKE_TOKEN(TOK_ERROR, start, lx, 3, wh_count); } + } + advance(lx); // consume escaped character + char_count++; + } else if (ch == '\'') { // Empty character literal report_lexer_error(lx, "LexerError", "unknown_file", @@ -497,6 +526,7 @@ Token next_token(Lexer *lx) { lx->col - 1, 2); advance(lx); // consume closing quote return MAKE_TOKEN(TOK_ERROR, start, lx, 2, wh_count); + } else if (ch == '\n' || ch == '\r') { // Newline in character literal report_lexer_error(lx, "LexerError", "unknown_file", @@ -504,9 +534,12 @@ Token next_token(Lexer *lx) { get_line_text_from_source(lx->src, lx->line), lx->line, lx->col - 1, 1); return MAKE_TOKEN(TOK_ERROR, start, lx, 1, wh_count); + } else { // Regular character + actual_char = ch; advance(lx); // consume the character + char_count = 1; } // Check for closing quote @@ -521,10 +554,16 @@ Token next_token(Lexer *lx) { advance(lx); // consume closing quote int total_len = (int)(lx->current - start); - int content_len = - (int)(lx->current - char_start - 1); // Length of content between quotes - return make_token(TOK_CHAR_LITERAL, char_start, lx->line, - lx->col - total_len, content_len, wh_count); + + // CRITICAL: Store the actual character value, not the escape sequence + // We'll create a single-character string with the resolved value + char *char_storage = (char *)arena_alloc(lx->arena, 2, 1); + char_storage[0] = actual_char; + char_storage[1] = '\0'; + + return make_token(TOK_CHAR_LITERAL, char_storage, lx->line, + lx->col - total_len, 1, + wh_count); // length = 1 (single char) } // Strings diff --git a/src/llvm/expr.c b/src/llvm/expr.c index 84dde289..48722e2e 100644 --- a/src/llvm/expr.c +++ b/src/llvm/expr.c @@ -1471,15 +1471,10 @@ LLVMValueRef codegen_expr_deref(CodeGenContext *ctx, AstNode *node) { LLVM_Symbol *sym = find_symbol(ctx, var_name); if (sym && !sym->is_function) { - LLVMTypeRef sym_type = sym->type; - - // If the symbol is a pointer type, we need to determine what it points to - if (LLVMGetTypeKind(sym_type) == LLVMPointerTypeKind) { - // For opaque pointers (newer LLVM), we need to infer the pointee type - // This is a simplified approach - in a real compiler you'd track this - // through your type system - - // Try to infer based on variable name patterns + if (sym->element_type) { + element_type = sym->element_type; + } else { + // Fallback: infer from variable name patterns if (strstr(var_name, "ptr") || strstr(var_name, "aligned_ptr")) { // Check if this looks like a void** -> void* case if (strstr(var_name, "aligned")) { @@ -1494,9 +1489,8 @@ LLVMValueRef codegen_expr_deref(CodeGenContext *ctx, AstNode *node) { } else if (strstr(var_name, "double")) { element_type = LLVMDoubleTypeInContext(ctx->context); } else { - // Default for void** -> void* - element_type = - LLVMPointerType(LLVMInt8TypeInContext(ctx->context), 0); + // Default for unknown pointer types + element_type = LLVMInt64TypeInContext(ctx->context); } } else { // Generic pointer dereference - assume int64 for safety @@ -1508,8 +1502,8 @@ LLVMValueRef codegen_expr_deref(CodeGenContext *ctx, AstNode *node) { // Final fallback if we couldn't determine the type if (!element_type) { - // Default to void* for unknown pointer dereferences - element_type = LLVMPointerType(LLVMInt8TypeInContext(ctx->context), 0); + fprintf(stderr, "Warning: Could not determine pointer element type for dereference, defaulting to i64\n"); + element_type = LLVMInt64TypeInContext(ctx->context); } return LLVMBuildLoad2(ctx->builder, element_type, ptr, "deref"); diff --git a/src/typechecker/expr.c b/src/typechecker/expr.c index a3a74a22..09a23c8b 100644 --- a/src/typechecker/expr.c +++ b/src/typechecker/expr.c @@ -891,7 +891,7 @@ AstNode *typecheck_system_expr(AstNode *expr, Scope *scope, } // Verify the command is a string type - AstNode *expected_string = create_basic_type(arena, "string", 0, 0); + AstNode *expected_string = create_basic_type(arena, "str", 0, 0); TypeMatchResult match = types_match(expected_string, command_type); if (match == TYPE_MATCH_NONE) { diff --git a/std/string.lx b/std/string.lx index d3f9749a..f0280e7c 100644 --- a/std/string.lx +++ b/std/string.lx @@ -106,3 +106,32 @@ pub const cat = fn (dest: *char, s1: *char, s2: *char) *char { return dest; } +pub const putchar = fn(c: char) int { + // allocate buffer for command + escaped char (32 bytes is enough) + let cmd: *char = cast<*char>(alloc(32 * sizeof)); + defer { free(cmd); } + + // build command inline + // Use integer values instead of character literals with escapes + let char_val: int = cast(c); + + switch(char_val) { + 10 => { system("echo"); } // '\n' + 9 => { system("printf '\\t'"); } // '\t' + 37 => cat(cmd, cast<*char>("printf '\\%'"), cast<*char>("")); // '%' + 92 => cat(cmd, cast<*char>("printf '\\\\'"), cast<*char>("")); // '\\' + 39 => cat(cmd, cast<*char>("printf '\\\''"), cast<*char>("")); // '\'' + 34 => cat(cmd, cast<*char>("printf '\\'"), cast<*char>("")); // '"' + 36 => cat(cmd, cast<*char>("printf '\\$'"), cast<*char>("")); // '$' + 96 => cat(cmd, cast<*char>("printf '\\`'"), cast<*char>("")); // '`' + _ => { + let s: *char = from_char(c); + defer { free(s); } + cat(cmd, cast<*char>("printf '%s' "), s); + } + } + + if (char_val != 10 && char_val != 9) { system(cmd); } + + return cast(c); +} \ No newline at end of file diff --git a/tests/test.lx b/tests/test.lx index 4eb489f4..f3173c1a 100644 --- a/tests/test.lx +++ b/tests/test.lx @@ -1,33 +1,11 @@ -// @module "main" - -// @use "string" as string -// @use "terminal" as term - -// pub const main = fn () int { -// output("Press any key (Press 'q' to quit): \n"); - -// loop { -// let c: char = term::getch(); - -// if (c == 'q') { -// output("\nQuiting...\n"); -// break; -// } - -// output("\nYou pressed: ", string::from_char(c), "\n"); -// } - -// return 0; -// } - @module "main" +@use "string" as string + pub const main = fn () int { - let person: [str; 7] = ["Software Dev", "Compiler Dev", - "Low-Level Dev", "C Dev", - "Rust Dev", "Zig Dev", "Zura Dev"]; - loop [i: int = 0](i < 7) : (++i) { - output("Hello there, ", person[i], "!\n"); - } + string::putchar('H'); + string::putchar('i'); + string::putchar('!'); + string::putchar('\n'); return 0; -} +} \ No newline at end of file From 5790966e1c1db1acde57bdafbf0c1b6f5af3bb1b Mon Sep 17 00:00:00 2001 From: TheDevConnor Date: Fri, 17 Oct 2025 04:18:07 -0400 Subject: [PATCH 3/3] Fixed struct indexing issue --- ideas.md | 3 ++ src/llvm/expr.c | 15 ++++++ src/llvm/llvm.h | 1 + src/llvm/struct.c | 19 ++++--- tests/test.lx | 126 ++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 149 insertions(+), 15 deletions(-) diff --git a/ideas.md b/ideas.md index 8675f765..c0fbc15e 100644 --- a/ideas.md +++ b/ideas.md @@ -205,3 +205,6 @@ Works within the existing build system architecture without requiring fundamenta 4. **Standard Library Package**: Pre-generate bindings for common C standard library headers This approach provides a foundation for C interoperability that feels natural to Luma developers while maintaining the language's design principles. + +## Look into adding in Multithreading +## Add in multiple return types. ``const createStack = fn (stackCeiling: int) <*Stack, *void>`` diff --git a/src/llvm/expr.c b/src/llvm/expr.c index 48722e2e..e4ec2d1e 100644 --- a/src/llvm/expr.c +++ b/src/llvm/expr.c @@ -985,6 +985,21 @@ LLVMValueRef codegen_expr_index(CodeGenContext *ctx, AstNode *node) { cast_node->expr.cast.type->type_data.pointer.pointee_type; pointee_type = codegen_type(ctx, pointee_node); } + } else if (node->expr.index.object->type == AST_EXPR_MEMBER) { + AstNode *member_expr = node->expr.index.object; + const char *field_name = member_expr->expr.member.member; + + // Get the struct info + StructInfo *struct_info = NULL; + for (StructInfo *info = ctx->struct_types; info; info = info->next) { + int field_idx = get_field_index(info, field_name); + if (field_idx >= 0) { + struct_info = info; + // Use the element type stored for this field + pointee_type = struct_info->field_element_types[field_idx]; + break; + } + } } // CRITICAL: Don't fall back to i8! This causes the bug. diff --git a/src/llvm/llvm.h b/src/llvm/llvm.h index 5faf97d7..dabb9bd3 100644 --- a/src/llvm/llvm.h +++ b/src/llvm/llvm.h @@ -60,6 +60,7 @@ typedef struct StructInfo { LLVMTypeRef llvm_type; char **field_names; LLVMTypeRef *field_types; + LLVMTypeRef *field_element_types; bool *field_is_public; size_t field_count; bool is_public; diff --git a/src/llvm/struct.c b/src/llvm/struct.c index 7ca7da4d..d299a78e 100644 --- a/src/llvm/struct.c +++ b/src/llvm/struct.c @@ -70,6 +70,8 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) { ctx->arena, sizeof(char *) * total_members, alignof(char *)); struct_info->field_types = (LLVMTypeRef *)arena_alloc( ctx->arena, sizeof(LLVMTypeRef) * total_members, alignof(LLVMTypeRef)); + struct_info->field_element_types = (LLVMTypeRef *)arena_alloc( // NEW + ctx->arena, sizeof(LLVMTypeRef) * total_members, alignof(LLVMTypeRef)); struct_info->field_is_public = (bool *)arena_alloc( ctx->arena, sizeof(bool) * total_members, alignof(bool)); @@ -94,10 +96,9 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) { } } - struct_info->field_names[field_index] = - arena_strdup(ctx->arena, field_name); - struct_info->field_types[field_index] = - codegen_type(ctx, field->stmt.field_decl.type); + struct_info->field_names[field_index] = arena_strdup(ctx->arena, field_name); + struct_info->field_types[field_index] = codegen_type(ctx, field->stmt.field_decl.type); + struct_info->field_element_types[field_index] = extract_element_type_from_ast(ctx, field->stmt.field_decl.type); struct_info->field_is_public[field_index] = true; if (!struct_info->field_types[field_index]) { @@ -129,12 +130,10 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) { } } - struct_info->field_names[field_index] = - arena_strdup(ctx->arena, field_name); - struct_info->field_types[field_index] = - codegen_type(ctx, field->stmt.field_decl.type); - struct_info->field_is_public[field_index] = - field->stmt.field_decl.is_public; + struct_info->field_names[field_index] = arena_strdup(ctx->arena, field_name); + struct_info->field_types[field_index] = codegen_type(ctx, field->stmt.field_decl.type); + struct_info->field_element_types[field_index] = extract_element_type_from_ast(ctx, field->stmt.field_decl.type); + struct_info->field_is_public[field_index] = field->stmt.field_decl.is_public; if (!struct_info->field_types[field_index]) { fprintf(stderr, diff --git a/tests/test.lx b/tests/test.lx index f3173c1a..bd59fb93 100644 --- a/tests/test.lx +++ b/tests/test.lx @@ -1,11 +1,127 @@ @module "main" -@use "string" as string +@use "memory" as mem + +// auto incrementing stack +const Stack = struct { + top: int, // top value in the stack + capacity: int, // total `length` of the stack + currentAmount: int, // spaces from 0 - capacity that are full + stackCeiling: int, // hard cap on the limit + array: *int // stack representation +}; + +#returns_ownership +const createStack = fn (stackCeiling: int) *Stack { + let stack: *Stack = cast<*Stack>(alloc(sizeof)); + + // check if allocation failed and return a void* + if (stack == cast<*Stack>(0)) { + return cast<*Stack>(0); + } + + stack.top = -1; + stack.capacity = 1; + stack.currentAmount = 0; + stack.stackCeiling = stackCeiling; + + // assume stack.array is empty + stack.array = cast<*int>(alloc(sizeof)); + + if (stack.array == cast<*int>(0)) { + free(stack); + return cast<*Stack>(0); + } + + return stack; +} + +const extendStack = fn (stack: *Stack, capacity: int) int { + // Cannot extend stack past ceiling + if (capacity > stack.stackCeiling) { + return 0; + } + + // frees itself on fail + stack.array = cast<*int>(mem::realloc(cast<*void>(stack.array), capacity * sizeof)); + + stack.capacity = capacity; + return 1; +} + +#takes_ownership +const freeStack = fn (stack: *Stack) void { + free(stack.array); + free(stack); +} + +const isEmpty = fn(stack: *Stack) int { + // goes to -1 instead of 1 so add - + return -cast(stack.top == -1); +} + +const isFull = fn(stack: *Stack) int { + // goes to -1 instead of 1 so add - + return -cast(stack.top == stack.capacity - 1); +} + +const push = fn(stack: *Stack, value: int) void { + if ((isFull(stack) == 1)) { + if (stack.capacity >= stack.stackCeiling) { + output("Stack ceiling reached\nCan not push ", value, "\n"); + return; + } + stack.currentAmount = stack.currentAmount + 1; + let newCapacity: int = stack.capacity * 2; + + if (newCapacity > stack.stackCeiling) { + newCapacity = stack.stackCeiling; + } + if (extendStack(stack, newCapacity) == 0) { + output("Failed to extend stack, can not push ", value "\n"); + } + output("Stack extended to capacity: ", stack.capacity, "\n"); + } + stack.top = stack.top + 1; + stack.array[stack.top] = value; + output("Pushed ", value, " to the stack\n"); +} + +const pop = fn(stack: *Stack) int { + if (isEmpty(stack) == 0) { + output("Stack underflow (empty stack)\n"); + return 0; + } + + let value: int = stack.array[stack.top]; + output("Popping ", value, " from the stack\n"); + + stack.currentAmount = stack.currentAmount - 1; + stack.top = stack.top - 1; + return value; +} + +const peek = fn(stack: *Stack) int { + if (isEmpty(stack) == 1) { + output("Stack is empty\n"); + return 0; + } + return stack.array[stack.top]; +} pub const main = fn () int { - string::putchar('H'); - string::putchar('i'); - string::putchar('!'); - string::putchar('\n'); + let stack: *Stack = createStack(20); + + push(stack, 20); + push(stack, 30); + push(stack, 50); + push(stack, 12); + push(stack, 311); + peek(stack); + pop(stack); + pop(stack); + + free(stack); + return 0; } \ No newline at end of file