Expressions #7

Merged
andre merged 21 commits from vm2 into master 2026-05-02 15:07:12 -05:00
20 changed files with 596 additions and 108 deletions

2
.gitignore vendored
View File

@@ -32,3 +32,5 @@
*.out *.out
*.app *.app
cmake-build-*/
build/

35
TODO.md
View File

@@ -19,7 +19,7 @@ After some additional development:
## VM ## VM
- [ ] VM - [x] VM
- [x] Code - [x] Code
- [x] Simple bytecode loader - [x] Simple bytecode loader
- [x] Output bytecode format - [x] Output bytecode format
@@ -29,10 +29,37 @@ After some additional development:
- [x] Code execution (except functions) - [x] Code execution (except functions)
- [x] Functions - [x] Functions
- [x] Print stack - [x] Print stack
- [x] Assembler
- [ ] Assembler - [ ] VM execution
- [x] Stack operations (nil, integer, float, string, function)
- [x] Integer
- [x] Float
- [x] String
- [x] Expressions
- [x] Integer
- [x] Float
- [x] String
- [ ] Local/global variables
- [ ] Functions
- [ ] Constants
- [ ] Other operations
- [ ] Arrays
- [ ] Iteration
- [ ] Expressions
- [ ] Tables
- [ ] Iteration
- [ ] Metatables
- [ ] Expressions
- [ ] Control flow
- [ ] Compilation
- [ ] Error handling
- [ ] C++ API
- [ ] Run native code on VM
- [ ] Run tyche code from C++
- [ ] C API
After some additional development: After some additional development:
- [ ] Bytecode loader - [ ] Bytecode loader
- Combine multiple chunks - Combine multiple chunks
- Resolve function ids, constant ids, etc - Resolve function ids, constant ids, etc
- [ ] Upvalues

View File

@@ -37,7 +37,7 @@ Local variables:
Function operations: Function operations:
a7 c7 e7 call [n_pars] Enter function on stack toplevel (passing n next stack values as parameters) a7 c7 e7 call [n_pars] Enter function on stack toplevel (passing n next stack values as parameters)
10 ret Leave a function (return value in stack) 10 ret Leave a function (return value in stack)
11 retn Leave a function (return nil) 11 retn Leave a function (return nil)
Table and array operations: Table and array operations:
@@ -65,17 +65,21 @@ Logical/arithmetic:
2b and Bitwise AND 2b and Bitwise AND
2c or Bitwise OR 2c or Bitwise OR
2d xor Bitwise XOR 2d xor Bitwise XOR
2e pow Power
2f shl Shift left
30 shr Shift right
31 mod Modulo
Other value operations: Other value operations:
30 len Get table, array or string size 40 len Get table, array or string size
31 type Get type from value at the top of the stack 41 type Get type from value at the top of the stack
b0 cast [type] Cast type to another type b0 cast [type] Cast type to another type
32 ver Return VM version 42 ver Return VM version
External code: External code:
38 cmpl Compile code to assembly 48 cmpl Compile code to assembly
39 asmbl Assemble code to bytecode format 49 asmbl Assemble code to bytecode format
3a load Load bytecode as function (will place function on stack) 4a load Load bytecode as function (will place function on stack)
Control flow: Control flow:
a8 c8 e8 bz [pc] Branch if zero a8 c8 e8 bz [pc] Branch if zero

View File

@@ -70,7 +70,7 @@ ByteArray Assembler::assemble()
case vm::OperandType::Int8: bp.functions.at(function_id).code.append_int8((int8_t) *oper); break; case vm::OperandType::Int8: bp.functions.at(function_id).code.append_int8((int8_t) *oper); break;
case vm::OperandType::Int16: bp.functions.at(function_id).code.append_int16((int16_t) *oper); break; case vm::OperandType::Int16: bp.functions.at(function_id).code.append_int16((int16_t) *oper); break;
case vm::OperandType::Int32: bp.functions.at(function_id).code.append_int32(*oper); break; case vm::OperandType::Int32: bp.functions.at(function_id).code.append_int32(*oper); break;
case vm::OperandType::NoOperand: default: case vm::OperandType::NoOperand: default: break;
} }
if (tt.type != TokenType::Enter) if (tt.type != TokenType::Enter)

View File

@@ -12,7 +12,7 @@ namespace tyche::as {
class Assembler { class Assembler {
public: public:
explicit Assembler(std::string source) : lexer_(std::move(source)) {} explicit Assembler(std::string source) : lexer_(std::move(source) + "\n") {}
[[nodiscard]] ByteArray assemble(); [[nodiscard]] ByteArray assemble();

View File

@@ -134,7 +134,7 @@ std::string ByteArray::hexdump() const
{ {
auto to_hex = [](uint32_t value, size_t n_chars) -> std::string { auto to_hex = [](uint32_t value, size_t n_chars) -> std::string {
char buf[15]; char buf[15];
snprintf(buf, sizeof buf, (std::string("%0") + std::to_string(n_chars) + "X").c_str(), value); snprintf(buf, sizeof buf, "%0*X", (int) n_chars, value);
return { buf }; return { buf };
}; };

View File

@@ -44,6 +44,8 @@ Operation Code::operation(Location const& location) const
.operator_ = bytecode_.get_code_int32(location.function_id, location.pc + 1), .operator_ = bytecode_.get_code_int32(location.function_id, location.pc + 1),
.next_location = { .function_id = location.function_id, .pc = location.pc + 5 }, .next_location = { .function_id = location.function_id, .pc = location.pc + 5 },
}; };
default:
break;
} }
throw std::logic_error("Should not get here"); throw std::logic_error("Should not get here");

View File

@@ -23,6 +23,8 @@ public:
[[nodiscard]] Operation operation(Location const& location) const; [[nodiscard]] Operation operation(Location const& location) const;
[[nodiscard]] bc::Bytecode const& bytecode() const { return bytecode_; }
private: private:
bc::Bytecode bytecode_; bc::Bytecode bytecode_;
}; };

