diff --git a/CMakeLists.txt b/CMakeLists.txt index 82920d0..ee4cd91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,6 @@ target_include_directories(fun_core PUBLIC # Playground / interpreter (future /usr/bin/fun) add_executable(fun src/fun.c - examples/hello_world.fun ) target_link_libraries(fun PRIVATE fun_core) diff --git a/examples/expressions_test.fun b/examples/expressions_test.fun index c2a4125..b2de749 100755 --- a/examples/expressions_test.fun +++ b/examples/expressions_test.fun @@ -8,7 +8,7 @@ print("=== Expressions test start ===") number a = 2 number b = 3 number n = 10 -string s = "Hello, world!" +string s = "Have, fun!" boolean flag = true // Arithmetic and precedence diff --git a/examples/functions_test.fun b/examples/functions_test.fun new file mode 100755 index 0000000..afd1b7e --- /dev/null +++ b/examples/functions_test.fun @@ -0,0 +1,40 @@ +#!/usr/bin/env fun + +// Functions test: definitions, calls, parameters, locals, returns, and if/else. + +print("=== Functions test start ===") + +fun add(a, b) + return a + b + +fun abs(x) + if (x < 0) + return -x + else + return x + +fun sum3(a, b, c) + // local typed declaration + number t = a + b + return t + c + +fun id(x) + return x + +// Calls in expressions +print(add(2, 3)) // expect 5 +print(add(10, -3)) // expect 7 +print(abs(-7)) // expect 7 +print(abs(4)) // expect 4 +print(sum3(1, 2, 3)) // expect 6 +print(add(add(1, 2), 3)) // expect 6 +print(id(42)) // expect 42 + +// Call as statement (result discarded) +add(100, 23) + +// Globals interacting with functions +number n = 5 +print(add(n, 10)) // expect 15 + +print("=== Functions test end ===") diff --git a/examples/have_fun.fun b/examples/have_fun.fun index af8bb43..98dea0c 100755 --- a/examples/have_fun.fun +++ b/examples/have_fun.fun @@ -1,7 +1,7 @@ #!/usr/bin/env fun /* - * Have fun! in Fun + * Have fun in Fun */ - print("Hello, fun!") +print("Have, fun!") diff --git a/examples/have_fun_function.fun b/examples/have_fun_function.fun new file mode 100755 index 0000000..febc739 --- /dev/null +++ b/examples/have_fun_function.fun @@ -0,0 +1,10 @@ +#!/usr/bin/env fun + +/* + * Have fun in Fun + */ + +fun have_fun() + print("Have, fun!") + +have_fun() diff --git a/play.fun b/play.fun index b2cc03f..c2ba191 100755 --- a/play.fun +++ b/play.fun @@ -7,4 +7,9 @@ print(n) string s = "Hello, world!" print(s) - \ No newline at end of file + +fun foo() + //print("Have fun!") + print("Running foo()... forever.") + +foo() diff --git a/src/parser.c b/src/parser.c index df4b1fe..5d4987c 100644 --- a/src/parser.c +++ b/src/parser.c @@ -217,6 +217,33 @@ static int sym_index(const char *name) { return G.count++; } +/* ---- locals environment for functions ---- */ +typedef struct { + char *names[FRAME_MAX_LOCALS]; + int count; +} LocalEnv; + +static LocalEnv *g_locals = NULL; + +static int local_find(const char *name) { + if (!g_locals) return -1; + for (int i = 0; i < g_locals->count; ++i) { + if (strcmp(g_locals->names[i], name) == 0) return i; + } + return -1; +} + +static int local_add(const char *name) { + if (!g_locals) return -1; + if (g_locals->count >= FRAME_MAX_LOCALS) { + parser_fail(0, "Too many local variables/parameters (max %d)", FRAME_MAX_LOCALS); + return -1; + } + int idx = g_locals->count++; + g_locals->names[idx] = strdup(name); + return idx; +} + /* forward declaration so helpers can recurse */ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos); @@ -267,10 +294,55 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) bytecode_add_instruction(bc, OP_LOAD_CONST, ci); return 1; } - int gi = sym_index(name); - free(name); - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); - return 1; + + /* call or variable load (locals preferred) */ + skip_spaces(src, len, pos); + int local_idx = local_find(name); + int is_call = (*pos < len && src[*pos] == '('); + + if (is_call) { + /* push function value first */ + if (local_idx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); + } else { + int gi = sym_index(name); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + /* parse arguments */ + (*pos)++; /* '(' */ + int argc = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + do { + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression as function argument"); + free(name); + return 0; + } + argc++; + skip_spaces(src, len, pos); + } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1)); + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after arguments"); + free(name); + return 0; + } + /* DEBUG: show compiled call */ + printf("compile: CALL %s with %d arg(s)\n", name, argc); + bytecode_add_instruction(bc, OP_CALL, argc); + free(name); + return 1; + } else { + if (local_idx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); + } else { + int gi = sym_index(name); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + free(name); + return 1; + } } return 0; @@ -472,7 +544,7 @@ static int read_line_start(const char *src, size_t len, size_t *pos, int *out_in if (p >= len) { *pos = p; return 0; } if (src[p] == '\n') { /* empty line */ p++; *pos = p; continue; } - /* comment-only line? */ + /* // comment-only line */ if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') { /* skip entire line */ while (p < len && src[p] != '\n') p++; @@ -481,6 +553,21 @@ static int read_line_start(const char *src, size_t len, size_t *pos, int *out_in continue; } + // block comment starting at line (treat as comment-only line) + if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') { + p += 2; + /* advance until we find closing block comment marker */ + while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) { + p++; + } + if (p + 1 < len) p += 2; /* consume closing block comment marker */ + /* consume to end of current line (if any leftover) */ + while (p < len && src[p] != '\n') p++; + if (p < len && src[p] == '\n') p++; + *pos = p; + continue; + } + if (spaces % 2 != 0) { parser_fail(p, "Indentation must be multiples of two spaces"); return 0; @@ -500,6 +587,26 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si size_t local_pos = *pos; char *name = NULL; if (read_identifier_into(src, len, &local_pos, &name)) { + /* return statement */ + if (strcmp(name, "return") == 0) { + free(name); + skip_spaces(src, len, &local_pos); + /* optional expression */ + size_t save_pos = local_pos; + if (emit_expression(bc, src, len, &local_pos)) { + /* expression result already on stack */ + } else { + /* no expression: return nil */ + local_pos = save_pos; + int ci = bytecode_add_constant(bc, make_nil()); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } + bytecode_add_instruction(bc, OP_RETURN, 0); + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + /* typed declarations: number|string|boolean (= expr)? */ if (strcmp(name, "number") == 0 || strcmp(name, "string") == 0 || strcmp(name, "boolean") == 0) { int is_number = (strcmp(name, "number") == 0); @@ -514,18 +621,31 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si parser_fail(local_pos, "Expected identifier after type declaration"); return; } - int gi = sym_index(varname); + + /* decide local vs global */ + int lidx = -1; + int gi = -1; + if (g_locals) { + int existing = local_find(varname); + if (existing >= 0) lidx = existing; + else lidx = local_add(varname); + } else { + gi = sym_index(varname); + } free(varname); skip_spaces(src, len, &local_pos); if (local_pos < len && src[local_pos] == '=') { local_pos++; /* '=' */ - if (emit_expression(bc, src, len, &local_pos)) { - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); - } else { + if (!emit_expression(bc, src, len, &local_pos)) { parser_fail(local_pos, "Expected initializer expression after '='"); return; } + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } } else { /* default initialize if no '=' given */ int ci = -1; @@ -536,7 +656,11 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } if (ci >= 0) { bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } } } *pos = local_pos; @@ -572,10 +696,12 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si skip_to_eol(src, len, pos); return; } else if (local_pos < len && src[local_pos] == '(') { - /* simple call form (argument ignored/emitted but no CALL yet) */ - local_pos++; /* '(' */ - (void)emit_expression(bc, src, len, &local_pos); - (void)consume_char(src, len, &local_pos, ')'); + /* call as a statement: compile full call expression and drop result */ + /* rewind to statement start and emit as expression */ + local_pos = *pos; + if (emit_expression(bc, src, len, &local_pos)) { + bytecode_add_instruction(bc, OP_POP, 0); /* discard return value */ + } *pos = local_pos; skip_to_eol(src, len, pos); return; @@ -628,6 +754,91 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, } /* at same indent -> parse statement */ + if (starts_with(src, len, *pos, "fun")) { + /* parse header: fun name(arg, ...) */ + *pos += 3; + skip_spaces(src, len, pos); + char *fname = NULL; + if (!read_identifier_into(src, len, pos, &fname)) { + parser_fail(*pos, "Expected function name after 'fun'"); + return; + } + int fgi = sym_index(fname); + skip_spaces(src, len, pos); + if (!consume_char(src, len, pos, '(')) { + parser_fail(*pos, "Expected '(' after function name"); + free(fname); + return; + } + + /* build locals from parameters */ + LocalEnv env = { {0}, 0 }; + LocalEnv *prev = g_locals; + g_locals = &env; + + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + for (;;) { + char *pname = NULL; + if (!read_identifier_into(src, len, pos, &pname)) { + parser_fail(*pos, "Expected parameter name"); + g_locals = prev; + free(fname); + return; + } + if (local_find(pname) >= 0) { + parser_fail(*pos, "Duplicate parameter name '%s'", pname); + free(pname); + g_locals = prev; + free(fname); + return; + } + local_add(pname); + free(pname); + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); continue; } + break; + } + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after parameter list"); + g_locals = prev; + free(fname); + return; + } + /* end header line */ + skip_to_eol(src, len, pos); + + /* compile body into separate Bytecode */ + Bytecode *fn_bc = bytecode_new(); + + /* parse body at increased indent if present */ + int body_indent = 0; + size_t look_body = *pos; + if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { + /* do not advance pos here; let parse_block consume the line */ + parse_block(fn_bc, src, len, pos, body_indent); + } else { + /* empty body: ok */ + } + /* ensure function returns */ + bytecode_add_instruction(fn_bc, OP_RETURN, 0); + + /* DEBUG: dump compiled function bytecode */ + printf("=== compiled function %s (%d params) ===\n", fname, env.count); + bytecode_dump(fn_bc); + printf("=== end function %s ===\n", fname); + + /* bind function to global: LOAD_CONST ; STORE_GLOBAL fgi */ + int fci = bytecode_add_constant(bc, make_function(fn_bc)); + bytecode_add_instruction(bc, OP_LOAD_CONST, fci); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, fgi); + + g_locals = prev; + free(fname); + continue; + } + if (starts_with(src, len, *pos, "if")) { int end_jumps[64]; int end_count = 0; @@ -655,10 +866,10 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, /* parse nested block if next line is indented */ int next_indent = 0; - size_t after_hdr = *pos; - if (read_line_start(src, len, pos, &next_indent)) { + size_t look_next = *pos; + if (read_line_start(src, len, &look_next, &next_indent)) { if (next_indent > current_indent) { - /* parse body at increased indent */ + /* parse body at increased indent (let parse_block consume the line) */ parse_block(bc, src, len, pos, next_indent); } else { /* empty body; keep *pos at start of that line */ @@ -703,7 +914,9 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, /* plain else: parse its block and finish the chain */ skip_to_eol(src, len, pos); int else_indent = 0; - if (read_line_start(src, len, pos, &else_indent) && else_indent > current_indent) { + size_t look_else = *pos; + if (read_line_start(src, len, &look_else, &else_indent) && else_indent > current_indent) { + /* let parse_block consume the line */ parse_block(bc, src, len, pos, else_indent); } else { /* empty else-body */ diff --git a/src/vm.c b/src/vm.c index 7c7925d..bbd7911 100644 --- a/src/vm.c +++ b/src/vm.c @@ -5,6 +5,16 @@ #include #include +static const char* value_type_name(ValueType t) { + switch (t) { + case VAL_INT: return "int"; + case VAL_STRING: return "string"; + case VAL_FUNCTION: return "function"; + case VAL_NIL: return "nil"; + default: return "unknown"; + } +} + void vm_clear_output(VM *vm) { for (int i = 0; i < vm->output_count; ++i) { free_value(vm->output[i]); @@ -144,7 +154,8 @@ void vm_run(VM *vm, Bytecode *entry) { Value b = pop_value(vm); Value a = pop_value(vm); if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: ADD expects ints\n"); + fprintf(stderr, "Runtime type error: ADD expects ints, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); exit(1); } Value res = make_int(a.i + b.i); @@ -158,7 +169,8 @@ void vm_run(VM *vm, Bytecode *entry) { Value b = pop_value(vm); Value a = pop_value(vm); if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: SUB expects ints\n"); + fprintf(stderr, "Runtime type error: SUB expects ints, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); exit(1); } Value res = make_int(a.i - b.i); @@ -172,7 +184,8 @@ void vm_run(VM *vm, Bytecode *entry) { Value b = pop_value(vm); Value a = pop_value(vm); if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: MUL expects ints\n"); + fprintf(stderr, "Runtime type error: MUL expects ints, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); exit(1); } Value res = make_int(a.i * b.i); @@ -186,7 +199,8 @@ void vm_run(VM *vm, Bytecode *entry) { Value b = pop_value(vm); Value a = pop_value(vm); if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: DIV expects ints\n"); + fprintf(stderr, "Runtime type error: DIV expects ints, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); exit(1); } if (b.i == 0) { @@ -204,7 +218,8 @@ void vm_run(VM *vm, Bytecode *entry) { Value b = pop_value(vm); Value a = pop_value(vm); if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: LT expects ints\n"); + fprintf(stderr, "Runtime type error: LT expects ints, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); exit(1); } Value res = make_int(a.i < b.i ? 1 : 0); @@ -441,7 +456,8 @@ void vm_run(VM *vm, Bytecode *entry) { Value b = pop_value(vm); Value a = pop_value(vm); if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: MOD expects ints\n"); + fprintf(stderr, "Runtime type error: MOD expects ints, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); exit(1); } if (b.i == 0) {