Add class support with constructors, fields, and methods
This commit is contained in:
11
projects/classes.j
Normal file
11
projects/classes.j
Normal file
@@ -0,0 +1,11 @@
|
||||
class Dog:
|
||||
fn init(self, name):
|
||||
self.name = name
|
||||
|
||||
fn bark(self):
|
||||
println("guau!")
|
||||
|
||||
d = Dog("ahi te va")
|
||||
x = d.bark()
|
||||
println("Hola ", d.name)
|
||||
debugHeap()
|
||||
@@ -1,4 +1,8 @@
|
||||
fn greet(name):
|
||||
println("Hola, " + name)
|
||||
x = "Hello world!"
|
||||
|
||||
greet("mundo!")
|
||||
fn suma(x, y):
|
||||
fn pow(z):
|
||||
return z * z
|
||||
return x + pow(y)
|
||||
|
||||
println(suma(2, 2))
|
||||
@@ -5,6 +5,18 @@
|
||||
#include "opcodes.h"
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
char *method_name;
|
||||
int entry_point;
|
||||
int param_count;
|
||||
char **param_names;
|
||||
} MethodEntry;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
MethodEntry methods[16];
|
||||
int method_count;
|
||||
} ClassEntry;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
@@ -22,6 +34,8 @@ typedef struct {
|
||||
int name_count;
|
||||
FunctionEntry functions[64];
|
||||
int func_count;
|
||||
ClassEntry classes[16];
|
||||
int class_count;
|
||||
} Chunk;
|
||||
|
||||
int emit(Chunk *chunk, Instruction instr) {
|
||||
@@ -86,6 +100,26 @@ int compile_node(Chunk *chunk, ASTNode *node) {
|
||||
compile_node(chunk, node->data.call.args[i]);
|
||||
}
|
||||
|
||||
// Verificar si es constructor de una clase
|
||||
for (int i = 0; i < chunk->class_count; i++) {
|
||||
if (strcmp(chunk->classes[i].name, node->data.call.name) == 0) {
|
||||
// Buscar init
|
||||
for (int m = 0; m < chunk->classes[i].method_count; m++) {
|
||||
if (strcmp(chunk->classes[i].methods[m].method_name, "init") == 0) {
|
||||
int expected =
|
||||
chunk->classes[i].methods[m].param_count - 1; // -1 por self
|
||||
if (node->data.call.arg_count != expected) {
|
||||
printf("error: %s() espera %d args, pero recibio %d\n",
|
||||
node->data.call.name, expected, node->data.call.arg_count);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Registrar el nombre de la funcion
|
||||
Instruction instr = make_instruction(OP_CALL);
|
||||
instr.operand.call.arg_count = node->data.call.arg_count;
|
||||
@@ -170,6 +204,13 @@ int compile_node(Chunk *chunk, ASTNode *node) {
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_RETURN: {
|
||||
if (node->data.ret.value) {
|
||||
compile_node(chunk, node->data.ret.value);
|
||||
}
|
||||
emit(chunk, make_instruction(OP_RETURN));
|
||||
return 0;
|
||||
}
|
||||
case NODE_FN_DEF: {
|
||||
// emitir jmp para ignorar la funcion por defecto
|
||||
Instruction jump = make_instruction(OP_JUMP);
|
||||
@@ -183,14 +224,14 @@ int compile_node(Chunk *chunk, ASTNode *node) {
|
||||
fn->entry_point = entry;
|
||||
fn->param_count = node->data.fn_def.param_count;
|
||||
fn->param_names = node->data.fn_def.params;
|
||||
|
||||
|
||||
// emitir store_var para cada parametro (orden inverso al stack)
|
||||
for (int i = node->data.fn_def.param_count - 1; i >= 0; i--) {
|
||||
Instruction store = make_instruction(OP_STORE_VAR);
|
||||
store.operand.var_index = add_name(chunk, node->data.fn_def.params[i]);
|
||||
emit(chunk, store);
|
||||
Instruction store = make_instruction(OP_STORE_VAR);
|
||||
store.operand.var_index = add_name(chunk, node->data.fn_def.params[i]);
|
||||
emit(chunk, store);
|
||||
}
|
||||
|
||||
|
||||
// compilar el cuerpo
|
||||
compile_node(chunk, node->data.fn_def.body);
|
||||
|
||||
@@ -201,6 +242,80 @@ int compile_node(Chunk *chunk, ASTNode *node) {
|
||||
chunk->code[jump_idx].operand.jump_target = chunk->code_count;
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_CLASS_DEF: {
|
||||
// Registrar ClassEntry
|
||||
ClassEntry *cls = &chunk->classes[chunk->class_count++];
|
||||
cls->name = node->data.class_def.name;
|
||||
cls->method_count = 0;
|
||||
|
||||
// Pre-registrar self en la tabla de nombres
|
||||
add_name(chunk, "self");
|
||||
|
||||
int totalMethods = node->data.class_def.method_count;
|
||||
for (int i = 0; i < totalMethods; i++) {
|
||||
ASTNode *method = node->data.class_def.methods[i];
|
||||
|
||||
// jump over
|
||||
Instruction jump = make_instruction(OP_JUMP);
|
||||
jump.operand.jump_target = -1;
|
||||
int jump_idx = emit(chunk, jump);
|
||||
|
||||
// Registrar method entry
|
||||
int entry = chunk->code_count;
|
||||
MethodEntry *me = &cls->methods[cls->method_count++];
|
||||
me->method_name = method->data.fn_def.name;
|
||||
me->entry_point = entry;
|
||||
me->param_count = method->data.fn_def.param_count;
|
||||
me->param_names = method->data.fn_def.params;
|
||||
|
||||
// store_var para cada parametro (orden inverso)
|
||||
for (int p = method->data.fn_def.param_count - 1; p >= 0; p--) {
|
||||
Instruction store = make_instruction(OP_STORE_VAR);
|
||||
store.operand.var_index =
|
||||
add_name(chunk, method->data.fn_def.params[p]);
|
||||
emit(chunk, store);
|
||||
}
|
||||
|
||||
// Compilar body
|
||||
compile_node(chunk, method->data.fn_def.body);
|
||||
|
||||
// return implicito
|
||||
emit(chunk, make_instruction(OP_RETURN));
|
||||
|
||||
// Backpatch
|
||||
chunk->code[jump_idx].operand.jump_target = chunk->code_count;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NODE_DOT_ACCESS: {
|
||||
compile_node(chunk, node->data.dot_access.object);
|
||||
Instruction instr = make_instruction(OP_GET_FIELD);
|
||||
instr.operand.var_index = add_name(chunk, node->data.dot_access.field);
|
||||
emit(chunk, instr);
|
||||
break;
|
||||
}
|
||||
case NODE_DOT_ASSIGN: {
|
||||
compile_node(chunk, node->data.dot_assign.value); // push valor
|
||||
compile_node(chunk, node->data.dot_assign.object); // push instancia
|
||||
Instruction instr = make_instruction(OP_SET_FIELD);
|
||||
instr.operand.var_index = add_name(chunk, node->data.dot_assign.field);
|
||||
emit(chunk, instr);
|
||||
break;
|
||||
}
|
||||
case NODE_METHOD_CALL: {
|
||||
compile_node(chunk, node->data.method_call.object); // push instancia
|
||||
for (int i = 0; i < node->data.method_call.arg_count; i++) {
|
||||
compile_node(chunk, node->data.method_call.args[i]);
|
||||
}
|
||||
Instruction instr = make_instruction(OP_CALL_METHOD);
|
||||
instr.operand.call.name_index =
|
||||
add_name(chunk, node->data.method_call.method);
|
||||
instr.operand.call.arg_count = node->data.method_call.arg_count;
|
||||
emit(chunk, instr);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -298,12 +413,57 @@ void print_chunk(Chunk *chunk) {
|
||||
case OP_HALT:
|
||||
printf("HALT");
|
||||
break;
|
||||
case OP_GET_FIELD:
|
||||
printf("GET_FIELD [%d] %s", instr.operand.var_index,
|
||||
chunk->names[instr.operand.var_index]);
|
||||
break;
|
||||
case OP_SET_FIELD:
|
||||
printf("SET_FIELD [%d] %s", instr.operand.var_index,
|
||||
chunk->names[instr.operand.var_index]);
|
||||
break;
|
||||
case OP_CALL_METHOD:
|
||||
printf("CALL_METHOD %s(%d args)",
|
||||
chunk->names[instr.operand.call.name_index],
|
||||
instr.operand.call.arg_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("UNKNOWN op=%d", instr.op);
|
||||
break;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
printf("=== User Functions ===\n");
|
||||
for (int i = 0; i < chunk->func_count; i++) {
|
||||
FunctionEntry *fn = &chunk->functions[i];
|
||||
printf("[%.4d] %s(", fn->entry_point, fn->name);
|
||||
|
||||
for (int p = 0; p < fn->param_count; p++) {
|
||||
printf("%s", fn->param_names[p]);
|
||||
|
||||
if (p < fn->param_count - 1) {
|
||||
printf(", ");
|
||||
}
|
||||
}
|
||||
printf(")\n");
|
||||
}
|
||||
|
||||
printf("=== Classes ===\n");
|
||||
for (int i = 0; i < chunk->class_count; i++) {
|
||||
ClassEntry *cls = &chunk->classes[i];
|
||||
printf("class %s (%d methods)\n", cls->name, cls->method_count);
|
||||
for (int m = 0; m < cls->method_count; m++) {
|
||||
MethodEntry *me = &cls->methods[m];
|
||||
printf(" [%.4d] %s(", me->entry_point, me->method_name);
|
||||
for (int p = 0; p < me->param_count; p++) {
|
||||
printf("%s", me->param_names[p]);
|
||||
if (p < me->param_count - 1)
|
||||
printf(", ");
|
||||
}
|
||||
printf(")\n");
|
||||
}
|
||||
}
|
||||
|
||||
printf("=== End ===\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,36 +2,42 @@
|
||||
#define JLANG_OPCODES_H
|
||||
|
||||
typedef enum {
|
||||
OP_CONST_INT, // push entero inmediato
|
||||
OP_CONST_STRING, // push string desde pool de constantes (alloc en heap)
|
||||
OP_POP, // descarta top del stack
|
||||
OP_ADD, OP_SUB, OP_MUL, OP_DIV, // aritmetica
|
||||
OP_NEG, // negacion unaria
|
||||
OP_CMP_LT, OP_CMP_GT, // comparacion -> push 0 o 1
|
||||
OP_LOAD_VAR, // push variable por indice
|
||||
OP_STORE_VAR, // pop -> guardar en variable por indice
|
||||
OP_JUMP, // salto incondicional
|
||||
OP_JUMP_IF_ZERO, // pop -> si false, saltar
|
||||
OP_CALL, // llamar built-in por indice de nombre
|
||||
OP_RETURN, // retornar de funcion (pop call frame)
|
||||
OP_NOP, OP_HALT,
|
||||
OP_CONST_INT, // push entero inmediato
|
||||
OP_CONST_STRING, // push string desde pool de constantes (alloc en heap)
|
||||
OP_POP, // descarta top del stack
|
||||
OP_ADD,
|
||||
OP_SUB,
|
||||
OP_MUL,
|
||||
OP_DIV, // aritmetica
|
||||
OP_NEG, // negacion unaria
|
||||
OP_CMP_LT,
|
||||
OP_CMP_GT, // comparacion -> push 0 o 1
|
||||
OP_LOAD_VAR, // push variable por indice
|
||||
OP_STORE_VAR, // pop -> guardar en variable por indice
|
||||
OP_JUMP, // salto incondicional
|
||||
OP_JUMP_IF_ZERO, // pop -> si false, saltar
|
||||
OP_CALL, // llamar built-in por indice de nombre
|
||||
OP_RETURN, // retornar de funcion (pop call frame)
|
||||
OP_NOP,
|
||||
OP_HALT,
|
||||
OP_GET_FIELD, // TOS=instance, operand=name_idx → push field value
|
||||
OP_SET_FIELD, // TOS=instance, TOS-1=value, operand=name_idx → set field
|
||||
OP_CALL_METHOD, // TOS-N-1=instance + N args, operand={name_idx, arg_count}
|
||||
|
||||
} OpCode;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
OpCode op;
|
||||
union
|
||||
{
|
||||
int int_val; // OP_CONST_INT
|
||||
int str_index; // OP_CONST_STRING: indice a pool de constantes
|
||||
int var_index; // OP_LOAD_VAR, OP_STORE_VAR
|
||||
int jump_target; // OP_JUMP, OP_JUMP_IF_ZERO
|
||||
struct
|
||||
{
|
||||
int name_index;
|
||||
int arg_count;
|
||||
} call; // OP_CALL
|
||||
} operand;
|
||||
typedef struct {
|
||||
OpCode op;
|
||||
union {
|
||||
int int_val; // OP_CONST_INT
|
||||
int str_index; // OP_CONST_STRING: indice a pool de constantes
|
||||
int var_index; // OP_LOAD_VAR, OP_STORE_VAR
|
||||
int jump_target; // OP_JUMP, OP_JUMP_IF_ZERO
|
||||
struct {
|
||||
int name_index;
|
||||
int arg_count;
|
||||
} call; // OP_CALL
|
||||
} operand;
|
||||
} Instruction;
|
||||
|
||||
#endif
|
||||
@@ -10,6 +10,8 @@ typedef struct {
|
||||
int saved_sp; // base del stack
|
||||
Value saved_vars[256]; // variables del caller (snapshot)
|
||||
int saved_var_set[256];
|
||||
int is_constructor;
|
||||
Value constructor_instance;
|
||||
} CallFrame;
|
||||
|
||||
typedef struct {
|
||||
@@ -115,6 +117,7 @@ void run_vm(VM *vm) {
|
||||
CallFrame *frame = &vm->frames[vm->frame_count++];
|
||||
frame->return_ip = vm->ip + 1; // volver a la siguiente instruccion
|
||||
frame->saved_sp = vm->sp - fn->param_count;
|
||||
frame->is_constructor = 0;
|
||||
memcpy(frame->saved_vars, vm->vars, sizeof(vm->vars));
|
||||
memcpy(frame->saved_var_set, vm->var_set, sizeof(vm->var_set));
|
||||
|
||||
@@ -123,6 +126,63 @@ void run_vm(VM *vm) {
|
||||
continue; // no hacer ip++
|
||||
}
|
||||
|
||||
// Buscar en classes[]
|
||||
ClassEntry *cls = NULL;
|
||||
int class_idx = -1;
|
||||
for (int i = 0; i < vm->chunk->class_count; i++) {
|
||||
if (strcmp(vm->chunk->classes[i].name, name) == 0) {
|
||||
cls = &vm->chunk->classes[i];
|
||||
class_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cls != NULL) {
|
||||
// alloc instancia
|
||||
size_t instOffset =
|
||||
obj_new_instance(vm->allocator, class_idx, 8, sizeof(Value));
|
||||
Value instVal = {0};
|
||||
instVal.type = VAL_OBJ;
|
||||
instVal.as.heap_offset = instOffset;
|
||||
|
||||
// buscar init
|
||||
MethodEntry *init = NULL;
|
||||
for (int m = 0; m < cls->method_count; m++) {
|
||||
if (strcmp(cls->methods[m].method_name, "init") == 0) {
|
||||
init = &cls->methods[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (init != NULL) {
|
||||
// insertar instancia bajo de los args para self
|
||||
int nArgs = instr.operand.call.arg_count;
|
||||
for (int a = vm->sp - 1; a >= vm->sp - nArgs; a--) {
|
||||
vm->stack[a + 1] = vm->stack[a];
|
||||
}
|
||||
vm->stack[vm->sp - nArgs] = instVal;
|
||||
vm->sp++;
|
||||
|
||||
// Save frame
|
||||
CallFrame *frame = &vm->frames[vm->frame_count++];
|
||||
frame->return_ip = vm->ip + 1;
|
||||
frame->saved_sp = vm->sp - nArgs - 1;
|
||||
frame->is_constructor = 1;
|
||||
frame->constructor_instance = instVal;
|
||||
memcpy(frame->saved_vars, vm->vars, sizeof(vm->vars));
|
||||
;
|
||||
memcpy(frame->saved_var_set, vm->var_set, sizeof(vm->var_set));
|
||||
|
||||
vm->ip = init->entry_point;
|
||||
continue;
|
||||
} else {
|
||||
// No hay init, descartar args y devolver instancia
|
||||
vm->sp -= instr.operand.call.arg_count;
|
||||
vm->stack[vm->sp++] = instVal;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (strcmp(name, "print") == 0 || strcmp(name, "println") == 0) {
|
||||
|
||||
int nParams = instr.operand.call.arg_count;
|
||||
@@ -178,7 +238,7 @@ void run_vm(VM *vm) {
|
||||
// Captrurar valor de retorno si hay alguno en el stack
|
||||
Value return_val = {0};
|
||||
int has_return = 0;
|
||||
if (vm->sp > vm->frames[vm->frame_count-1].saved_sp) {
|
||||
if (vm->sp > vm->frames[vm->frame_count - 1].saved_sp) {
|
||||
return_val = vm->stack[--vm->sp];
|
||||
has_return = 1;
|
||||
}
|
||||
@@ -191,8 +251,13 @@ void run_vm(VM *vm) {
|
||||
memcpy(vm->var_set, frame->saved_var_set, sizeof(vm->var_set));
|
||||
|
||||
// Push return value
|
||||
if (has_return) {
|
||||
if (frame->is_constructor) {
|
||||
vm->stack[vm->sp++] = frame->constructor_instance;
|
||||
} else if (has_return) {
|
||||
vm->stack[vm->sp++] = return_val;
|
||||
} else {
|
||||
Value nil = {0};
|
||||
vm->stack[vm->sp++] = nil;
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -322,6 +387,19 @@ void run_vm(VM *vm) {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (vm->var_set[i] && vm->vars[i].type == VAL_OBJ) {
|
||||
roots[root_count++] = vm->vars[i].as.heap_offset;
|
||||
|
||||
// si es instancia agregar fields
|
||||
Object *obj =
|
||||
JLANG_RESOLVE(vm->allocator, vm->vars[i].as.heap_offset);
|
||||
if (obj->type == OBJ_INSTANCE) {
|
||||
Value *values = (Value *)JLANG_RESOLVE(
|
||||
vm->allocator, obj->data.instance_val.field_values);
|
||||
for (int f = 0; f < obj->data.instance_val.field_count; f++) {
|
||||
if (values[f].type == VAL_OBJ) {
|
||||
roots[root_count++] = values[f].as.heap_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +414,98 @@ void run_vm(VM *vm) {
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_GET_FIELD: {
|
||||
Value instance = vm->stack[--vm->sp];
|
||||
Object *obj = JLANG_RESOLVE(vm->allocator, instance.as.heap_offset);
|
||||
int name_idx = instr.operand.var_index;
|
||||
|
||||
int *names = (int *)JLANG_RESOLVE(vm->allocator,
|
||||
obj->data.instance_val.field_names);
|
||||
Value *values = (Value *)JLANG_RESOLVE(
|
||||
vm->allocator, obj->data.instance_val.field_values);
|
||||
|
||||
int found = 0;
|
||||
for (int i = 0; i < obj->data.instance_val.field_count; i++) {
|
||||
if (names[i] == name_idx) {
|
||||
vm->stack[vm->sp++] = values[i];
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
printf("error: field '%s' not found\n", vm->chunk->names[name_idx]);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SET_FIELD: {
|
||||
Value instance = vm->stack[--vm->sp];
|
||||
Value value = vm->stack[--vm->sp];
|
||||
int name_idx = instr.operand.var_index;
|
||||
|
||||
Object *obj = JLANG_RESOLVE(vm->allocator, instance.as.heap_offset);
|
||||
int *names = (int *)JLANG_RESOLVE(vm->allocator,
|
||||
obj->data.instance_val.field_names);
|
||||
Value *values = (Value *)JLANG_RESOLVE(
|
||||
vm->allocator, obj->data.instance_val.field_values);
|
||||
|
||||
int found = 0;
|
||||
for (int i = 0; i < obj->data.instance_val.field_count; i++) {
|
||||
if (names[i] == name_idx) {
|
||||
values[i] = value;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Agregar campo nuevo
|
||||
int idx = obj->data.instance_val.field_count++;
|
||||
names[idx] = name_idx;
|
||||
values[idx] = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_CALL_METHOD: {
|
||||
int method_name_idx = instr.operand.call.name_index;
|
||||
int arg_count = instr.operand.call.arg_count;
|
||||
char *method_name = vm->chunk->names[method_name_idx];
|
||||
|
||||
// la instancia está bajo de los args
|
||||
Value instance = vm->stack[vm->sp - arg_count - 1];
|
||||
Object *obj = JLANG_RESOLVE(vm->allocator, instance.as.heap_offset);
|
||||
ClassEntry *cls = &vm->chunk->classes[obj->data.instance_val.class_index];
|
||||
|
||||
// Buscar metodo
|
||||
MethodEntry *method = NULL;
|
||||
for (int i = 0; i < cls->method_count; i++) {
|
||||
if (strcmp(cls->methods[i].method_name, method_name) == 0) {
|
||||
method = &cls->methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (method == NULL) {
|
||||
printf("error: method '%s' not found in class '%s'\n", method_name,
|
||||
cls->name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save frame
|
||||
CallFrame *frame = &vm->frames[vm->frame_count++];
|
||||
frame->return_ip = vm->ip + 1;
|
||||
frame->saved_sp = vm->sp - arg_count - 1;
|
||||
frame->is_constructor = 0;
|
||||
memcpy(frame->saved_vars, vm->vars, sizeof(vm->vars));
|
||||
memcpy(frame->saved_var_set, vm->var_set, sizeof(vm->var_set));
|
||||
|
||||
vm->ip = method->entry_point;
|
||||
continue;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,12 @@ typedef enum {
|
||||
TOK_STRING, // "hola"
|
||||
|
||||
// Identificadores y keywords
|
||||
TOK_ID, // x, foo, mi_var
|
||||
TOK_IF, // if
|
||||
TOK_WHILE, // while
|
||||
TOK_FN, // fn
|
||||
TOK_RETURN, // return
|
||||
TOK_ID, // x, foo, mi_var
|
||||
TOK_IF, // if
|
||||
TOK_WHILE, // while
|
||||
TOK_FN, // fn
|
||||
TOK_RETURN, // return
|
||||
TOK_CLASS, // class
|
||||
|
||||
// Operadores
|
||||
TOK_ASSIGN, // =
|
||||
@@ -36,6 +37,7 @@ typedef enum {
|
||||
TOK_RPAREN, // )
|
||||
TOK_COLON, // :
|
||||
TOK_COMMA, // ,
|
||||
TOK_DOT, // .
|
||||
TOK_NEWLINE, // \n (significativo, como en Python)
|
||||
TOK_INDENT, // aumento de indentacion
|
||||
TOK_DEDENT, // reduccion de indentacion
|
||||
@@ -89,12 +91,17 @@ Token *tokenize(const char *source, int *token_count) {
|
||||
pos++;
|
||||
}
|
||||
int new_level = spaces / 4; // 4 espacios = 1 nivel
|
||||
if (new_level > indent_level) {
|
||||
tokens[count++] = make_token(TOK_INDENT, "INDENT");
|
||||
} else if (new_level < indent_level) {
|
||||
tokens[count++] = make_token(TOK_DEDENT, "DEDENT");
|
||||
if (source[pos] != '\n' && source[pos] != '\0') {
|
||||
if (new_level > indent_level) {
|
||||
for (int l = indent_level; l < new_level; l++)
|
||||
tokens[count++] = make_token(TOK_INDENT, "INDENT");
|
||||
} else if (new_level < indent_level) {
|
||||
for (int l = indent_level; l > new_level; l--)
|
||||
tokens[count++] = make_token(TOK_DEDENT, "DEDENT");
|
||||
}
|
||||
indent_level = new_level;
|
||||
}
|
||||
indent_level = new_level;
|
||||
|
||||
} else if (c == '+') {
|
||||
tokens[count++] = make_token(TOK_PLUS, "+");
|
||||
pos++;
|
||||
@@ -122,6 +129,9 @@ Token *tokenize(const char *source, int *token_count) {
|
||||
} else if (c == ',') {
|
||||
tokens[count++] = make_token(TOK_COMMA, ",");
|
||||
pos++;
|
||||
} else if (c == '.') {
|
||||
tokens[count++] = make_token(TOK_DOT, ".");
|
||||
pos++;
|
||||
} else if (c == '(') {
|
||||
tokens[count++] = make_token(TOK_LPAREN, "(");
|
||||
pos++;
|
||||
@@ -142,10 +152,10 @@ Token *tokenize(const char *source, int *token_count) {
|
||||
while (source[pos] >= '0' && source[pos] <= '9')
|
||||
pos++;
|
||||
tokens[count++] = make_token(TOK_INT, substr(source, start, pos));
|
||||
} else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
|
||||
} else if ((c >= 'a' && c <= 'z') || (c == '_') || (c >= 'A' && c <= 'Z')) {
|
||||
// Leer todos los caracteres consecutivos
|
||||
int start = pos;
|
||||
while (isalnum(source[pos]))
|
||||
while (isalnum(source[pos]) || source[pos] == '_')
|
||||
pos++;
|
||||
char *word = substr(source, start, pos);
|
||||
// tokens[count++] = make_token(TOK_ID, word);
|
||||
@@ -159,6 +169,8 @@ Token *tokenize(const char *source, int *token_count) {
|
||||
tokens[count++] = make_token(TOK_FN, word);
|
||||
} else if (strcmp(word, "return") == 0) {
|
||||
tokens[count++] = make_token(TOK_RETURN, word);
|
||||
} else if (strcmp(word, "class") == 0) {
|
||||
tokens[count++] = make_token(TOK_CLASS, word);
|
||||
} else {
|
||||
tokens[count++] = make_token(TOK_ID, word);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ typedef enum {
|
||||
NODE_BLOCK, // secuencia de statements
|
||||
NODE_CALL,
|
||||
NODE_FN_DEF, // definicion de funcion
|
||||
NODE_RETURN, // return expr
|
||||
NODE_RETURN, // return expr
|
||||
NODE_CLASS_DEF, // definicion de clase
|
||||
NODE_DOT_ACCESS,
|
||||
NODE_DOT_ASSIGN,
|
||||
NODE_METHOD_CALL,
|
||||
} NodeType;
|
||||
|
||||
typedef struct ASTNode {
|
||||
@@ -63,7 +67,27 @@ typedef struct ASTNode {
|
||||
} fn_def; // NODE_FN_DEF
|
||||
struct {
|
||||
struct ASTNode *value; // expresion de retorno
|
||||
} ret; // NODE_RETURN
|
||||
} ret; // NODE_RETURN
|
||||
struct {
|
||||
char *name;
|
||||
struct ASTNode **methods;
|
||||
int method_count;
|
||||
} class_def;
|
||||
struct {
|
||||
struct ASTNode *object;
|
||||
char *field;
|
||||
} dot_access;
|
||||
struct {
|
||||
struct ASTNode *object;
|
||||
char *field;
|
||||
struct ASTNode *value;
|
||||
} dot_assign;
|
||||
struct {
|
||||
struct ASTNode *object;
|
||||
char *method;
|
||||
struct ASTNode **args;
|
||||
int arg_count;
|
||||
} method_call;
|
||||
} data;
|
||||
} ASTNode;
|
||||
|
||||
@@ -91,6 +115,7 @@ ASTNode *parse_factor(Token *tokens) {
|
||||
pos++;
|
||||
return node;
|
||||
} else if (tokens[pos].type == TOK_ID) {
|
||||
ASTNode *node;
|
||||
if (tokens[pos + 1].type == TOK_LPAREN) {
|
||||
// Function call
|
||||
char *name = tokens[pos].value;
|
||||
@@ -110,15 +135,50 @@ ASTNode *parse_factor(Token *tokens) {
|
||||
}
|
||||
}
|
||||
pos++; // consumir ")"
|
||||
ASTNode *node = make_node(NODE_CALL);
|
||||
node = make_node(NODE_CALL);
|
||||
node->data.call.name = name;
|
||||
node->data.call.args = args;
|
||||
node->data.call.arg_count = arg_count;
|
||||
return node;
|
||||
} else {
|
||||
node = make_node(NODE_VAR);
|
||||
node->data.string_val = tokens[pos].value;
|
||||
pos++;
|
||||
}
|
||||
ASTNode *node = make_node(NODE_VAR);
|
||||
node->data.string_val = tokens[pos].value;
|
||||
pos++;
|
||||
|
||||
// Loop post-fix para dot access
|
||||
while (tokens[pos].type == TOK_DOT) {
|
||||
pos++; // consumir .
|
||||
char *field = tokens[pos].value;
|
||||
pos++; // consumir field name
|
||||
|
||||
if (tokens[pos].type == TOK_LPAREN) {
|
||||
// obj.method(args...)
|
||||
pos++; // consumir '('
|
||||
ASTNode **args = (ASTNode **)malloc(sizeof(ASTNode *) * 16);
|
||||
int arg_count = 0;
|
||||
if (tokens[pos].type != TOK_RPAREN) {
|
||||
args[arg_count++] = parse_expr(tokens);
|
||||
while (tokens[pos].type == TOK_COMMA) {
|
||||
pos++;
|
||||
args[arg_count++] = parse_expr(tokens);
|
||||
}
|
||||
}
|
||||
pos++; // consumir ')'
|
||||
ASTNode *mc = make_node(NODE_METHOD_CALL);
|
||||
mc->data.method_call.object = node;
|
||||
mc->data.method_call.method = field;
|
||||
mc->data.method_call.args = args;
|
||||
mc->data.method_call.arg_count = arg_count;
|
||||
node = mc;
|
||||
} else {
|
||||
// obj.field
|
||||
ASTNode *da = make_node(NODE_DOT_ACCESS);
|
||||
da->data.dot_access.object = node;
|
||||
da->data.dot_access.field = field;
|
||||
node = da;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
} else if (tokens[pos].type == TOK_LPAREN) {
|
||||
@@ -179,6 +239,24 @@ ASTNode *parse_term(Token *tokens) {
|
||||
|
||||
ASTNode *parse_statement(Token *tokens) {
|
||||
if (tokens[pos].type == TOK_ID) {
|
||||
if (tokens[pos + 1].type == TOK_DOT) {
|
||||
ASTNode *expr = parse_expr(tokens);
|
||||
|
||||
if (tokens[pos].type == TOK_ASSIGN) {
|
||||
// dot_assign: self.name = expr
|
||||
pos++; // consumir '='
|
||||
ASTNode *value = parse_expr(tokens);
|
||||
ASTNode *node = make_node(NODE_DOT_ASSIGN);
|
||||
node->data.dot_assign.object = expr->data.dot_access.object;
|
||||
node->data.dot_assign.field = expr->data.dot_access.field;
|
||||
node->data.dot_assign.value = value;
|
||||
return node;
|
||||
}
|
||||
|
||||
// si no hay '=', es un method call como (d.speak())
|
||||
return expr;
|
||||
}
|
||||
|
||||
if (tokens[pos + 1].type == TOK_LPAREN) {
|
||||
// Es una funcion
|
||||
char *name = tokens[pos].value;
|
||||
@@ -246,7 +324,7 @@ ASTNode *parse_statement(Token *tokens) {
|
||||
while (tokens[pos].type != TOK_DEDENT) {
|
||||
body->data.block.stmts[body->data.block.count++] =
|
||||
parse_statement(tokens);
|
||||
if (tokens[pos].type == TOK_NEWLINE) {
|
||||
while (tokens[pos].type == TOK_NEWLINE) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
@@ -272,7 +350,7 @@ ASTNode *parse_statement(Token *tokens) {
|
||||
while (tokens[pos].type != TOK_DEDENT) {
|
||||
body->data.block.stmts[body->data.block.count++] =
|
||||
parse_statement(tokens);
|
||||
if (tokens[pos].type == TOK_NEWLINE) {
|
||||
while (tokens[pos].type == TOK_NEWLINE) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
@@ -284,9 +362,34 @@ ASTNode *parse_statement(Token *tokens) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (tokens[pos].type == TOK_CLASS) {
|
||||
pos++; // consumir 'class'
|
||||
char *name = tokens[pos].value;
|
||||
pos++; // consumir nombre
|
||||
pos++; // consumir :
|
||||
pos++; // consumir NEWLINE
|
||||
pos++; // consumir INDENT
|
||||
|
||||
ASTNode **methods = (ASTNode **)malloc(sizeof(ASTNode *) * 16);
|
||||
int method_count = 0;
|
||||
while (tokens[pos].type != TOK_DEDENT) {
|
||||
methods[method_count++] = parse_statement(tokens);
|
||||
while (tokens[pos].type == TOK_NEWLINE) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
pos++; // consumir DEDENT
|
||||
|
||||
ASTNode *node = make_node(NODE_CLASS_DEF);
|
||||
node->data.class_def.name = name;
|
||||
node->data.class_def.methods = methods;
|
||||
node->data.class_def.method_count = method_count;
|
||||
return node;
|
||||
}
|
||||
|
||||
if (tokens[pos].type == TOK_FN) {
|
||||
pos++; // consumir "fn"
|
||||
char* name = tokens[pos].value;
|
||||
char *name = tokens[pos].value;
|
||||
pos++; // consumir name
|
||||
pos++; // consumir "("
|
||||
|
||||
@@ -314,13 +417,13 @@ ASTNode *parse_statement(Token *tokens) {
|
||||
while (tokens[pos].type != TOK_DEDENT) {
|
||||
body->data.block.stmts[body->data.block.count++] =
|
||||
parse_statement(tokens);
|
||||
if (tokens[pos].type == TOK_NEWLINE) {
|
||||
while (tokens[pos].type == TOK_NEWLINE) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
pos++; // Consumir DEDENT
|
||||
|
||||
ASTNode*node = make_node(NODE_FN_DEF);
|
||||
ASTNode *node = make_node(NODE_FN_DEF);
|
||||
node->data.fn_def.name = name;
|
||||
node->data.fn_def.params = params;
|
||||
node->data.fn_def.param_count = param_count;
|
||||
@@ -334,7 +437,7 @@ ASTNode *parse_statement(Token *tokens) {
|
||||
node->data.ret.value = parse_expr(tokens);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
printf("ERROR: statement inesperado\n");
|
||||
exit(1);
|
||||
}
|
||||
@@ -345,7 +448,7 @@ ASTNode *parse(Token *tokens, int token_count) {
|
||||
block->data.block.count = 0;
|
||||
|
||||
while (pos < token_count) {
|
||||
if (tokens[pos].type == TOK_NEWLINE) {
|
||||
while (tokens[pos].type == TOK_NEWLINE) {
|
||||
pos++; // Saltar newlines sueltos
|
||||
continue;
|
||||
}
|
||||
@@ -443,6 +546,36 @@ void ast_print(ASTNode *node, const char *prefix, int is_last) {
|
||||
}
|
||||
break;
|
||||
|
||||
case NODE_CLASS_DEF:
|
||||
printf("NODE_CLASS_DEF(\"%s\")\n", node->data.class_def.name);
|
||||
for (int i = 0; i < node->data.class_def.method_count; i++) {
|
||||
ast_print(node->data.class_def.methods[i], new_prefix,
|
||||
i == node->data.class_def.method_count - 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case NODE_DOT_ACCESS:
|
||||
printf("NODE_DOT_ACCESS(.%s)\n", node->data.dot_access.field);
|
||||
ast_print(node->data.dot_access.object, new_prefix, 1);
|
||||
break;
|
||||
|
||||
case NODE_DOT_ASSIGN:
|
||||
printf("NODE_DOT_ASSIGN(.%s)\n", node->data.dot_assign.field);
|
||||
ast_print(node->data.dot_assign.object, new_prefix, 0);
|
||||
ast_print(node->data.dot_assign.value, new_prefix, 1);
|
||||
break;
|
||||
|
||||
case NODE_METHOD_CALL:
|
||||
printf("NODE_METHOD_CALL(.%s, %d args)\n", node->data.method_call.method,
|
||||
node->data.method_call.arg_count);
|
||||
ast_print(node->data.method_call.object, new_prefix,
|
||||
node->data.method_call.arg_count == 0);
|
||||
for (int i = 0; i < node->data.method_call.arg_count; i++) {
|
||||
ast_print(node->data.method_call.args[i], new_prefix,
|
||||
i == node->data.method_call.arg_count - 1);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("UNKNOWN\n");
|
||||
break;
|
||||
|
||||
113
src/memory/gc.h
113
src/memory/gc.h
@@ -4,6 +4,7 @@
|
||||
#include "../objects/object.h"
|
||||
#include "allocator.h"
|
||||
|
||||
|
||||
void gc_collect(JLANG_memory_allocator *allocPtr, size_t *roots,
|
||||
int root_count) {
|
||||
// Stage 1. Mark blocks
|
||||
@@ -29,67 +30,79 @@ void gc_collect(JLANG_memory_allocator *allocPtr, size_t *roots,
|
||||
objPtr->data.string_val.chars -
|
||||
sizeof(JLANG_metadata));
|
||||
itemsHeader->marked = 1;
|
||||
}
|
||||
}
|
||||
} else if (objPtr->type == OBJ_INSTANCE) {
|
||||
JLANG_metadata *namesHeader =
|
||||
(JLANG_metadata *)((char *)allocPtr->memory +
|
||||
objPtr->data.instance_val.field_names -
|
||||
sizeof(JLANG_metadata));
|
||||
namesHeader->marked = 1;
|
||||
|
||||
// Stage 2. Sweep memory
|
||||
JLANG_metadata *currentHead = (JLANG_metadata *)allocPtr->memory;
|
||||
while (currentHead->size != 0) {
|
||||
size_t blockOffset = (char *)currentHead - (char *)allocPtr->memory;
|
||||
|
||||
if (currentHead->marked == 0 && currentHead->in_use) {
|
||||
// Free block
|
||||
JLANG_free(allocPtr, blockOffset + sizeof(JLANG_metadata));
|
||||
JLANG_metadata *valuesHeader =
|
||||
(JLANG_metadata *)((char *)allocPtr->memory +
|
||||
objPtr->data.instance_val.field_values -
|
||||
sizeof(JLANG_metadata));
|
||||
valuesHeader->marked = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset mark
|
||||
currentHead->marked = 0;
|
||||
// Stage 2. Sweep memory
|
||||
JLANG_metadata *currentHead = (JLANG_metadata *)allocPtr->memory;
|
||||
while (currentHead->size != 0) {
|
||||
size_t blockOffset = (char *)currentHead - (char *)allocPtr->memory;
|
||||
|
||||
currentHead = (JLANG_metadata *)((char *)currentHead + currentHead->size +
|
||||
sizeof(JLANG_metadata));
|
||||
}
|
||||
if (currentHead->marked == 0 && currentHead->in_use) {
|
||||
// Free block
|
||||
JLANG_free(allocPtr, blockOffset + sizeof(JLANG_metadata));
|
||||
}
|
||||
|
||||
// Stage 3. Join free blocks
|
||||
currentHead = (JLANG_metadata *)allocPtr->memory;
|
||||
while (currentHead->size != 0) {
|
||||
// Reset mark
|
||||
currentHead->marked = 0;
|
||||
|
||||
if (currentHead->in_use == 0) {
|
||||
// iterate until last next free block
|
||||
JLANG_metadata *nextHeader =
|
||||
(JLANG_metadata *)((char *)currentHead + sizeof(JLANG_metadata) +
|
||||
currentHead->size);
|
||||
JLANG_metadata *lastHeader = currentHead;
|
||||
while (1) {
|
||||
if (nextHeader->in_use == 1 || nextHeader->size == 0) {
|
||||
// Volvemos atrás
|
||||
break;
|
||||
currentHead = (JLANG_metadata *)((char *)currentHead + currentHead->size +
|
||||
sizeof(JLANG_metadata));
|
||||
}
|
||||
|
||||
// Stage 3. Join free blocks
|
||||
currentHead = (JLANG_metadata *)allocPtr->memory;
|
||||
while (currentHead->size != 0) {
|
||||
|
||||
if (currentHead->in_use == 0) {
|
||||
// iterate until last next free block
|
||||
JLANG_metadata *nextHeader =
|
||||
(JLANG_metadata *)((char *)currentHead + sizeof(JLANG_metadata) +
|
||||
currentHead->size);
|
||||
JLANG_metadata *lastHeader = currentHead;
|
||||
while (1) {
|
||||
if (nextHeader->in_use == 1 || nextHeader->size == 0) {
|
||||
// Volvemos atrás
|
||||
break;
|
||||
}
|
||||
|
||||
lastHeader = nextHeader;
|
||||
nextHeader =
|
||||
(JLANG_metadata *)((char *)nextHeader + sizeof(JLANG_metadata) +
|
||||
nextHeader->size);
|
||||
}
|
||||
|
||||
lastHeader = nextHeader;
|
||||
nextHeader =
|
||||
(JLANG_metadata *)((char *)nextHeader + sizeof(JLANG_metadata) +
|
||||
nextHeader->size);
|
||||
// Make from currentHead to nextHeader + nextHeader(size) one block
|
||||
size_t startIndex = (char *)currentHead - (char *)allocPtr->memory;
|
||||
size_t endIndex =
|
||||
((char *)lastHeader + sizeof(JLANG_metadata) + lastHeader->size) -
|
||||
(char *)allocPtr->memory;
|
||||
|
||||
// Set to 0
|
||||
for (int i = 0; i < (endIndex - startIndex); i++) {
|
||||
allocPtr->memory[i + startIndex] = 0;
|
||||
}
|
||||
|
||||
// Create valid header
|
||||
currentHead = (JLANG_metadata *)((char *)allocPtr->memory + startIndex);
|
||||
currentHead->size = (endIndex - startIndex) - sizeof(JLANG_metadata);
|
||||
}
|
||||
|
||||
// Make from currentHead to nextHeader + nextHeader(size) one block
|
||||
size_t startIndex = (char *)currentHead - (char *)allocPtr->memory;
|
||||
size_t endIndex =
|
||||
((char *)lastHeader + sizeof(JLANG_metadata) + lastHeader->size) -
|
||||
(char *)allocPtr->memory;
|
||||
|
||||
// Set to 0
|
||||
for (int i = 0; i < (endIndex - startIndex); i++) {
|
||||
allocPtr->memory[i + startIndex] = 0;
|
||||
}
|
||||
|
||||
// Create valid header
|
||||
currentHead = (JLANG_metadata *) ((char *)allocPtr->memory + startIndex);
|
||||
currentHead->size = (endIndex - startIndex) - sizeof(JLANG_metadata);
|
||||
currentHead = (JLANG_metadata *)((char *)currentHead + currentHead->size +
|
||||
sizeof(JLANG_metadata));
|
||||
}
|
||||
|
||||
currentHead = (JLANG_metadata *)((char *)currentHead + currentHead->size +
|
||||
sizeof(JLANG_metadata));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
|
||||
#include "../memory/allocator.h"
|
||||
|
||||
|
||||
#define JLANG_RESOLVE(alloc, offset) \
|
||||
((void *)(((JLANG_memory_allocator *)(alloc))->memory + (offset)))
|
||||
|
||||
typedef enum { OBJ_INT, OBJ_FLOAT, OBJ_STRING, OBJ_LIST, OBJ_NONE } ObjectType;
|
||||
typedef enum { OBJ_INT, OBJ_FLOAT, OBJ_STRING, OBJ_LIST, OBJ_NONE, OBJ_INSTANCE } ObjectType;
|
||||
|
||||
typedef struct Object {
|
||||
ObjectType type;
|
||||
@@ -23,9 +22,38 @@ typedef struct Object {
|
||||
int count;
|
||||
int capacity;
|
||||
} list_val;
|
||||
struct {
|
||||
int class_index; // indice en Chunk.classes[]
|
||||
size_t field_names; // heap offset -> array de int
|
||||
size_t field_values; // heap offset -> array de Value
|
||||
int field_count;
|
||||
int field_capacity;
|
||||
} instance_val;
|
||||
} data;
|
||||
} Object;
|
||||
|
||||
size_t obj_new_instance(void *allocator, int class_index, int capacity, size_t value_size) {
|
||||
size_t offset = JLANG_malloc(allocator, sizeof(Object));
|
||||
Object *objPtr = (Object *)JLANG_RESOLVE(allocator, offset);
|
||||
objPtr->type = OBJ_INSTANCE;
|
||||
objPtr->data.instance_val.class_index = class_index;
|
||||
objPtr->data.instance_val.field_count = 0;
|
||||
objPtr->data.instance_val.field_capacity = capacity;
|
||||
|
||||
// alloc array de nombres
|
||||
size_t namesOffset = JLANG_malloc(allocator, capacity * sizeof(int));
|
||||
objPtr = (Object *) JLANG_RESOLVE(allocator, offset); // re-resolve
|
||||
|
||||
// alloc array de valores
|
||||
size_t valuesOffset = JLANG_malloc(allocator, capacity * value_size);
|
||||
objPtr = (Object *) JLANG_RESOLVE(allocator, offset); // re-resolve
|
||||
|
||||
objPtr->data.instance_val.field_names = namesOffset;
|
||||
objPtr->data.instance_val.field_values = valuesOffset;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
size_t obj_new_int(void *allocator, int value) {
|
||||
// Object *objPtr = (Object *)JLANG_malloc(allocator, sizeof(Object));
|
||||
size_t offset = JLANG_malloc(allocator, sizeof(Object));
|
||||
@@ -91,10 +119,16 @@ void obj_free(void *allocator, size_t offset) {
|
||||
JLANG_free(allocator, obj->data.list_val.items);
|
||||
}
|
||||
|
||||
if (obj->type == OBJ_INSTANCE) {
|
||||
JLANG_free(allocator, obj->data.instance_val.field_names);
|
||||
JLANG_free(allocator, obj->data.instance_val.field_values);
|
||||
}
|
||||
|
||||
JLANG_free(allocator, offset);
|
||||
}
|
||||
|
||||
void obj_print(void *allocator, size_t offset, const char *preffix, const char *suffix) {
|
||||
void obj_print(void *allocator, size_t offset, const char *preffix,
|
||||
const char *suffix) {
|
||||
Object *obj = (Object *)JLANG_RESOLVE(allocator, offset);
|
||||
|
||||
switch (obj->type) {
|
||||
|
||||
Reference in New Issue
Block a user