View File

@@ -1,18 +1,114 @@
#include "expr.hh" #include "expr.hh"
#include <cmath>
#include <functional>
#include "vm_exceptions.hh" #include "vm_exceptions.hh"
namespace tyche::vm { namespace tyche::vm {
std::function<Value(Value const&, Value const&)> binary_ops[(size_t) BinaryOperationType::COUNT][(size_t) Type::COUNT][(size_t) Type::COUNT];
static int init_ = []() {
// every combination, except when explicit, return type error
for (size_t i = 0; i < (size_t) BinaryOperationType::COUNT; ++i) {
for (size_t j = 0; j < (size_t) Type::COUNT; ++j) {
for (size_t k = 0; k < (size_t) Type::COUNT; ++k) {
binary_ops[i][j][k] = [&i](Value const& a, Value const& b) -> Value {
throw VMInvalidOperation((BinaryOperationType) i, a.type(), b.type());
};
}
}
}
// every equality/inequality, by default, return inequal
for (size_t j = 0; j < (size_t) Type::COUNT; ++j) {
for (size_t k = 0; k < (size_t) Type::COUNT; ++k) {
binary_ops[(size_t) BinaryOperationType::Equality][j][k] = [](Value const&, Value const&) { return Value::createFalse(); };
binary_ops[(size_t) BinaryOperationType::Inequality][j][k] = [](Value const&, Value const&) { return Value::createTrue(); };
}
}
#define BIN_OP(op, t1, t2) binary_ops[(size_t) BinaryOperationType::op][(size_t) Type::t1][(size_t) Type::t2] = [](Value const& b, Value const& a)
BIN_OP(Sum, Integer, Integer) { return Value::createInteger(a.as_integer() + b.as_integer()); };
BIN_OP(Sum, Integer, Float) { return Value::createFloat((float) a.as_integer() + b.as_float()); };
BIN_OP(Sum, Float, Integer) { return Value::createFloat(a.as_float() + (float) b.as_integer()); };
BIN_OP(Sum, Float, Float) { return Value::createFloat(a.as_float() + b.as_float()); };
BIN_OP(Sum, String, String) { return Value::createString(a.as_string() + b.as_string()); };
BIN_OP(Subtraction, Integer, Integer) { return Value::createInteger(a.as_integer() - b.as_integer()); };
BIN_OP(Subtraction, Integer, Float) { return Value::createFloat((float) a.as_integer() - b.as_float()); };
BIN_OP(Subtraction, Float, Integer) { return Value::createFloat(a.as_float() - (float) b.as_integer()); };
BIN_OP(Subtraction, Float, Float) { return Value::createFloat(a.as_float() - b.as_float()); };
BIN_OP(Multiplication, Integer, Integer) { return Value::createInteger(a.as_integer() * b.as_integer()); };
BIN_OP(Multiplication, Integer, Float) { return Value::createFloat((float) a.as_integer() * b.as_float()); };
BIN_OP(Multiplication, Float, Integer) { return Value::createFloat(a.as_float() * (float) b.as_integer()); };
BIN_OP(Multiplication, Float, Float) { return Value::createFloat(a.as_float() * b.as_float()); };
BIN_OP(Division, Integer, Integer) { return Value::createFloat((float) a.as_integer() / (float) b.as_integer()); };
BIN_OP(Division, Integer, Float) { return Value::createFloat((float) a.as_integer() / b.as_float()); };
BIN_OP(Division, Float, Integer) { return Value::createFloat(a.as_float() / (float) b.as_integer()); };
BIN_OP(Division, Float, Float) { return Value::createFloat(a.as_float() / b.as_float()); };
BIN_OP(IntegerDivision, Integer, Integer) { return Value::createInteger(a.as_integer() / b.as_integer()); };
BIN_OP(IntegerDivision, Integer, Float) { return Value::createInteger(a.as_integer() / (int32_t) b.as_float()); };
BIN_OP(IntegerDivision, Float, Integer) { return Value::createInteger((int32_t) a.as_float() / b.as_integer()); };
BIN_OP(IntegerDivision, Float, Float) { return Value::createInteger((int32_t) a.as_float() / (int32_t) b.as_float()); };
BIN_OP(Equality, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() == b.as_integer()); };
BIN_OP(Equality, Integer, Float) { return Value::createIntegerFromBool(std::abs((float) a.as_integer() - b.as_float()) < FLOAT_EPSILON); };
BIN_OP(Equality, Float, Integer) { return Value::createIntegerFromBool(std::abs(a.as_float() - (float) b.as_integer()) < FLOAT_EPSILON); };
BIN_OP(Equality, Float, Float) { return Value::createIntegerFromBool(std::abs(a.as_float() - b.as_float()) < FLOAT_EPSILON); };
BIN_OP(Equality, String, String) { return Value::createIntegerFromBool(a.as_string() == b.as_string()); };
BIN_OP(Inequality, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() != b.as_integer()); };
BIN_OP(Inequality, Integer, Float) { return Value::createIntegerFromBool(std::abs((float) a.as_integer() - b.as_float()) >= FLOAT_EPSILON); };
BIN_OP(Inequality, Float, Integer) { return Value::createIntegerFromBool(std::abs(a.as_float() - (float) b.as_integer()) >= FLOAT_EPSILON); };
BIN_OP(Inequality, Float, Float) { return Value::createIntegerFromBool(std::abs(a.as_float() - b.as_float()) >= FLOAT_EPSILON); };
BIN_OP(Inequality, String, String) { return Value::createIntegerFromBool(a.as_string() != b.as_string()); };
BIN_OP(LessThan, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() < b.as_integer()); };
BIN_OP(LessThan, Integer, Float) { return Value::createIntegerFromBool((float) a.as_integer() < b.as_float()); };
BIN_OP(LessThan, Float, Integer) { return Value::createIntegerFromBool(a.as_float() < (float) b.as_integer()); };
BIN_OP(LessThan, Float, Float) { return Value::createIntegerFromBool(a.as_float() < b.as_float()); };
BIN_OP(LessThanOrEquals, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() <= b.as_integer()); };
BIN_OP(LessThanOrEquals, Integer, Float) { return Value::createIntegerFromBool((float) a.as_integer() <= b.as_float()); };
BIN_OP(LessThanOrEquals, Float, Integer) { return Value::createIntegerFromBool(a.as_float() <= (float) b.as_integer()); };
BIN_OP(LessThanOrEquals, Float, Float) { return Value::createIntegerFromBool(a.as_float() <= b.as_float()); };
BIN_OP(GreaterThan, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() > b.as_integer()); };
BIN_OP(GreaterThan, Integer, Float) { return Value::createIntegerFromBool((float) a.as_integer() > b.as_float()); };
BIN_OP(GreaterThan, Float, Integer) { return Value::createIntegerFromBool(a.as_float() > (float) b.as_integer()); };
BIN_OP(GreaterThan, Float, Float) { return Value::createIntegerFromBool(a.as_float() > b.as_float()); };
BIN_OP(GreaterThanOrEquals, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() >= b.as_integer()); };
BIN_OP(GreaterThanOrEquals, Integer, Float) { return Value::createIntegerFromBool((float) a.as_integer() >= b.as_float()); };
BIN_OP(GreaterThanOrEquals, Float, Integer) { return Value::createIntegerFromBool(a.as_float() >= (float) b.as_integer()); };
BIN_OP(GreaterThanOrEquals, Float, Float) { return Value::createIntegerFromBool(a.as_float() >= b.as_float()); };
BIN_OP(Power, Integer, Integer) { return Value::createInteger((int32_t) powl(a.as_integer(), b.as_integer())); };
BIN_OP(Power, Integer, Float) { return Value::createFloat(powf((float) a.as_integer(), b.as_float())); };
BIN_OP(Power, Float, Integer) { return Value::createFloat(powf(a.as_float(), (float) b.as_integer())); };
BIN_OP(Power, Float, Float) { return Value::createFloat(powf(a.as_float(), b.as_float())); };
BIN_OP(Modulo, Integer, Integer) { return Value::createInteger(a.as_integer() % b.as_integer()); };
BIN_OP(ShiftLeft, Integer, Integer) { return Value::createInteger(a.as_integer() << b.as_integer()); };
BIN_OP(ShiftRight, Integer, Integer) { return Value::createInteger(a.as_integer() >> b.as_integer()); };
BIN_OP(BitwiseAnd, Integer, Integer) { return Value::createInteger(a.as_integer() & b.as_integer()); };
BIN_OP(BitwiseOr, Integer, Integer) { return Value::createInteger(a.as_integer() | b.as_integer()); };
BIN_OP(BitwiseXor, Integer, Integer) { return Value::createInteger(a.as_integer() ^ b.as_integer()); };
#undef BIN_OP
return 0;
}();
Value binary_operation(Value const& a, Value const& b, BinaryOperationType op) Value binary_operation(Value const& a, Value const& b, BinaryOperationType op)
{ {
// TODO - this is temporary code return binary_ops[(size_t) op][(size_t) b.type()][(size_t) a.type()](a, b);
if (a.type() == Type::Integer && b.type() == Type::Integer && op == BinaryOperationType::Sum) {
return Value::CreateInteger(a.as_integer() + b.as_integer());
} else {
throw VMInvalidOperation(op, a.type(), b.type());
}
} }
} }

