#include "vm.h" #include #include #include #include namespace camellya { VM::VM() : current_frame(nullptr) { register_builtin_functions(); } bool VM::execute(std::shared_ptr chunk) { if (!chunk) { runtime_error("Cannot execute null chunk"); return false; } frames.clear(); frames.emplace_back(chunk, 0); current_frame = &frames.back(); stack.clear(); try { return run(); } catch (const std::exception& e) { runtime_error(e.what()); return false; } } bool VM::run() { while (true) { // Debug: print instruction #ifdef DEBUG_TRACE_EXECUTION std::cout << "Stack: "; for (const auto& value : stack) { std::cout << "[ " << value->to_string() << " ]"; } std::cout << std::endl; current_frame->chunk->disassemble_instruction(current_frame->ip); #endif OpCode instruction = static_cast(read_byte()); switch (instruction) { case OpCode::OP_CONSTANT: { ValuePtr constant = read_constant(); push(constant); break; } case OpCode::OP_NIL: push(std::make_shared()); break; case OpCode::OP_TRUE: push(std::make_shared(true)); break; case OpCode::OP_FALSE: push(std::make_shared(false)); break; case OpCode::OP_ADD: case OpCode::OP_SUBTRACT: case OpCode::OP_MULTIPLY: case OpCode::OP_DIVIDE: case OpCode::OP_MODULO: case OpCode::OP_GREATER: case OpCode::OP_GREATER_EQUAL: case OpCode::OP_LESS: case OpCode::OP_LESS_EQUAL: if (!binary_op(instruction)) { return false; } break; case OpCode::OP_EQUAL: { ValuePtr b = pop(); ValuePtr a = pop(); push(std::make_shared(values_equal(a, b))); break; } case OpCode::OP_NOT_EQUAL: { ValuePtr b = pop(); ValuePtr a = pop(); push(std::make_shared(!values_equal(a, b))); break; } case OpCode::OP_NEGATE: { ValuePtr value = pop(); if (value->type() != Type::NUMBER) { runtime_error("Operand must be a number."); return false; } double num = std::dynamic_pointer_cast(value)->value; push(std::make_shared(-num)); break; } case OpCode::OP_NOT: { ValuePtr value = pop(); push(std::make_shared(is_falsey(value))); break; } case OpCode::OP_POP: pop(); break; case OpCode::OP_POPN: { uint8_t count = read_byte(); for (int i = 0; i < count; i++) { pop(); } break; } case OpCode::OP_DUP: push(peek(0)); break; case OpCode::OP_GET_GLOBAL: { std::string name = read_string(); auto it = globals.find(name); if (it == globals.end()) { // Fallback: check if 'this' is at slot 0 and has the property ValuePtr receiver = stack[current_frame->stack_offset]; if (receiver && receiver->type() == Type::INSTANCE) { auto instance = std::dynamic_pointer_cast(receiver); if (instance->fields.find(name) != instance->fields.end()) { push(instance->get(name)); break; } } runtime_error("Undefined variable '" + name + "'."); return false; } push(it->second); break; } case OpCode::OP_SET_GLOBAL: { std::string name = read_string(); auto it = globals.find(name); if (it == globals.end()) { // Fallback: check if 'this' is at slot 0 and has the property ValuePtr receiver = stack[current_frame->stack_offset]; if (receiver && receiver->type() == Type::INSTANCE) { auto instance = std::dynamic_pointer_cast(receiver); if (instance->fields.find(name) != instance->fields.end()) { instance->set(name, peek(0)); break; } } runtime_error("Undefined variable '" + name + "'."); return false; } it->second = peek(0); break; } case OpCode::OP_DEFINE_GLOBAL: { std::string name = read_string(); globals[name] = pop(); break; } case OpCode::OP_GET_LOCAL: { uint8_t slot = read_byte(); push(stack[current_frame->stack_offset + slot]); break; } case OpCode::OP_SET_LOCAL: { uint8_t slot = read_byte(); stack[current_frame->stack_offset + slot] = peek(0); break; } case OpCode::OP_JUMP: { uint16_t offset = read_short(); current_frame->ip += offset; break; } case OpCode::OP_JUMP_IF_FALSE: { uint16_t offset = read_short(); if (is_falsey(peek(0))) { current_frame->ip += offset; } break; } case OpCode::OP_LOOP: { uint16_t offset = read_short(); current_frame->ip -= offset; break; } case OpCode::OP_CALL: { uint8_t arg_count = read_byte(); ValuePtr callee = peek(arg_count); if (callee->type() == Type::FUNCTION) { auto func = std::dynamic_pointer_cast(callee); if (func->is_native) { // Native function call std::vector args; if (func->bound_instance) { args.push_back(func->bound_instance); } for (int i = arg_count - 1; i >= 0; i--) { args.push_back(peek(i)); } ValuePtr result = func->native_func(args); // Pop arguments and function for (int i = 0; i <= arg_count; i++) { pop(); } push(result); } else { // Script function - call bytecode if (frames.size() >= 64) { runtime_error("Stack overflow."); return false; } // Parameters are already on the stack // Function is at stack index: stack.size() - arg_count - 1 size_t stack_offset = stack.size() - arg_count - 1; // If it's a bound method, replace function on stack with 'this' if (func->bound_instance) { stack[stack_offset] = func->bound_instance; } frames.emplace_back(func->chunk, stack_offset); current_frame = &frames.back(); } } else if (callee->type() == Type::CLASS) { // Class instantiation auto klass = std::dynamic_pointer_cast(callee); auto instance = std::make_shared(klass); // The class is at stack[stack.size() - arg_count - 1] // Replace it with the instance stack[stack.size() - arg_count - 1] = instance; // Check if class has init method auto init_method = klass->methods.find("init"); if (init_method != klass->methods.end()) { auto init_func = init_method->second; if (init_func->is_native) { // ... native init (rare) std::vector args; for (int i = arg_count - 1; i >= 0; i--) { args.push_back(peek(i)); } init_func->native_func(args); // pop arguments, instance stays on stack for (int i = 0; i < arg_count; i++) pop(); } else { // Script init - push a new frame if (frames.size() >= 64) { runtime_error("Stack overflow."); return false; } // stack_offset points to the instance we just put there size_t stack_offset = stack.size() - arg_count - 1; frames.emplace_back(init_func->chunk, stack_offset); current_frame = &frames.back(); } } else { // No init, just pop arguments for (int i = 0; i < arg_count; i++) { pop(); } } } else { runtime_error("Can only call functions and classes."); return false; } break; } case OpCode::OP_RETURN: { ValuePtr result = pop(); // Pop the frame size_t stack_offset = current_frame->stack_offset; frames.pop_back(); // Pop locals and function from the stack while (stack.size() > stack_offset) { pop(); } if (frames.empty()) { return true; } current_frame = &frames.back(); push(result); break; } case OpCode::OP_BUILD_LIST: { uint8_t count = read_byte(); auto list = std::make_shared(); std::vector elements; for (int i = 0; i < count; i++) { elements.push_back(pop()); } // Reverse because we popped in reverse order for (int i = count - 1; i >= 0; i--) { list->push(elements[i]); } push(list); break; } case OpCode::OP_BUILD_MAP: { uint8_t count = read_byte(); auto map = std::make_shared(); for (int i = 0; i < count; i++) { ValuePtr value = pop(); ValuePtr key = pop(); if (key->type() != Type::STRING) { runtime_error("Map keys must be strings."); return false; } std::string key_str = std::dynamic_pointer_cast(key)->value; map->set(key_str, value); } push(map); break; } case OpCode::OP_INDEX: { ValuePtr index = pop(); ValuePtr object = pop(); if (object->type() == Type::LIST) { auto list = std::dynamic_pointer_cast(object); if (index->type() != Type::NUMBER) { runtime_error("List index must be a number."); return false; } size_t idx = static_cast( std::dynamic_pointer_cast(index)->value); push(list->get(idx)); } else if (object->type() == Type::MAP) { auto map = std::dynamic_pointer_cast(object); if (index->type() != Type::STRING) { runtime_error("Map key must be a string."); return false; } std::string key = std::dynamic_pointer_cast(index)->value; push(map->get(key)); } else { runtime_error("Only lists and maps support indexing."); return false; } break; } case OpCode::OP_INDEX_SET: { ValuePtr value = pop(); ValuePtr index = pop(); ValuePtr object = pop(); if (object->type() == Type::LIST) { auto list = std::dynamic_pointer_cast(object); if (index->type() != Type::NUMBER) { runtime_error("List index must be a number."); return false; } size_t idx = static_cast( std::dynamic_pointer_cast(index)->value); list->set(idx, value); push(value); } else if (object->type() == Type::MAP) { auto map = std::dynamic_pointer_cast(object); if (index->type() != Type::STRING) { runtime_error("Map key must be a string."); return false; } std::string key = std::dynamic_pointer_cast(index)->value; map->set(key, value); push(value); } else { runtime_error("Only lists and maps support index assignment."); return false; } break; } case OpCode::OP_CLASS: { std::string name = read_string(); auto klass = std::make_shared(name); push(klass); break; } case OpCode::OP_GET_PROPERTY: { std::string name = read_string(); ValuePtr object = pop(); if (object->type() == Type::INSTANCE) { auto instance = std::dynamic_pointer_cast(object); push(instance->get(name)); } else { auto it = builtin_classes.find(object->type_name()); if (it != builtin_classes.end()) { auto klass = it->second; auto method_it = klass->methods.find(name); if (method_it != klass->methods.end()) { auto bound = std::make_shared(*method_it->second); bound->bound_instance = object; push(bound); break; } } runtime_error("Only instances and types with registered built-in classes have properties/methods."); return false; } break; } case OpCode::OP_SET_PROPERTY: { std::string name = read_string(); ValuePtr value = pop(); ValuePtr object = pop(); if (object->type() == Type::INSTANCE) { auto instance = std::dynamic_pointer_cast(object); instance->set(name, value); push(value); } else { runtime_error("Only instances have fields."); return false; } break; } case OpCode::OP_PRINT: { std::cout << pop()->to_string() << std::endl; break; } case OpCode::OP_HALT: return true; default: runtime_error("Unknown opcode: " + std::to_string(static_cast(instruction))); return false; } } } uint8_t VM::read_byte() { return current_frame->chunk->get_code(current_frame->ip++); } uint16_t VM::read_short() { uint16_t high = read_byte(); uint16_t low = read_byte(); return (high << 8) | low; } ValuePtr VM::read_constant() { uint8_t constant_idx = read_byte(); return current_frame->chunk->get_constant(constant_idx); } std::string VM::read_string() { ValuePtr constant = read_constant(); if (constant->type() != Type::STRING) { throw RuntimeError("Expected string constant"); } return std::dynamic_pointer_cast(constant)->value; } void VM::runtime_error(const std::string& message) { error_message = message; std::cerr << "Runtime Error: " << message << std::endl; // Print stack trace for (auto it = frames.rbegin(); it != frames.rend(); ++it) { size_t instruction = it->ip - 1; int line = it->chunk->get_line(instruction); std::cerr << "[line " << line << "] in script" << std::endl; } } bool VM::is_falsey(ValuePtr value) { if (!value || value->type() == Type::NIL) return true; if (value->type() == Type::BOOL) { return !std::dynamic_pointer_cast(value)->value; } return false; } bool VM::binary_op(OpCode op) { ValuePtr b = pop(); ValuePtr a = pop(); if (op == OpCode::OP_ADD) { if (a->type() == Type::NUMBER && b->type() == Type::NUMBER) { double av = std::dynamic_pointer_cast(a)->value; double bv = std::dynamic_pointer_cast(b)->value; push(std::make_shared(av + bv)); return true; } if (a->type() == Type::STRING && b->type() == Type::STRING) { std::string av = std::dynamic_pointer_cast(a)->value; std::string bv = std::dynamic_pointer_cast(b)->value; push(std::make_shared(av + bv)); return true; } runtime_error("Operands must be two numbers or two strings."); return false; } // Other arithmetic operations require numbers if (a->type() != Type::NUMBER || b->type() != Type::NUMBER) { runtime_error("Operands must be numbers."); return false; } double av = std::dynamic_pointer_cast(a)->value; double bv = std::dynamic_pointer_cast(b)->value; switch (op) { case OpCode::OP_SUBTRACT: push(std::make_shared(av - bv)); break; case OpCode::OP_MULTIPLY: push(std::make_shared(av * bv)); break; case OpCode::OP_DIVIDE: if (bv == 0) { runtime_error("Division by zero."); return false; } push(std::make_shared(av / bv)); break; case OpCode::OP_MODULO: push(std::make_shared(std::fmod(av, bv))); break; case OpCode::OP_GREATER: push(std::make_shared(av > bv)); break; case OpCode::OP_GREATER_EQUAL: push(std::make_shared(av >= bv)); break; case OpCode::OP_LESS: push(std::make_shared(av < bv)); break; case OpCode::OP_LESS_EQUAL: push(std::make_shared(av <= bv)); break; default: runtime_error("Unknown binary operator."); return false; } return true; } void VM::register_builtin_functions() { // print function auto print_func = std::make_shared("print", [](const std::vector& args) -> ValuePtr { if (args.empty()) { std::cout << std::endl; return std::make_shared(); } for (size_t i = 0; i < args.size(); ++i) { if (i > 0) std::cout << " "; std::cout << args[i]->to_string(); } std::cout << std::endl; return std::make_shared(); }); globals["print"] = print_func; // len function auto len_func = std::make_shared("len", [](const std::vector& args) -> ValuePtr { if (args.size() != 1) { throw RuntimeError("len() expects 1 argument."); } auto& arg = args[0]; if (arg->type() == Type::LIST) { auto list = std::dynamic_pointer_cast(arg); return std::make_shared(static_cast(list->size())); } else if (arg->type() == Type::STRING) { auto str = std::dynamic_pointer_cast(arg); return std::make_shared(static_cast(str->value.length())); } else if (arg->type() == Type::MAP) { auto map = std::dynamic_pointer_cast(arg); return std::make_shared(static_cast(map->pairs.size())); } throw RuntimeError("len() expects list, string, or map."); }); globals["len"] = len_func; // Define list class auto list_klass = std::make_shared("list"); list_klass->add_method("push", std::make_shared("push", [](const std::vector& args) -> ValuePtr { if (args.size() != 2) throw RuntimeError("list.push() expects 1 argument."); auto list = std::dynamic_pointer_cast(args[0]); list->push(args[1]); return args[1]; })); list_klass->add_method("pop", std::make_shared("pop", [](const std::vector& args) -> ValuePtr { if (args.size() != 1) throw RuntimeError("list.pop() expects 0 arguments."); auto list = std::dynamic_pointer_cast(args[0]); if (list->elements.empty()) return std::make_shared(); ValuePtr val = list->elements.back(); list->elements.pop_back(); return val; })); list_klass->add_method("len", std::make_shared("len", [](const std::vector& args) -> ValuePtr { if (args.size() != 1) throw RuntimeError("list.len() expects 0 arguments."); auto list = std::dynamic_pointer_cast(args[0]); return std::make_shared(static_cast(list->size())); })); register_builtin_class("list", list_klass); // Define map class auto map_klass = std::make_shared("map"); map_klass->add_method("set", std::make_shared("set", [](const std::vector& args) -> ValuePtr { if (args.size() != 3) throw RuntimeError("map.set() expects 2 arguments."); auto map = std::dynamic_pointer_cast(args[0]); if (args[1]->type() != Type::STRING) throw RuntimeError("Map key must be a string."); std::string key = std::dynamic_pointer_cast(args[1])->value; map->set(key, args[2]); return args[2]; })); map_klass->add_method("get", std::make_shared("get", [](const std::vector& args) -> ValuePtr { if (args.size() != 2) throw RuntimeError("map.get() expects 1 argument."); auto map = std::dynamic_pointer_cast(args[0]); if (args[1]->type() != Type::STRING) throw RuntimeError("Map key must be a string."); std::string key = std::dynamic_pointer_cast(args[1])->value; return map->get(key); })); map_klass->add_method("has", std::make_shared("has", [](const std::vector& args) -> ValuePtr { if (args.size() != 2) throw RuntimeError("map.has() expects 1 argument."); auto map = std::dynamic_pointer_cast(args[0]); if (args[1]->type() != Type::STRING) throw RuntimeError("Map key must be a string."); std::string key = std::dynamic_pointer_cast(args[1])->value; return std::make_shared(map->has(key)); })); map_klass->add_method("len", std::make_shared("len", [](const std::vector& args) -> ValuePtr { if (args.size() != 1) throw RuntimeError("map.len() expects 0 arguments."); auto map = std::dynamic_pointer_cast(args[0]); return std::make_shared(static_cast(map->pairs.size())); })); register_builtin_class("map", map_klass); } void VM::register_native_function(const std::string& name, NativeFunction func) { auto func_value = std::make_shared(name, func); globals[name] = func_value; } void VM::register_builtin_class(const std::string& type_name, std::shared_ptr klass) { builtin_classes[type_name] = klass; globals[klass->name] = klass; } void VM::set_global(const std::string& name, ValuePtr value) { globals[name] = value; } ValuePtr VM::get_global(const std::string& name) { auto it = globals.find(name); if (it == globals.end()) { return nullptr; } return it->second; } void VM::push(ValuePtr value) { stack.push_back(value); } ValuePtr VM::pop() { if (stack.empty()) { throw RuntimeError("Stack underflow"); } ValuePtr value = stack.back(); stack.pop_back(); return value; } ValuePtr VM::peek(int distance) { if (distance >= static_cast(stack.size())) { throw RuntimeError("Stack underflow in peek"); } return stack[stack.size() - 1 - distance]; } } // namespace camellya