Skip to content
Open
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
9 changes: 9 additions & 0 deletions include/ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ typedef enum {
EXPR_FUNCTION,
EXPR_ARRAY_LITERAL,
EXPR_OBJECT_LITERAL,
EXPR_TUPLE_LITERAL, // Tuple literal: (expr1, expr2, ...)
EXPR_PREFIX_INC,
EXPR_PREFIX_DEC,
EXPR_POSTFIX_INC,
Expand Down Expand Up @@ -156,6 +157,10 @@ struct Expr {
Expr **field_values;
int num_fields;
} object_literal;
struct {
Expr **elements;
int num_elements;
} tuple_literal;
struct {
Expr *operand;
} prefix_inc;
Expand Down Expand Up @@ -211,6 +216,7 @@ typedef enum {
TYPE_PTR,
TYPE_BUFFER,
TYPE_ARRAY, // Typed array (e.g., array<u8>)
TYPE_TUPLE, // Tuple type (e.g., (i32, string))
TYPE_NULL,
TYPE_INFER, // No annotation, infer from value
TYPE_CUSTOM_OBJECT, // Custom object type (Person, User, etc.)
Expand All @@ -223,6 +229,8 @@ struct Type {
TypeKind kind;
char *type_name; // For TYPE_CUSTOM_OBJECT (e.g., "Person")
struct Type *element_type; // For TYPE_ARRAY (element type)
struct Type **element_types; // For TYPE_TUPLE (array of element types)
int num_element_types; // For TYPE_TUPLE (number of elements)
int nullable; // If true, type allows null (e.g., string?)
};

Expand Down Expand Up @@ -382,6 +390,7 @@ Expr* expr_index_assign(Expr *object, Expr *index, Expr *value);
Expr* expr_function(int is_async, char **param_names, Type **param_types, Expr **param_defaults, int *param_is_ref, int num_params, char *rest_param, Type *rest_param_type, Type *return_type, Stmt *body);
Expr* expr_array_literal(Expr **elements, int num_elements);
Expr* expr_object_literal(char **field_names, Expr **field_values, int num_fields);
Expr* expr_tuple_literal(Expr **elements, int num_elements);
Expr* expr_prefix_inc(Expr *operand);
Expr* expr_prefix_dec(Expr *operand);
Expr* expr_postfix_inc(Expr *operand);
Expand Down
17 changes: 17 additions & 0 deletions include/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ typedef enum {
VAL_PTR,
VAL_BUFFER,
VAL_ARRAY, // Dynamic array
VAL_TUPLE, // Fixed-size heterogeneous tuple
VAL_OBJECT, // JavaScript-style object
VAL_FILE, // File handle
VAL_SOCKET, // Socket handle
Expand Down Expand Up @@ -72,6 +73,13 @@ typedef struct {
_Atomic int freed; // Atomic flag: 1 if freed via free(), 0 otherwise
} Array;

// Tuple struct (fixed-size heterogeneous container)
typedef struct {
Value *elements; // Array of values
int length; // Number of elements (fixed at creation)
int ref_count; // Reference count for memory management
} Tuple;

// File handle struct
typedef struct {
FILE *fp; // C file pointer
Expand Down Expand Up @@ -227,6 +235,7 @@ typedef struct Value {
void *as_ptr;
Buffer *as_buffer;
Array *as_array;
Tuple *as_tuple;
FileHandle *as_file;
SocketHandle *as_socket;
WebSocketHandle *as_websocket;
Expand Down Expand Up @@ -329,6 +338,14 @@ Value array_pop(Array *arr);
Value array_get(Array *arr, int index, ExecutionContext *ctx);
void array_set(Array *arr, int index, Value val, ExecutionContext *ctx);

// Tuple operations
Tuple* tuple_new(int length);
void tuple_free(Tuple *tuple);
void tuple_retain(Tuple *tuple);
void tuple_release(Tuple *tuple);
Value tuple_get(Tuple *tuple, int index, ExecutionContext *ctx);
Value val_tuple(Tuple *tuple);

// File operations
void file_free(FileHandle *file);

Expand Down
18 changes: 15 additions & 3 deletions runtime/include/hemlock_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// Forward declarations for heap-allocated types
typedef struct HmlString HmlString;
typedef struct HmlArray HmlArray;
typedef struct HmlTuple HmlTuple;
typedef struct HmlObject HmlObject;
typedef struct HmlBuffer HmlBuffer;
typedef struct HmlFunction HmlFunction;
Expand Down Expand Up @@ -48,6 +49,7 @@ typedef enum {
HML_VAL_PTR,
HML_VAL_BUFFER,
HML_VAL_ARRAY,
HML_VAL_TUPLE,
HML_VAL_OBJECT,
HML_VAL_FILE,
HML_VAL_FUNCTION,
Expand Down Expand Up @@ -82,6 +84,7 @@ typedef struct HmlValue {
void *as_ptr;
HmlBuffer *as_buffer;
HmlArray *as_array;
HmlTuple *as_tuple;
HmlObject *as_object;
HmlFileHandle *as_file;
HmlFunction *as_function;
Expand Down Expand Up @@ -120,6 +123,13 @@ struct HmlArray {
_Atomic int freed; // Atomic flag: 1 if freed via free(), 0 otherwise
};

// Tuple struct (fixed-size heterogeneous container)
struct HmlTuple {
HmlValue *elements;
int length; // Fixed at creation
int ref_count;
};

// Object struct (JavaScript-style)
struct HmlObject {
char *type_name; // NULL for anonymous
Expand Down Expand Up @@ -232,6 +242,8 @@ HmlValue hml_val_rune(uint32_t codepoint);
HmlValue hml_val_ptr(void *ptr);
HmlValue hml_val_buffer(int size);
HmlValue hml_val_array(void);
HmlValue hml_val_tuple(int length);
HmlValue hml_tuple_get(HmlValue tuple, int index);
HmlValue hml_val_object(void);
HmlValue hml_val_null(void);
HmlValue hml_val_function(void *fn_ptr, int num_params, int num_required, int is_async);
Expand Down Expand Up @@ -459,9 +471,9 @@ static inline HmlValue hml_i64_rshift(HmlValue left, HmlValue right) {
// Defined early so it can be used by array_get_i32_fast
static inline int hml_needs_refcount(HmlValue val) {
return val.type == HML_VAL_STRING || val.type == HML_VAL_BUFFER ||
val.type == HML_VAL_ARRAY || val.type == HML_VAL_OBJECT ||
val.type == HML_VAL_FUNCTION || val.type == HML_VAL_TASK ||
val.type == HML_VAL_CHANNEL;
val.type == HML_VAL_ARRAY || val.type == HML_VAL_TUPLE ||
val.type == HML_VAL_OBJECT || val.type == HML_VAL_FUNCTION ||
val.type == HML_VAL_TASK || val.type == HML_VAL_CHANNEL;
}

// Fast path: array[i32] access (bounds checked, skip retain for primitives)
Expand Down
61 changes: 61 additions & 0 deletions runtime/src/value.c
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,43 @@ HmlValue hml_val_array(void) {
return v;
}

HmlValue hml_val_tuple(int length) {
if (length < 2) {
hml_runtime_error("Tuple must have at least 2 elements");
}

HmlValue v;
v.type = HML_VAL_TUPLE;

HmlTuple *t = malloc(sizeof(HmlTuple));
t->elements = malloc(sizeof(HmlValue) * length);
t->length = length;
t->ref_count = 1;

// Initialize all elements to null
for (int i = 0; i < length; i++) {
t->elements[i] = hml_val_null();
}

v.as.as_tuple = t;
return v;
}

HmlValue hml_tuple_get(HmlValue tuple, int index) {
if (tuple.type != HML_VAL_TUPLE) {
hml_runtime_error("Cannot get element from non-tuple");
}
HmlTuple *t = tuple.as.as_tuple;
if (index < 0 || index >= t->length) {
hml_runtime_error("Tuple index %d out of bounds (length %d)", index, t->length);
}
HmlValue result = t->elements[index];
if (hml_needs_refcount(result)) {
hml_retain(&result);
}
return result;
}

HmlValue hml_val_object(void) {
HmlValue v;
v.type = HML_VAL_OBJECT;
Expand Down Expand Up @@ -356,6 +393,9 @@ void hml_retain(HmlValue *val) {
case HML_VAL_ARRAY:
if (val->as.as_array) val->as.as_array->ref_count++;
break;
case HML_VAL_TUPLE:
if (val->as.as_tuple) val->as.as_tuple->ref_count++;
break;
case HML_VAL_OBJECT:
if (val->as.as_object) val->as.as_object->ref_count++;
break;
Expand Down Expand Up @@ -398,6 +438,17 @@ static void array_free(HmlArray *arr) {
}
}

static void tuple_free(HmlTuple *tuple) {
if (tuple) {
// Release all elements
for (int i = 0; i < tuple->length; i++) {
hml_release(&tuple->elements[i]);
}
free(tuple->elements);
free(tuple);
}
}

static void object_free(HmlObject *obj) {
if (obj) {
// Free field names and release field values
Expand Down Expand Up @@ -452,6 +503,15 @@ void hml_release(HmlValue *val) {
val->as.as_array = NULL;
}
break;
case HML_VAL_TUPLE:
if (val->as.as_tuple) {
val->as.as_tuple->ref_count--;
if (val->as.as_tuple->ref_count <= 0) {
tuple_free(val->as.as_tuple);
}
val->as.as_tuple = NULL;
}
break;
case HML_VAL_OBJECT:
if (val->as.as_object) {
val->as.as_object->ref_count--;
Expand Down Expand Up @@ -704,6 +764,7 @@ const char* hml_type_name(HmlValueType type) {
case HML_VAL_PTR: return "ptr";
case HML_VAL_BUFFER: return "buffer";
case HML_VAL_ARRAY: return "array";
case HML_VAL_TUPLE: return "tuple";
case HML_VAL_OBJECT: return "object";
case HML_VAL_FILE: return "file";
case HML_VAL_FUNCTION: return "function";
Expand Down
54 changes: 51 additions & 3 deletions src/backends/compiler/codegen_expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,10 @@ char* codegen_expr(CodegenContext *ctx, Expr *expr) {
codegen_indent_inc(ctx);
codegen_writeln(ctx, "%s = hml_array_length(%s);", result, obj);
codegen_indent_dec(ctx);
codegen_writeln(ctx, "} else if (%s.type == HML_VAL_TUPLE) {", obj);
codegen_indent_inc(ctx);
codegen_writeln(ctx, "%s = hml_val_i32(%s.as.as_tuple->length);", result, obj);
codegen_indent_dec(ctx);
codegen_writeln(ctx, "} else if (%s.type == HML_VAL_STRING) {", obj);
codegen_indent_inc(ctx);
codegen_writeln(ctx, "%s = hml_string_length(%s);", result, obj);
Expand Down Expand Up @@ -1168,9 +1172,36 @@ char* codegen_expr(CodegenContext *ctx, Expr *expr) {
codegen_indent_dec(ctx);
codegen_writeln(ctx, "}");
} else {
// Regular property access - throws error if field not found (parity with interpreter)
codegen_writeln(ctx, "HmlValue %s = hml_object_get_field_required(%s, \"%s\");",
result, obj, expr->as.get_property.property);
// Check if property is a numeric index (for tuples: .0, .1, .2, etc.)
const char *prop = expr->as.get_property.property;
int is_numeric = (prop[0] >= '0' && prop[0] <= '9');
if (is_numeric) {
// Check that entire property is numeric
for (const char *p = prop; *p; p++) {
if (*p < '0' || *p > '9') {
is_numeric = 0;
break;
}
}
}

if (is_numeric) {
int index = atoi(prop);
codegen_writeln(ctx, "HmlValue %s;", result);
codegen_writeln(ctx, "if (%s.type == HML_VAL_TUPLE) {", obj);
codegen_indent_inc(ctx);
codegen_writeln(ctx, "%s = hml_tuple_get(%s, %d);", result, obj, index);
codegen_indent_dec(ctx);
codegen_writeln(ctx, "} else {");
codegen_indent_inc(ctx);
codegen_writeln(ctx, "hml_runtime_error(\"Cannot access .%s on non-tuple type\");", prop);
codegen_indent_dec(ctx);
codegen_writeln(ctx, "}");
} else {
// Regular property access - throws error if field not found (parity with interpreter)
codegen_writeln(ctx, "HmlValue %s = hml_object_get_field_required(%s, \"%s\");",
result, obj, expr->as.get_property.property);
}
}
codegen_writeln(ctx, "hml_release(&%s);", obj);
free(obj);
Expand Down Expand Up @@ -1237,6 +1268,10 @@ char* codegen_expr(CodegenContext *ctx, Expr *expr) {
codegen_indent_inc(ctx);
codegen_writeln(ctx, "%s = hml_array_get(%s, %s);", result, obj, idx);
codegen_indent_dec(ctx);
codegen_writeln(ctx, "} else if (%s.type == HML_VAL_TUPLE) {", obj);
codegen_indent_inc(ctx);
codegen_writeln(ctx, "%s = hml_tuple_get(%s, hml_to_i32(%s));", result, obj, idx);
codegen_indent_dec(ctx);
codegen_writeln(ctx, "} else if (%s.type == HML_VAL_STRING) {", obj);
codegen_indent_inc(ctx);
codegen_writeln(ctx, "%s = hml_string_index(%s, %s);", result, obj, idx);
Expand Down Expand Up @@ -1352,6 +1387,19 @@ char* codegen_expr(CodegenContext *ctx, Expr *expr) {
break;
}

case EXPR_TUPLE_LITERAL: {
int num_elements = expr->as.tuple_literal.num_elements;
codegen_writeln(ctx, "HmlValue %s = hml_val_tuple(%d);", result, num_elements);
for (int i = 0; i < num_elements; i++) {
char *elem = codegen_expr(ctx, expr->as.tuple_literal.elements[i]);
codegen_writeln(ctx, "%s.as.as_tuple->elements[%d] = %s;", result, i, elem);
codegen_writeln(ctx, "hml_retain(&%s.as.as_tuple->elements[%d]);", result, i);
codegen_writeln(ctx, "hml_release(&%s);", elem);
free(elem);
}
break;
}

case EXPR_OBJECT_LITERAL: {
codegen_writeln(ctx, "HmlValue %s = hml_val_object();", result);
for (int i = 0; i < expr->as.object_literal.num_fields; i++) {
Expand Down
Loading
Loading