View File

@@ -4,7 +4,16 @@
namespace tyche::vm { namespace tyche::vm {
enum class BinaryOperationType { Sum }; enum class BinaryOperationType
{
Sum, Subtraction, Multiplication, Division, IntegerDivision,
Equality, Inequality, LessThan, LessThanOrEquals,
GreaterThan, GreaterThanOrEquals, Power, Modulo,
BitwiseAnd, BitwiseOr, BitwiseXor, ShiftLeft, ShiftRight,
COUNT
};
constexpr float FLOAT_EPSILON = 0.000001f;
Value binary_operation(Value const& a, Value const& b, BinaryOperationType op); Value binary_operation(Value const& a, Value const& b, BinaryOperationType op);

View File

@@ -5,54 +5,59 @@
namespace tyche::vm { namespace tyche::vm {
const std::unordered_map<std::string, vm::Instruction> instruction_names = { const std::unordered_map<std::string, Instruction> instruction_names = {
{ "pushi", vm::Instruction::PushInt8 }, { "pushi", Instruction::PushInt8 },
{ "pushc", vm::Instruction::PushConstant8 }, { "pushc", Instruction::PushConstant8 },
{ "pushz", vm::Instruction::PushZero }, { "pushz", Instruction::PushZero },
{ "pusht", vm::Instruction::PushTrue }, { "pusht", Instruction::PushTrue },
{ "newa", vm::Instruction::NewArray }, { "pushf", Instruction::PushFunction8 },
{ "newt", vm::Instruction::NewTable }, { "newa", Instruction::NewArray },
{ "pop", vm::Instruction::Pop }, { "newt", Instruction::NewTable },
{ "dup", vm::Instruction::Duplicate }, { "pop", Instruction::Pop },
{ "setl", vm::Instruction::SetLocal8 }, { "dup", Instruction::Duplicate },
{ "getl", vm::Instruction::GetLocal8 }, { "setl", Instruction::SetLocal8 },
{ "setg", vm::Instruction::SetGlobal8 }, { "getl", Instruction::GetLocal8 },
{ "getl", vm::Instruction::GetGlobal8 }, { "setg", Instruction::SetGlobal8 },
{ "call8", vm::Instruction::Call8 }, { "getl", Instruction::GetGlobal8 },
{ "ret", vm::Instruction::Return }, { "call8", Instruction::Call8 },
{ "retn", vm::Instruction::ReturnNil }, { "ret", Instruction::Return },
{ "getkv", vm::Instruction::GetKeyValue }, { "retn", Instruction::ReturnNil },
{ "setkv", vm::Instruction::SetKeyValue }, { "getkv", Instruction::GetKeyValue },
{ "geta", vm::Instruction::GetArrayItem }, { "setkv", Instruction::SetKeyValue },
{ "seta", vm::Instruction::SetArrayItem }, { "geta", Instruction::GetArrayItem },
{ "appnd", vm::Instruction::Append }, { "seta", Instruction::SetArrayItem },
{ "next", vm::Instruction::Next }, { "appnd", Instruction::Append },
{ "smt", vm::Instruction::SetMetatable }, { "next", Instruction::Next },
{ "mt", vm::Instruction::GetMetatable }, { "smt", Instruction::SetMetatable },
{ "sum", vm::Instruction::Sum }, { "mt", Instruction::GetMetatable },
{ "sub", vm::Instruction::Subtract }, { "sum", Instruction::Sum },
{ "mul", vm::Instruction::Multiply }, { "sub", Instruction::Subtract },
{ "div", vm::Instruction::Divide }, { "mul", Instruction::Multiply },
{ "idiv", vm::Instruction::DivideInt }, { "div", Instruction::Divide },
{ "eq", vm::Instruction::Equals }, { "idiv", Instruction::DivideInt },
{ "neq", vm::Instruction::NotEquals }, { "eq", Instruction::Equals },
{ "lt", vm::Instruction::LessThan }, { "neq", Instruction::NotEquals },
{ "lte", vm::Instruction::LessThanEq }, { "lt", Instruction::LessThan },
{ "gt", vm::Instruction::GreaterThan }, { "lte", Instruction::LessThanEq },
{ "gte", vm::Instruction::GreaterThanEq }, { "gt", Instruction::GreaterThan },
{ "and", vm::Instruction::And }, { "gte", Instruction::GreaterThanEq },
{ "or", vm::Instruction::Or }, { "and", Instruction::And },
{ "xor", vm::Instruction::Xor }, { "or", Instruction::Or },
{ "len", vm::Instruction::Len }, { "xor", Instruction::Xor },
{ "type", vm::Instruction::Type }, { "pow", Instruction::Power },
{ "cast", vm::Instruction::Cast }, { "shl", Instruction::ShiftLeft },
{ "ver", vm::Instruction::Version }, { "shr", Instruction::ShiftRight },
{ "bz", vm::Instruction::BranchIfZero8 }, { "mod", Instruction::Modulo },
{ "bnz", vm::Instruction::BranchIfNotZero8 }, { "len", Instruction::Len },
{ "jmp", vm::Instruction::Jump8 }, { "type", Instruction::Type },
{ "cmpl", vm::Instruction::Compile }, { "cast", Instruction::Cast },
{ "asmbl", vm::Instruction::Assemble }, { "ver", Instruction::Version },
{ "load", vm::Instruction::Load }, { "bz", Instruction::BranchIfZero8 },
{ "bnz", Instruction::BranchIfNotZero8 },
{ "jmp", Instruction::Jump8 },
{ "cmpl", Instruction::Compile },
{ "asmbl", Instruction::Assemble },
{ "load", Instruction::Load },
}; };
@@ -72,6 +77,11 @@ std::pair<std::string, size_t> debug_instruction(Instruction inst, int oper)
case Instruction::PushConstant32: case Instruction::PushConstant32:
out = "pushc"; out = "pushc";
break; break;
case Instruction::PushFunction8:
case Instruction::PushFunction16:
case Instruction::PushFunction32:
out = "pushf";
break;
case Instruction::PushZero: out = "pushz"; break; case Instruction::PushZero: out = "pushz"; break;
case Instruction::PushTrue: out = "pusht"; break; case Instruction::PushTrue: out = "pusht"; break;
case Instruction::NewArray: out = "newa"; break; case Instruction::NewArray: out = "newa"; break;
@@ -127,6 +137,10 @@ std::pair<std::string, size_t> debug_instruction(Instruction inst, int oper)
case Instruction::And: out = "and"; break; case Instruction::And: out = "and"; break;
case Instruction::Or: out = "or"; break; case Instruction::Or: out = "or"; break;
case Instruction::Xor: out = "xor"; break; case Instruction::Xor: out = "xor"; break;
case Instruction::Power: out = "pow"; break;
case Instruction::ShiftLeft: out = "shl"; break;
case Instruction::ShiftRight: out = "shr"; break;
case Instruction::Modulo: out = "mod"; break;
case Instruction::Len: out = "len"; break; case Instruction::Len: out = "len"; break;
case Instruction::Type: out = "type"; break; case Instruction::Type: out = "type"; break;
case Instruction::Cast: out = "cast"; break; case Instruction::Cast: out = "cast"; break;
@@ -181,6 +195,7 @@ std::pair<std::string, size_t> debug_instruction(bc::Bytecode const& bt, uint32_
case OperandType::Int32: case OperandType::Int32:
return debug_instruction(inst, bt.get_code_int32(function_id, addr + 1)); return debug_instruction(inst, bt.get_code_int32(function_id, addr + 1));
default: default:
break;
} }
return { "???", 1 }; return { "???", 1 };

View File

@@ -21,6 +21,9 @@ enum class Instruction : uint8_t {
PushConstant8 = 0xa1, PushConstant8 = 0xa1,
PushConstant16 = 0xc1, PushConstant16 = 0xc1,
PushConstant32 = 0xe1, PushConstant32 = 0xe1,
PushFunction8 = 0xa2,
PushFunction16 = 0xc2,
PushFunction32 = 0xe2,
PushZero = 0x00, PushZero = 0x00,
PushTrue = 0x01, PushTrue = 0x01,
NewArray = 0x02, NewArray = 0x02,
@@ -74,12 +77,16 @@ enum class Instruction : uint8_t {
And = 0x2b, And = 0x2b,
Or = 0x2c, Or = 0x2c,
Xor = 0x2d, Xor = 0x2d,
Power = 0x2e,
ShiftLeft = 0x2f,
ShiftRight = 0x30,
Modulo = 0x31,
// other value operations // other value operations
Len = 0x30, Len = 0x40,
Type = 0x31, Type = 0x41,
Cast = 0x32, Cast = 0x42,
Version = 0x33, Version = 0x43,
// control flow // control flow
BranchIfZero8 = 0xa8, BranchIfZero8 = 0xa8,
@@ -93,9 +100,9 @@ enum class Instruction : uint8_t {
Jump32 = 0xea, Jump32 = 0xea,
// external code // external code
Compile = 0x38, Compile = 0x48,
Assemble = 0x39, Assemble = 0x49,
Load = 0x3a, Load = 0x4a,
}; };
std::pair<std::string, size_t> debug_instruction(Instruction inst, int oper=0); std::pair<std::string, size_t> debug_instruction(Instruction inst, int oper=0);

View File

@@ -24,6 +24,14 @@ Value Stack::pop()
return v; return v;
} }
Value Stack::peek() const
{
if (stack_.size() <= fps_.top())
throw VMStackUnderflow();
return stack_.back();
}
Value Stack::at(int pos) const Value Stack::at(int pos) const
{ {
try { try {

View File

@@ -14,6 +14,7 @@ public:
void push(Value const& value); void push(Value const& value);
Value pop(); Value pop();
[[nodiscard]] Value peek() const;
[[nodiscard]] Value at(int pos) const; [[nodiscard]] Value at(int pos) const;
[[nodiscard]] size_t size() const; [[nodiscard]] size_t size() const;

View File

@@ -1,8 +1,8 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "../bytecode/bytecodeprototype.hh" #include "../bytecode/bytecodeprototype.hh"
#include "../common/bytearray.hh"
#include "../bytecode/bytecode.hh" #include "../bytecode/bytecode.hh"
#include "../assembler/assembler.hh"
#include "code.hh" #include "code.hh"
#include "stack.hh" #include "stack.hh"
#include "vm.hh" #include "vm.hh"
@@ -11,6 +11,17 @@ using namespace tyche;
using namespace tyche::bc; using namespace tyche::bc;
using namespace tyche::vm; using namespace tyche::vm;
static VM run(std::string oper) {
return VM().load_bytecode(as::Assembler(std::format(R"(
.const
0: 3.14
1: "Hello world"
.func 0
{}
ret
)", oper)).assemble()).call(0);
}
TEST(Code, ImportSingleAndDebug) TEST(Code, ImportSingleAndDebug)
{ {
BytecodePrototype bp; BytecodePrototype bp;
@@ -35,9 +46,9 @@ TEST(Code, ImportSingleAndDebug)
TEST(Stack, PushPullGet) TEST(Stack, PushPullGet)
{ {
Stack stack; Stack stack;
stack.push(Value::CreateInteger(10)); stack.push(Value::createInteger(10));
stack.push(Value::CreateInteger(20)); stack.push(Value::createInteger(20));
stack.push(Value::CreateInteger(30)); stack.push(Value::createInteger(30));
ASSERT_EQ(stack.size(), 3); ASSERT_EQ(stack.size(), 3);
ASSERT_EQ(stack.at(0).as_integer(), 10); ASSERT_EQ(stack.at(0).as_integer(), 10);
@@ -49,12 +60,12 @@ TEST(Stack, PushPullGet)
TEST(Stack, FramePointer) TEST(Stack, FramePointer)
{ {
Stack stack; Stack stack;
stack.push(Value::CreateInteger(10)); stack.push(Value::createInteger(10));
stack.push(Value::CreateInteger(20)); stack.push(Value::createInteger(20));
stack.push_fp(); stack.push_fp();
stack.push(Value::CreateInteger(30)); stack.push(Value::createInteger(30));
stack.push(Value::CreateInteger(40)); stack.push(Value::createInteger(40));
stack.push(Value::CreateInteger(50)); stack.push(Value::createInteger(50));
ASSERT_EQ(stack.size(), 3); ASSERT_EQ(stack.size(), 3);
ASSERT_EQ(stack.at(0).as_integer(), 30); ASSERT_EQ(stack.at(0).as_integer(), 30);
@@ -92,6 +103,171 @@ TEST(VM, BasicCode)
ASSERT_EQ(result, 5); ASSERT_EQ(result, 5);
} }
TEST(VM, StackOperations)
{
ASSERT_EQ(run("pushi 5000").to_integer(-1), 5000);
ASSERT_EQ(run("pushi -5000").to_integer(-1), -5000);
ASSERT_FLOAT_EQ(run("pushi 5000").to_float(-1), 5000.f);
ASSERT_FLOAT_EQ(run("pushc 0").to_float(-1), 3.14f);
ASSERT_EQ(run("pushc 0").to_integer(-1), 3);
ASSERT_EQ(run("pushc 1").to_string(-1), "Hello world");
ASSERT_TRUE(run("pushf 0").is_function(-1));
ASSERT_EQ(run("pushi 2\n pushi 3\n pop").to_integer(-1), 2);
}
TEST(VM, IntegerIntegerOperations)
{
auto test_op = [](int32_t op1, int32_t op2, std::string oper) {
return VM().load_bytecode(as::Assembler(std::format(R"(
.func 0
pushi {}
pushi {}
{}
ret
)", op1, op2, oper)).assemble()).call(0).to_integer(-1);
};
ASSERT_EQ(test_op(2, 3, "sum"), 5);
ASSERT_EQ(test_op(2, 3, "sub"), -1);
ASSERT_EQ(test_op(2, 3, "mul"), 6);
ASSERT_EQ(test_op(20, 3, "idiv"), 6);
ASSERT_EQ(test_op(2, 3, "eq"), 0);
ASSERT_EQ(test_op(2, 3, "neq"), 1);
ASSERT_EQ(test_op(2, 3, "lt"), 1);
ASSERT_EQ(test_op(2, 3, "lte"), 1);
ASSERT_EQ(test_op(3, 3, "lte"), 1);
ASSERT_EQ(test_op(4, 3, "lte"), 0);
ASSERT_EQ(test_op(2, 3, "gt"), 0);
ASSERT_EQ(test_op(2, 3, "gte"), 0);
ASSERT_EQ(test_op(3, 3, "gte"), 1);
ASSERT_EQ(test_op(4, 3, "gte"), 1);
ASSERT_EQ(test_op(2, 3, "and"), 2);
ASSERT_EQ(test_op(2, 3, "or"), 3);
ASSERT_EQ(test_op(2, 3, "xor"), 1);
ASSERT_EQ(test_op(2, 3, "pow"), 8);
ASSERT_EQ(test_op(2, 3, "shl"), 16);
ASSERT_EQ(test_op(30, 2, "shr"), 7);
ASSERT_EQ(test_op(8, 3, "mod"), 2);
ASSERT_FLOAT_EQ(run("pushi 3\n pushi 2\n div").to_float(-1), 1.5f);
}
TEST(VM, IntegerFloatOperations)
{
auto test_op = [](int op1, std::string const& op2, std::string oper) -> VM {
return VM().load_bytecode(as::Assembler(std::format(R"(
.const
0: {}
.func 0
pushi {}
pushc 0
{}
ret
)", op2, op1, oper)).assemble()).call(0);
};
ASSERT_FLOAT_EQ(test_op(2, "3.5", "sum").to_float(-1), 5.5f);
ASSERT_FLOAT_EQ(test_op(2, "3.5", "sub").to_float(-1), -1.5f);
ASSERT_FLOAT_EQ(test_op(2, "3.5", "mul").to_float(-1), 7.f);
ASSERT_FLOAT_EQ(test_op(20, "3.5", "idiv").to_integer(-1), 6);
ASSERT_FLOAT_EQ(test_op(20, "3.5", "div").to_float(-1), 5.7142859);
ASSERT_FLOAT_EQ(test_op(3, "3.5", "eq").to_integer(-1), 0);
ASSERT_FLOAT_EQ(test_op(3, "3.0", "eq").to_integer(-1), 1);
}
TEST(VM, FloatIntegerOperations)
{
auto test_op = [](std::string const& op1, int op2, std::string oper) -> VM {
return VM().load_bytecode(as::Assembler(std::format(R"(
.const
0: {}
.func 0
pushc 0
pushi {}
{}
ret
)", op1, op2, oper)).assemble()).call(0);
};
ASSERT_FLOAT_EQ(test_op("3.5", 2, "sum").to_float(-1), 5.5f);
ASSERT_FLOAT_EQ(test_op("3.5", 2, "sub").to_float(-1), 1.5f);
ASSERT_FLOAT_EQ(test_op("3.5", 2, "mul").to_float(-1), 7.f);
ASSERT_FLOAT_EQ(test_op("3.5", 2, "idiv").to_integer(-1), 1);
ASSERT_FLOAT_EQ(test_op("3.5", 2, "div").to_float(-1), 1.75f);
ASSERT_FLOAT_EQ(test_op("3.5", 3, "eq").to_integer(-1), 0);
ASSERT_FLOAT_EQ(test_op("3.0", 3, "eq").to_integer(-1), 1);
}
TEST(VM, FloatFloatOperations)
{
auto test_op = [](std::string const& op1, std::string const& op2, std::string oper) -> VM {
return VM().load_bytecode(as::Assembler(std::format(R"(
.const
0: {}
1: {}
.func 0
pushc 0
pushc 1
{}
ret
)", op1, op2, oper)).assemble()).call(0);
};
ASSERT_FLOAT_EQ(test_op("3.5", "2.2", "sum").to_float(-1), 5.7f);
ASSERT_FLOAT_EQ(test_op("3.5", "2.2", "sub").to_float(-1), 1.3f);
ASSERT_FLOAT_EQ(test_op("3.5", "2.2", "mul").to_float(-1), 7.7f);
ASSERT_FLOAT_EQ(test_op("3.5", "2.2", "idiv").to_integer(-1), 1);
ASSERT_FLOAT_EQ(test_op("4.5", "2.5", "div").to_float(-1), 1.8f);
ASSERT_FLOAT_EQ(test_op("3.2005", "3.2", "eq").to_integer(-1), 0);
ASSERT_FLOAT_EQ(test_op("3.2", "3.2", "eq").to_integer(-1), 1);
}
TEST(VM, StringString)
{
ASSERT_EQ(run(R"(
.const
0: "Hello"
1: "World"
.func 0
pushc 0
pushc 1
sum
ret
)").to_string(-1), "HelloWorld");
ASSERT_EQ(run(R"(
.const
0: "Hello"
1: "World"
.func 0
pushc 0
pushc 1
eq
ret
)").to_integer(-1), 0);
ASSERT_EQ(run(R"(
.const
0: "Hello"
1: "Hello"
.func 0
pushc 0
pushc 1
eq
ret
)").to_integer(-1), 1);
ASSERT_EQ(run(R"(
.const
0: "Hello"
.func 0
pushc 0
pushi 1
eq
ret
)").to_integer(-1), 0);
}
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);

View File

@@ -4,6 +4,21 @@
namespace tyche::vm { namespace tyche::vm {
std::string type_name(Type type)
{
switch (type) {
case Type::Nil: return "nil";
case Type::Integer: return "integer";
case Type::Float: return "float";
case Type::String: return "string";
case Type::Array: return "array";
case Type::Table: return "table";
case Type::Function: return "function";
case Type::NativePointer: return "native pointer";
case Type::COUNT: default: return "???";
}
}
Type Value::type() const Type Value::type() const
{ {
return std::visit(overloaded { return std::visit(overloaded {

View File

@@ -1,6 +1,7 @@
#ifndef TYCHE_VALUE_HH #ifndef TYCHE_VALUE_HH
#define TYCHE_VALUE_HH #define TYCHE_VALUE_HH
#include <cstdint>
#include <string> #include <string>
#include <variant> #include <variant>
@@ -10,9 +11,10 @@ using FunctionId = uint32_t;
enum class Type : uint8_t enum class Type : uint8_t
{ {
Nil = 0, Integer, Float, String, Array, Table, Function, NativePointer, Nil = 0, Integer, Float, String, Array, Table, Function, NativePointer, COUNT
}; };
std::string type_name(Type type);
class Value { class Value {
struct Function { FunctionId f_id; }; struct Function { FunctionId f_id; };
@@ -20,11 +22,15 @@ class Value {
public: public:
Value() : value_(std::monostate()) {} Value() : value_(std::monostate()) {}
static Value CreateNil() { return Value(std::monostate()); } static Value createNil() { return Value(std::monostate()); }
static Value CreateInteger(int32_t v) { return Value(v); } static Value createInteger(int32_t v) { return Value(v); }
static Value CreateFloat(float f) { return Value(f); } static Value createFloat(float f) { return Value(f); }
static Value CreateString(std::string const& str) { return Value(str); } static Value createString(std::string const& str) { return Value(str); }
static Value CreateFunctionId(FunctionId f_id) { return Value(Function { f_id }); } static Value createFunctionId(FunctionId f_id) { return Value(Function { f_id }); }
static Value createFalse() { return createInteger(0); }
static Value createTrue() { return createInteger(1); }
static Value createIntegerFromBool(bool b) { return createInteger(b ? 1 : 0); }
[[nodiscard]] Type type() const; [[nodiscard]] Type type() const;

View File

@@ -5,13 +5,14 @@
namespace tyche::vm { namespace tyche::vm {
void VM::load_bytecode(ByteArray const& ba) VM& VM::load_bytecode(ByteArray const& ba)
{ {
FunctionId f_id = code_.import_bytecode(ba); FunctionId f_id = code_.import_bytecode(ba);
stack_.push(Value::CreateFunctionId(f_id)); stack_.push(Value::createFunctionId(f_id));
return *this;
} }
void VM::call(size_t n_params) VM& VM::call(size_t n_params)
{ {
// TODO - parameters // TODO - parameters
@@ -24,18 +25,59 @@ void VM::call(size_t n_params)
run_until_return(); run_until_return();
// stack_.pop_fp(); // stack_.pop_fp();
loc_.pop(); loc_.pop();
return *this;
} }
int32_t VM::to_integer(int index) const int32_t VM::to_integer(int index) const
{ {
Value i = stack_.at(index); Value i = stack_.at(index);
assert_type(i, Type::Integer); if (i.type() == Type::Integer)
return i.as_integer(); return i.as_integer();
if (i.type() == Type::Float)
return (int32_t) i.as_float();
throw VMTypeError(Type::Integer, i.type());
} }
void VM::push_integer(int32_t value) float VM::to_float(int index) const
{ {
stack_.push(Value::CreateInteger(value)); Value f = stack_.at(index);
if (f.type() == Type::Float)
return f.as_float();
if (f.type() == Type::Integer)
return (float) f.as_integer();
throw VMTypeError(Type::Float, f.type());
}
std::string VM::to_string(int index) const
{
Value i = stack_.at(index);
assert_type(i, Type::String);
return i.as_string();
}
VM& VM::push_nil()
{
stack_.push(Value::createNil());
return *this;
}
VM& VM::push_integer(int32_t value)
{
stack_.push(Value::createInteger(value));
return *this;
}
VM& VM::push_float(float value)
{
stack_.push(Value::createFloat(value));
return *this;
}
VM& VM::push_string(std::string const& str)
{
stack_.push(Value::createString(str));
return *this;
} }
void VM::run_until_return() void VM::run_until_return()
@@ -48,16 +90,76 @@ void VM::run_until_return()
void VM::step() void VM::step()
{ {
Operation op = code_.operation(loc_.top()); Operation op = code_.operation(loc_.top());
switch (op.instruction) { switch (op.instruction) {
//
// stack management
//
case Instruction::PushInt8: case Instruction::PushInt8:
case Instruction::PushInt16: case Instruction::PushInt16:
case Instruction::PushInt32: case Instruction::PushInt32:
push_integer(op.operator_); push_integer(op.operator_);
break; break;
case Instruction::Sum:
stack_.push(binary_operation(stack_.pop(), stack_.pop(), BinaryOperationType::Sum)); case Instruction::PushConstant8:
case Instruction::PushConstant16:
case Instruction::PushConstant32: {
auto cnst = code_.bytecode().get_constant(op.operator_);
if (auto f = std::get_if<float>(&cnst))
push_float(*f);
else if (auto s = std::get_if<std::string>(&cnst))
push_string(*s);
else
throw std::logic_error("Shouldn't get here");
break; break;
}
case Instruction::PushFunction8:
case Instruction::PushFunction16:
case Instruction::PushFunction32:
stack_.push(Value::createFunctionId(op.operator_));
break;
case Instruction::Pop:
stack_.pop();
break;
case Instruction::Duplicate:
stack_.push(stack_.peek());
break;
//
// logical/arithmetic
//
#define BIN_OP(op) { Value a = stack_.pop(); Value b = stack_.pop(); stack_.push(binary_operation(a, b, BinaryOperationType::op)); }
case Instruction::Sum: BIN_OP(Sum) break;
case Instruction::Subtract: BIN_OP(Subtraction) break;
case Instruction::Multiply: BIN_OP(Multiplication) break;
case Instruction::Divide: BIN_OP(Division) break;
case Instruction::DivideInt: BIN_OP(IntegerDivision) break;
case Instruction::Equals: BIN_OP(Equality) break;
case Instruction::NotEquals: BIN_OP(Inequality) break;
case Instruction::LessThan: BIN_OP(LessThan) break;
case Instruction::LessThanEq: BIN_OP(LessThanOrEquals) break;
case Instruction::GreaterThan: BIN_OP(GreaterThan) break;
case Instruction::GreaterThanEq: BIN_OP(GreaterThanOrEquals) break;
case Instruction::And: BIN_OP(BitwiseAnd) break;
case Instruction::Or: BIN_OP(BitwiseOr) break;
case Instruction::Xor: BIN_OP(BitwiseXor) break;
case Instruction::Power: BIN_OP(Power) break;
case Instruction::ShiftLeft: BIN_OP(ShiftLeft) break;
case Instruction::ShiftRight: BIN_OP(ShiftRight) break;
case Instruction::Modulo: BIN_OP(Modulo) break;
#undef BIN_OP
//
// function operations
//
case Instruction::Return: { case Instruction::Return: {
Value v = stack_.pop(); Value v = stack_.pop();
stack_.pop_fp(); stack_.pop_fp();

View File

@@ -9,15 +9,31 @@ namespace tyche::vm {
class VM { class VM {
public: public:
void load_bytecode(ByteArray const& ba); VM& load_bytecode(ByteArray const& ba);
void call(size_t n_params); VM& call(size_t n_params);
[[nodiscard]] int32_t to_integer(int index) const; [[nodiscard]] bool is_nil(int index) const { return stack_.at(index).type() == Type::Nil; }
[[nodiscard]] bool is_integer(int index) const { return stack_.at(index).type() == Type::Integer; }
[[nodiscard]] bool is_float(int index) const { return stack_.at(index).type() == Type::Float; }
[[nodiscard]] bool is_string(int index) const { return stack_.at(index).type() == Type::String; }
[[nodiscard]] bool is_array(int index) const { return stack_.at(index).type() == Type::Array; }
[[nodiscard]] bool is_table(int index) const { return stack_.at(index).type() == Type::Table; }
[[nodiscard]] bool is_function(int index) const { return stack_.at(index).type() == Type::Function; }
[[nodiscard]] bool is_native_pointer(int index) const { return stack_.at(index).type() == Type::NativePointer; }
void push_integer(int32_t value); [[nodiscard]] size_t stack_sz() const { return stack_.size(); }
[[nodiscard]] std::string debug_stack() const { return stack_.debug(); } VM& push_nil();
VM& push_integer(int32_t value);
VM& push_float(float value);
VM& push_string(std::string const& string);
[[nodiscard]] int32_t to_integer(int index) const;
[[nodiscard]] float to_float(int index) const;
[[nodiscard]] std::string to_string(int index) const;
[[nodiscard]] std::string debug_stack() const { return stack_.debug(); }
private: private:
void run_until_return(); void run_until_return();

View File

@@ -29,7 +29,7 @@ public:
class VMTypeError : public VMRuntimeError class VMTypeError : public VMRuntimeError
{ {
public: public:
explicit VMTypeError(Type expected, Type found) : VMRuntimeError("Type error") {} // TODO - print types explicit VMTypeError(Type expected, Type found) : VMRuntimeError("Type error (expected " + type_name(expected) + ", found " + type_name(found) + ")") {}
}; };
class VMInvalidOpcode : public VMRuntimeError class VMInvalidOpcode : public VMRuntimeError
@@ -41,7 +41,7 @@ public:
class VMInvalidOperation : public VMRuntimeError class VMInvalidOperation : public VMRuntimeError
{ {
public: public:
explicit VMInvalidOperation(BinaryOperationType op, Type type1, Type type2) : VMRuntimeError("Invalid binary operation") {} // TODO - print types explicit VMInvalidOperation(BinaryOperationType op, Type type1, Type type2) : VMRuntimeError("Invalid binary operation for types " + type_name(type1) + " and " + type_name(type2)) {}
}; };
} }