Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions c_test_files/putchar.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <stdlib.h>
#include <stdio.h>

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;
}
3 changes: 3 additions & 0 deletions ideas.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>``
55 changes: 47 additions & 8 deletions src/lexer/lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -497,16 +526,20 @@ 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",
"Newline in character literal",
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
Expand All @@ -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
Expand Down
37 changes: 23 additions & 14 deletions src/llvm/expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -1471,15 +1486,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")) {
Expand All @@ -1494,9 +1504,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
Expand All @@ -1508,8 +1517,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");
Expand Down
1 change: 1 addition & 0 deletions src/llvm/llvm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 9 additions & 10 deletions src/llvm/struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand All @@ -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]) {
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/typechecker/expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/typechecker/lookup.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions src/typechecker/type.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -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;
}
}
Expand Down
29 changes: 29 additions & 0 deletions std/string.lx
Original file line number Diff line number Diff line change
Expand Up @@ -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<char>));
defer { free(cmd); }

// build command inline
// Use integer values instead of character literals with escapes
let char_val: int = cast<int>(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<int>(c);
}
Loading