2024-11-08 12:22:42 +01:00
|
|
|
#include "vm.hpp"
|
|
|
|
#include "arch.hpp"
|
2024-11-11 15:31:54 +01:00
|
|
|
#include <cstdint>
|
2024-12-13 20:05:27 +01:00
|
|
|
#include <cstdio>
|
2024-11-11 15:31:54 +01:00
|
|
|
#include <cstdlib>
|
2024-11-08 12:22:42 +01:00
|
|
|
#include <format>
|
|
|
|
#include <iostream>
|
2024-11-20 14:46:19 +01:00
|
|
|
#include <string>
|
2024-11-11 15:31:54 +01:00
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
2024-11-08 12:22:42 +01:00
|
|
|
|
|
|
|
using namespace sliger;
|
|
|
|
|
2024-11-19 05:06:27 +01:00
|
|
|
void VM::run_until_done()
|
2024-11-08 12:22:42 +01:00
|
|
|
{
|
2024-12-15 03:07:33 +01:00
|
|
|
while (not done()) {
|
2024-11-19 05:06:27 +01:00
|
|
|
run_instruction();
|
|
|
|
}
|
2024-11-21 04:12:07 +01:00
|
|
|
this->flame_graph.calculate_midway_result(this->instruction_counter);
|
2024-11-19 05:06:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void VM::run_n_instructions(size_t amount)
|
|
|
|
{
|
2024-12-15 03:07:33 +01:00
|
|
|
for (size_t i = 0; i < amount and not done(); ++i) {
|
2024-11-19 05:06:27 +01:00
|
|
|
run_instruction();
|
|
|
|
}
|
2024-11-21 04:12:07 +01:00
|
|
|
this->flame_graph.calculate_midway_result(this->instruction_counter);
|
2024-11-19 05:06:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void VM::run_instruction()
|
|
|
|
{
|
2024-12-13 20:17:22 +01:00
|
|
|
if (this->opts.print_debug) {
|
2024-12-13 06:09:10 +01:00
|
|
|
// std::cout << std::format(" {:>4}: {:<12}{}\n", this->pc,
|
|
|
|
// maybe_op_to_string(this->program[this->pc]),
|
|
|
|
// stack_repr_string(8));
|
|
|
|
auto stack_frame_size = this->stack.size() - this->bp;
|
2024-12-12 10:17:09 +01:00
|
|
|
std::cout << std::format(" {:>4}: {:<12}{}\n", this->pc,
|
2024-12-13 06:09:10 +01:00
|
|
|
maybe_op_to_string(this->program[this->pc]),
|
|
|
|
stack_repr_string(stack_frame_size));
|
2024-12-12 10:17:09 +01:00
|
|
|
}
|
2024-11-19 05:06:27 +01:00
|
|
|
auto op = eat_op();
|
|
|
|
switch (op) {
|
|
|
|
case Op::Nop:
|
|
|
|
// nothing
|
|
|
|
break;
|
|
|
|
case Op::PushNull:
|
|
|
|
this->stack.push_back(Null {});
|
|
|
|
break;
|
|
|
|
case Op::PushInt: {
|
|
|
|
assert_program_has(1);
|
|
|
|
auto value = eat_int32();
|
|
|
|
this->stack.push_back(Int { value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::PushBool: {
|
|
|
|
assert_program_has(1);
|
|
|
|
auto value = eat_int32();
|
|
|
|
this->stack.push_back(Bool { .value = value != 0 });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::PushString: {
|
|
|
|
assert_program_has(1);
|
|
|
|
auto string_length = eat_uint32();
|
|
|
|
assert_program_has(string_length);
|
|
|
|
auto value = std::string();
|
|
|
|
for (uint32_t i = 0; i < string_length; ++i) {
|
|
|
|
auto ch = eat_uint32();
|
|
|
|
value.push_back(static_cast<char>(ch));
|
|
|
|
}
|
|
|
|
stack_push(String { .value = std::move(value) });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::PushPtr: {
|
|
|
|
assert_program_has(1);
|
|
|
|
auto value = eat_uint32();
|
|
|
|
this->stack.push_back(Ptr { value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Pop: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
this->stack.pop_back();
|
|
|
|
break;
|
|
|
|
}
|
2024-12-11 00:18:51 +01:00
|
|
|
case Op::ReserveStatic: {
|
|
|
|
assert_program_has(1);
|
|
|
|
auto value = eat_uint32();
|
|
|
|
this->statics.reserve(value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::LoadStatic: {
|
|
|
|
assert_program_has(1);
|
|
|
|
auto loc = eat_uint32();
|
|
|
|
auto value = this->statics.at(loc);
|
|
|
|
stack_push(value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::StoreStatic: {
|
|
|
|
assert_program_has(1);
|
|
|
|
auto loc = eat_uint32();
|
|
|
|
auto value = stack_pop();
|
|
|
|
this->statics.at(loc) = value;
|
|
|
|
break;
|
|
|
|
}
|
2024-11-19 05:06:27 +01:00
|
|
|
case Op::LoadLocal: {
|
2024-12-11 00:18:51 +01:00
|
|
|
assert_program_has(1);
|
2024-11-19 05:06:27 +01:00
|
|
|
auto loc = eat_uint32();
|
|
|
|
assert_fn_stack_has(loc);
|
|
|
|
auto value = fn_stack_at(loc);
|
|
|
|
stack_push(value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::StoreLocal: {
|
2024-12-11 00:18:51 +01:00
|
|
|
assert_program_has(1);
|
2024-11-19 05:06:27 +01:00
|
|
|
auto loc = eat_uint32();
|
|
|
|
assert_fn_stack_has(loc + 1);
|
|
|
|
auto value = stack_pop();
|
|
|
|
fn_stack_at(loc) = value;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Call: {
|
|
|
|
assert_program_has(1);
|
|
|
|
auto arg_count = eat_uint32();
|
|
|
|
assert_stack_has(arg_count + 1);
|
|
|
|
auto fn_ptr = stack_pop();
|
|
|
|
auto arguments = std::vector<Value>();
|
|
|
|
for (uint32_t i = 0; i < arg_count; ++i) {
|
|
|
|
arguments.push_back(stack_pop());
|
|
|
|
}
|
|
|
|
stack_push(Ptr { .value = this->pc });
|
|
|
|
stack_push(Ptr { .value = this->bp });
|
2024-12-11 12:36:19 +01:00
|
|
|
this->pc = fn_ptr.as_ptr().value;
|
|
|
|
this->bp = static_cast<uint32_t>(this->stack.size());
|
2024-12-13 06:09:10 +01:00
|
|
|
for (auto&& arg = arguments.rbegin(); arg != arguments.rend();
|
2024-12-13 20:05:27 +01:00
|
|
|
++arg) {
|
2024-12-13 06:09:10 +01:00
|
|
|
stack_push(*arg);
|
2024-11-19 05:06:27 +01:00
|
|
|
}
|
|
|
|
if (this->opts.flame_graph) {
|
|
|
|
this->flame_graph.report_call(
|
|
|
|
fn_ptr.as_ptr().value, this->instruction_counter);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Return: {
|
|
|
|
assert_stack_has(3);
|
|
|
|
auto ret_val = stack_pop();
|
2024-12-11 12:36:19 +01:00
|
|
|
while (this->stack.size() > this->bp) {
|
|
|
|
stack_pop();
|
|
|
|
}
|
2024-11-19 05:06:27 +01:00
|
|
|
auto bp_val = stack_pop();
|
|
|
|
auto pc_val = stack_pop();
|
|
|
|
this->bp = bp_val.as_ptr().value;
|
|
|
|
stack_push(ret_val);
|
|
|
|
this->pc = pc_val.as_ptr().value;
|
|
|
|
if (this->opts.flame_graph) {
|
|
|
|
this->flame_graph.report_return(this->instruction_counter);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Jump: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto addr = stack_pop();
|
|
|
|
this->pc = addr.as_ptr().value;
|
|
|
|
break;
|
|
|
|
}
|
2024-12-11 00:18:51 +01:00
|
|
|
case Op::JumpIfTrue: {
|
2024-11-19 05:06:27 +01:00
|
|
|
assert_stack_has(2);
|
|
|
|
auto addr = stack_pop();
|
|
|
|
auto cond = stack_pop();
|
2024-12-11 00:18:51 +01:00
|
|
|
if (cond.as_bool().value) {
|
2024-11-11 15:31:54 +01:00
|
|
|
this->pc = addr.as_ptr().value;
|
2024-11-18 15:01:24 +01:00
|
|
|
}
|
2024-11-19 05:06:27 +01:00
|
|
|
break;
|
|
|
|
}
|
2024-12-11 00:18:51 +01:00
|
|
|
case Op::Builtin: {
|
|
|
|
assert_program_has(1);
|
|
|
|
auto builtin_id = eat_uint32();
|
|
|
|
run_builtin(static_cast<Builtin>(builtin_id));
|
|
|
|
break;
|
|
|
|
}
|
2024-12-17 02:10:11 +01:00
|
|
|
case Op::Duplicate: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto value = stack_pop();
|
|
|
|
stack_push(value);
|
|
|
|
stack_push(value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Swap: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop();
|
|
|
|
auto left = stack_pop();
|
|
|
|
stack_push(right);
|
|
|
|
stack_push(left);
|
|
|
|
break;
|
|
|
|
}
|
2024-11-19 05:06:27 +01:00
|
|
|
case Op::Add: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_int().value;
|
|
|
|
auto left = stack_pop().as_int().value;
|
|
|
|
auto value = left + right;
|
|
|
|
stack_push(Int { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Subtract: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_int().value;
|
|
|
|
auto left = stack_pop().as_int().value;
|
|
|
|
auto value = left - right;
|
|
|
|
stack_push(Int { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Multiply: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_int().value;
|
|
|
|
auto left = stack_pop().as_int().value;
|
|
|
|
auto value = left * right;
|
|
|
|
stack_push(Int { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Divide: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_int().value;
|
|
|
|
auto left = stack_pop().as_int().value;
|
|
|
|
auto value = left / right;
|
|
|
|
stack_push(Int { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Remainder: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_int().value;
|
|
|
|
auto left = stack_pop().as_int().value;
|
|
|
|
auto value = left % right;
|
|
|
|
stack_push(Int { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Equal: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_int().value;
|
|
|
|
auto left = stack_pop().as_int().value;
|
|
|
|
auto value = left == right;
|
|
|
|
stack_push(Bool { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::LessThan: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_int().value;
|
|
|
|
auto left = stack_pop().as_int().value;
|
|
|
|
auto value = left < right;
|
|
|
|
stack_push(Bool { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::And: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_bool().value;
|
|
|
|
auto left = stack_pop().as_bool().value;
|
|
|
|
auto value = left && right;
|
|
|
|
stack_push(Bool { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Or: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_bool().value;
|
|
|
|
auto left = stack_pop().as_bool().value;
|
|
|
|
auto value = left || right;
|
|
|
|
stack_push(Bool { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Xor: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto right = stack_pop().as_bool().value;
|
|
|
|
auto left = stack_pop().as_bool().value;
|
|
|
|
auto value = (left || !right) || (!left && right);
|
|
|
|
stack_push(Bool { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::Not: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto value = !stack_pop().as_bool().value;
|
|
|
|
stack_push(Bool { .value = value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Op::SourceMap: {
|
|
|
|
assert_program_has(3);
|
|
|
|
auto index = eat_int32();
|
|
|
|
auto line = eat_int32();
|
|
|
|
auto col = eat_int32();
|
|
|
|
if (opts.code_coverage) {
|
|
|
|
this->code_coverage.report_cover(this->current_pos);
|
|
|
|
}
|
|
|
|
this->current_pos = { index, line, col };
|
|
|
|
break;
|
2024-11-08 12:22:42 +01:00
|
|
|
}
|
2024-12-31 03:38:38 +01:00
|
|
|
default:
|
|
|
|
std::cerr << std::format("unrecognized instruction '{}', pc = {}",
|
|
|
|
std::to_underlying(op), this->pc);
|
|
|
|
std::exit(1);
|
|
|
|
break;
|
2024-11-08 12:22:42 +01:00
|
|
|
}
|
2024-11-19 05:06:27 +01:00
|
|
|
this->instruction_counter += 1;
|
2024-11-08 12:22:42 +01:00
|
|
|
}
|
2024-12-11 00:18:51 +01:00
|
|
|
|
|
|
|
void VM::run_builtin(Builtin builtin_id)
|
|
|
|
{
|
2024-12-13 20:17:22 +01:00
|
|
|
if (this->opts.print_debug) {
|
2024-12-13 20:05:27 +01:00
|
|
|
std::cout << std::format("Running builtin {}\n",
|
|
|
|
maybe_builtin_to_string(static_cast<uint32_t>(builtin_id)));
|
2024-12-13 06:09:10 +01:00
|
|
|
}
|
2024-12-11 00:18:51 +01:00
|
|
|
switch (builtin_id) {
|
2024-12-22 02:30:23 +01:00
|
|
|
case Builtin::Exit: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto status_code = stack_pop().as_int().value;
|
|
|
|
std::exit(status_code);
|
|
|
|
break;
|
|
|
|
}
|
2024-12-13 16:03:01 +01:00
|
|
|
case Builtin::IntToString: {
|
|
|
|
assert_stack_has(1);
|
2024-12-22 02:30:23 +01:00
|
|
|
auto number = stack_pop().as_int().value;
|
2024-12-13 16:03:01 +01:00
|
|
|
auto str = std::to_string(number);
|
|
|
|
stack_push(String(str));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-12-13 20:05:27 +01:00
|
|
|
case Builtin::StringConcat:
|
|
|
|
case Builtin::StringEqual:
|
|
|
|
case Builtin::StringCharAt:
|
|
|
|
case Builtin::StringLength:
|
|
|
|
case Builtin::StringPushChar:
|
|
|
|
case Builtin::StringToInt:
|
|
|
|
run_string_builtin(builtin_id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Builtin::ArrayNew:
|
|
|
|
case Builtin::ArraySet:
|
|
|
|
case Builtin::ArrayPush:
|
|
|
|
case Builtin::ArrayAt:
|
|
|
|
case Builtin::ArrayLength:
|
|
|
|
run_array_builtin(builtin_id);
|
|
|
|
break;
|
|
|
|
|
2024-12-31 03:38:38 +01:00
|
|
|
case Builtin::StructNew:
|
|
|
|
case Builtin::StructSet:
|
|
|
|
case Builtin::StructAt:
|
|
|
|
run_struct_builtin(builtin_id);
|
2024-12-13 20:05:27 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case Builtin::Print:
|
|
|
|
case Builtin::FileOpen:
|
|
|
|
case Builtin::FileClose:
|
|
|
|
case Builtin::FileWriteString:
|
2024-12-13 20:29:06 +01:00
|
|
|
case Builtin::FileReadChar:
|
2024-12-13 20:05:27 +01:00
|
|
|
case Builtin::FileReadToString:
|
|
|
|
case Builtin::FileFlush:
|
|
|
|
case Builtin::FileEof:
|
|
|
|
run_file_builtin(builtin_id);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VM::run_string_builtin(Builtin builtin_id)
|
|
|
|
{
|
|
|
|
switch (builtin_id) {
|
2024-12-11 00:18:51 +01:00
|
|
|
case Builtin::StringConcat: {
|
|
|
|
assert_stack_has(2);
|
2024-12-11 12:36:19 +01:00
|
|
|
auto right = stack_pop();
|
2024-12-13 06:09:10 +01:00
|
|
|
auto left = stack_pop();
|
2024-12-11 00:18:51 +01:00
|
|
|
stack_push(
|
2024-12-13 06:09:10 +01:00
|
|
|
String(left.as_string().value + right.as_string().value));
|
2024-12-11 00:18:51 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::StringEqual: {
|
|
|
|
assert_stack_has(2);
|
2024-12-11 12:36:19 +01:00
|
|
|
auto right = stack_pop();
|
2024-12-13 06:09:10 +01:00
|
|
|
auto left = stack_pop();
|
|
|
|
stack_push(Bool(left.as_string().value == right.as_string().value));
|
2024-12-11 13:37:26 +01:00
|
|
|
break;
|
|
|
|
}
|
2024-12-12 16:07:59 +01:00
|
|
|
case Builtin::StringCharAt: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto index_value = stack_pop();
|
2024-12-13 16:03:01 +01:00
|
|
|
auto string_value = stack_pop();
|
|
|
|
auto index = static_cast<int32_t>(index_value.as_int().value);
|
|
|
|
auto string = string_value.as_string();
|
|
|
|
stack_push(Int(string.at(index)));
|
2024-12-12 16:07:59 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::StringLength: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto str = stack_pop().as_string().value;
|
2024-12-13 06:09:10 +01:00
|
|
|
|
2024-12-12 16:07:59 +01:00
|
|
|
auto length = static_cast<int32_t>(str.length());
|
|
|
|
stack_push(Int(length));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::StringPushChar: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto ch = stack_pop();
|
2024-12-13 06:09:10 +01:00
|
|
|
auto str = stack_pop();
|
|
|
|
|
2024-12-12 16:07:59 +01:00
|
|
|
auto new_str = std::string(str.as_string().value);
|
|
|
|
new_str.push_back(static_cast<char>(ch.as_int().value));
|
|
|
|
stack_push(String(new_str));
|
|
|
|
break;
|
|
|
|
}
|
2024-12-13 16:03:01 +01:00
|
|
|
case Builtin::StringToInt: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto str = stack_pop().as_string().value;
|
|
|
|
auto number = atoi(str.c_str());
|
|
|
|
stack_push(Int(number));
|
|
|
|
break;
|
|
|
|
}
|
2024-12-13 20:05:27 +01:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2024-12-31 03:38:38 +01:00
|
|
|
|
2024-12-13 20:05:27 +01:00
|
|
|
void VM::run_array_builtin(Builtin builtin_id)
|
|
|
|
{
|
|
|
|
switch (builtin_id) {
|
2024-12-12 16:07:59 +01:00
|
|
|
case Builtin::ArrayNew: {
|
|
|
|
auto alloc_res = this->heap.alloc<heap::AllocType::Array>();
|
|
|
|
stack_push(Ptr(alloc_res.val()));
|
|
|
|
break;
|
|
|
|
}
|
2024-12-13 06:09:10 +01:00
|
|
|
case Builtin::ArraySet: {
|
|
|
|
assert_stack_has(2);
|
2024-12-13 16:03:01 +01:00
|
|
|
auto index = stack_pop().as_int().value;
|
|
|
|
auto array_ptr = stack_pop().as_ptr().value;
|
|
|
|
auto value = stack_pop();
|
2024-12-16 14:37:24 +01:00
|
|
|
|
|
|
|
this->heap.at(array_ptr).val()->as_array().at(index) = value;
|
2024-12-13 16:03:01 +01:00
|
|
|
stack_push(Null());
|
2024-12-13 06:09:10 +01:00
|
|
|
break;
|
|
|
|
}
|
2024-12-12 16:07:59 +01:00
|
|
|
case Builtin::ArrayPush: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto value = stack_pop();
|
2024-12-13 06:09:10 +01:00
|
|
|
auto array_ptr = stack_pop().as_ptr().value;
|
|
|
|
|
|
|
|
this->heap.at(array_ptr).val()->as_array().values.push_back(value);
|
2024-12-12 16:07:59 +01:00
|
|
|
stack_push(Null());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::ArrayAt: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto index = stack_pop().as_int().value;
|
2024-12-13 06:09:10 +01:00
|
|
|
auto array_ptr = stack_pop().as_ptr().value;
|
|
|
|
|
2024-12-12 16:07:59 +01:00
|
|
|
auto array = this->heap.at(array_ptr).val()->as_array();
|
2024-12-13 16:03:01 +01:00
|
|
|
stack_push(array.at(index));
|
2024-12-12 16:07:59 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::ArrayLength: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto array_ptr = stack_pop().as_ptr().value;
|
2024-12-13 06:09:10 +01:00
|
|
|
|
2024-12-12 16:07:59 +01:00
|
|
|
auto array = this->heap.at(array_ptr).val()->as_array();
|
|
|
|
stack_push(Int(static_cast<int32_t>(array.values.size())));
|
|
|
|
break;
|
|
|
|
}
|
2024-12-13 20:05:27 +01:00
|
|
|
default:
|
2024-12-13 06:09:10 +01:00
|
|
|
break;
|
2024-12-13 20:05:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-31 03:38:38 +01:00
|
|
|
void VM::run_struct_builtin(Builtin builtin_id)
|
|
|
|
{
|
|
|
|
switch (builtin_id) {
|
|
|
|
case Builtin::StructNew: {
|
|
|
|
auto alloc_res = this->heap.alloc<heap::AllocType::Struct>();
|
|
|
|
stack_push(Ptr(alloc_res.val()));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::StructSet: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto field = stack_pop().as_string().value;
|
|
|
|
auto struct_ptr = stack_pop().as_ptr().value;
|
|
|
|
auto value = stack_pop();
|
|
|
|
|
|
|
|
this->heap.at(struct_ptr)
|
|
|
|
.val()
|
|
|
|
->as_struct()
|
|
|
|
.assign(field, std::move(value));
|
|
|
|
stack_push(Null());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::StructAt: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto field = stack_pop().as_string().value;
|
|
|
|
auto struct_ptr = stack_pop().as_ptr().value;
|
|
|
|
|
|
|
|
stack_push(this->heap.at(struct_ptr).val()->as_struct().at(field));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-13 20:05:27 +01:00
|
|
|
void VM::run_file_builtin(Builtin builtin_id)
|
|
|
|
{
|
|
|
|
switch (builtin_id) {
|
2024-12-13 06:09:10 +01:00
|
|
|
case Builtin::Print: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto message = stack_pop().as_string().value;
|
|
|
|
std::cout << message;
|
|
|
|
stack_push(Null());
|
|
|
|
break;
|
|
|
|
}
|
2024-12-13 20:05:27 +01:00
|
|
|
case Builtin::FileOpen: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto mode = stack_pop().as_string().value;
|
|
|
|
auto filename = stack_pop().as_string().value;
|
|
|
|
FILE* fp = std::fopen(filename.c_str(), mode.c_str());
|
|
|
|
if (fp == nullptr) {
|
2024-12-16 18:23:51 +01:00
|
|
|
std::cerr << std::format(
|
|
|
|
"error: could not open file '{}'\n", filename);
|
2024-12-16 14:37:24 +01:00
|
|
|
std::exit(1);
|
2024-12-13 20:05:27 +01:00
|
|
|
}
|
|
|
|
auto file_id = this->file_id_counter;
|
|
|
|
this->file_id_counter += 1;
|
|
|
|
this->open_files.insert_or_assign(file_id, fp);
|
|
|
|
stack_push(Int(file_id));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::FileClose: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto file_id = stack_pop().as_int().value;
|
|
|
|
auto fp = this->open_files.find(file_id);
|
|
|
|
if (fp != this->open_files.end()) {
|
|
|
|
std::fclose(fp->second);
|
|
|
|
this->open_files.erase(file_id);
|
|
|
|
}
|
|
|
|
stack_push(Null());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::FileWriteString: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto content = stack_pop().as_string().value;
|
|
|
|
auto file_id = stack_pop().as_int().value;
|
|
|
|
auto fp = this->open_files.find(file_id);
|
|
|
|
if (fp == this->open_files.end()) {
|
|
|
|
std::cerr << std::format("error: no open file {}\n", file_id);
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
auto res = std::fputs(content.c_str(), fp->second);
|
|
|
|
if (res <= 0) {
|
|
|
|
stack_push(Int(-1));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
stack_push(Int(0));
|
|
|
|
break;
|
|
|
|
}
|
2024-12-13 20:29:06 +01:00
|
|
|
case Builtin::FileReadChar: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto file_id = stack_pop().as_int().value;
|
|
|
|
auto fp = this->open_files.find(file_id);
|
|
|
|
if (fp == this->open_files.end()) {
|
|
|
|
std::cerr << std::format("error: no open file {}\n", file_id);
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
int value = std::fgetc(fp->second);
|
|
|
|
stack_push(Int(value));
|
|
|
|
break;
|
|
|
|
}
|
2024-12-13 20:05:27 +01:00
|
|
|
case Builtin::FileReadToString: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto file_id = stack_pop().as_int().value;
|
|
|
|
auto fp = this->open_files.find(file_id);
|
|
|
|
if (fp == this->open_files.end()) {
|
|
|
|
std::cerr << std::format("error: no open file {}\n", file_id);
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
auto content = std::string();
|
|
|
|
while (true) {
|
2024-12-16 14:37:24 +01:00
|
|
|
constexpr size_t buf_size = 129;
|
2024-12-13 20:05:27 +01:00
|
|
|
char buf[buf_size] = "";
|
2024-12-16 14:37:24 +01:00
|
|
|
auto res = std::fread(buf, 1, buf_size - 1, fp->second);
|
2024-12-13 20:05:27 +01:00
|
|
|
if (res == 0) {
|
|
|
|
break;
|
|
|
|
}
|
2024-12-16 14:37:24 +01:00
|
|
|
buf[res] = '\0';
|
2024-12-13 20:05:27 +01:00
|
|
|
content.append(std::string(buf));
|
|
|
|
}
|
|
|
|
stack_push(String(std::move(content)));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::FileFlush: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto file_id = stack_pop().as_int().value;
|
|
|
|
auto fp = this->open_files.find(file_id);
|
|
|
|
if (fp == this->open_files.end()) {
|
|
|
|
std::cerr << std::format("error: no open file {}\n", file_id);
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
std::fflush(fp->second);
|
|
|
|
stack_push(Null());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::FileEof: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto file_id = stack_pop().as_int().value;
|
|
|
|
auto fp = this->open_files.find(file_id);
|
|
|
|
if (fp == this->open_files.end()) {
|
|
|
|
std::cerr << std::format("error: no open file {}\n", file_id);
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
stack_push(Bool(std::feof(fp->second) != 0));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
2024-12-11 00:18:51 +01:00
|
|
|
}
|
|
|
|
}
|
2024-12-13 20:05:27 +01:00
|
|
|
|
2024-12-13 16:11:16 +01:00
|
|
|
auto VM::stack_repr_string(size_t max_items) const -> std::string
|
|
|
|
{
|
|
|
|
auto result = std::string();
|
|
|
|
result += "→";
|
|
|
|
const auto& stack = view_stack();
|
|
|
|
for (size_t i = 0; i < stack.size() and i < max_items; ++i) {
|
|
|
|
if (i != 0) {
|
|
|
|
result += " ";
|
|
|
|
}
|
|
|
|
result += std::format(
|
|
|
|
"{:<11}", stack[stack.size() - i - 1].to_repr_string());
|
|
|
|
}
|
|
|
|
if (stack.size() > max_items) {
|
|
|
|
result += std::format(" ... + {}", stack.size() - max_items);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2024-12-16 18:23:51 +01:00
|
|
|
void VM::assert_program_has(size_t count)
|
2024-12-13 16:11:16 +01:00
|
|
|
{
|
2024-12-16 18:23:51 +01:00
|
|
|
if (this->pc + count > program.size()) {
|
|
|
|
std::cerr << std::format("malformed program, pc = {}", this->pc);
|
2024-12-13 16:11:16 +01:00
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-16 18:23:51 +01:00
|
|
|
void VM::assert_fn_stack_has(size_t count)
|
2024-12-13 16:11:16 +01:00
|
|
|
{
|
2024-12-16 18:23:51 +01:00
|
|
|
if (this->stack.size() - this->bp < count) {
|
2024-12-13 16:11:16 +01:00
|
|
|
std::cerr << std::format("stack underflow, pc = {}\n", this->pc);
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-16 18:23:51 +01:00
|
|
|
void VM::assert_stack_has(size_t count)
|
2024-12-13 16:11:16 +01:00
|
|
|
{
|
2024-12-16 18:23:51 +01:00
|
|
|
if (this->stack.size() < count) {
|
|
|
|
std::cerr << std::format("stack underflow, pc = {}\n", this->pc);
|
2024-12-13 16:11:16 +01:00
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
}
|