diff --git a/.aiignore b/.aiignore new file mode 100644 index 0000000..5682e0f --- /dev/null +++ b/.aiignore @@ -0,0 +1,7 @@ +.DS_Store +*.log +*.tmp +build/ +dist/ +out/ +tmp/ diff --git a/.gitignore b/.gitignore index 0bbbad2..7cda0e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +.idea/ +build/ +dist/ +out/ src/*.o tmp/ fun diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2455fe8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.16) +project(fun C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# Core VM/library sources +add_library(fun_core + src/bytecode.c + src/value.c + src/vm.c +) + +target_include_directories(fun_core PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +# Playground / interpreter (future /usr/bin/fun) +add_executable(fun + src/fun.c + examples/hello_world.fun +) +target_link_libraries(fun PRIVATE fun_core) + +# Internal test programs +add_executable(fun_test + src/fun_test.c +) +target_link_libraries(fun_test PRIVATE fun_core) + +add_executable(test_opcodes + src/test_opcodes.c +) +target_link_libraries(test_opcodes PRIVATE fun_core) + +# Convenience targets for cleaning (optional) +add_custom_target(clean-build + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean + COMMENT "Clean build outputs (objects, binaries) in the build directory" +) +add_custom_target(dist-clean + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean + COMMAND ${CMAKE_COMMAND} -E rm -rf + ${CMAKE_BINARY_DIR}/CMakeCache.txt + ${CMAKE_BINARY_DIR}/CMakeFiles + ${CMAKE_BINARY_DIR}/cmake_install.cmake + ${CMAKE_BINARY_DIR}/install_manifest.txt + ${CMAKE_BINARY_DIR}/Makefile + ${CMAKE_BINARY_DIR}/*.ninja + ${CMAKE_BINARY_DIR}/.ninja_* + COMMENT "Remove build outputs and CMake-generated files (reconfigure needed)" +) diff --git a/examples/hello_world.fun b/examples/hello_world.fun new file mode 100755 index 0000000..4c9d305 --- /dev/null +++ b/examples/hello_world.fun @@ -0,0 +1,5 @@ +#!/usr/bin/env fun + +fun hello_world() { + print("Hello, world!") +} diff --git a/draft.fun b/spec/draft.fun similarity index 100% rename from draft.fun rename to spec/draft.fun diff --git a/src/bytecode.c b/src/bytecode.c index 5ac8ad9..062604b 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -41,3 +41,56 @@ void bytecode_free(Bytecode *bc) { free(bc); } +static const char *opcode_name(OpCode op) { + switch (op) { + case OP_NOP: return "NOP"; + case OP_LOAD_CONST: return "LOAD_CONST"; + case OP_LOAD_LOCAL: return "LOAD_LOCAL"; + case OP_STORE_LOCAL: return "STORE_LOCAL"; + case OP_LOAD_GLOBAL: return "LOAD_GLOBAL"; + case OP_STORE_GLOBAL: return "STORE_GLOBAL"; + case OP_ADD: return "ADD"; + case OP_SUB: return "SUB"; + case OP_MUL: return "MUL"; + case OP_DIV: return "DIV"; + case OP_LT: return "LT"; + case OP_LTE: return "LTE"; + case OP_GT: return "GT"; + case OP_GTE: return "GTE"; + case OP_EQ: return "EQ"; + case OP_NEQ: return "NEQ"; + case OP_POP: return "POP"; + case OP_JUMP: return "JUMP"; + case OP_JUMP_IF_FALSE: return "JUMP_IF_FALSE"; + case OP_CALL: return "CALL"; + case OP_RETURN: return "RETURN"; + case OP_PRINT: return "PRINT"; + case OP_HALT: return "HALT"; + case OP_MOD: return "MOD"; + case OP_AND: return "AND"; + case OP_OR: return "OR"; + case OP_NOT: return "NOT"; + case OP_DUP: return "DUP"; + case OP_SWAP: return "SWAP"; + default: return "???"; + } +} + +void bytecode_dump(const Bytecode *bc) { + if (!bc) { + printf("\n"); + return; + } + printf("Constants (%d):\n", bc->const_count); + for (int i = 0; i < bc->const_count; ++i) { + printf(" [%d] ", i); + print_value(&bc->constants[i]); + printf("\n"); + } + printf("Instructions (%d):\n", bc->instr_count); + for (int i = 0; i < bc->instr_count; ++i) { + const Instruction *ins = &bc->instructions[i]; + printf(" %3d: %-15s %d\n", i, opcode_name(ins->op), ins->operand); + } +} + diff --git a/src/bytecode.h b/src/bytecode.h index 108035d..3e705cc 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -66,5 +66,8 @@ int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand); void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand); /* patching */ void bytecode_free(Bytecode *bc); +// utilities +void bytecode_dump(const Bytecode *bc); + #endif diff --git a/src/fun_test.c b/src/fun_test.c new file mode 100644 index 0000000..892a166 --- /dev/null +++ b/src/fun_test.c @@ -0,0 +1,133 @@ +#include "bytecode.h" +#include "value.h" +#include "vm.h" +#include + +#define ASSERT_EQ(val, expected) \ + if ((val).type != VAL_INT || (val).i != (expected)) { \ + fprintf(stderr, "Assertion failed: expected %lld, got ", (long long)(expected)); \ + print_value(&(val)); \ + printf("\n"); \ + return 1; \ + } + +int main(void) { + VM vm; + vm_init(&vm); + + Bytecode *bc = bytecode_new(); + + // constants + int c0 = bytecode_add_constant(bc, make_int(0)); + int c1 = bytecode_add_constant(bc, make_int(1)); + int c2 = bytecode_add_constant(bc, make_int(2)); + int c3 = bytecode_add_constant(bc, make_int(3)); + int c10 = bytecode_add_constant(bc, make_int(10)); + int c42 = bytecode_add_constant(bc, make_int(42)); + + // ---------- Arithmetic ---------- + bytecode_add_instruction(bc, OP_LOAD_CONST, c42); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); // 42+1=43 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c10); + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_SUB, 0); // 10-3=7 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_MUL, 0); // 2*3=6 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c10); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_DIV, 0); // 10/2=5 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c10); + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_MOD, 0); // 10%3=1 + bytecode_add_instruction(bc, OP_PRINT, 0); + + // ---------- Comparisons ---------- + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LT, 0); // 1<2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LTE, 0); // 2<=2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_GT, 0); // 3>2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_GTE, 0); // 2>=2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_EQ, 0); // 2==2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_NEQ, 0); // 2!=3=1 + bytecode_add_instruction(bc, OP_PRINT, 0); + + // ---------- Logical ---------- + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_AND, 0); // 1&&0=0 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_OR, 0); // 1||0=1 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_NOT, 0); // !0=1 + bytecode_add_instruction(bc, OP_PRINT, 0); + + // ---------- Stack ---------- + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_DUP, 0); // duplicate 1 + bytecode_add_instruction(bc, OP_ADD, 0); // 1+1=2 + bytecode_add_instruction(bc, OP_PRINT, 0); + + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_SWAP, 0); // swap top two + bytecode_add_instruction(bc, OP_PRINT, 0); // top=1 + + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_POP, 0); // discard 1 (stack now empty) + + // ---------- HALT ---------- + bytecode_add_instruction(bc, OP_HALT, 0); + + printf("=== Bytecode dump ===\n"); + for (int i = 0; i < bc->instr_count; ++i) { + Instruction instr = bc->instructions[i]; + printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand); + } + printf("=====================\n"); + + // run VM + vm_run(&vm, bc); + + printf("All tests executed. Output count: %d\n", vm.output_count); + + vm_clear_output(&vm); + bytecode_free(bc); + return 0; +} + diff --git a/src/test_opcodes.c b/src/test_opcodes.c index 2914857..541c2a3 100644 --- a/src/test_opcodes.c +++ b/src/test_opcodes.c @@ -4,33 +4,40 @@ #include int main() { + + VM vm; + vm_init(&vm); + Bytecode *bc = bytecode_new(); // Example: test OP_ADD int c1 = bytecode_add_constant(bc, make_int(5)); int c2 = bytecode_add_constant(bc, make_int(3)); - bytecode_add_instruction(bc, OP_CONSTANT, c1); - bytecode_add_instruction(bc, OP_CONSTANT, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_PRINT, 0); - VM vm; - vm_init(&vm); - printf("=== Bytecode dump ===\n"); - bytecode_dump(bc); - - if (vm_run(&vm, bc) != 0) { - printf("Runtime error!\n"); + for (int i = 0; i < bc->instr_count; ++i) { + Instruction instr = bc->instructions[i]; + printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand); } + printf("=====================\n"); + bytecode_dump(bc); + printf("=====================\n"); + + vm_run(&vm, bc); printf("Output count: %d\n", vm.output_count); for (int i = 0; i < vm.output_count; i++) { - printf("Output[%d] = %ld\n", i, vm.output[i].i); + printf("Output[%d] = ", i); + print_value(&vm.output[i]); + printf("\n"); } + vm_clear_output(&vm); vm_free(&vm); bytecode_free(bc); return 0; } - diff --git a/src/vm.c b/src/vm.c index 9e34ecf..7c7925d 100644 --- a/src/vm.c +++ b/src/vm.c @@ -3,6 +3,7 @@ #include "value.h" #include #include +#include void vm_clear_output(VM *vm) { for (int i = 0; i < vm->output_count; ++i) { @@ -259,7 +260,29 @@ void vm_run(VM *vm, Bytecode *entry) { case OP_EQ: { Value b = pop_value(vm); Value a = pop_value(vm); - push_value(vm, make_int((a.type == b.type && a.i == b.i) ? 1 : 0)); // for ints only, extend later + int eq = 0; + if (a.type == b.type) { + switch (a.type) { + case VAL_INT: + eq = (a.i == b.i); + break; + case VAL_STRING: + eq = (a.s && b.s) ? (strcmp(a.s, b.s) == 0) : (a.s == b.s); + break; + case VAL_FUNCTION: + eq = (a.fn == b.fn); + break; + case VAL_NIL: + eq = 1; + break; + default: + eq = 0; + break; + } + } else { + eq = 0; + } + push_value(vm, make_int(eq ? 1 : 0)); free_value(a); free_value(b); break; } @@ -267,11 +290,34 @@ void vm_run(VM *vm, Bytecode *entry) { case OP_NEQ: { Value b = pop_value(vm); Value a = pop_value(vm); - push_value(vm, make_int((a.type != b.type || a.i != b.i) ? 1 : 0)); // for ints only + int neq = 1; + if (a.type == b.type) { + switch (a.type) { + case VAL_INT: + neq = (a.i != b.i); + break; + case VAL_STRING: + neq = (a.s && b.s) ? (strcmp(a.s, b.s) != 0) : (a.s != b.s); + break; + case VAL_FUNCTION: + neq = (a.fn != b.fn); + break; + case VAL_NIL: + neq = 0; + break; + default: + neq = 1; + break; + } + } else { + neq = 1; + } + push_value(vm, make_int(neq ? 1 : 0)); free_value(a); free_value(b); break; } + case OP_JUMP_IF_FALSE: { Value cond = pop_value(vm); int truthy = value_is_truthy(&cond);