5 Commits

Author SHA1 Message Date
417747f5ee . 2026-04-27 06:00:07 -05:00
88d9ee42e1 . 2026-04-27 05:32:57 -05:00
6fd407b8ea . 2026-04-27 05:30:30 -05:00
95b5405e1c . 2026-04-26 20:22:56 -05:00
77fe7745db . 2026-04-26 20:22:47 -05:00
70 changed files with 132 additions and 8484 deletions

8
.gitignore vendored
View File

@@ -32,11 +32,3 @@
*.out
*.app
cmake-build-*/
build/
tyche
tyche-test
libtyche.a
libtyche.so*
lib/compiler/compiler.lua.h

37
.idea/editor.xml generated
View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BackendCodeEditorSettings">
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefinitionsOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppCodeStyle/CVQualifiersPlacement/@EntryValue" value="AfterType" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_TERNARY/@EntryValue" value="ALIGN_ALL" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/EMPTY_BLOCK_STYLE/@EntryValue" value="TOGETHER_SAME_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ENABLE_SLATE_FORMAT/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_GOTO_LABELS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/MAX_ENUM_MEMBERS_ON_LINE/@EntryValue" value="6" type="long" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_INDENTATION/@EntryValue" value="None" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/REQUIRES_EXPRESSION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue" value="LINE_BREAK" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_LINES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Classes_0020and_0020structs/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;1&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;__interface&quot; /&gt;&lt;type Name=&quot;class&quot; /&gt;&lt;type Name=&quot;struct&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Concepts/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;2&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;concept&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Enum_0020members/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;14&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;scoped enumerator&quot; /&gt;&lt;type Name=&quot;unscoped enumerator&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Enums/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;3&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;enum&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Other_0020constants/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;15&quot;&gt;&lt;Descriptor Static=&quot;True&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;True&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;class field&quot; /&gt;&lt;type Name=&quot;local variable&quot; /&gt;&lt;type Name=&quot;struct field&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AA_BB&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Unions/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;4&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;union&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
</component>
</project>

24
.idea/misc.xml generated
View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakePythonSetting">
<option name="pythonIntegrationState" value="YES" />
</component>
<component name="CMakeWorkspace">
<contentRoot DIR="$PROJECT_DIR$" />
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MakefileSettings">
<option name="linkedExternalProjectsSettings">
<MakefileProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
<option name="version" value="2" />
</MakefileProjectSettings>
</option>
</component>
<component name="MakefileWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

8
.idea/tyche.iml generated
View File

@@ -1,2 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="External" external.linked.project.id="tyche" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="Makefile" type="CPP_MODULE" version="4" />
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -1,127 +0,0 @@
cmake_minimum_required (VERSION 3.24)
project(tyche
VERSION 0.0.1
DESCRIPTION "An embeddable/standalone programming language"
LANGUAGES C CXX ASM)
#
# project options / configuration
#
set(CMAKE_C_STANDARD 17)
set(CMAKE_CXX_STANDARD 23 CACHE STRING "C++ Standard")
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set_property(GLOBAL PROPERTY CXX_EXTENSIONS OFF)
set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
set_property(GLOBAL PROPERTY LINK_WHAT_YOU_USE TRUE)
# warnings / flags
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(warnings -Wall -Wextra -Wformat-nonliteral -Wundef -Wshadow -Wwrite-strings -Wfloat-equal -Wswitch-default -Wmissing-format-attribute -Wswitch-enum -Wmissing-noreturn -Wno-unused-parameter -Wno-unused)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(warnings ${warnings} -Wsuggest-attribute=pure -Wsuggest-attribute=const -Wsuggest-attribute=noreturn -Wsuggest-attribute=malloc -Wsuggest-attribute=format -Wsuggest-attribute=cold)
endif()
endif()
# try to use ccache, if available
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
# ignore warnings in imported files
set_source_files_properties(${IMGUI_SRC} PROPERTIES COMPILE_FLAGS "-w")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-ggdb -O0)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Release")
set(DEF B_PRODUCTION_MODE=ON)
add_compile_options(-Ofast -flto)
endif()
#
# libraries
#
include(FetchContent)
FetchContent_Declare(
googletest
# Specify the commit you depend on and update it regularly.
URL https://github.com/google/googletest/releases/download/v1.17.0/googletest-1.17.0.tar.gz
)
FetchContent_MakeAvailable(googletest)
#
# library
#
add_library(lib${PROJECT_NAME} SHARED
src/common/overloaded.hh
src/common/bytearray.hh
src/common/bytearray.cc
src/bytecode/bytecode.cc
src/bytecode/bytecode.hh
src/bytecode/bytecodeprototype.hh
src/bytecode/constant.hh
src/vm/code.cc
src/vm/code.hh
src/vm/instruction.hh
src/vm/instruction.cc
src/vm/value.cc
src/vm/value.hh
src/vm/stack.cc
src/vm/stack.hh
src/vm/vm_exceptions.hh
src/vm/vm.cc
src/vm/vm.hh
src/vm/expr.cc
src/vm/expr.hh
src/vm/location.hh
src/assembler/lexer.cc
src/assembler/lexer.hh
src/assembler/assembler.cc
src/assembler/assembler.hh
src/assembler/as_exceptions.hh
src/bytecode/bc_exceptions.hh
)
target_compile_options(lib${PROJECT_NAME} PRIVATE ${warnings})
#
# tests
#
enable_testing()
add_executable(${PROJECT_NAME}-bytecode-test src/bytecode/tests.cc)
target_link_libraries(${PROJECT_NAME}-bytecode-test lib${PROJECT_NAME} gtest_main)
add_test(NAME tyche_bytecode_test COMMAND ${PROJECT_NAME}-bytecode-test)
add_executable(${PROJECT_NAME}-vm-test src/vm/tests.cc)
target_link_libraries(${PROJECT_NAME}-vm-test lib${PROJECT_NAME} gtest_main)
add_test(NAME tyche_vm_test COMMAND ${PROJECT_NAME}-vm-test)
add_executable(${PROJECT_NAME}-as-test src/assembler/tests.cc)
target_link_libraries(${PROJECT_NAME}-as-test lib${PROJECT_NAME} gtest_main)
add_test(NAME tyche_as_test COMMAND ${PROJECT_NAME}-as-test)
#
# check for leaks
#
add_custom_target(leaks-vm-test)
add_custom_command(TARGET leaks-vm-test
POST_BUILD
COMMENT "Check for leaks using valgrind."
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMAND valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --suppressions=${CMAKE_SOURCE_DIR}/valgrind.supp ./${PROJECT_NAME}-vm-test
)
#
# installation
#
install(TARGETS lib${CMAKE_PROJECT_NAME} RUNTIME DESTINATION lib)

View File

@@ -1,18 +0,0 @@
#ifndef TYCHE_VM_EXCEPTIONS_HH
#define TYCHE_VM_EXCEPTIONS_HH
#include <stdexcept>
#include <string>
namespace tyche::as {
class AssemblyError : public std::runtime_error
{
public:
explicit AssemblyError(std::string const& str, size_t line, size_t column)
: std::runtime_error((str + " at: line " + std::to_string(line) + ", column: " + std::to_string(column)).c_str()) {}
};
}
#endif //TYCHE_VM_EXCEPTIONS_HH

View File

@@ -1,98 +0,0 @@
#include "assembler.hh"
#include <unordered_map>
#include "as_exceptions.hh"
#include "../bytecode/bytecode.hh"
#include "../vm/instruction.hh"
using namespace std::string_literals;
namespace tyche::as {
ByteArray Assembler::assemble()
{
bc::BytecodePrototype bp;
lexer_.reset();
enum class Section { Const, Function } section;
uint32_t function_id = 0;
for (;;) {
Token t = lexer_.ingest();
if (t.type == TokenType::Enter)
continue;
if (t.type == TokenType::Directive) {
if (std::get<std::string>(t.token) == ".const") {
section = Section::Const;
expect_token(TokenType::Enter);
} else if (std::get<std::string>(t.token) == ".func") {
section = Section::Function;
function_id = std::get<int>(expect_token(TokenType::Integer));
if (function_id >= bp.functions.size())
bp.functions.resize(function_id + 1, { 0, 0 });
expect_token(TokenType::Enter);
} else {
throw AssemblyError("Invalid directive " + std::get<std::string>(t.token), t.line, t.column);
}
} else if (section == Section::Const && t.type == TokenType::Integer) {
int index = std::get<int>(t.token);
if ((size_t) index >= bp.constants.size())
bp.constants.resize(index + 1);
expect_token(TokenType::Colon);
Token tt = lexer_.ingest();
if (tt.type == TokenType::Float)
bp.constants[index] = std::get<float>(tt.token);
else if (tt.type == TokenType::String)
bp.constants[index] = std::get<std::string>(tt.token);
else
throw AssemblyError("Expected float or string as constant", tt.line, tt.column);
expect_token(TokenType::Enter);
} else if (section == Section::Function && t.type == TokenType::Instruction) {
std::string instruction = std::get<std::string>(t.token);
std::optional<int> oper = {};
Token tt = lexer_.ingest();
if (tt.type == TokenType::Integer) {
oper = std::get<int>(tt.token);
tt = lexer_.ingest();
}
auto oinst = vm::translate_instruction(instruction, oper);
if (!oinst)
throw AssemblyError("Invalid or misused instruction '" + instruction + "'", tt.line, tt.column);
bp.functions.at(function_id).code.append_byte((uint8_t) *oinst);
switch (vm::instruction_operand_type(*oinst)) {
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::Int32: bp.functions.at(function_id).code.append_int32(*oper); break;
case vm::OperandType::NoOperand: default: break;
}
if (tt.type != TokenType::Enter)
throw AssemblyError("Expected enter", tt.line, tt.column);
} else if (t.type == TokenType::EOF_) {
break;
} else if (t.type != TokenType::Enter) {
throw AssemblyError("Unexpected token of type " + token_type_name(t.type) + ")", t.line, t.column);
}
}
return bc::Bytecode::generate(bp);
}
TokenValue Assembler::expect_token(TokenType type)
{
Token t = lexer_.ingest();
if (t.type != type)
throw AssemblyError("Expected " + token_type_name(t.type), t.line, t.column);
return t.token;
}
} // tyche

View File

@@ -1,27 +0,0 @@
#ifndef TYCHE_ASSEMBLER_HH
#define TYCHE_ASSEMBLER_HH
#include <optional>
#include <string>
#include "lexer.hh"
#include "../common/bytearray.hh"
#include "../bytecode/bytecodeprototype.hh"
namespace tyche::as {
class Assembler {
public:
explicit Assembler(std::string source) : lexer_(std::move(source) + "\n") {}
[[nodiscard]] ByteArray assemble();
private:
Lexer lexer_;
TokenValue expect_token(TokenType type);
};
} // tyche
#endif //TYCHE_ASSEMBLER_HH

View File

@@ -1,122 +0,0 @@
#include "lexer.hh"
#include "as_exceptions.hh"
namespace tyche::as {
std::string token_type_name(TokenType type)
{
switch (type) {
case TokenType::BOF: return "BOF";
case TokenType::Directive: return "directive";
case TokenType::Instruction: return "instruction";
case TokenType::Integer: return "integer";
case TokenType::Float: return "float";
case TokenType::String: return "string";
case TokenType::Enter: return "enter";
case TokenType::Colon: return "colon";
case TokenType::EOF_: return "EOF";
default: return "???";
}
}
void Lexer::reset()
{
pos_ = 0;
ingest_next_token();
}
Token Lexer::peek() const
{
return current_token_;
}
Token Lexer::ingest()
{
Token t = current_token_;
ingest_next_token();
return t;
}
void Lexer::ingest_next_token()
{
size_t current_line_pos = 1;
size_t current_line = 1;
if (pos_ >= source_.size()) {
current_token_ = { TokenType::EOF_ };
return;
}
char c = source_.at(pos_);
TokenType type {};
std::string stoken;
TokenValue value = std::monostate();
if (c == '.') {
type = TokenType::Directive;
stoken += '.';
while (c = source_.at(++pos_), isalpha(c) || c == '_')
stoken += c;
value = stoken;
} else if (c == '"') {
type = TokenType::String;
++pos_;
while (true) {
if (source_.at(pos_) == '\\') { // TODO - improve this for special characters
++pos_;
} else if (source_.at(pos_) == '"') {
++pos_;
break;
} else if (pos_ >= source_.size()) {
throw AssemblyError("Unterminated string", current_line, pos_ - current_line_pos);
}
stoken += source_.at(pos_++);
}
value = stoken;
} else if (isdigit(c) || c == '-') {
type = TokenType::Integer;
stoken += c;
while (c = source_.at(++pos_), isdigit(c) || c == '.') {
stoken += c;
if (c == '.') {
if (type == TokenType::Integer)
type = TokenType::Float;
else
throw AssemblyError("Double point in floating point number", current_line, pos_ - current_line_pos);
}
}
if (type == TokenType::Integer)
value = std::stoi(stoken);
else
value = std::stof(stoken);
} else if (isalpha(c)) {
type = TokenType::Instruction;
stoken += c;
while (c = source_.at(++pos_), isalpha(c))
stoken += c;
value = stoken;
} else if (c == ':') {
type = TokenType::Colon;
++pos_;
} else if (c == '\n' || c == ';') {
while (pos_ < source_.size() && source_.at(pos_) != '\n')
++pos_;
type = TokenType::Enter;
value = "\n";
++pos_;
++current_line;
current_line_pos = pos_;
} else {
throw AssemblyError(std::string("Unexpected character '") + c + "' (ascii: " + std::to_string((int) c) + ")", current_line, pos_ - current_line_pos);
}
// skip ignored tokens
while (pos_ < source_.size() && (source_.at(pos_) == ' ' || source_.at(pos_) == '\t' || source_.at(pos_) == '\r'))
++pos_;
current_token_ = { .type = type, .token = value, .line = current_line, .column = pos_ - current_line_pos };
}
} // tyche

View File

@@ -1,45 +0,0 @@
#ifndef TYCHE_LEXER_HH
#define TYCHE_LEXER_HH
#include <string>
#include <utility>
#include <variant>
namespace tyche::as {
enum class TokenType {
BOF, Directive, Instruction, Integer, Float, String, Enter, Colon, EOF_
};
using TokenValue = std::variant<std::monostate, int, float, std::string>;
struct Token {
TokenType type;
TokenValue token = std::monostate();
size_t line = 0;
size_t column = 0;
friend bool operator==(Token const& lhs, Token const& rhs) { return std::tie(lhs.type, lhs.token) == std::tie(rhs.type, rhs.token); }
};
std::string token_type_name(TokenType type);
class Lexer {
public:
explicit Lexer(std::string source) : source_(std::move(source)) { reset(); }
void reset();
[[nodiscard]] Token peek() const;
[[nodiscard]] Token ingest();
private:
const std::string source_;
size_t pos_ = 0;
Token current_token_ { TokenType::BOF };
void ingest_next_token();
};
} // tyche
#endif //TYCHE_LEXER_HH

View File

@@ -1,76 +0,0 @@
#include "assembler.hh"
#include "gtest/gtest.h"
#include "../bytecode/bytecodeprototype.hh"
#include "../bytecode/bytecode.hh"
#include "../vm/instruction.hh"
using namespace tyche;
using namespace tyche::as;
using namespace tyche::bc;
using namespace tyche::vm;
TEST(Lexer, Lexer)
{
Token t;
Lexer lexer(".dir push 382 -12 3.14 -12.8 \"Hello\" \"Hel\\\"lo\"\n");
ASSERT_EQ(lexer.ingest(), (Token { TokenType::Directive, ".dir" }));
ASSERT_EQ(lexer.ingest(), (Token { TokenType::Instruction, "push" }));
t = lexer.ingest(); ASSERT_EQ(t.type, TokenType::Integer); ASSERT_EQ(std::get<int>(t.token), 382);
t = lexer.ingest(); ASSERT_EQ(t.type, TokenType::Integer); ASSERT_EQ(std::get<int>(t.token), -12);
t = lexer.ingest(); ASSERT_EQ(t.type, TokenType::Float); ASSERT_FLOAT_EQ(std::get<float>(t.token), 3.14f);
t = lexer.ingest(); ASSERT_EQ(t.type, TokenType::Float); ASSERT_FLOAT_EQ(std::get<float>(t.token), -12.8f);
ASSERT_EQ(lexer.ingest(), (Token { TokenType::String, "Hello" }));
ASSERT_EQ(lexer.ingest(), (Token { TokenType::String, "Hel\"lo" }));
ASSERT_EQ(lexer.ingest(), (Token { TokenType::Enter, "\n" }));
ASSERT_EQ(lexer.ingest(), (Token { TokenType::EOF_ }));
ASSERT_EQ(lexer.ingest(), (Token { TokenType::EOF_ }));
ASSERT_EQ(lexer.ingest(), (Token { TokenType::EOF_ }));
lexer.reset();
ASSERT_EQ(lexer.ingest(), (Token { TokenType::Directive, ".dir" }));
}
TEST(Assember, Assembler)
{
BytecodePrototype bp;
bp.constants.emplace_back(3.14f);
bp.constants.emplace_back("Hello world");
bp.functions.emplace_back(0, 0);
bp.functions.at(0).code.append_byte((uint8_t) Instruction::PushInt8);
bp.functions.at(0).code.append_int8(2);
bp.functions.at(0).code.append_byte((uint8_t) Instruction::PushInt8);
bp.functions.at(0).code.append_int8(3);
bp.functions.at(0).code.append_byte((uint8_t) Instruction::Sum);
bp.functions.at(0).code.append_byte((uint8_t) Instruction::Return);
bp.functions.emplace_back(0, 0);
bp.functions.at(1).code.append_byte((uint8_t) Instruction::PushInt16);
bp.functions.at(1).code.append_int16(5000);
bp.functions.at(1).code.append_byte((uint8_t) Instruction::Return);
ByteArray expected = Bytecode::generate(bp);
std::string src = R"(
.const
0: 3.14
1: "Hello world"
.func 0
pushi 2 ; this is a comment
pushi 3
sum
ret
.func 1
pushi 5000
ret
)";
ByteArray actual = Assembler(src).assemble();
ASSERT_EQ(expected, actual);
}
int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -1,15 +0,0 @@
#ifndef TYCHE_BC_EXCEPTIONS_HH
#define TYCHE_BC_EXCEPTIONS_HH
#include <stdexcept>
namespace tyche::bc {
class BytecodeParsingError : public std::runtime_error {
public:
explicit BytecodeParsingError(std::string const& str) : std::runtime_error(str.c_str()) {}
};
}
#endif //TYCHE_BC_EXCEPTIONS_HH

View File

@@ -1,166 +0,0 @@
#include "bytecode.hh"
#include "bc_exceptions.hh"
#include "../common/overloaded.hh"
namespace tyche::bc {
Bytecode::Bytecode(ByteArray ba)
: byte_array_(std::move(ba))
{
// check file size
if (byte_array_.size() < (TOC_START + TOC_SZ))
throw BytecodeParsingError("Invalid bytecode format (file too short)");
// check magic number and version
if (byte_array_.get_uint32(0) != MAGIC_NUMBER)
throw BytecodeParsingError("Invalid bytecode format (magic number not matching)");
if (byte_array_.get_uint32(4) != BYTECODE_VERSION)
throw BytecodeParsingError("Unexpected bytecode format version");
// load cache
cache_.constants_idx_addr = byte_array_.get_uint32(TOC_START);
cache_.n_constants = byte_array_.get_uint16(TOC_START + 4);
cache_.functions_idx_addr = byte_array_.get_uint32(TOC_START + (1 * TOC_RECORD_SZ));
cache_.n_functions = byte_array_.get_uint16(TOC_START + (1 * TOC_RECORD_SZ) + 4);
cache_.constants_start_addr = byte_array_.get_uint32(TOC_START + (2 * TOC_RECORD_SZ));
uint32_t code_start = byte_array_.get_uint32(TOC_START + (3 * TOC_RECORD_SZ));
for (uint32_t i = 0; i < cache_.n_functions; ++i) {
cache_.function_addr.emplace_back(code_start + byte_array_.get_uint32(cache_.functions_idx_addr + (i * FUNCTION_RECORD_SZ)));
cache_.function_sz.emplace_back(byte_array_.get_uint32(cache_.functions_idx_addr + (i * FUNCTION_RECORD_SZ) + 8));
}
}
uint32_t Bytecode::n_constants() const
{
return cache_.n_constants;
}
uint32_t Bytecode::n_functions() const
{
return cache_.n_functions;
}
ConstantValue Bytecode::get_constant(uint32_t idx) const
{
uint32_t constant_idx = byte_array_.get_uint32(cache_.constants_idx_addr + (idx * CONST_RECORD_SZ));
switch ((ConstantType) byte_array_.get_byte(cache_.constants_start_addr + constant_idx)) {
case CONST_TYPE_FLOAT:
return byte_array_.get_float(cache_.constants_start_addr + constant_idx + 1);
case CONST_TYPE_STRING:
return byte_array_.get_string(cache_.constants_start_addr + constant_idx + 1).first;
default:
throw BytecodeParsingError("Invalid bytecode format (invalid constant type)");
}
}
Bytecode::FunctionDef Bytecode::get_function_def(uint32_t function_id) const
{
uint32_t idx = cache_.functions_idx_addr + (function_id * FUNCTION_RECORD_SZ);
return {
.n_params = byte_array_.get_uint16(idx + 4),
.locals = byte_array_.get_uint16(idx + 6),
};
}
uint32_t Bytecode::get_function_sz(uint32_t function_id) const
{
return cache_.function_sz.at(function_id);
}
uint8_t Bytecode::get_code_byte(uint32_t function_id, uint32_t idx) const
{
return byte_array_.get_byte(cache_.function_addr.at(function_id) + idx);
}
int8_t Bytecode::get_code_int8(uint32_t function_id, uint32_t idx) const
{
return byte_array_.get_int8(cache_.function_addr.at(function_id) + idx);
}
int16_t Bytecode::get_code_int16(uint32_t function_id, uint32_t idx) const
{
return byte_array_.get_int16(cache_.function_addr.at(function_id) + idx);
}
int32_t Bytecode::get_code_int32(uint32_t function_id, uint32_t idx) const
{
return byte_array_.get_int32(cache_.function_addr.at(function_id) + idx);
}
ByteArray Bytecode::generate(BytecodePrototype const& bp)
{
// header section
ByteArray header;
header.set_uint32(0, MAGIC_NUMBER);
header.set_byte(4, BYTECODE_VERSION);
// constants
ByteArray constant_indexes;
ByteArray raw_constants;
uint32_t idx = 0;
for (auto const& constant: bp.constants) {
constant_indexes.append_uint32(idx);
std::visit(overloaded {
[&](float f) {
raw_constants.append_byte(CONST_TYPE_FLOAT);
raw_constants.append_float(f);
},
[&](std::string const& s) {
raw_constants.append_byte(CONST_TYPE_STRING);
raw_constants.append_string(s);
},
}, constant);
idx = raw_constants.size();
}
// functions
ByteArray functions_indexes;
ByteArray raw_code;
uint32_t idx_idx = 0, code_idx = 0;
for (auto const& f: bp.functions) {
functions_indexes.set_uint32(idx_idx, code_idx);
functions_indexes.set_uint16(idx_idx + 4, f.n_pars);
functions_indexes.set_uint16(idx_idx + 6, f.n_locals);
functions_indexes.set_uint32(idx_idx + 8, f.code.size());
raw_code.append_bytearray(f.code);
code_idx = raw_code.size();
idx_idx += FUNCTION_RECORD_SZ;
}
// table of contents
uint32_t function_idx_start = CONST_IDX_START + constant_indexes.size();
uint32_t raw_constant_start = function_idx_start + functions_indexes.size();
uint32_t raw_code_start = raw_constant_start + raw_constants.size();
ByteArray toc;
if (!bp.constants.empty()) {
toc.set_uint32(SEC_CONST_IDX * TOC_RECORD_SZ, CONST_IDX_START);
toc.set_uint32(SEC_CONST_IDX * TOC_RECORD_SZ + 4, constant_indexes.size() / CONST_RECORD_SZ);
toc.set_uint32(SEC_CONST_DATA * TOC_RECORD_SZ, raw_constant_start);
toc.set_uint32(SEC_CONST_DATA * TOC_RECORD_SZ + 4, raw_constants.size());
}
if (!bp.functions.empty()) {
toc.set_uint32(SEC_FUNC_IDX * TOC_RECORD_SZ, function_idx_start);
toc.set_uint32(SEC_FUNC_IDX * TOC_RECORD_SZ + 4, functions_indexes.size() / FUNCTION_RECORD_SZ);
toc.set_uint32(SEC_CODE * TOC_RECORD_SZ, raw_code_start);
toc.set_uint32(SEC_CODE * TOC_RECORD_SZ + 4, raw_code.size());
}
//
// assemble bytecode
//
ByteArray ba;
ba.set_bytearray(0, header);
ba.set_bytearray(TOC_START, toc);
ba.set_bytearray(CONST_IDX_START, constant_indexes);
ba.set_bytearray(function_idx_start, functions_indexes);
ba.set_bytearray(raw_constant_start, raw_constants);
ba.set_bytearray(raw_code_start, raw_code);
return ba;
}
}

View File

@@ -1,62 +0,0 @@
#ifndef TYCHE_BYTECODE_HH
#define TYCHE_BYTECODE_HH
#include "../common/bytearray.hh"
#include "bytecodeprototype.hh"
namespace tyche::bc {
class Bytecode {
public:
Bytecode() = default;
explicit Bytecode(ByteArray ba);
[[nodiscard]] uint32_t n_constants() const;
[[nodiscard]] uint32_t n_functions() const;
[[nodiscard]] ConstantValue get_constant(uint32_t idx) const;
struct FunctionDef { uint16_t n_params, locals; };
[[nodiscard]] FunctionDef get_function_def(uint32_t function_id) const;
[[nodiscard]] uint32_t get_function_sz(uint32_t function_id) const;
[[nodiscard]] uint8_t get_code_byte(uint32_t function_id, uint32_t idx) const;
[[nodiscard]] int8_t get_code_int8(uint32_t function_id, uint32_t idx) const;
[[nodiscard]] int16_t get_code_int16(uint32_t function_id, uint32_t idx) const;
[[nodiscard]] int32_t get_code_int32(uint32_t function_id, uint32_t idx) const;
// TODO - debugging info
[[nodiscard]] static ByteArray generate(BytecodePrototype const& bp);
private:
ByteArray byte_array_; // the actual data
static constexpr uint8_t BYTECODE_VERSION = 1;
static constexpr uint32_t MAGIC_NUMBER = 0x74b3c138;
static constexpr uint32_t TOC_START = 16,
TOC_N_RECORDS = 8,
TOC_RECORD_SZ = 8,
TOC_SZ = TOC_N_RECORDS * TOC_RECORD_SZ;
static constexpr uint32_t CONST_IDX_START = TOC_START + TOC_SZ,
CONST_RECORD_SZ = 4;
static constexpr uint32_t FUNCTION_RECORD_SZ = 12;
enum Sections { SEC_CONST_IDX = 0, SEC_FUNC_IDX = 1, SEC_CONST_DATA = 2, SEC_CODE = 3 };
// caching for faster reading of data
struct Cache {
uint32_t constants_idx_addr;
uint16_t n_constants;
uint32_t constants_start_addr;
uint32_t functions_idx_addr;
uint32_t n_functions;
std::vector<uint32_t> function_addr;
std::vector<uint32_t> function_sz;
};
Cache cache_ {};
};
}
#endif //TYCHE_BYTECODE_HH

View File

@@ -1,30 +0,0 @@
#ifndef TYCHE_BYTECODEPROTOTYPE_HH
#define TYCHE_BYTECODEPROTOTYPE_HH
#include <cstdint>
#include <string>
#include <variant>
#include <vector>
#include "constant.hh"
#include "../common/bytearray.hh"
namespace tyche::bc {
struct BytecodePrototype {
struct Function {
uint16_t n_pars;
uint16_t n_locals;
ByteArray code {};
Function(uint16_t n_pars_, uint16_t n_locals_) : n_pars(n_pars_), n_locals(n_locals_), code(ByteArray {}) {}
};
std::vector<ConstantValue> constants {};
std::vector<Function> functions {};
// TODO - debugging info
};
}
#endif //TYCHE_BYTECODEPROTOTYPE_HH

View File

@@ -1,15 +0,0 @@
#ifndef TYCHE_CONSTANT_HH
#define TYCHE_CONSTANT_HH
#include <string>
#include <variant>
namespace tyche::bc {
using ConstantValue = std::variant<float, std::string>;
enum ConstantType : uint8_t { CONST_TYPE_FLOAT = 1, CONST_TYPE_STRING = 2 };
}
#endif //TYCHE_CONSTANT_HH

View File

@@ -1,167 +0,0 @@
#include "gtest/gtest.h"
#include <cstring>
#include <functional>
#include "../common/bytearray.hh"
#include "bytecodeprototype.hh"
#include "bytecode.hh"
using namespace tyche;
using namespace tyche::bc;
TEST(ByteArray, ByteArray)
{
auto test = [](std::function<void(ByteArray&)> const& f, std::vector<uint8_t> const& expected) {
ByteArray ba;
f(ba);
ASSERT_EQ(ba.data().size(), expected.size());
ASSERT_EQ(std::memcmp(ba.data().data(), expected.data(), ba.data().size()), 0);
};
#define TESTX(a, ...) test([](ByteArray& ba) { a; }, std::vector<uint8_t>({ __VA_ARGS__ }));
TESTX(ba.set_byte(1, 0xab), 0x00, 0xab)
ByteArray ba;
ba.set_byte(1, 0xab); ASSERT_EQ(ba.get_byte(1), 0xab);
ba.set_int8(1, 12); ASSERT_EQ(ba.get_int8(1), 12);
ba.set_int8(1, -12); ASSERT_EQ(ba.get_int8(1), -12);
ba.set_int16(1, 5000); ASSERT_EQ(ba.get_int16(1), 5000);
ba.set_int32(1, 5000300); ASSERT_EQ(ba.get_int32(1), 5000300);
ba.set_int32(1, -5000300); ASSERT_EQ(ba.get_int32(1), -5000300);
ba.set_float(1, 3.14); ASSERT_FLOAT_EQ(ba.get_float(1), 3.14);
ba.set_float(1, -3.14); ASSERT_FLOAT_EQ(ba.get_float(1), -3.14);
ba.set_float(1, -5000300.1324); ASSERT_FLOAT_EQ(ba.get_float(1), -5000300.1324);
ba.set_string(1, "Hello world!"); ASSERT_EQ(ba.get_string(1), std::make_pair("Hello world!", 13));
#undef TESTX
}
TEST(Bytecode, Constants)
{
BytecodePrototype bp;
bp.constants.emplace_back(42.3f);
bp.constants.emplace_back("HELLO");
std::vector<uint8_t> expected = {
// header
0x38, 0xc1, 0xb3, 0x74, // magic
0x01, 0x00, 0x00, 0x00, // version
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// index
0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // constant index
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // function undex
0x58, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, // raw constants
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // raw code
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// constant indexes
0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00,
// constant values
CONST_TYPE_FLOAT, 0x33, 0x33, 0x29, 0x42, // float: 42.3f
CONST_TYPE_STRING, 'H', 'E', 'L', 'L', 'O', 0x00
};
ByteArray ba = Bytecode::generate(bp);
ASSERT_EQ(ba.data(), expected);
}
TEST(Bytecode, Code)
{
BytecodePrototype bp;
auto& f = bp.functions.emplace_back(0, 0);
f.code.append_byte(0x68);
f.code.append_int8(42);
auto& f2 = bp.functions.emplace_back(2, 1);
f2.code.append_byte(0x42);
std::vector<uint8_t> expected = {
// header
0x38, 0xc1, 0xb3, 0x74, // magic
0x01, 0x00, 0x00, 0x00, // version
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// index
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // constant index
0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // variable index
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // raw constants
0x68, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // raw code
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// function definitions
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
// code
0x68, 42, 0x42,
};
ByteArray ba = Bytecode::generate(bp);
ASSERT_EQ(ba.data(), expected);
}
TEST(Bytecode, Parsing)
{
// write bytecode
BytecodePrototype bp;
bp.constants.emplace_back(3.14f);
bp.constants.emplace_back("HELLO");
auto& f = bp.functions.emplace_back(0, 0);
f.code.append_byte(0x68);
f.code.append_int8(42);
auto& ff = bp.functions.emplace_back(2, 1);
ff.code.append_byte(0x42);
ByteArray ba = Bytecode::generate(bp);
// print(ba.data());
// read bytecode
Bytecode bc(std::move(ba));
ASSERT_EQ(bc.n_constants(), 2);
ASSERT_EQ(bc.n_functions(), 2);
ASSERT_EQ(bc.get_function_sz(0), 2);
ASSERT_EQ(bc.get_function_sz(1), 1);
ASSERT_FLOAT_EQ(std::get<float>(bc.get_constant(0)), 3.14f);
ASSERT_EQ(std::get<std::string>(bc.get_constant(1)), "HELLO");
Bytecode::FunctionDef f1 = bc.get_function_def(0);
ASSERT_EQ(f1.n_params, 0);
ASSERT_EQ(f1.locals, 0);
Bytecode::FunctionDef f2 = bc.get_function_def(1);
ASSERT_EQ(f2.n_params, 2);
ASSERT_EQ(f2.locals, 1);
ASSERT_EQ(bc.get_code_byte(0, 0), 0x68);
ASSERT_EQ(bc.get_code_int8(0, 1), 42);
ASSERT_EQ(bc.get_code_byte(1, 0), 0x42);
}
int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -1,152 +0,0 @@
#include "bytearray.hh"
#include <cstring>
#include <cstdio>
namespace tyche {
void ByteArray::set_byte(uint32_t addr, uint8_t byte)
{
if (data_.size() < (addr + 1))
data_.resize(addr + 1, 0);
data_.at(addr) = byte;
}
void ByteArray::set_int8(uint32_t addr, int8_t value)
{
set_byte(addr, (uint8_t) value);
}
void ByteArray::set_int16(uint32_t addr, int16_t value)
{
set_byte(addr, (uint8_t) (value));
set_byte(addr+1, (uint8_t) (value >> 8));
}
void ByteArray::set_int32(uint32_t addr, int32_t value)
{
set_byte(addr, (uint8_t) (value));
set_byte(addr+1, (uint8_t) (value >> 8));
set_byte(addr+2, (uint8_t) (value >> 16));
set_byte(addr+3, (uint8_t) (value >> 24));
}
void ByteArray::set_uint16(uint32_t addr, uint16_t value)
{
set_byte(addr, (uint8_t) (value));
set_byte(addr+1, (uint8_t) (value >> 8));
}
void ByteArray::set_uint32(uint32_t addr, uint32_t value)
{
set_byte(addr, (uint8_t) (value));
set_byte(addr+1, (uint8_t) (value >> 8));
set_byte(addr+2, (uint8_t) (value >> 16));
set_byte(addr+3, (uint8_t) (value >> 24));
}
void ByteArray::set_float(uint32_t addr, float value)
{
uint32_t bits;
std::memcpy(&bits, &value, 4);
set_byte(addr, (uint8_t) (bits));
set_byte(addr+1, (uint8_t) (bits >> 8));
set_byte(addr+2, (uint8_t) (bits >> 16));
set_byte(addr+3, (uint8_t) (bits >> 24));
}
void ByteArray::set_string(uint32_t addr, std::string const& str)
{
for (uint8_t c: str)
set_byte(addr++, c);
set_byte(addr, 0);
}
void ByteArray::set_bytearray(uint32_t addr, ByteArray const& bytearray)
{
for (uint8_t byte: bytearray.data())
set_byte(addr++, byte);
}
uint8_t ByteArray::get_byte(uint32_t addr) const
{
return data_.at(addr);
}
uint16_t ByteArray::get_uint16(uint32_t addr) const
{
return (uint32_t) get_byte(addr)
| (uint32_t) get_byte(addr+1) << 8;
}
uint32_t ByteArray::get_uint32(uint32_t addr) const
{
return (uint32_t) get_byte(addr)
| (uint32_t) get_byte(addr+1) << 8
| (uint32_t) get_byte(addr+2) << 16
| (uint32_t) get_byte(addr+3) << 24;
}
int8_t ByteArray::get_int8(uint32_t addr) const
{
return std::bit_cast<int8_t>(get_byte(addr));
}
int16_t ByteArray::get_int16(uint32_t addr) const
{
return (uint16_t) get_byte(addr)
| (uint16_t) get_byte(addr+1) << 8;
}
int32_t ByteArray::get_int32(uint32_t addr) const
{
return std::bit_cast<int32_t>((uint32_t) get_byte(addr)
| (uint32_t) get_byte(addr+1) << 8
| (uint32_t) get_byte(addr+2) << 16
| (uint32_t) get_byte(addr+3) << 24);
}
float ByteArray::get_float(uint32_t addr) const
{
uint32_t bits = (uint32_t) get_byte(addr)
| (uint32_t) get_byte(addr+1) << 8
| (uint32_t) get_byte(addr+2) << 16
| (uint32_t) get_byte(addr+3) << 24;
float value;
std::memcpy(&value, &bits, 4);
return value;
}
std::pair<std::string, size_t> ByteArray::get_string(uint32_t addr) const
{
std::string data;
while (char c = (char) get_byte(addr++))
data += c;
return { data, data.size() + 1 };
}
void ByteArray::append_bytearray(ByteArray const& bytearray)
{
data_.insert(data_.end(), bytearray.data().begin(), bytearray.data().end());
}
std::string ByteArray::hexdump() const
{
auto to_hex = [](uint32_t value, size_t n_chars) -> std::string {
char buf[15];
snprintf(buf, sizeof buf, "%0*X", (int) n_chars, value);
return { buf };
};
std::string out;
for (size_t i = 0; i < data_.size(); ++i) {
if (i % 16 == 0)
out += to_hex(i, 4) + " | ";
out += to_hex(data_.at(i), 2) + " ";
if (i % 16 == 15)
out += "\n";
}
return out + "\n";
}
}

View File

@@ -1,58 +0,0 @@
#ifndef TYCHE_BYTEARRAY_HH
#define TYCHE_BYTEARRAY_HH
#include <cstdint>
#include <stdexcept>
#include <string>
#include <vector>
namespace tyche {
class ByteArray {
public:
ByteArray() = default;
explicit ByteArray(std::vector<uint8_t> data) : data_(std::move(data)) {}
void set_byte(uint32_t addr, uint8_t byte);
void set_uint16(uint32_t addr, uint16_t value);
void set_uint32(uint32_t addr, uint32_t value);
void set_int8(uint32_t addr, int8_t value);
void set_int16(uint32_t addr, int16_t value);
void set_int32(uint32_t addr, int32_t value);
void set_float(uint32_t addr, float value);
void set_string(uint32_t addr, std::string const& str);
void set_bytearray(uint32_t addr, ByteArray const& bytearray);
void append_byte(uint8_t byte) { set_byte(data_.size(), byte); }
void append_uint16(uint16_t value) { set_uint16(data_.size(), value); }
void append_uint32(uint32_t value) { set_uint32(data_.size(), value); }
void append_int8(int8_t value) { set_int8(data_.size(), value); }
void append_int16(int16_t value) { set_int16(data_.size(), value); }
void append_int32(int32_t value) { set_int32(data_.size(), value); }
void append_float(float value) { set_float(data_.size(), value); }
void append_string(std::string const& str) { set_string(data_.size(), str); }
void append_bytearray(ByteArray const& bytearray);
[[nodiscard]] uint8_t get_byte(uint32_t addr) const;
[[nodiscard]] uint16_t get_uint16(uint32_t addr) const;
[[nodiscard]] uint32_t get_uint32(uint32_t addr) const;
[[nodiscard]] int8_t get_int8(uint32_t addr) const;
[[nodiscard]] int16_t get_int16(uint32_t addr) const;
[[nodiscard]] int32_t get_int32(uint32_t addr) const;
[[nodiscard]] float get_float(uint32_t addr) const;
[[nodiscard]] std::pair<std::string, size_t> get_string(uint32_t addr) const;
[[nodiscard]] std::vector<uint8_t> const& data() const { return data_; }
[[nodiscard]] size_t size() const { return data_.size(); }
[[nodiscard]] std::string hexdump() const;
friend bool operator==(ByteArray const& lhs, ByteArray const& rhs) { return lhs.data_ == rhs.data_; }
private:
std::vector<uint8_t> data_ {};
};
}
#endif //TYCHE_BYTEARRAY_HH

View File

@@ -1,8 +0,0 @@
#ifndef TYCHE_OVERLOADED_HH
#define TYCHE_OVERLOADED_HH
// used by std::visitor
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
#endif //TYCHE_OVERLOADED_HH

View File

@@ -1,82 +0,0 @@
#include "code.hh"
#include "../common/overloaded.hh"
#include "instruction.hh"
namespace tyche::vm {
FunctionId Code::import_bytecode(ByteArray incoming)
{
bc::Bytecode bc(std::move(incoming));
// TODO - adjust function calls, constants
bytecode_ = std::move(bc);
return 0; // TODO
}
Operation Code::operation(Location const& location) const
{
Instruction inst = (Instruction) bytecode_.get_code_byte(location.function_id, location.pc);
OperandType opet = instruction_operand_type(inst);
switch (opet) {
case OperandType::NoOperand:
return {
.instruction = inst,
.operator_ = 0,
.next_location = { .function_id = location.function_id, .pc = location.pc + 1 },
};
case OperandType::Int8:
return {
.instruction = inst,
.operator_ = bytecode_.get_code_int8(location.function_id, location.pc + 1),
.next_location = { .function_id = location.function_id, .pc = location.pc + 2 },
};
case OperandType::Int16:
return {
.instruction = inst,
.operator_ = bytecode_.get_code_int16(location.function_id, location.pc + 1),
.next_location = { .function_id = location.function_id, .pc = location.pc + 3 },
};
case OperandType::Int32:
return {
.instruction = inst,
.operator_ = bytecode_.get_code_int32(location.function_id, location.pc + 1),
.next_location = { .function_id = location.function_id, .pc = location.pc + 5 },
};
default:
break;
}
throw std::logic_error("Should not get here");
}
std::string Code::disassemble() const
{
std::string out;
out += ".const\n";
for (size_t i = 0; i < bytecode_.n_constants(); ++i) {
out += "\t" + std::to_string(i) + ": ";
std::visit(overloaded {
[&out](float f) { out += std::to_string(f); },
[&out](std::string const& str) { out += "\"" + str + "\""; },
}, bytecode_.get_constant(i));
out += "\n";
}
out += "\n";
for (size_t i = 0; i < bytecode_.n_functions(); ++i) {
out += ".func " + std::to_string(i) + "\n";
uint32_t addr = 0;
while (addr < bytecode_.get_function_sz(i)) {
auto [op, sz] = debug_instruction(bytecode_, i, addr);
out += "\t" + op + "\n";
addr += sz;
}
}
return out;
}
} // tyche

View File

@@ -1,34 +0,0 @@
#ifndef TYCHE_CODE_HH
#define TYCHE_CODE_HH
#include "instruction.hh"
#include "location.hh"
#include "value.hh"
#include "../bytecode/bytecode.hh"
namespace tyche::vm {
struct Operation
{
Instruction instruction;
int32_t operator_;
Location next_location;
};
class Code {
public:
FunctionId import_bytecode(ByteArray incoming);
[[nodiscard]] std::string disassemble() const;
[[nodiscard]] Operation operation(Location const& location) const;
[[nodiscard]] bc::Bytecode const& bytecode() const { return bytecode_; }
private:
bc::Bytecode bytecode_;
};
} // tyche
#endif //TYCHE_CODE_HH

View File

@@ -1,114 +0,0 @@
#include "expr.hh"
#include <cmath>
#include <functional>
#include "vm_exceptions.hh"
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)
{
return binary_ops[(size_t) op][(size_t) b.type()][(size_t) a.type()](a, b);
}
}

View File

@@ -1,21 +0,0 @@
#ifndef TYCHE_EXPR_HH
#define TYCHE_EXPR_HH
#include "value.hh"
namespace tyche::vm {
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);
}
#endif //TYCHE_EXPR_HH

View File

@@ -1,244 +0,0 @@
#include "instruction.hh"
#include <limits>
#include <unordered_map>
namespace tyche::vm {
const std::unordered_map<std::string, Instruction> instruction_names = {
{ "pushi", Instruction::PushInt8 },
{ "pushc", Instruction::PushConstant8 },
{ "pushz", Instruction::PushZero },
{ "pusht", Instruction::PushTrue },
{ "pushf", Instruction::PushFunction8 },
{ "newa", Instruction::NewArray },
{ "newt", Instruction::NewTable },
{ "pop", Instruction::Pop },
{ "dup", Instruction::Duplicate },
{ "pushv", Instruction::PushValues8 },
{ "set", Instruction::SetValue8 },
{ "dupv", Instruction::DuplicateValue8 },
{ "setg", Instruction::SetGlobal8 },
{ "getl", Instruction::GetGlobal8 },
{ "call8", Instruction::Call8 },
{ "ret", Instruction::Return },
{ "retn", Instruction::ReturnNil },
{ "getkv", Instruction::GetKeyValue },
{ "setkv", Instruction::SetKeyValue },
{ "geta", Instruction::GetArrayItem },
{ "seta", Instruction::SetArrayItem },
{ "appnd", Instruction::Append },
{ "next", Instruction::Next },
{ "smt", Instruction::SetMetatable },
{ "mt", Instruction::GetMetatable },
{ "sum", Instruction::Sum },
{ "sub", Instruction::Subtract },
{ "mul", Instruction::Multiply },
{ "div", Instruction::Divide },
{ "idiv", Instruction::DivideInt },
{ "eq", Instruction::Equals },
{ "neq", Instruction::NotEquals },
{ "lt", Instruction::LessThan },
{ "lte", Instruction::LessThanEq },
{ "gt", Instruction::GreaterThan },
{ "gte", Instruction::GreaterThanEq },
{ "and", Instruction::And },
{ "or", Instruction::Or },
{ "xor", Instruction::Xor },
{ "pow", Instruction::Power },
{ "shl", Instruction::ShiftLeft },
{ "shr", Instruction::ShiftRight },
{ "mod", Instruction::Modulo },
{ "len", Instruction::Len },
{ "type", Instruction::Type },
{ "cast", Instruction::Cast },
{ "ver", Instruction::Version },
{ "bz", Instruction::BranchIfZero8 },
{ "bnz", Instruction::BranchIfNotZero8 },
{ "jmp", Instruction::Jump8 },
{ "cmpl", Instruction::Compile },
{ "asmbl", Instruction::Assemble },
{ "load", Instruction::Load },
};
std::pair<std::string, size_t> debug_instruction(Instruction inst, int oper)
{
std::string out;
switch (inst) {
case Instruction::PushInt8:
case Instruction::PushInt16:
case Instruction::PushInt32:
out = "pushi";
break;
case Instruction::PushConstant8:
case Instruction::PushConstant16:
case Instruction::PushConstant32:
out = "pushc";
break;
case Instruction::PushFunction8:
case Instruction::PushFunction16:
case Instruction::PushFunction32:
out = "pushf";
break;
case Instruction::PushZero: out = "pushz"; break;
case Instruction::PushTrue: out = "pusht"; break;
case Instruction::NewArray: out = "newa"; break;
case Instruction::NewTable: out = "newt"; break;
case Instruction::Pop: out = "pop"; break;
case Instruction::Duplicate: out = "dup"; break;
case Instruction::PushValues8:
case Instruction::PushValues16:
case Instruction::PushValues32:
out = "pushv";
break;
case Instruction::SetValue8:
case Instruction::SetValue16:
case Instruction::SetValue32:
out = "set";
break;
case Instruction::DuplicateValue8:
case Instruction::DuplicateValue16:
case Instruction::DuplicateValue32:
out = "dupv";
break;
case Instruction::SetGlobal8:
case Instruction::SetGlobal16:
case Instruction::SetGlobal32:
out = "setg";
break;
case Instruction::GetGlobal8:
case Instruction::GetGlobal16:
case Instruction::GetGlobal32:
out = "getg";
break;
case Instruction::Call8:
case Instruction::Call16:
case Instruction::Call32:
out = "call";
break;
case Instruction::Return: out = "ret"; break;
case Instruction::ReturnNil: out = "retn"; break;
case Instruction::GetKeyValue: out = "getkv"; break;
case Instruction::SetKeyValue: out = "setkv"; break;
case Instruction::GetArrayItem: out = "geta"; break;
case Instruction::SetArrayItem: out = "seta"; break;
case Instruction::Append: out = "appnd"; break;
case Instruction::Next: out = "next"; break;
case Instruction::SetMetatable: out = "smt"; break;
case Instruction::GetMetatable: out = "mt"; break;
case Instruction::Sum: out = "sum"; break;
case Instruction::Subtract: out = "sub"; break;
case Instruction::Multiply: out = "mul"; break;
case Instruction::Divide: out = "div"; break;
case Instruction::DivideInt: out = "idiv"; break;
case Instruction::Equals: out = "eq"; break;
case Instruction::NotEquals: out = "neq"; break;
case Instruction::LessThan: out = "lt"; break;
case Instruction::LessThanEq: out = "lte"; break;
case Instruction::GreaterThan: out = "gt"; break;
case Instruction::GreaterThanEq: out = "gte"; break;
case Instruction::And: out = "and"; break;
case Instruction::Or: out = "or"; 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::Type: out = "type"; break;
case Instruction::Cast: out = "cast"; break;
case Instruction::Version: out = "ver"; break;
case Instruction::BranchIfZero8:
case Instruction::BranchIfZero16:
case Instruction::BranchIfZero32:
out = "bz";
break;
case Instruction::BranchIfNotZero8:
case Instruction::BranchIfNotZero16:
case Instruction::BranchIfNotZero32:
out = "bnz";
break;
case Instruction::Jump8:
case Instruction::Jump16:
case Instruction::Jump32:
out = "jmp";
break;
case Instruction::Compile: out = "cmpl"; break;
case Instruction::Assemble: out = "asmbl"; break;
case Instruction::Load: out = "load"; break;
default:
out = "???";
}
OperandType operands = instruction_operand_type(inst);
if (operands == OperandType::NoOperand)
return { out, 1 };
out += " " + std::to_string(oper);
if (operands == OperandType::Int32)
return { out, 5 };
if (operands == OperandType::Int16)
return { out, 3 };
return { out, 2 };
}
std::pair<std::string, size_t> debug_instruction(bc::Bytecode const& bt, uint32_t function_id, uint32_t addr)
{
auto inst = (Instruction) bt.get_code_byte(function_id, addr);
switch (instruction_operand_type(inst)) {
case OperandType::NoOperand:
return debug_instruction(inst);
case OperandType::Int8:
return debug_instruction(inst, bt.get_code_int8(function_id, addr + 1));
case OperandType::Int16:
return debug_instruction(inst, bt.get_code_int16(function_id, addr + 1));
case OperandType::Int32:
return debug_instruction(inst, bt.get_code_int32(function_id, addr + 1));
default:
break;
}
return { "???", 1 };
}
OperandType instruction_operand_type(Instruction inst)
{
if ((uint8_t) inst >= 0xe0)
return OperandType::Int32;
if ((uint8_t) inst >= 0xc0)
return OperandType::Int16;
if ((uint8_t) inst >= 0xa0)
return OperandType::Int8;
return OperandType::NoOperand;
}
std::optional<Instruction> translate_instruction(std::string const& txt, std::optional<int> op)
{
auto it = instruction_names.find(txt);
if (it == instruction_names.end())
return {};
Instruction inst = it->second;
OperandType optype = instruction_operand_type(inst);
if (optype == OperandType::NoOperand && op)
return {};
if (optype != OperandType::NoOperand && !op)
return {};
if (optype == OperandType::NoOperand)
return inst;
if (op >= std::numeric_limits<int8_t>::min() && op <= std::numeric_limits<int8_t>::max())
return inst;
if (op >= std::numeric_limits<int16_t>::min() && op <= std::numeric_limits<int16_t>::max())
return (Instruction) ((uint8_t) inst + OPCODE_NEXT_SIZE);
return (Instruction) ((uint8_t) inst + (OPCODE_NEXT_SIZE * 2));
}
}

View File

@@ -1,121 +0,0 @@
#ifndef TYCHE_INSTRUCTION_HH
#define TYCHE_INSTRUCTION_HH
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include "../bytecode/bytecode.hh"
namespace tyche::vm {
constexpr uint8_t OPCODE_NEXT_SIZE = 0x20;
enum class Instruction : uint8_t {
// stack operations
PushInt8 = 0xa0,
PushInt16 = 0xc0,
PushInt32 = 0xe0,
PushConstant8 = 0xa1,
PushConstant16 = 0xc1,
PushConstant32 = 0xe1,
PushFunction8 = 0xa2,
PushFunction16 = 0xc2,
PushFunction32 = 0xe2,
PushZero = 0x00,
PushTrue = 0x01,
NewArray = 0x02,
NewTable = 0x03,
Pop = 0x04,
Duplicate = 0x05,
// local variables
PushValues8 = 0xa3,
PushValues16 = 0xc3,
PushValues32 = 0xe3,
SetValue8 = 0xab,
SetValue16 = 0xcb,
SetValue32 = 0xeb,
DuplicateValue8 = 0xa4,
DuplicateValue16 = 0xc4,
DuplicateValue32 = 0xe4,
SetGlobal8 = 0xa5,
SetGlobal16 = 0xc5,
SetGlobal32 = 0xe5,
GetGlobal8 = 0xa6,
GetGlobal16 = 0xc6,
GetGlobal32 = 0xe6,
// function operations
Call8 = 0xa7,
Call16 = 0xc7,
Call32 = 0xe7,
Return = 0x10,
ReturnNil = 0x11,
// table and array operations
GetKeyValue = 0x16,
SetKeyValue = 0x17,
GetArrayItem = 0x18,
SetArrayItem = 0x19,
Append = 0x1a,
Next = 0x1b,
SetMetatable = 0x1c,
GetMetatable = 0x1d,
// logical/arithmetic
Sum = 0x20,
Subtract = 0x21,
Multiply = 0x22,
Divide = 0x23,
DivideInt = 0x24,
Equals = 0x25,
NotEquals = 0x26,
LessThan = 0x27,
LessThanEq = 0x28,
GreaterThan = 0x29,
GreaterThanEq = 0x2a,
And = 0x2b,
Or = 0x2c,
Xor = 0x2d,
Power = 0x2e,
ShiftLeft = 0x2f,
ShiftRight = 0x30,
Modulo = 0x31,
// other value operations
Len = 0x40,
Type = 0x41,
Cast = 0x42,
Version = 0x43,
// control flow
BranchIfZero8 = 0xa8,
BranchIfZero16 = 0xc8,
BranchIfZero32 = 0xe8,
BranchIfNotZero8 = 0xa9,
BranchIfNotZero16 = 0xc9,
BranchIfNotZero32 = 0xe9,
Jump8 = 0xaa,
Jump16 = 0xca,
Jump32 = 0xea,
// external code
Compile = 0x48,
Assemble = 0x49,
Load = 0x4a,
};
std::pair<std::string, size_t> debug_instruction(Instruction inst, int oper=0);
std::pair<std::string, size_t> debug_instruction(bc::Bytecode const& bt, uint32_t function_id, uint32_t addr);
enum class OperandType { NoOperand, Int8, Int16, Int32 };
OperandType instruction_operand_type(Instruction instruction);
std::optional<Instruction> translate_instruction(std::string const& txt, std::optional<int> op);
}
#endif //TYCHE_INSTRUCTION_HH

View File

@@ -1,16 +0,0 @@
#ifndef TYCHE_LOCATION_HH
#define TYCHE_LOCATION_HH
#include <cstdint>
namespace tyche::vm {
struct Location
{
uint32_t function_id;
uint32_t pc;
};
}
#endif //TYCHE_LOCATION_HH

View File

@@ -1,95 +0,0 @@
#include "stack.hh"
#include "vm_exceptions.hh"
namespace tyche::vm {
Stack::Stack()
{
fps_.push(0);
}
void Stack::push(Value const& value)
{
stack_.push_back(value);
}
Value Stack::pop()
{
if (stack_.size() <= fps_.top())
throw VMStackUnderflow();
Value v = stack_.back();
stack_.pop_back();
return v;
}
Value Stack::peek() const
{
if (stack_.size() <= fps_.top())
throw VMStackUnderflow();
return stack_.back();
}
Value Stack::at(int pos) const
{
try {
if (pos >= 0) {
return stack_.at(fps_.top() + pos);
} else {
if ((int) fps_.top() + (int) stack_.size() + pos < 0)
throw VMStackOutOfRange();
return stack_.at(stack_.size() + pos);
}
} catch (std::out_of_range&) {
throw VMStackOutOfRange();
}
}
void Stack::set(int pos, Value const& val)
{
try {
if (pos >= 0) {
stack_.at(fps_.top() + pos) = val;
} else {
if ((int) fps_.top() + (int) stack_.size() + pos < 0)
throw VMStackOutOfRange();
stack_.at(stack_.size() + pos) = val;
}
} catch (std::out_of_range&) {
throw VMStackOutOfRange();
}
}
size_t Stack::size() const
{
return stack_.size() - fps_.top();
}
void Stack::push_fp()
{
fps_.push(stack_.size());
}
void Stack::pop_fp()
{
if (fps_.size() == 1)
throw VMStackUnderflow();
stack_.resize(fps_.top());
fps_.pop();
}
std::string Stack::debug() const
{
if (stack_.empty())
return "empty";
std::string out;
for (size_t i = 0; i < stack_.size(); ++i)
out += "[" + stack_.at(i).to_string() + "] ";
return out;
}
} // tyche

View File

@@ -1,38 +0,0 @@
#ifndef TYCHE_STACK_HH
#define TYCHE_STACK_HH
#include <stack>
#include <vector>
#include "value.hh"
namespace tyche::vm {
class Stack {
public:
Stack();
void push(Value const& value);
Value pop();
[[nodiscard]] Value peek() const;
[[nodiscard]] Value at(int pos) const;
[[nodiscard]] size_t size() const;
void set(int pos, Value const& val);
void push_fp();
void pop_fp();
[[nodiscard]] size_t fp_level() const { return fps_.size(); }
[[nodiscard]] std::string debug() const;
private:
std::vector<Value> stack_;
std::stack<size_t> fps_;
};
} // tyche
#endif //TYCHE_STACK_HH

View File

@@ -1,292 +0,0 @@
#include "gtest/gtest.h"
#include "../bytecode/bytecodeprototype.hh"
#include "../bytecode/bytecode.hh"
#include "../assembler/assembler.hh"
#include "code.hh"
#include "stack.hh"
#include "vm.hh"
using namespace tyche;
using namespace tyche::bc;
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)
{
BytecodePrototype bp;
bp.constants.emplace_back(3.14f);
bp.constants.emplace_back("HELLO");
bp.functions.emplace_back(0, 0);
bp.functions.at(0).code.append_byte(0xa0); // pushi
bp.functions.at(0).code.append_int8(42);
bp.functions.emplace_back(2, 1);
bp.functions.at(1).code.append_byte(0x1a); // appnd
ByteArray ba = Bytecode::generate(bp);
Code code;
code.import_bytecode(std::move(ba));
printf("%s\n", code.disassemble().c_str());
}
TEST(Stack, PushPullGet)
{
Stack stack;
stack.push(Value::createInteger(10));
stack.push(Value::createInteger(20));
stack.push(Value::createInteger(30));
ASSERT_EQ(stack.size(), 3);
ASSERT_EQ(stack.at(0).as_integer(), 10);
ASSERT_EQ(stack.at(1).as_integer(), 20);
ASSERT_EQ(stack.at(-1).as_integer(), 30);
ASSERT_EQ(stack.at(-2).as_integer(), 20);
}
TEST(Stack, FramePointer)
{
Stack stack;
stack.push(Value::createInteger(10));
stack.push(Value::createInteger(20));
stack.push_fp();
stack.push(Value::createInteger(30));
stack.push(Value::createInteger(40));
stack.push(Value::createInteger(50));
ASSERT_EQ(stack.size(), 3);
ASSERT_EQ(stack.at(0).as_integer(), 30);
ASSERT_EQ(stack.at(1).as_integer(), 40);
ASSERT_EQ(stack.at(-1).as_integer(), 50);
ASSERT_EQ(stack.at(-2).as_integer(), 40);
stack.pop_fp();
ASSERT_EQ(stack.size(), 2);
ASSERT_EQ(stack.at(0).as_integer(), 10);
ASSERT_EQ(stack.at(1).as_integer(), 20);
ASSERT_EQ(stack.at(-1).as_integer(), 20);
ASSERT_EQ(stack.at(-2).as_integer(), 10);
}
TEST(VM, BasicCode)
{
// code (2+3)
BytecodePrototype bp;
bp.functions.emplace_back(0, 0);
bp.functions.at(0).code.append_byte((uint8_t) Instruction::PushInt8);
bp.functions.at(0).code.append_int8(2);
bp.functions.at(0).code.append_byte((uint8_t) Instruction::PushInt8);
bp.functions.at(0).code.append_int8(3);
bp.functions.at(0).code.append_byte((uint8_t) Instruction::Sum);
bp.functions.at(0).code.append_byte((uint8_t) Instruction::Return);
ByteArray ba = Bytecode::generate(bp);
VM vm;
vm.load_bytecode(std::move(ba));
vm.call(0);
int32_t result = vm.to_integer(-1);
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);
}
TEST(VM, LocalVariables)
{
VM vm = run(R"(
.func 0
pushv 2 ; local a, b
pushi 3 ; a = 3
set 0
pushi 4 ; b = 4
set 1
dupv 0 ; return a
ret
)");
ASSERT_EQ(vm.stack_sz(), 1);
ASSERT_EQ(vm.to_integer(-1), 3);
}
int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -1,44 +0,0 @@
#include "value.hh"
#include "../common/overloaded.hh"
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
{
return std::visit(overloaded {
[](std::monostate) { return Type::Nil; },
[](int32_t) { return Type::Integer; },
[](float) { return Type::Float; },
[](std::string const&) { return Type::String; },
[](Function const&) { return Type::Function; },
}, value_);
}
std::string Value::to_string() const
{
return std::visit(overloaded {
[](std::monostate) { return std::string("nil"); },
[](int32_t i) { return std::to_string(i); },
[](float f) { return std::to_string(f); },
[](std::string const& s) { return s; },
[](Function const& f) { return "@" + std::to_string(f.f_id); }
}, value_);
}
}

View File

@@ -1,53 +0,0 @@
#ifndef TYCHE_VALUE_HH
#define TYCHE_VALUE_HH
#include <cstdint>
#include <string>
#include <variant>
namespace tyche::vm {
using FunctionId = uint32_t;
enum class Type : uint8_t
{
Nil = 0, Integer, Float, String, Array, Table, Function, NativePointer, COUNT
};
std::string type_name(Type type);
class Value {
struct Function { FunctionId f_id; };
public:
Value() : value_(std::monostate()) {}
static Value createNil() { return Value(std::monostate()); }
static Value createInteger(int32_t v) { return Value(v); }
static Value createFloat(float f) { return Value(f); }
static Value createString(std::string const& str) { return Value(str); }
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]] int32_t as_integer() const { return std::get<int32_t>(value_); }
[[nodiscard]] float as_float() const { return std::get<float>(value_); }
[[nodiscard]] std::string as_string() const { return std::get<std::string>(value_); }
[[nodiscard]] FunctionId as_function_id() const { return std::get<Function>(value_).f_id; }
[[nodiscard]] std::string to_string() const;
private:
using Internal = std::variant<std::monostate, int32_t, float, std::string, Function>;
Internal value_;
explicit Value(Internal const& internal) : value_(internal) {}
};
}
#endif //TYCHE_VALUE_HH

View File

@@ -1,208 +0,0 @@
#include "vm.hh"
#include "vm_exceptions.hh"
#include "expr.hh"
namespace tyche::vm {
VM& VM::load_bytecode(ByteArray const& ba)
{
FunctionId f_id = code_.import_bytecode(ba);
stack_.push(Value::createFunctionId(f_id));
return *this;
}
VM& VM::call(size_t n_params)
{
// TODO - parameters
Value f = stack_.pop();
if (f.type() != Type::Function)
throw VMTypeError(Type::Function, f.type());
loc_.emplace(f.as_function_id(), 0);
stack_.push_fp();
run_until_return();
// stack_.pop_fp();
loc_.pop();
return *this;
}
int32_t VM::to_integer(int index) const
{
Value i = stack_.at(index);
if (i.type() == Type::Integer)
return i.as_integer();
if (i.type() == Type::Float)
return (int32_t) i.as_float();
throw VMTypeError(Type::Integer, i.type());
}
float VM::to_float(int index) const
{
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()
{
size_t level = stack_.fp_level();
while (stack_.fp_level() >= level)
step();
}
void VM::step()
{
Operation op = code_.operation(loc_.top());
switch (op.instruction) {
//
// stack management
//
case Instruction::PushInt8:
case Instruction::PushInt16:
case Instruction::PushInt32:
push_integer(op.operator_);
break;
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;
}
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;
//
// variables
//
case Instruction::PushValues8:
case Instruction::PushValues16:
case Instruction::PushValues32:
for (int i = 0; i < op.operator_; ++i)
push_nil();
break;
case Instruction::SetValue8:
case Instruction::SetValue16:
case Instruction::SetValue32: {
Value a = stack_.pop();
stack_.set(op.operator_, a);
break;
}
case Instruction::DuplicateValue8:
case Instruction::DuplicateValue16:
case Instruction::DuplicateValue32: {
Value a = stack_.at(op.operator_);
stack_.push(a);
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: {
Value v = stack_.pop();
stack_.pop_fp();
stack_.push(v);
return;
}
default:
throw VMInvalidOpcode((uint8_t) op.instruction);
}
loc_.top() = op.next_location;
}
void VM::assert_type(Value const& val, Type type)
{
if (val.type() != type)
throw VMTypeError(type, val.type());
}
} // tyche

View File

@@ -1,51 +0,0 @@
#ifndef TYCHE_VM_HH
#define TYCHE_VM_HH
#include "code.hh"
#include "location.hh"
#include "stack.hh"
namespace tyche::vm {
class VM {
public:
VM& load_bytecode(ByteArray const& ba);
VM& call(size_t n_params);
[[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; }
[[nodiscard]] size_t stack_sz() const { return stack_.size(); }
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:
void run_until_return();
void step();
static void assert_type(Value const& val, Type type);
Stack stack_;
Code code_;
std::stack<Location> loc_;
};
} // tyche
#endif //TYCHE_VM_HH

View File

@@ -1,49 +0,0 @@
#ifndef TYCHE_VM_EXCEPTIONS_HH
#define TYCHE_VM_EXCEPTIONS_HH
#include <stdexcept>
#include <string>
#include "expr.hh"
namespace tyche::vm {
class VMRuntimeError : public std::runtime_error
{
public:
explicit VMRuntimeError(std::string const& str) : std::runtime_error(str.c_str()) {}
};
class VMStackUnderflow : public VMRuntimeError
{
public:
explicit VMStackUnderflow() : VMRuntimeError("Stack underflow") {}
};
class VMStackOutOfRange : public VMRuntimeError
{
public:
explicit VMStackOutOfRange() : VMRuntimeError("Item does not exist in stack") {}
};
class VMTypeError : public VMRuntimeError
{
public:
explicit VMTypeError(Type expected, Type found) : VMRuntimeError("Type error (expected " + type_name(expected) + ", found " + type_name(found) + ")") {}
};
class VMInvalidOpcode : public VMRuntimeError
{
public:
explicit VMInvalidOpcode(uint8_t opcode) : VMRuntimeError("Invalid opcode " + std::to_string(opcode)) {}
};
class VMInvalidOperation : public VMRuntimeError
{
public:
explicit VMInvalidOperation(BinaryOperationType op, Type type1, Type type2) : VMRuntimeError("Invalid binary operation for types " + type_name(type1) + " and " + type_name(type2)) {}
};
}
#endif //TYCHE_VM_EXCEPTIONS_HH

View File

@@ -1,175 +0,0 @@
cmake_minimum_required(VERSION 3.20)
project(tyche
VERSION 0.1.0
LANGUAGES C
)
# ---------------------------------------------------------------------------
# Project-wide settings
# ---------------------------------------------------------------------------
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
# Default to Release if not explicitly set (single-config generators only)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel)
endif()
# Toggle between STATIC and SHARED for the main library
option(BUILD_SHARED_LIB "Build mylib as a shared library" OFF)
include(GNUInstallDirs)
# ---------------------------------------------------------------------------
# Warning flags loaded from external files
# ---------------------------------------------------------------------------
# Each file contains one flag per line, e.g.:
# -Wall
# -Wextra
# -Wshadow
# Lines starting with `#` are treated as comments.
function(read_flags_file path out_var)
set(flags "")
if(EXISTS "${path}")
file(STRINGS "${path}" lines)
foreach(line IN LISTS lines)
string(STRIP "${line}" line)
if(line AND NOT line MATCHES "^#")
list(APPEND flags "${line}")
endif()
endforeach()
else()
message(WARNING "Warnings file not found: ${path}")
endif()
set(${out_var} "${flags}" PARENT_SCOPE)
endfunction()
set(WARNINGS_DIR "${CMAKE_SOURCE_DIR}/cmake/warnings")
read_flags_file("${WARNINGS_DIR}/common.txt" COMMON_WARNINGS)
read_flags_file("${WARNINGS_DIR}/gcc.txt" GCC_WARNINGS)
read_flags_file("${WARNINGS_DIR}/clang.txt" CLANG_WARNINGS)
# Build a single list of warnings appropriate for the current compiler
set(PROJECT_WARNINGS ${COMMON_WARNINGS})
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
list(APPEND PROJECT_WARNINGS ${GCC_WARNINGS})
elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") # matches Clang and AppleClang
list(APPEND PROJECT_WARNINGS ${CLANG_WARNINGS})
endif()
# ---------------------------------------------------------------------------
# Per-config compile/link options exposed via an INTERFACE target
# ---------------------------------------------------------------------------
# Anything that links `project_options` inherits warnings + debug/release tweaks.
add_library(project_options INTERFACE)
# Warnings apply to every build type
target_compile_options(project_options INTERFACE ${PROJECT_WARNINGS})
# Debug-only flags
target_compile_options(project_options INTERFACE
"$<$<CONFIG:Debug>:-O0>"
"$<$<CONFIG:Debug>:-g3>"
"$<$<CONFIG:Debug>:-fno-omit-frame-pointer>"
"$<$<CONFIG:Debug>:-fsanitize=address>"
"$<$<CONFIG:Debug>:-fsanitize=undefined>"
)
target_link_options(project_options INTERFACE
"$<$<CONFIG:Debug>:-fsanitize=address>"
"$<$<CONFIG:Debug>:-fsanitize=undefined>"
)
# LeakSanitizer is not supported on macOS — gate it on platform.
# (On Linux it's already bundled into ASan, but adding the flag is harmless
# and makes intent explicit; on macOS it would fail to link.)
if(NOT APPLE)
target_compile_options(project_options INTERFACE
"$<$<CONFIG:Debug>:-fsanitize=leak>"
)
target_link_options(project_options INTERFACE
"$<$<CONFIG:Debug>:-fsanitize=leak>"
)
endif()
target_compile_definitions(project_options INTERFACE
"$<$<CONFIG:Debug>:DEBUG_BUILD=1>"
)
# Release-only flags
target_compile_options(project_options INTERFACE
"$<$<CONFIG:Release>:-O3>"
"$<$<CONFIG:Release>:-DNDEBUG>"
)
target_link_options(project_options INTERFACE
"$<$<CONFIG:Release>:-Wl,--as-needed>"
)
# ---------------------------------------------------------------------------
# The library
# ---------------------------------------------------------------------------
if(BUILD_SHARED_LIB)
set(MYLIB_KIND SHARED)
else()
set(MYLIB_KIND STATIC)
endif()
add_library(mylib ${MYLIB_KIND}
src/mylib/foo.c
src/mylib/bar.c
)
add_library(MyProject::mylib ALIAS mylib)
target_include_directories(mylib
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
PRIVATE
"${CMAKE_SOURCE_DIR}/src"
)
target_link_libraries(mylib PRIVATE project_options)
set_target_properties(mylib PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
POSITION_INDEPENDENT_CODE ON
)
# ---------------------------------------------------------------------------
# Main executable
# ---------------------------------------------------------------------------
add_executable(myapp src/app/main.c)
target_link_libraries(myapp PRIVATE MyProject::mylib project_options)
# ---------------------------------------------------------------------------
# Test executable
# ---------------------------------------------------------------------------
enable_testing()
add_executable(mylib_tests tests/test_main.c)
target_link_libraries(mylib_tests PRIVATE MyProject::mylib project_options)
add_test(NAME mylib_tests COMMAND mylib_tests)
# ---------------------------------------------------------------------------
# Install rules (executable and library only — tests excluded)
# ---------------------------------------------------------------------------
install(TARGETS mylib myapp
EXPORT MyProjectTargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h"
)
# Optional: export a CMake package config so downstream projects can do
# find_package(MyProject CONFIG REQUIRED)
install(EXPORT MyProjectTargets
FILE MyProjectTargets.cmake
NAMESPACE MyProject::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
)

117
Makefile
View File

@@ -1,117 +0,0 @@
#
# user overwritable variables
#
# install prefix
PREFIX ?= /usr/local
# add functions to debug assembly to console
DEBUG_ASSEMBLY ?= 0
#
# internal flags/options
#
# version
VERSION_MAJOR=0
VERSION_MINOR=1
VERSION=${VERSION_MAJOR}.${VERSION_MINOR}
# add compiler-specific warnings
IS_CLANG := $(shell $(CC) -dM -E - < /dev/null | grep -c __clang__)
WARNINGS=@config/WARNINGS
ADD_DBG_FLAGS=
ifeq ($(IS_CLANG),1)
WARNINGS += @config/WARNINGS_CLANG
else
WARNINGS += @config/WARNINGS_GCC
ADD_DBG_FLAGS=-fanalyzer
endif
# debug and release flags
DEBUG_CFLAGS=-O0 -ggdb3 ${WARNINGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined \
-fno-sanitize-recover=all -fstack-protector-strong -fstack-clash-protection -fno-common ${ADD_DBG_FLAGS} \
-DCHECK_TYCHE_BUGS=1
DEBUG_LDFLAGS=-fsanitize=address -fsanitize=undefined
# apple clang doesn't support -fsanitize=leak
UNAME_S := $(shell uname -s)
ifneq ($(UNAME_S),Darwin)
DEBUG_CFLAGS += -fsanitize=leak
DEBUG_LDFLAGS += -fsanitize=leak
endif
RELEASE_CFLAGS=-O3 -flto=auto -march=native -mtune=native -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -fstack-protector-strong
RELEASE_LDFLAGS=-flto=auto
CFLAGS+=-std=c99 -D_GNU_SOURCE -fPIC -fvisibility=hidden -isystem lib/contrib -MMD -MP
LDFLAGS+=
ifeq ($(DEBUG_ASSEMBLY),1)
CFLAGS += -DDEBUG_ASSEMBLY
endif
#
# generic targets
#
all: tyche libtyche.a libtyche.so.${VERSION}
check: tyche-test
./tyche-test
clean:
rm -f tyche libtyche.a libtyche.so* tyche-test **/*.o **/*.d lib/compiler/compiler.lua.h
install: tyche libtyche.a libtyche.so.${VERSION} lib/tyche.h
install -m 644 libtyche.a libtyche.so.${VERSION} ${PREFIX}/lib
install tyche ${PREFIX}/bin
install -m 644 lib/tyche.h ${PREFIX}/include
ln -s ${PREFIX}/lib/libtyche.so.${VERSION} ${PREFIX}/lib/libtyche.so.${VERSION_MAJOR}
ln -s ${PREFIX}/lib/libtyche.so.${VERSION_MAJOR} ${PREFIX}/lib/libtyche.so
uninstall:
rm -f ${PREFIX}/lib/libtyche.* ${PREFIX}/bin/tyche ${PREFIX}/include/tyche.h
.PHONY: all check clean install uninstall
#
# TODO - temporary, using Lua for compilation for now
#
CFLAGS+=`pkg-config --cflags lua`
LDFLAGS+=`pkg-config --libs lua`
lib/compiler/compiler.lua.h: lib/compiler/compiler.lua
luac -o lib/compiler/compiler.out lib/compiler/compiler.lua
xxd -i lib/compiler/compiler.out > lib/compiler/compiler.lua.h
rm lib/compiler/compiler.out
lib/compiler.o: lib/compiler.c lib/compiler/compiler.lua.h
#
# executable files
#
LIB_SRC=lib/value.o lib/stack.o lib/array.o lib/table.o lib/heap.o lib/vm.o lib/expr.o lib/compiler.o lib/code.o lib/utils.o
tyche: CFLAGS += ${RELEASE_CFLAGS}
tyche: LDFLAGS += ${RELEASE_LDFLAGS}
tyche: src/tyche.o libtyche.a
$(CC) -o $@ $^ ${LDFLAGS}
strip $@
tyche-test: CFLAGS += ${DEBUG_CFLAGS} -DDEBUG_ASSEMBLY
tyche-test: LDFLAGS += ${DEBUG_LDFLAGS}
tyche-test: test/tests.o libtyche.a
$(CC) -o $@ $^ ${LDFLAGS} -I../lib
libtyche.a: ${LIB_SRC}
ar rcs $@ $^
libtyche.so.${VERSION}: LDFLAGS += ${RELEASE_LDFLAGS}
libtyche.so.${VERSION}: ${LIB_SRC}
$(CC) -shared -o $@ -Wl,-soname,libfoo.so.${VERSION_MAJOR} $^ ${LDFLAGS}
-include $(LIB_SRC:.o=.d)

67
TODO.md
View File

@@ -1,57 +1,12 @@
## C
## Chunk
Decisions:
- How to handle errors
- How values and heap values will be represented
- Transparency and log levels
- [x] Makefile
- [x] VALUE
- [x] Stack
- [x] Test application
- [x] Heap
- [x] Heap value
- [x] Strings
- [x] Arrays
- [x] Tables
- [ ] VM
- [x] (Lua interface) call assembler
- [x] (Lua) generate bytecode
- [x] Labels
- [x] Code
- [x] Interpret bytecode (fast)
- [x] Execution loop (fast)
- [ ] VM operations
- [x] Expressions
- [x] Local variables
- [x] Functions
- [x] With parameters
- [x] Debug VM execution
- [x] Control flow
- [x] Recursion
- [ ] Strings
- [ ] From constants
- [ ] Garbage collection
- [ ] Arrays
- [ ] Garbage collection
- [ ] Tables
- [ ] Garbage collection
- [ ] Metatables
- [ ] Iteration
- [ ] Floats (real)
- [ ] Globals (?)
- [ ] Error handling
- [ ] Stack trace in case of errors
- [ ] Closure/upvalues
- [ ] Rest of opcodes
- [ ] Prepare for release
- [ ] Documentation and webpage
## Future versions
- [ ] Debugging information
- [ ] Debugger
- [ ] Dynamic language
- [ ] Support tools
- [ ] Editor syntax file, etc
- [ ] Static language
- [ ] Byte array
- Auto-expand
- Add/retrive byte/int/float/string
- Should not be larger than the byte array itself
- [ ] Chunk
- Add/retrive all types of data
- Keeps no memory except for caching
- [ ] Chunk loader
- Combine multiple chunks
- Resolve function ids, constant ids, etc

View File

@@ -1,22 +0,0 @@
-Wall
-Wextra
-Wpedantic
-Wshadow
-Wmissing-prototypes
-Wcast-qual
-Wcast-align
-Wconversion
-Wsign-conversion
-Wdouble-promotion
-Wformat=2
-Wformat-security
-Wnull-dereference
-Wstrict-overflow=4
-Wundef
-Wswitch-enum
-Wswitch-default
-Wfloat-equal
-Wpointer-arith
-Wwrite-strings
-Wredundant-decls
-Wstack-protector

View File

@@ -1,5 +0,0 @@
-Wshadow-all
-Wcomma
-Wassign-enum
-Wno-newline-eof
-Wno-unused-command-line-argument

View File

@@ -1,7 +0,0 @@
-Wlogical-op
-Wjump-misses-init
-Wduplicated-cond
-Wduplicated-branches
-Wtrampolines
-Walloc-zero
-Walloca

View File

@@ -1,34 +0,0 @@
Bytecode format
---------------
The bytecode file is composed of the following sections:
* HEADER: 8-byte header
[0:3]: Magic
[4:5]: Bytecode version
[6:7]: Reserved for future use
* CONSTANTS
[0:3]: Code start address
[4:7]: Number of constants
Each constant:
[0]: Type (0 = string, 1 = real)
if string:
[...]: string (NULL terminated)
if real
[1..4]: real
* CODE
[0:3]: Debug start address (or zero)
[4:7]: Number of functions
Each function:
[0:3] Address of next function
[...] Code
[0] : Opcode
[between 1 and 4] : Operand
The max file size is 2 Gb.
## Values can be encoded in the following ways:
* The type is defined by the operator.
* Constant indexes and function ids are encoded as ints

View File

@@ -1,96 +1,125 @@
Operations
----------
Operations take either 0 or 1 parameter. The ones that take a parameter, it can be either a int8, int16 or int32.
Stack operations: (0x00~0x1f)
pushn [int] Push int
pushr [float] Push float (real)
pushs [string] Push string
pshcn [index] Push int from constant list
pshcr [index] Push float from constant list
pshcs [index] Push string from constant list
pushf [function] Push function id
pushz Push zero (or false)
pusht Push true
newa [array] Push (create) empty array
newt [table] Push (create) empty table
pop
dup
Instructions follow this logic:
Local variables: (0x20~0x2f)
setl [int] Set stack top as indexed local variable
getl [int] Get indexed local variable and place on stack
setg [int] Set global variable
getg [int] Get global variable
00 ~ 9F : no parameter
A0 ~ BF : int8 (1 byte)
C0 ~ DF : int16 (2 bytes)
E0 ~ FF : int32 (4 bytes)
Function operations: (0x30~0x3f)
call [n_pars] Enter function on stack toplevel (passing n next stack values as parameters)
ret Leave a function (return value in stack)
retn Leave a function (return nil)
The operations of 1, 2 and 4 bytes are always interchangeable by adding/subtracting 0x20.
,----------- no parameter
| ,-------- int8
| | ,----- int16
| | | ,-- int32
NP I8 I16 I32 Opc Instruction Description
Stack operations:
a0 c0 e0 pushi [int] Push int
a1 c1 e1 pushc [index] Push constant
a2 c2 e2 pushf [function] Push function id
00 pushn Push nil
01 pushz Push zero (or false)
02 pusht Push true
03 newa Push (create) empty array
04 newt Push (create) empty table
05 pop
06 dup
Local variables:
a3 c3 e3 pushv [int] Push n nil values into the stack (used to init local vars)
ae ce ee set [index] Set value in stack position (set local variable)
a4 c4 e4 dupv [index] Duplicate stack value (load local variable)
a5 c5 e5 setg [int] Set global variable
a6 c6 e6 getg [int] Get global variable
Function operations:
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)
11 retn Leave a function (return nil)
Table and array operations:
16 getkv Get table's value based on key (pull 1 value, push 1 value)
17 setkv Set table's key and value (pull 2 values from stack)
a8 c8 e8 geti Get array's position value
a9 c9 e9 seti Set array's position value
18 appnd Add value to the end of array
19 next Push the next pair into the stack (for loops)
1a smt Set value metatable
1b mt Get value metatable
Logical/arithmetic:
20 sum Sum top 2 values in stack
21 sub Subtract top 2 values in stack
22 mul Multiply top 2 values in stack
23 div Float division
24 idiv Integer division
25 mod Modulo
26 eq Equality
27 neq Inequality
28 lt Less than
29 lte Less than or equals
2a gt Greater than
2b gte Greater than or equals
2c and Bitwise AND
2d or Bitwise OR
2e xor Bitwise XOR
2f pow Power
30 shl Shift left
31 shr Shift right
Other value operations:
40 len Get table, array or string size
41 type Get type from value at the top of the stack
ad cast [type] Cast type to another type
42 ver Return VM version
External code:
48 cmpl Compile code to assembly
49 asmbl Assemble code to bytecode format
4a load Load bytecode as function (will place function on stack)
Control flow (the destination is always a 16-bit field):
ca bz [pc] Branch if zero
cb bnz [pc] Branch if not zero
cc jmp [pc] Unconditional jump
Control flow: (0x40~0x4f)
bz [pc] Branch if zero
bnz [pc] Branch if not zero
jmp [pc] Unconditional jump
* Jumps can only happen within the same function.
Memory management:
4b gc Call garbage collector
Logical/arithmetic: (0x50~0x6f)
sum Sum top 2 values in stack
sub Subtract top 2 values in stack
mul Multiply top 2 values in stack
div Float division
idiv Integer division
eq Equality
neq Inequality
lt Less than
lte Less than or equals
gt Greater than
gte Greater than or equals
and Bitwise AND
or Bitwise OR
xor Bitwise XOR
Table and array operations: (0x70~07xf)
getkv Get table's value based on key (pull 1 value, push 1 value)
setkv Set table's key and value (pull 2 values from stack)
geta Get array's position value
seta Set array's position value (pull 2 values from stack)
appnd Add value to the end of array
next Push the next pair into the stack (for loops)
smt Set value metatable
mt Get value metatable
Other value operations: (0x80~0x8f)
len Get table, array or string size
type Get type from value at the top of the stack
cast [type] Cast type to another type
ver Return VM version
External code: (0x90~0x9f)
cmpl Compile code to assembly
asmbl Assemble code to chunk format
load Load chunk as function (will place function on stack)
Error handling: (0xa0~0xaf)
???
Chunk format
------------
The bytecode file is composed of the following sections:
* [0x0] 16-byte header
[00]: VM format
[??]: reserved
* [0x1] Index: pointers to each one of the sections, up to 8
Each pointer: 4 bits
* [0x2] Constants: all constants (such as strings) used in the code
* Table of 4-bit constant indexes with pointer to constant
* Raw constant data
* [0x3] Functions: Pointer to functions within the code
[0:3]: function pointer
[4:5]: number of parameters
[6:7]: number of local variables
* [0x4] Code: executable code
[1-byte]: operation
[variable]: operand (see value encoding below)
* [0x5] Debugging info
???
The max file size is 2 Gb.
## Values can be encoded in the following ways:
* The type is defined by the operator.
* Encoding varies according to the type:
int: use protobuf format
float: 4-bit floating point
string: int-defined length, followed by the string proper - no null terminator
* Constant indexes and function ids are encoded as ints
Internal handling of values
---------------------------
## Supported types
Nil 0
Integer 1
Float 2
String 3
Array 4
Table 5
Function 6
NativePointer 7
## Internal format
???

15
doc/VM
View File

@@ -1,15 +0,0 @@
Internal handling of values
---------------------------
## Supported types
Nil 0
Integer 1
Float 2
String 3
Array 4
Table 5
Function 6
NativePointer 7
## Internal format
???

View File

@@ -1,58 +0,0 @@
#include "priv.h"
#include <stdlib.h>
struct Array {
size_t n;
size_t cap;
VALUE* items;
};
Array* array_new(void)
{
Array* a = xcalloc(1, sizeof(Array));
a->n = 0;
a->cap = 1;
a->items = xmalloc(1 * sizeof(Array));
a->items[0] = create_value_nil();
return a;
}
void array_destroy(Array* a)
{
free(a->items);
free(a);
}
size_t array_len(Array const* a)
{
return a->n;
}
VALUE array_get(Array const* a, size_t pos)
{
if (pos >= a->n)
return create_value_nil();
return a->items[pos];
}
void array_set(Array* a, size_t pos, VALUE v)
{
// extend array
if (pos > a->cap - 1) {
a->cap *= 2;
a->items = xrealloc(a->items, a->cap * sizeof(VALUE));
for (size_t i = a->n; i < a->cap; ++i)
a->items[i] = create_value_nil();
}
// set item
a->items[pos] = v;
if (pos + 1 > a->n)
a->n = pos + 1;
}
void array_append(Array* a, VALUE v)
{
array_set(a, a->n, v);
}

View File

@@ -1,285 +0,0 @@
#include "priv.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# error Sorry, big endian architectures are not supported at this time.
#endif
#define MAGIC 0xa7d6e9b1
#define VERSION_ADDR 0x04
#define CODE_START_ADDR 0x08
#define N_CONST_ADDR 0x0c
#define CONST_START 0x10
#define OP_8BIT_OPERAND 0xa0
#define OP_16BIT_OPERAND 0xc0
#define OP_32BIT_OPERAND 0xe0
struct Code {
uint8_t const* bytecode;
size_t bytecode_sz;
uint32_t* const_addr;
uint32_t fn_count;
uint32_t* fn_addr;
uint32_t* fn_sz;
};
Code* code_new(void)
{
Code* code = xcalloc(1, sizeof(Code));
return code;
}
void code_destroy(Code* code)
{
free(code->const_addr);
free(code->fn_addr);
free(code->fn_sz);
free(code);
}
TYC_RESULT code_load_bytecode(Code* code, uint8_t const* bytecode, size_t bytecode_sz)
{
// TODO - linking
if (bytecode_sz < 24)
return T_ERR_BYTECODE_TOO_SMALL;
uint32_t magic;
memcpy(&magic, bytecode, sizeof(magic));
if (magic != MAGIC)
return T_ERR_BYTECODE_INVALID_MAGIC;
code->bytecode = bytecode;
code->bytecode_sz = bytecode_sz;
/*
for (size_t i = 0; i < bytecode_sz; ++i) {
if (i % 16 == 0)
printf("%04X: ", i);
printf("%02x ", bytecode[i]);
if (i % 16 == 15)
printf("\n");
}
printf("\n");
*/
uint32_t n_consts = code_n_consts(code);
code->const_addr = xcalloc(n_consts, sizeof(uint32_t));
uint32_t addr = CONST_START;
for (size_t i = 0; i < n_consts; ++i) {
code->const_addr[i] = addr;
switch (code_const_type(code, i)) {
case TC_STRING: {
uint32_t sz = (uint32_t) strlen((const char*) &bytecode[code->const_addr[i] + 1]);
addr += sz + 2; // 2 = constant type + NULL terminator
break;
}
case TC_REAL:
addr += 5; // 5 = constant type + float
break;
case TC_INVALID_TYPE:
default:
__builtin_unreachable();
}
}
addr += 4; // skip debug start address
memcpy(&code->fn_count, &bytecode[addr], sizeof(uint32_t)); // number of functions
addr += 4;
code->fn_addr = xcalloc(code->fn_count, sizeof(uint32_t));
code->fn_sz = xcalloc(code->fn_count, sizeof(uint32_t));
code->fn_addr[0] = addr;
uint32_t addr_next;
for (size_t i = 1; i < code->fn_count; ++i) {
memcpy(&addr_next, &bytecode[addr], sizeof(uint32_t));
code->fn_sz[i-1] = addr_next - addr - 4;
addr = code->fn_addr[i] = addr_next;
}
memcpy(&addr_next, &bytecode[addr], sizeof(uint32_t));
code->fn_sz[code->fn_count-1] = addr_next - addr - 4;
return T_OK;
}
uint32_t code_n_consts(Code const* code)
{
uint32_t n_consts; memcpy(&n_consts, &code->bytecode[N_CONST_ADDR], sizeof(uint32_t));
return n_consts;
}
TYC_CONST_TYPE code_const_type(Code const* code, size_t n)
{
uint8_t t = code->bytecode[code->const_addr[n]];
if (t >= TC_INVALID_TYPE)
return TC_INVALID_TYPE;
return t;
}
T_REAL code_const_real(Code const* code, size_t n)
{
float f;
memcpy(&f, &code->bytecode[code->const_addr[n] + 1], sizeof(float));
return f;
}
const char* code_const_string(Code const* code, size_t n)
{
return (const char*) &code->bytecode[code->const_addr[n] + 1];
}
uint32_t code_n_functions(Code const* code)
{
return code->fn_count;
}
uint32_t code_function_sz(Code const* code, uint32_t f_id)
{
return code->fn_sz[f_id];
}
Instruction code_next_instruction(Code const* code, uint32_t function_id, uint32_t pc)
{
uint32_t addr = code->fn_addr[function_id] + 4 + pc;
uint8_t opcode = code->bytecode[addr];
int32_t operand = 0;
uint8_t sz = 1;
if (opcode >= OP_8BIT_OPERAND && opcode < OP_16BIT_OPERAND) {
operand = (int8_t) code->bytecode[addr + 1];
sz = 2;
} else if (opcode >= OP_16BIT_OPERAND && opcode < OP_32BIT_OPERAND) {
opcode -= 0x20;
operand = (int16_t) ((uint16_t) code->bytecode[addr + 1] |
(uint16_t) (code->bytecode[addr + 2] << 8));
sz = 3;
} else if (opcode >= OP_32BIT_OPERAND) {
opcode -= 0x40;
operand = (int32_t) ((uint32_t) code->bytecode[addr + 1] |
(uint32_t) (code->bytecode[addr + 2] << 8) |
(uint32_t) (code->bytecode[addr + 3] << 16) |
(uint32_t) (code->bytecode[addr + 4] << 24));
sz = 5;
}
return (Instruction) {
.operator = (TYC_INST) opcode,
.operand = operand,
.sz = sz,
};
}
#ifdef DEBUG_ASSEMBLY
void code_debug_bytecode(Code const* code)
{
for (int i = 0; i < code->bytecode_sz; ++i) {
if (i % 16 == 0)
printf("%04X : ", i);
printf("%02X ", code->bytecode[i]);
if (i % 16 == 15)
printf("\n");
}
printf("\n");
}
void code_decompile(Code const* code)
{
if (code_n_consts(code) > 0)
printf(".const\n");
for (size_t const_id = 0; const_id < code_n_consts(code); ++const_id) {
TYC_CONST_TYPE type = code_const_type(code, const_id);
if (type == TC_STRING)
printf(" %03zu: \"%s\"\n", const_id, code_const_string(code, const_id));
else if (type == TC_REAL)
printf(" %03zu: %f\n", const_id, (double) code_const_real(code, const_id));
}
for (uint32_t f_id = 0; f_id < code_n_functions(code); ++f_id) {
printf(".func %d\n", f_id);
uint32_t pc = 0;
while (pc < code_function_sz(code, f_id)) {
Instruction inst = code_next_instruction(code, f_id, pc);
char buf[50];
code_parse_instruction(inst, buf, sizeof buf);
printf(" %s ; %d\n", buf, pc);
pc += inst.sz;
}
}
}
void code_parse_instruction(Instruction inst, char* outbuf, size_t sz)
{
int n;
switch (inst.operator) {
case TO_PUSHI: n = snprintf(outbuf, sz, "pushi "); break;
case TO_PUSHC: n = snprintf(outbuf, sz, "pushc "); break;
case TO_PUSHF: n = snprintf(outbuf, sz, "pushf "); break;
case TO_PUSHN: n = snprintf(outbuf, sz, "pushn "); break;
case TO_PUSHZ: n = snprintf(outbuf, sz, "pushz "); break;
case TO_PUSHT: n = snprintf(outbuf, sz, "pusht "); break;
case TO_NEWA: n = snprintf(outbuf, sz, "newa "); break;
case TO_NEWT: n = snprintf(outbuf, sz, "newt "); break;
case TO_POP: n = snprintf(outbuf, sz, "pop "); break;
case TO_DUP: n = snprintf(outbuf, sz, "dup "); break;
case TO_PUSHV: n = snprintf(outbuf, sz, "pushv "); break;
case TO_SET: n = snprintf(outbuf, sz, "set "); break;
case TO_DUPV: n = snprintf(outbuf, sz, "dupv "); break;
case TO_SETG: n = snprintf(outbuf, sz, "setg "); break;
case TO_GETG: n = snprintf(outbuf, sz, "getg "); break;
case TO_CALL: n = snprintf(outbuf, sz, "call "); break;
case TO_RET: n = snprintf(outbuf, sz, "ret "); break;
case TO_RETI: n = snprintf(outbuf, sz, "reti "); break;
case TO_GETKV: n = snprintf(outbuf, sz, "getkv "); break;
case TO_SETKV: n = snprintf(outbuf, sz, "setkv "); break;
case TO_GETI: n = snprintf(outbuf, sz, "geti "); break;
case TO_SETI: n = snprintf(outbuf, sz, "seti "); break;
case TO_APPND: n = snprintf(outbuf, sz, "appnd "); break;
case TO_NEXT: n = snprintf(outbuf, sz, "next "); break;
case TO_SMT: n = snprintf(outbuf, sz, "smt "); break;
case TO_MT: n = snprintf(outbuf, sz, "mt "); break;
case TO_SUM: n = snprintf(outbuf, sz, "sum "); break;
case TO_SUB: n = snprintf(outbuf, sz, "sub "); break;
case TO_MUL: n = snprintf(outbuf, sz, "mul "); break;
case TO_DIV: n = snprintf(outbuf, sz, "div "); break;
case TO_IDIV: n = snprintf(outbuf, sz, "idiv "); break;
case TO_MOD: n = snprintf(outbuf, sz, "mod "); break;
case TO_EQ: n = snprintf(outbuf, sz, "eq "); break;
case TO_NEQ: n = snprintf(outbuf, sz, "neq "); break;
case TO_LT: n = snprintf(outbuf, sz, "lt "); break;
case TO_LTE: n = snprintf(outbuf, sz, "lte "); break;
case TO_GT: n = snprintf(outbuf, sz, "gt "); break;
case TO_GTE: n = snprintf(outbuf, sz, "gte "); break;
case TO_AND: n = snprintf(outbuf, sz, "and "); break;
case TO_OR: n = snprintf(outbuf, sz, "or "); break;
case TO_XOR: n = snprintf(outbuf, sz, "xor "); break;
case TO_POW: n = snprintf(outbuf, sz, "pow "); break;
case TO_SHL: n = snprintf(outbuf, sz, "shl "); break;
case TO_SHR: n = snprintf(outbuf, sz, "shr "); break;
case TO_LEN: n = snprintf(outbuf, sz, "len "); break;
case TO_TYPE: n = snprintf(outbuf, sz, "type "); break;
case TO_CAST: n = snprintf(outbuf, sz, "cast "); break;
case TO_VER: n = snprintf(outbuf, sz, "ver "); break;
case TO_CMPL: n = snprintf(outbuf, sz, "cmpl "); break;
case TO_ASMBL: n = snprintf(outbuf, sz, "asmbl "); break;
case TO_LOAD: n = snprintf(outbuf, sz, "load "); break;
case TO_BZ: n = snprintf(outbuf, sz, "bz "); break;
case TO_BNZ: n = snprintf(outbuf, sz, "bnz "); break;
case TO_JMP: n = snprintf(outbuf, sz, "jmp "); break;
case TO_GC: n = snprintf(outbuf, sz, "gc "); break;
default: n = snprintf(outbuf, sz, "??? "); break;
}
if (inst.operator >= OP_8BIT_OPERAND)
snprintf(&outbuf[n], sz + (size_t) n, "%2d", inst.operand);
else
snprintf(&outbuf[n], sz + (size_t) n, " ");
}
#endif

View File

@@ -1,44 +0,0 @@
#include "priv.h"
#include <stdlib.h>
#include <string.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "compiler/compiler.lua.h"
TYC_RESULT code_assemble(const char* code, uint8_t** bytecode, size_t* bytecode_sz)
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
int r = luaL_loadbufferx(L, (const char *) lib_compiler_compiler_out, lib_compiler_compiler_out_len, "compiler", "b");
if (r == LUA_ERRSYNTAX)
abort();
else if (r == LUA_ERRMEM)
out_of_memory();
lua_call(L, 0, 1);
lua_pushstring(L, code);
r = lua_pcall(L, 1, 1, 0);
if (r == LUA_ERRMEM) {
out_of_memory();
} else if (r == LUA_ERRERR) {
abort();
} else if (r == LUA_ERRRUN) {
fprintf(stderr, "%s\n", lua_tostring(L, -1));
return T_ERR_ASSEMBLER_SYNTAX_ERROR;
}
if (!lua_isstring(L, -1))
abort();
*bytecode_sz = (size_t) luaL_len(L, -1);
*bytecode = malloc(*bytecode_sz);
memcpy(*bytecode, lua_tostring(L, -1), *bytecode_sz);
lua_close(L);
return T_OK;
}

View File

@@ -1,302 +0,0 @@
----------------------
-- --
-- PARSER --
-- --
----------------------
local function parse_assembly(source)
local proto = {
constants = {},
functions = {},
}
local section = ''
local current_f_id = 0
local next_label = nil
for line in source:gmatch("([^\n]+)") do
local line = line:gsub("%s*;.*$", "") -- remove comments
line = line:match("^%s*(.-)%s*$") -- trim
if #line == 0 then goto continue end
if line == ".const" then
section = 'const'
elseif line:match("%.func%s+%d+") then
section = 'function'
local f_id = tonumber(line:match("%.func%s+(%d+)"))
proto.functions[f_id] = {}
current_f_id = f_id
elseif section == 'const' then
local k, v = line:match("^%s*(%d+)%s*:%s*(.+)$")
if not k then error("Invalid row for constant: " .. line) end
if v:sub(1, 1) == '"' then
proto.constants[tonumber(k)] = line:match('"(.*)"')
else
proto.constants[tonumber(k)] = tonumber(v)
end
elseif section == 'function' then
local regexes = {
"^%s*(%a+)%s+(-?%d+)%s*$", -- instruction + parameter
"^%s*(%a+)%s+(@[%a_][%a%d_]*)%s*$", -- instruction + label
"^%s*(%a+)%s*$", -- instruction only
"^(@[%a_][%a%d_]*):%s*$", -- label
}
local match = false
for i, regex in ipairs(regexes) do
local inst, par = line:match(regex)
if inst then
match = true
if i == 1 then -- instruction + parameter
table.insert(proto.functions[current_f_id], { inst, tonumber(par), labels = next_label })
elseif i == 2 then -- instruction + label
table.insert(proto.functions[current_f_id], { inst, par, labels = next_label })
elseif i == 3 then -- instruction only
table.insert(proto.functions[current_f_id], { inst, labels = next_label })
elseif i == 4 then -- label
if not next_label then
next_label = { inst }
else
table.insert(next_label, inst)
end
end
if i ~= 4 then
next_label = nil
end
end
end
if not match then error("Invalid instruction: " .. line) end
end
::continue::
end
return proto
end
----------------------
-- --
-- BINARY --
-- --
----------------------
local instructions = {
-- stack operations
pushi = 0xa0,
pushc = 0xa1,
pushf = 0xa2,
pushn = 0x00,
pushz = 0x01,
pusht = 0x02,
newa = 0x03,
newt = 0x04,
pop = 0x05,
dup = 0x06,
-- local variables
pushv = 0xa3,
set = 0xae,
dupv = 0xa4,
setg = 0xa5,
getg = 0xa6,
-- function operations
call = 0xa7,
ret = 0x10,
reti = 0x11,
-- table and array operations
getkv = 0x16,
setkv = 0x17,
geti = 0xa8,
seti = 0xa9,
appnd = 0x18,
next = 0x19,
smt = 0x1a,
mt = 0x1b,
-- logical/arithmetic
sum = 0x20,
sub = 0x21,
mul = 0x22,
div = 0x23,
idiv = 0x24,
mod = 0x25,
eq = 0x26,
neq = 0x27,
lt = 0x28,
lte = 0x29,
gt = 0x2a,
gte = 0x2b,
['and'] = 0x2c,
['or'] = 0x2d,
xor = 0x2e,
pow = 0x2f,
shl = 0x30,
shr = 0x31,
-- other value operations
len = 0x40,
type = 0x41,
cast = 0xad,
ver = 0x42,
-- external code
cmpl = 0x48,
asmbl = 0x49,
load = 0x4a,
-- control flow
bz = 0xca,
bnz = 0xcb,
jmp = 0xcc,
-- memory management
gc = 0x4b,
}
local MAGIC = 0xa7d6e9b1
local VERSION = 1
local function assemble(proto)
local bin = {}
local push8 = function(data)
table.insert(bin, data & 0xff)
end
local push16 = function(data)
table.insert(bin, data & 0xff)
table.insert(bin, (data >> 8) & 0xff)
return #bin - 1
end
local push32 = function(data)
table.insert(bin, data & 0xff)
table.insert(bin, (data >> 8) & 0xff)
table.insert(bin, (data >> 16) & 0xff)
table.insert(bin, (data >> 24) & 0xff)
return #bin - 3
end
local replace16 = function(pos, data)
bin[pos] = data & 0xff
bin[pos + 1] = (data >> 8) & 0xff
end
local replace32 = function(pos, data)
bin[pos] = data & 0xff
bin[pos + 1] = (data >> 8) & 0xff
bin[pos + 2] = (data >> 16) & 0xff
bin[pos + 3] = (data >> 24) & 0xff
end
local function float_to_bits(f)
local bytes = string.pack("<f", f)
return string.unpack("<I4", bytes)
end
-- header
push32(MAGIC)
push16(VERSION)
push16(0)
-- constants
local code_addr_pos = push32(0) -- code address, to be replaced
-- number of constants
if proto.constants[0] then
push32(#proto.constants + 1)
else
push32(0)
end
for i=0,#proto.constants do
local const = proto.constants[i]
if type(const) == 'string' then
push8(0) -- string type
for c in const:gmatch('.') do
push8(c:byte())
end
push8(0) -- string terminator
elseif type(const) == 'number' then
push8(1) -- float type
push32(float_to_bits(const))
end
end
replace32(code_addr_pos, #bin)
-- code
push32(0) -- debug address (TODO)
push32(#proto.functions + 1) -- number of functions
for i = 0, #proto.functions do
local func = proto.functions[i]
local next_function_pos = #bin + 1
push32(0) -- to be replaced with next function address
local function_start = #bin
local labels = {}
for _, inst in ipairs(func) do
-- add labels
if inst.labels then
for _, lbl in ipairs(inst.labels) do
labels[lbl] = #bin - function_start
end
end
local opcode, operand = instructions[inst[1]], inst[2]
if opcode == nil then error("Unknown instruction " .. inst[1]) end
if operand == nil then
push8(opcode)
elseif type(operand) == 'string' then
push8(opcode)
table.insert(bin, operand) -- insert the label
push8(0) -- byte to be replaced (label is 16-bit)
else
if opcode >= 0xc0 and opcode < 0xe0 then
push8(opcode)
push16(operand)
elseif operand >= -128 and operand <= 127 then
push8(opcode)
push8(operand)
elseif operand >= -32768 and operand <= 32767 then
push8(opcode + 0x20)
push16(operand)
else
push8(opcode + 0x40)
push32(operand)
end
end
end
-- replace labels
for i=function_start,#bin do
if type(bin[i]) == 'string' then
local label_addr = labels[bin[i]]
if label_addr == nil then error("Label not found: " .. bin[i]) end
replace16(i, label_addr)
end
end
replace32(next_function_pos, #bin)
end
-- for _, b in ipairs(bin) do io.write(string.format("%02x", b) .. ' ') end; print()
return string.char(table.unpack(bin))
end
----------------------
-- --
-- GENERIC --
-- --
----------------------
return function(source)
return assemble(parse_assembly(source))
end

View File

@@ -1,627 +0,0 @@
/* The MIT License
Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
An example:
#include "khash.h"
KHASH_MAP_INIT_INT(32, char)
int main() {
int ret, is_missing;
khiter_t k;
khash_t(32) *h = kh_init(32);
k = kh_put(32, h, 5, &ret);
kh_value(h, k) = 10;
k = kh_get(32, h, 10);
is_missing = (k == kh_end(h));
k = kh_get(32, h, 5);
kh_del(32, h, k);
for (k = kh_begin(h); k != kh_end(h); ++k)
if (kh_exist(h, k)) kh_value(h, k) = 1;
kh_destroy(32, h);
return 0;
}
*/
/*
2013-05-02 (0.2.8):
* Use quadratic probing. When the capacity is power of 2, stepping function
i*(i+1)/2 guarantees to traverse each bucket. It is better than double
hashing on cache performance and is more robust than linear probing.
In theory, double hashing should be more robust than quadratic probing.
However, my implementation is probably not for large hash tables, because
the second hash function is closely tied to the first hash function,
which reduce the effectiveness of double hashing.
Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
2011-12-29 (0.2.7):
* Minor code clean up; no actual effect.
2011-09-16 (0.2.6):
* The capacity is a power of 2. This seems to dramatically improve the
speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
- http://code.google.com/p/ulib/
- http://nothings.org/computer/judy/
* Allow to optionally use linear probing which usually has better
performance for random input. Double hashing is still the default as it
is more robust to certain non-random input.
* Added Wang's integer hash function (not used by default). This hash
function is more robust to certain non-random input.
2011-02-14 (0.2.5):
* Allow to declare global functions.
2009-09-26 (0.2.4):
* Improve portability
2008-09-19 (0.2.3):
* Corrected the example
* Improved interfaces
2008-09-11 (0.2.2):
* Improved speed a little in kh_put()
2008-09-10 (0.2.1):
* Added kh_clear()
* Fixed a compiling error
2008-09-02 (0.2.0):
* Changed to token concatenation which increases flexibility.
2008-08-31 (0.1.2):
* Fixed a bug in kh_get(), which has not been tested previously.
2008-08-31 (0.1.1):
* Added destructor
*/
#ifndef __AC_KHASH_H
#define __AC_KHASH_H
/*!
@header
Generic hash table library.
*/
#define AC_VERSION_KHASH_H "0.2.8"
#include <stdlib.h>
#include <string.h>
#include <limits.h>
/* compiler specific configuration */
#if UINT_MAX == 0xffffffffu
typedef unsigned int khint32_t;
#elif ULONG_MAX == 0xffffffffu
typedef unsigned long khint32_t;
#endif
#if ULONG_MAX == ULLONG_MAX
typedef unsigned long khint64_t;
#else
typedef unsigned long long khint64_t;
#endif
#ifndef kh_inline
#ifdef _MSC_VER
#define kh_inline __inline
#else
#define kh_inline inline
#endif
#endif /* kh_inline */
#ifndef klib_unused
#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3)
#define klib_unused __attribute__ ((__unused__))
#else
#define klib_unused
#endif
#endif /* klib_unused */
typedef khint32_t khint_t;
typedef khint_t khiter_t;
#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
#ifndef kroundup32
#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
#endif
#ifndef kcalloc
#define kcalloc(N,Z) calloc(N,Z)
#endif
#ifndef kmalloc
#define kmalloc(Z) malloc(Z)
#endif
#ifndef krealloc
#define krealloc(P,Z) realloc(P,Z)
#endif
#ifndef kfree
#define kfree(P) free(P)
#endif
static const double __ac_HASH_UPPER = 0.77;
#define __KHASH_TYPE(name, khkey_t, khval_t) \
typedef struct kh_##name##_s { \
khint_t n_buckets, size, n_occupied, upper_bound; \
khint32_t *flags; \
khkey_t *keys; \
khval_t *vals; \
} kh_##name##_t;
#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
extern kh_##name##_t *kh_init_##name(void); \
extern void kh_destroy_##name(kh_##name##_t *h); \
extern void kh_clear_##name(kh_##name##_t *h); \
extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
extern void kh_del_##name(kh_##name##_t *h, khint_t x);
#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
SCOPE kh_##name##_t *kh_init_##name(void) { \
return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
} \
SCOPE void kh_destroy_##name(kh_##name##_t *h) \
{ \
if (h) { \
kfree((void *)h->keys); kfree(h->flags); \
kfree((void *)h->vals); \
kfree(h); \
} \
} \
SCOPE void kh_clear_##name(kh_##name##_t *h) \
{ \
if (h && h->flags) { \
memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
h->size = h->n_occupied = 0; \
} \
} \
SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
{ \
if (h->n_buckets) { \
khint_t k, i, last, mask, step = 0; \
mask = h->n_buckets - 1; \
k = __hash_func(key); i = k & mask; \
last = i; \
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
i = (i + (++step)) & mask; \
if (i == last) return h->n_buckets; \
} \
return __ac_iseither(h->flags, i)? h->n_buckets : i; \
} else return 0; \
} \
SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
khint32_t *new_flags = 0; \
khint_t j = 1; \
{ \
kroundup32(new_n_buckets); \
if (new_n_buckets < 4) new_n_buckets = 4; \
if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
else { /* hash table size to be changed (shrink or expand); rehash */ \
new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
if (!new_flags) return -1; \
memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
if (h->n_buckets < new_n_buckets) { /* expand */ \
khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
if (!new_keys) { kfree(new_flags); return -1; } \
h->keys = new_keys; \
if (kh_is_map) { \
khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
if (!new_vals) { kfree(new_flags); return -1; } \
h->vals = new_vals; \
} \
} /* otherwise shrink */ \
} \
} \
if (j) { /* rehashing is needed */ \
for (j = 0; j != h->n_buckets; ++j) { \
if (__ac_iseither(h->flags, j) == 0) { \
khkey_t key = h->keys[j]; \
khval_t val; \
khint_t new_mask; \
new_mask = new_n_buckets - 1; \
if (kh_is_map) val = h->vals[j]; \
__ac_set_isdel_true(h->flags, j); \
while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
khint_t k, i, step = 0; \
k = __hash_func(key); \
i = k & new_mask; \
while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
__ac_set_isempty_false(new_flags, i); \
if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
} else { /* write the element and jump out of the loop */ \
h->keys[i] = key; \
if (kh_is_map) h->vals[i] = val; \
break; \
} \
} \
} \
} \
if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
} \
kfree(h->flags); /* free the working space */ \
h->flags = new_flags; \
h->n_buckets = new_n_buckets; \
h->n_occupied = h->size; \
h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
} \
return 0; \
} \
SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
{ \
khint_t x; \
if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
if (h->n_buckets > (h->size<<1)) { \
if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
*ret = -1; return h->n_buckets; \
} \
} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
*ret = -1; return h->n_buckets; \
} \
} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
{ \
khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
else { \
last = i; \
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
if (__ac_isdel(h->flags, i)) site = i; \
i = (i + (++step)) & mask; \
if (i == last) { x = site; break; } \
} \
if (x == h->n_buckets) { \
if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
else x = i; \
} \
} \
} \
if (__ac_isempty(h->flags, x)) { /* not present at all */ \
h->keys[x] = key; \
__ac_set_isboth_false(h->flags, x); \
++h->size; ++h->n_occupied; \
*ret = 1; \
} else if (__ac_isdel(h->flags, x)) { /* deleted */ \
h->keys[x] = key; \
__ac_set_isboth_false(h->flags, x); \
++h->size; \
*ret = 2; \
} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
return x; \
} \
SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
{ \
if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
__ac_set_isdel_true(h->flags, x); \
--h->size; \
} \
}
#define KHASH_DECLARE(name, khkey_t, khval_t) \
__KHASH_TYPE(name, khkey_t, khval_t) \
__KHASH_PROTOTYPES(name, khkey_t, khval_t)
#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
__KHASH_TYPE(name, khkey_t, khval_t) \
__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
/* --- BEGIN OF HASH FUNCTIONS --- */
/*! @function
@abstract Integer hash function
@param key The integer [khint32_t]
@return The hash value [khint_t]
*/
#define kh_int_hash_func(key) (khint32_t)(key)
/*! @function
@abstract Integer comparison function
*/
#define kh_int_hash_equal(a, b) ((a) == (b))
/*! @function
@abstract 64-bit integer hash function
@param key The integer [khint64_t]
@return The hash value [khint_t]
*/
#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
/*! @function
@abstract 64-bit integer comparison function
*/
#define kh_int64_hash_equal(a, b) ((a) == (b))
/*! @function
@abstract const char* hash function
@param s Pointer to a null terminated string
@return The hash value
*/
static kh_inline khint_t __ac_X31_hash_string(const char *s)
{
khint_t h = (khint_t)*s;
if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
return h;
}
/*! @function
@abstract Another interface to const char* hash function
@param key Pointer to a null terminated string [const char*]
@return The hash value [khint_t]
*/
#define kh_str_hash_func(key) __ac_X31_hash_string(key)
/*! @function
@abstract Const char* comparison function
*/
#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
static kh_inline khint_t __ac_Wang_hash(khint_t key)
{
key += ~(key << 15);
key ^= (key >> 10);
key += (key << 3);
key ^= (key >> 6);
key += ~(key << 11);
key ^= (key >> 16);
return key;
}
#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key)
/* --- END OF HASH FUNCTIONS --- */
/* Other convenient macros... */
/*!
@abstract Type of the hash table.
@param name Name of the hash table [symbol]
*/
#define khash_t(name) kh_##name##_t
/*! @function
@abstract Initiate a hash table.
@param name Name of the hash table [symbol]
@return Pointer to the hash table [khash_t(name)*]
*/
#define kh_init(name) kh_init_##name()
/*! @function
@abstract Destroy a hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
*/
#define kh_destroy(name, h) kh_destroy_##name(h)
/*! @function
@abstract Reset a hash table without deallocating memory.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
*/
#define kh_clear(name, h) kh_clear_##name(h)
/*! @function
@abstract Resize a hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@param s New size [khint_t]
*/
#define kh_resize(name, h, s) kh_resize_##name(h, s)
/*! @function
@abstract Insert a key to the hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@param k Key [type of keys]
@param r Extra return code: -1 if the operation failed;
0 if the key is present in the hash table;
1 if the bucket is empty (never used); 2 if the element in
the bucket has been deleted [int*]
@return Iterator to the inserted element [khint_t]
*/
#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
/*! @function
@abstract Retrieve a key from the hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@param k Key [type of keys]
@return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
*/
#define kh_get(name, h, k) kh_get_##name(h, k)
/*! @function
@abstract Remove a key from the hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@param k Iterator to the element to be deleted [khint_t]
*/
#define kh_del(name, h, k) kh_del_##name(h, k)
/*! @function
@abstract Test whether a bucket contains data.
@param h Pointer to the hash table [khash_t(name)*]
@param x Iterator to the bucket [khint_t]
@return 1 if containing data; 0 otherwise [int]
*/
#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
/*! @function
@abstract Get key given an iterator
@param h Pointer to the hash table [khash_t(name)*]
@param x Iterator to the bucket [khint_t]
@return Key [type of keys]
*/
#define kh_key(h, x) ((h)->keys[x])
/*! @function
@abstract Get value given an iterator
@param h Pointer to the hash table [khash_t(name)*]
@param x Iterator to the bucket [khint_t]
@return Value [type of values]
@discussion For hash sets, calling this results in segfault.
*/
#define kh_val(h, x) ((h)->vals[x])
/*! @function
@abstract Alias of kh_val()
*/
#define kh_value(h, x) ((h)->vals[x])
/*! @function
@abstract Get the start iterator
@param h Pointer to the hash table [khash_t(name)*]
@return The start iterator [khint_t]
*/
#define kh_begin(h) (khint_t)(0)
/*! @function
@abstract Get the end iterator
@param h Pointer to the hash table [khash_t(name)*]
@return The end iterator [khint_t]
*/
#define kh_end(h) ((h)->n_buckets)
/*! @function
@abstract Get the number of elements in the hash table
@param h Pointer to the hash table [khash_t(name)*]
@return Number of elements in the hash table [khint_t]
*/
#define kh_size(h) ((h)->size)
/*! @function
@abstract Get the number of buckets in the hash table
@param h Pointer to the hash table [khash_t(name)*]
@return Number of buckets in the hash table [khint_t]
*/
#define kh_n_buckets(h) ((h)->n_buckets)
/*! @function
@abstract Iterate over the entries in the hash table
@param h Pointer to the hash table [khash_t(name)*]
@param kvar Variable to which key will be assigned
@param vvar Variable to which value will be assigned
@param code Block of code to execute
*/
#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
if (!kh_exist(h,__i)) continue; \
(kvar) = kh_key(h,__i); \
(vvar) = kh_val(h,__i); \
code; \
} }
/*! @function
@abstract Iterate over the values in the hash table
@param h Pointer to the hash table [khash_t(name)*]
@param vvar Variable to which value will be assigned
@param code Block of code to execute
*/
#define kh_foreach_value(h, vvar, code) { khint_t __i; \
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
if (!kh_exist(h,__i)) continue; \
(vvar) = kh_val(h,__i); \
code; \
} }
/* More convenient interfaces */
/*! @function
@abstract Instantiate a hash set containing integer keys
@param name Name of the hash table [symbol]
*/
#define KHASH_SET_INIT_INT(name) \
KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
/*! @function
@abstract Instantiate a hash map containing integer keys
@param name Name of the hash table [symbol]
@param khval_t Type of values [type]
*/
#define KHASH_MAP_INIT_INT(name, khval_t) \
KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
/*! @function
@abstract Instantiate a hash set containing 64-bit integer keys
@param name Name of the hash table [symbol]
*/
#define KHASH_SET_INIT_INT64(name) \
KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
/*! @function
@abstract Instantiate a hash map containing 64-bit integer keys
@param name Name of the hash table [symbol]
@param khval_t Type of values [type]
*/
#define KHASH_MAP_INIT_INT64(name, khval_t) \
KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
typedef const char *kh_cstr_t;
/*! @function
@abstract Instantiate a hash map containing const char* keys
@param name Name of the hash table [symbol]
*/
#define KHASH_SET_INIT_STR(name) \
KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
/*! @function
@abstract Instantiate a hash map containing const char* keys
@param name Name of the hash table [symbol]
@param khval_t Type of values [type]
*/
#define KHASH_MAP_INIT_STR(name, khval_t) \
KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
#endif /* __AC_KHASH_H */

View File

@@ -1,66 +0,0 @@
#include "priv.h"
#include <stdbool.h>
#include <math.h>
static bool was_init = false;
typedef TYC_RESULT(*BIN_EXPR_FN)(VALUE, VALUE, VALUE*);
static BIN_EXPR_FN bin_expr_fn[TX_COUNT__][TT_COUNT__][TT_COUNT__];
static TYC_RESULT default_bin_op(VALUE a, VALUE b, VALUE* r) { (void) a; (void) b, (void) r; return T_ERR_EXPR_INCORRECT_TYPES; }
#define BIN_OP(name) static TYC_RESULT name(VALUE a, VALUE b, VALUE* r)
BIN_OP(sum_int_int) { *r = create_value_integer(value_integer(a) + value_integer(b)); return T_OK; }
BIN_OP(sub_int_int) { *r = create_value_integer(value_integer(a) - value_integer(b)); return T_OK; }
BIN_OP(mul_int_int) { *r = create_value_integer(value_integer(a) * value_integer(b)); return T_OK; }
BIN_OP(idiv_int_int) { *r = create_value_integer(value_integer(a) / value_integer(b)); return T_OK; }
BIN_OP(eq_int_int) { *r = create_value_from_bool(value_integer(a) == value_integer(b)); return T_OK; }
BIN_OP(neq_int_int) { *r = create_value_from_bool(value_integer(a) != value_integer(b)); return T_OK; }
BIN_OP(lt_int_int) { *r = create_value_from_bool(value_integer(a) < value_integer(b)); return T_OK; }
BIN_OP(lte_int_int) { *r = create_value_from_bool(value_integer(a) <= value_integer(b)); return T_OK; }
BIN_OP(gt_int_int) { *r = create_value_from_bool(value_integer(a) > value_integer(b)); return T_OK; }
BIN_OP(gte_int_int) { *r = create_value_from_bool(value_integer(a) >= value_integer(b)); return T_OK; }
BIN_OP(and_int_int) { *r = create_value_integer(value_integer(a) & value_integer(b)); return T_OK; }
BIN_OP(or_int_int) { *r = create_value_integer(value_integer(a) | value_integer(b)); return T_OK; }
BIN_OP(xor_int_int) { *r = create_value_integer(value_integer(a) ^ value_integer(b)); return T_OK; }
BIN_OP(pow_int_int) { *r = create_value_integer((int32_t) powl(value_integer(a), value_integer(b))); return T_OK; }
BIN_OP(shl_int_int) { *r = create_value_integer(value_integer(a) << value_integer(b)); return T_OK; }
BIN_OP(shr_int_int) { *r = create_value_integer(value_integer(a) >> value_integer(b)); return T_OK; }
BIN_OP(mod_int_int) { *r = create_value_integer(value_integer(a) % value_integer(b)); return T_OK; }
void expr_init(void)
{
if (was_init)
return;
for (size_t i = 0; i < TX_COUNT__; ++i)
for (size_t j = 0; j < TT_COUNT__; ++j)
for (size_t k = 0; k < TT_COUNT__; ++k)
bin_expr_fn[i][j][k] = default_bin_op;
bin_expr_fn[TX_SUM][TT_INTEGER][TT_INTEGER] = sum_int_int;
bin_expr_fn[TX_SUB][TT_INTEGER][TT_INTEGER] = sub_int_int;
bin_expr_fn[TX_MUL][TT_INTEGER][TT_INTEGER] = mul_int_int;
bin_expr_fn[TX_IDIV][TT_INTEGER][TT_INTEGER] = idiv_int_int;
bin_expr_fn[TX_EQ][TT_INTEGER][TT_INTEGER] = eq_int_int;
bin_expr_fn[TX_NEQ][TT_INTEGER][TT_INTEGER] = neq_int_int;
bin_expr_fn[TX_LT][TT_INTEGER][TT_INTEGER] = lt_int_int;
bin_expr_fn[TX_LTE][TT_INTEGER][TT_INTEGER] = lte_int_int;
bin_expr_fn[TX_GT][TT_INTEGER][TT_INTEGER] = gt_int_int;
bin_expr_fn[TX_GTE][TT_INTEGER][TT_INTEGER] = gte_int_int;
bin_expr_fn[TX_AND][TT_INTEGER][TT_INTEGER] = and_int_int;
bin_expr_fn[TX_OR][TT_INTEGER][TT_INTEGER] = or_int_int;
bin_expr_fn[TX_XOR][TT_INTEGER][TT_INTEGER] = xor_int_int;
bin_expr_fn[TX_POW][TT_INTEGER][TT_INTEGER] = pow_int_int;
bin_expr_fn[TX_SHL][TT_INTEGER][TT_INTEGER] = shl_int_int;
bin_expr_fn[TX_SHR][TT_INTEGER][TT_INTEGER] = shr_int_int;
bin_expr_fn[TX_MOD][TT_INTEGER][TT_INTEGER] = mod_int_int;
was_init = true;
}
TYC_RESULT binary_expr(TYC_EXPR op, VALUE a, VALUE b, VALUE* result)
{
return bin_expr_fn[op][value_type(a)][value_type(b)](a, b, result);
}

View File

@@ -1,141 +0,0 @@
#include "priv.h"
#include <stdlib.h>
#include <string.h>
#include "khash.h"
typedef enum {
TH_STRING, TH_ARRAY, TH_TABLE,
} TYC_HEAP_TYPE;
typedef struct {
TYC_HEAP_TYPE type;
union {
char* str;
// TODO - array and table
} value;
} HeapValue;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
KHASH_MAP_INIT_INT64(HEAP, HeapValue)
KHASH_MAP_INIT_INT64(MARK, bool)
#pragma GCC diagnostic pop
struct Heap {
khash_t(HEAP) *items;
};
Heap* heap_new(void)
{
Heap* h = xcalloc(1, sizeof(Heap));
h->items = kh_init(HEAP);
return h;
}
static void heap_free_item(HeapValue value)
{
switch (value.type) {
case TH_STRING:
free(value.value.str);
break;
case TH_ARRAY:
abort(); // not implemented yet
case TH_TABLE:
abort(); // not implemented yet
default:
__builtin_unreachable();
}
}
void heap_destroy(Heap* h)
{
for (khiter_t k = kh_begin(h->items); k != kh_end(h->items); ++k) {
if (kh_exist(h->items, k)) {
HeapValue value = kh_value(h->items, k);
heap_free_item(value);
}
}
kh_destroy(HEAP, h->items);
free(h);
}
HEAP_KEY heap_add_string(Heap* h, const char* value)
{
int ret;
khiter_t k;
HEAP_KEY key;
do {
key = (HEAP_KEY) rand();
k = kh_get(HEAP, h->items, key);
} while (k != kh_end(h->items));
k = kh_put(HEAP, h->items, key, &ret);
if (ret < 0)
out_of_memory();
kh_value(h->items, k) = (HeapValue) {
.type = TH_STRING,
.value = { .str = strdup(value) }
};
return key;
}
TYC_RESULT heap_get_string(Heap const* h, HEAP_KEY key, const char** value)
{
khiter_t k = kh_get(HEAP, h->items, key);
bool is_missing = (k == kh_end(h->items));
if (is_missing)
return T_ERR_HEAP_KEY_NOT_FOUND;
*value = kh_value(h->items, k).value.str;
return T_OK;
}
size_t heap_size(Heap const* h)
{
return kh_size(h->items);
}
//
// GC
//
void heap_gc(Heap* h, VALUE const* roots, size_t n_roots)
{
//
// mark
//
khash_t(MARK) *marked = kh_init(MARK);
for (size_t i = 0; i < n_roots; ++i) {
if (value_type(roots[i]) == TT_STRING) {
int ret;
uint32_t key = value_idx(roots[i]);
khiter_t k = kh_put(MARK, marked, key, &ret);
if (ret < 0)
out_of_memory();
kh_value(marked, k) = true;
}
}
//
// sweep
//
for (khiter_t k = kh_begin(h->items); k != kh_end(h->items); ++k) {
if (kh_exist(h->items, k)) {
HEAP_KEY key = (HEAP_KEY) kh_key(h->items, k);
if (kh_get(MARK, marked, key) == kh_end(marked)) {
khiter_t kk = kh_get(HEAP, h->items, key);
heap_free_item(kh_value(h->items, kk));
kh_del(HEAP, h->items, kk);
}
}
}
kh_destroy(MARK, marked);
}

View File

@@ -1,241 +0,0 @@
#ifndef TYCHE_PRIV_H
#define TYCHE_PRIV_H
#include "tyche.h"
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
//
// INSTRUCTIONS
//
typedef enum {
// STACK OPERATIONS
TO_PUSHI = 0XA0,
TO_PUSHC = 0XA1,
TO_PUSHF = 0XA2,
TO_PUSHN = 0X00,
TO_PUSHZ = 0X01,
TO_PUSHT = 0X02,
TO_NEWA = 0X03,
TO_NEWT = 0X04,
TO_POP = 0X05,
TO_DUP = 0X06,
// LOCAL VARIABLES
TO_PUSHV = 0XA3,
TO_SET = 0XAE,
TO_DUPV = 0XA4,
TO_SETG = 0XA5,
TO_GETG = 0XA6,
// FUNCTION OPERATIONS
TO_CALL = 0XA7,
TO_RET = 0X10,
TO_RETI = 0X11,
// TABLE AND ARRAY OPERATIONS
TO_GETKV = 0X16,
TO_SETKV = 0X17,
TO_GETI = 0XA8,
TO_SETI = 0XA9,
TO_APPND = 0X18,
TO_NEXT = 0X19,
TO_SMT = 0X1A,
TO_MT = 0X1B,
// LOGICAL/ARITHMETIC
TO_SUM = 0X20,
TO_SUB = 0X21,
TO_MUL = 0X22,
TO_DIV = 0X23,
TO_IDIV = 0X24,
TO_MOD = 0X25,
TO_EQ = 0X26,
TO_NEQ = 0X27,
TO_LT = 0X28,
TO_LTE = 0X29,
TO_GT = 0X2A,
TO_GTE = 0X2B,
TO_AND = 0X2C,
TO_OR = 0X2D,
TO_XOR = 0X2E,
TO_POW = 0X2F,
TO_SHL = 0X30,
TO_SHR = 0X31,
// OTHER VALUE OPERATIONS
TO_LEN = 0X40,
TO_TYPE = 0X41,
TO_CAST = 0XAD,
TO_VER = 0X42,
// EXTERNAL CODE
TO_CMPL = 0X48,
TO_ASMBL = 0X49,
TO_LOAD = 0X4A,
// CONTROL FLOW
TO_BZ = 0XAA,
TO_BNZ = 0XAB,
TO_JMP = 0XAC,
// MEMORY MANAGEMENT
TO_GC = 0X4B,
} TYC_INST;
//
// TYPE DECLARATION
//
typedef struct {
TYC_TYPE type;
union {
int32_t i;
float f;
uint32_t idx;
} v;
} VALUE;
typedef struct Stack Stack;
typedef struct Array Array;
typedef struct Table Table;
typedef struct Heap Heap;
typedef struct Code Code;
typedef uint32_t HEAP_KEY;
typedef uint64_t TABLE_HASH;
typedef enum {
TC_STRING, TC_REAL, TC_INVALID_TYPE
} TYC_CONST_TYPE;
typedef struct Instruction {
TYC_INST operator;
int32_t operand;
uint8_t sz;
} Instruction;
//
// UTILS
//
__attribute__((noreturn)) void out_of_memory(void);
void* xmalloc(size_t n);
void* xcalloc(size_t n, size_t size);
void* xrealloc(void* p, size_t n);
//
// VALUE
//
TYC_TYPE value_type(VALUE v);
bool type_is_collectable(TYC_TYPE t);
int32_t value_integer(VALUE v);
float value_real(VALUE v);
uint32_t value_idx(VALUE v);
bool value_is_zero(VALUE v);
VALUE create_value_nil(void);
VALUE create_value_from_bool(bool b);
VALUE create_value_integer(int32_t v);
VALUE create_value_real(float f);
VALUE create_value_idx(TYC_TYPE type, uint32_t idx);
//
// STACK
//
Stack* stack_new(void);
void stack_destroy(Stack* s);
TYC_RESULT stack_push(Stack* s, VALUE v);
TYC_RESULT stack_peek(Stack const* s, VALUE* v_out);
TYC_RESULT stack_pop(Stack* s, VALUE* v_out);
size_t stack_size(Stack const* s);
TYC_RESULT stack_at(Stack const* s, int32_t key, VALUE* v);
TYC_RESULT stack_set(Stack* s, int32_t key, VALUE v);
size_t stack_top_fp(Stack const* s);
TYC_RESULT stack_push_fp(Stack* s);
TYC_RESULT stack_pop_fp(Stack* s);
size_t stack_fp_level(Stack const* s);
size_t stack_collectable_array(Stack const* s, VALUE** values);
//
// HEAP ARRAY
//
Array* array_new(void);
void array_destroy(Array* a);
size_t array_len(Array const* a);
VALUE array_get(Array const* a, size_t pos);
void array_set(Array* a, size_t pos, VALUE v);
void array_append(Array* a, VALUE v);
//
// HEAP TABLE
//
Table* table_new(Heap const* heap);
void table_destroy(Table* t);
size_t table_len(Table* t);
TYC_RESULT table_get(Table const* t, VALUE key, VALUE* value);
void table_set(Table* t, VALUE key, VALUE value);
void table_del(Table* t, VALUE key);
//
// HEAP
//
Heap* heap_new(void);
void heap_destroy(Heap* h);
HEAP_KEY heap_add_string(Heap* h, const char* value);
TYC_RESULT heap_get_string(Heap const* h, HEAP_KEY key, const char** value);
size_t heap_size(Heap const* h);
void heap_gc(Heap* h, VALUE const* roots, size_t n_roots);
//
// CODE
//
TYC_RESULT code_assemble(const char* code, uint8_t** bytecode, size_t* bytecode_sz);
Code* code_new(void);
void code_destroy(Code* code);
TYC_RESULT code_load_bytecode(Code* code, uint8_t const* bytecode, size_t bytecode_sz);
uint32_t code_n_consts(Code const* code);
TYC_CONST_TYPE code_const_type(Code const* code, size_t n);
T_REAL code_const_real(Code const* code, size_t n);
const char* code_const_string(Code const* code, size_t n);
uint32_t code_n_functions(Code const* code);
uint32_t code_function_sz(Code const* code, uint32_t f_id);
Instruction code_next_instruction(Code const* code, uint32_t function_id, uint32_t pc);
void code_debug_bytecode(Code const* code);
void code_decompile(Code const* code);
void code_parse_instruction(Instruction inst, char* outbuf, size_t sz);
//
// EXPRESSIONS
//
void expr_init(void);
TYC_RESULT binary_expr(TYC_EXPR op, VALUE a, VALUE b, VALUE* result);
#endif //TYCHE_PRIV_H

View File

@@ -1,148 +0,0 @@
#include "priv.h"
#include <assert.h>
#include <stdlib.h>
struct Stack {
VALUE* stack;
size_t stack_n;
size_t stack_cap;
uint32_t* fp;
size_t fp_n;
size_t fp_cap;
};
Stack* stack_new(void)
{
Stack* s = xcalloc(1, sizeof(Stack));
s->stack_n = 0;
s->fp_n = 0;
s->stack_cap = 64;
s->fp_cap = 8;
s->stack = xmalloc(s->stack_cap * sizeof s->stack[0]);
s->fp = xmalloc(s->stack_cap * sizeof s->fp[0]);
assert(s->stack);
assert(s->fp);
stack_push_fp(s);
return s;
}
void stack_destroy(Stack* s)
{
free(s->stack);
free(s->fp);
free(s);
}
TYC_RESULT stack_push(Stack* s, VALUE v)
{
if (s->stack_n == s->stack_cap) {
s->stack_cap *= 2;
s->stack = xrealloc(s->stack, s->stack_cap * sizeof s->stack[0]);
assert(s->stack);
}
s->stack[s->stack_n] = v;
++s->stack_n;
return T_OK;
}
size_t stack_top_fp(Stack const* s)
{
return s->fp[s->fp_n - 1];
}
TYC_RESULT stack_peek(Stack const* s, VALUE* v_out)
{
if (s->stack_n <= stack_top_fp(s))
return T_ERR_STACK_UNDERFLOW;
if (v_out)
*v_out = s->stack[s->stack_n - 1];
return T_OK;
}
TYC_RESULT stack_pop(Stack* s, VALUE* v_out)
{
TYC_RESULT err = stack_peek(s, v_out);
if (err)
return err;
--s->stack_n;
return T_OK;
}
size_t stack_size(Stack const* s)
{
return s->stack_n - stack_top_fp(s);
}
TYC_RESULT stack_at(Stack const* s, int32_t key, VALUE* v)
{
if (key >= 0) {
if ((int) stack_top_fp(s) + key >= (int) s->stack_n)
return T_ERR_STACK_ACCESS_OUT_OF_RANGE;
*v = s->stack[(int) stack_top_fp(s) + key];
} else {
if ((int) s->stack_n + key < (int) stack_top_fp(s))
return T_ERR_STACK_ACCESS_OUT_OF_RANGE;
*v = s->stack[(int) s->stack_n + key];
}
return T_OK;
}
TYC_RESULT stack_set(Stack* s, int32_t key, VALUE v)
{
if (key >= 0) {
if ((int) stack_top_fp(s) + key >= (int) s->stack_n)
return T_ERR_STACK_ACCESS_OUT_OF_RANGE;
s->stack[(int) stack_top_fp(s) + key] = v;
} else {
if ((int) s->stack_n + key < (int) stack_top_fp(s))
return T_ERR_STACK_ACCESS_OUT_OF_RANGE;
s->stack[(int) s->stack_n + key] = v;
}
return T_OK;
}
TYC_RESULT stack_push_fp(Stack* s)
{
if (s->fp_n == s->fp_cap) {
s->fp_cap *= 2;
s->fp = xrealloc(s->fp, s->fp_cap * sizeof s->fp[0]);
assert(s->fp);
}
s->fp[s->fp_n] = (uint32_t) s->stack_n;
++s->fp_n;
return T_OK;
}
TYC_RESULT stack_pop_fp(Stack* s)
{
if (s->fp_n == 1)
return T_ERR_STACK_FP_UNDERFLOW;
s->stack_n = stack_top_fp(s);
--s->fp_n;
return T_OK;
}
size_t stack_fp_level(Stack const* s)
{
return s->fp_n;
}
size_t stack_collectable_array(Stack const* s, VALUE** values)
{
size_t j = 0;
*values = xmalloc(stack_size(s) * sizeof(VALUE));
for (size_t i = 0; i < s->stack_n; ++i)
if (type_is_collectable(s->stack[i].type))
(*values)[j++] = s->stack[i];
return j;
}

View File

@@ -1,132 +0,0 @@
#include "priv.h"
#include "khash.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
KHASH_MAP_INIT_INT64(TABLE_INT, VALUE)
KHASH_MAP_INIT_STR(TABLE_STR, VALUE)
#pragma GCC diagnostic pop
struct Table {
khash_t(TABLE_INT)* tbl_int;
khash_t(TABLE_STR)* tbl_str;
Heap const* heap;
};
Table* table_new(Heap const* heap)
{
Table* t = xcalloc(1, sizeof(Table));
t->tbl_int = kh_init(TABLE_INT);
t->tbl_str = kh_init(TABLE_STR);
t->heap = heap;
return t;
}
void table_destroy(Table* t)
{
kh_destroy(TABLE_INT, t->tbl_int);
kh_destroy(TABLE_STR, t->tbl_str);
free(t);
}
size_t table_len(Table* t)
{
return kh_size(t->tbl_int) + kh_size(t->tbl_str);
}
static TABLE_HASH value_hash(VALUE v)
{
switch (value_type(v)) {
case TT_NIL:
return 0;
case TT_INTEGER:
return (uint64_t) value_integer(v);
case TT_REAL: {
uint32_t vv;
float f = value_real(v);
memcpy(&vv, &f, sizeof(uint32_t));
break;
}
case TT_STRING_CONST:
return (TABLE_HASH) value_idx(v) | ((TABLE_HASH) 1 << 33);
case TT_ARRAY:
return (TABLE_HASH) value_idx(v) | ((TABLE_HASH) 1 << 34);
case TT_TABLE:
return (TABLE_HASH) value_idx(v) | ((TABLE_HASH) 1 << 35);
case TT_FUNCTION:
return (TABLE_HASH) value_idx(v) | ((TABLE_HASH) 1 << 36);
case TT_NATIVE_PTR:
return (TABLE_HASH) value_idx(v) | ((TABLE_HASH) 1 << 37);
case TT_STRING:
case TT_COUNT__:
default:
__builtin_unreachable();
}
return 0;
}
TYC_RESULT table_get(Table const* t, VALUE key, VALUE* value)
{
if (value_type(key) == TT_STRING) {
const char* skey;
if (heap_get_string(t->heap, value_idx(key), &skey) != T_OK)
abort();
khiter_t k = kh_get(TABLE_STR, t->tbl_str, skey);
if (k == kh_end(t->tbl_str))
return T_ERR_TABLE_KEY_NOT_FOUND;
*value = kh_value(t->tbl_str, k);
} else {
TABLE_HASH hash = value_hash(key);
khiter_t k = kh_get(TABLE_INT, t->tbl_int, hash);
if (k == kh_end(t->tbl_int))
return T_ERR_TABLE_KEY_NOT_FOUND;
*value = kh_value(t->tbl_int, k);
}
return T_OK;
}
void table_set(Table* t, VALUE key, VALUE value)
{
int ret;
if (value_type(key) == TT_STRING) {
const char* skey;
if (heap_get_string(t->heap, value_idx(key), &skey) != T_OK)
abort();
khiter_t k = kh_put(TABLE_STR, t->tbl_str, skey, &ret);
if (ret < 0)
out_of_memory();
kh_value(t->tbl_str, k) = value;
} else {
TABLE_HASH hash = value_hash(key);
khiter_t k = kh_put(TABLE_INT, t->tbl_int, hash, &ret);
if (ret < 0)
out_of_memory();
kh_value(t->tbl_int, k) = value;
}
}
void table_del(Table* t, VALUE key)
{
if (value_type(key) == TT_STRING) {
const char* skey;
if (heap_get_string(t->heap, value_idx(key), &skey) != T_OK)
abort();
khiter_t k = kh_get(TABLE_STR, t->tbl_str, skey);
if (k == kh_end(t->tbl_str))
return;
kh_del(TABLE_STR, t->tbl_str, k);
} else {
TABLE_HASH hash = value_hash(key);
khiter_t k = kh_get(TABLE_INT, t->tbl_int, hash);
if (k == kh_end(t->tbl_int))
return;
kh_del(TABLE_INT, t->tbl_int, k);
}
}

View File

@@ -1,55 +0,0 @@
#ifndef TYCHE_TYCHE_H
#define TYCHE_TYCHE_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
typedef enum {
TT_NIL, TT_INTEGER, TT_REAL, TT_STRING, TT_STRING_CONST, TT_ARRAY, TT_TABLE, TT_FUNCTION, TT_NATIVE_PTR,
TT_COUNT__
} TYC_TYPE;
typedef enum {
T_OK = 0,
T_ERR_STACK_UNDERFLOW = -1, T_ERR_STACK_FP_UNDERFLOW = -2, T_ERR_STACK_ACCESS_OUT_OF_RANGE = -3,
T_ERR_HEAP_KEY_NOT_FOUND = -10,
T_ERR_TABLE_KEY_NOT_FOUND = -20,
T_ERR_ASSEMBLER_SYNTAX_ERROR = -30,
T_ERR_BYTECODE_TOO_SMALL = -40, T_ERR_BYTECODE_INVALID_MAGIC = -41,
T_ERR_TYPE_UNEXPECTED = -50, T_ERR_INVALID_OPCODE = -51, T_ERR_EXPR_INCORRECT_TYPES = -52, T_ERR_VALUE_OUT_OF_RANGE = -53,
} TYC_RESULT;
typedef enum {
TX_SUM, TX_SUB, TX_MUL, TX_IDIV, TX_EQ, TX_NEQ, TX_LT, TX_LTE, TX_GT, TX_GTE, TX_AND, TX_OR, TX_XOR, TX_POW,
TX_SHL, TX_SHR, TX_MOD,
TX_COUNT__
} TYC_EXPR;
#define T_REAL float
typedef struct TycheVM TycheVM;
// create/destroy VM
TycheVM* tyc_new(void);
void tyc_destroy(TycheVM* t);
// debugging (DEBUG_ASSEMBLY needs to be setup in compilation options)
void tyc_debug_to_console(TycheVM* T, bool activate);
void tyc_assembly_decompile(TycheVM* T);
void tyc_print_bytecode(TycheVM* T);
// code loading and execution
TYC_RESULT tyc_load_bytecode(TycheVM* T, uint8_t const* bytecode, size_t bytecode_sz);
TYC_RESULT tyc_call(TycheVM* t, uint16_t n_pars);
// stack manipulation and query
size_t tyc_stack_size(TycheVM* T);
void tyc_pushnil(TycheVM* T);
void tyc_pushinteger(TycheVM* T, int32_t value);
TYC_RESULT tyc_type(TycheVM* T, int idx, TYC_TYPE* type);
TYC_RESULT tyc_tointeger(TycheVM* T, int idx, int32_t* value);
TYC_RESULT tyc_tostring(TycheVM* T, int idx, const char** str);
TYC_RESULT tyc_expr(TycheVM* T, TYC_EXPR expr);
#endif //TYCHE_TYCHE_H

View File

@@ -1,31 +0,0 @@
#include "priv.h"
#include <stdlib.h>
#include <stdio.h>
__attribute__((noreturn)) void out_of_memory(void)
{
fprintf(stderr, "out of memory\n");
abort();
}
void* xmalloc(size_t n)
{
void* p = malloc(n);
if (!p) out_of_memory();
return p;
}
void* xcalloc(size_t n, size_t size)
{
void* p = calloc(n, size);
if (!p) out_of_memory();
return p;
}
void* xrealloc(void* p, size_t n)
{
void* q = realloc(p, n);
if (!q) out_of_memory();
return q;
}

View File

@@ -1,76 +0,0 @@
#include "priv.h"
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
TYC_TYPE value_type(VALUE v)
{
return v.type;
}
bool type_is_collectable(TYC_TYPE t)
{
return t == TT_STRING || t == TT_ARRAY || t == TT_TABLE;
}
int32_t value_integer(VALUE v)
{
#ifdef CHECK_TYCHE_BUGS
if (v.type != TT_INTEGER)
abort();
#endif
return v.v.i;
}
float value_real(VALUE v)
{
#ifdef CHECK_TYCHE_BUGS
if (v.type != TT_REAL)
abort();
#endif
return v.v.f;
}
uint32_t value_idx(VALUE v)
{
#ifdef CHECK_TYCHE_BUGS
if (v.type != TT_FUNCTION && v.type != TT_NATIVE_PTR && v.type != TT_ARRAY && v.type != TT_TABLE && v.type != TT_STRING && v.type != TT_STRING_CONST)
abort();
#endif
return v.v.idx;
}
VALUE create_value_nil(void)
{
return (VALUE) { .type = TT_NIL };
}
VALUE create_value_from_bool(bool b)
{
return b ? create_value_integer(1) : create_value_integer(0);
}
VALUE create_value_integer(int32_t v)
{
return (VALUE) { .type = TT_INTEGER, .v = { .i = v } };
}
VALUE create_value_real(float f)
{
return (VALUE) { .type = TT_REAL, .v = { .f = f } };
}
VALUE create_value_idx(TYC_TYPE type, uint32_t idx)
{
#ifdef CHECK_TYCHE_BUGS
if (type != TT_FUNCTION && type != TT_NATIVE_PTR && type != TT_ARRAY && type != TT_TABLE && type != TT_STRING && type != TT_STRING_CONST)
abort();
#endif
return (VALUE) { .type = type, .v = { .idx = idx } };
}
bool value_is_zero(VALUE v)
{
return v.type == TT_NIL || (v.type == TT_INTEGER && v.v.i == 0);
}

468
lib/vm.c
View File

@@ -1,468 +0,0 @@
#include "priv.h"
#include <stdlib.h>
#include <stdio.h>
typedef struct Location {
uint32_t function_id;
uint32_t pc;
} Location;
typedef struct LocationStack {
Location* locations;
size_t sz;
size_t cap;
} LocationStack;
struct TycheVM {
Stack* stack;
Heap* heap;
Code* code;
LocationStack location_stack;
bool debug;
};
static TYC_RESULT step(TycheVM* T);
#define TRY(x) if ((r = (x)) != T_OK) { return r; }
//
// CREATE/DESTROY VM
//
TycheVM* tyc_new(void)
{
TycheVM* t = xcalloc(1, sizeof(TycheVM));
t->stack = stack_new();
t->heap = heap_new();
t->code = code_new();
t->location_stack = (LocationStack) {
.locations = xmalloc(4 * sizeof(Location)),
.cap = 4,
.sz = 0,
};
t->debug = false;
expr_init();
return t;
}
void tyc_destroy(TycheVM* t)
{
free(t->location_stack.locations);
code_destroy(t->code);
heap_destroy(t->heap);
stack_destroy(t->stack);
free(t);
}
//
// DEBUGGING
//
void tyc_debug_to_console(TycheVM* T, bool activate)
{
T->debug = activate;
}
#ifdef DEBUG_ASSEMBLY
static void debug_instruction(TycheVM* T, Location* loc, Instruction inst)
{
if (!T->debug)
return;
char buf[50];
code_parse_instruction(inst, buf, sizeof(buf));
printf(": %02d-%04d %s ", loc->function_id, loc->pc, buf);
}
static void debug_value(TycheVM* T, VALUE a)
{
switch (value_type(a)) {
case TT_NIL:
printf("[nil]");
break;
case TT_INTEGER:
printf("[%d]", value_integer(a));
break;
case TT_REAL:
printf("[%f]", (double) value_real(a));
break;
case TT_STRING: {
const char* str;
if (heap_get_string(T->heap, value_idx(a), &str) == T_OK)
printf("[\"%s\"]", str);
else
printf("[\"(not found)\"]");
break;
}
case TT_STRING_CONST: {
if (code_const_type(T->code, value_idx(a)) != TC_STRING)
printf("[\"(const not a string)\"]");
else
printf("[\"%s\"]", code_const_string(T->code, value_idx(a)));
break;
}
case TT_ARRAY:
printf("[(not implemented)]\n");
abort();
case TT_TABLE:
printf("[(not implemented )]\n");
abort();
case TT_FUNCTION:
printf("[func %d]", value_idx(a));
break;
case TT_NATIVE_PTR:
printf("[ptr %p]", (void *) (intptr_t) value_idx(a));
break;
case TT_COUNT__:
__builtin_unreachable();
}
}
static void debug_stack(TycheVM* T)
{
if (!T->debug)
return;
if (stack_size(T->stack) == 0) {
printf("|empty|\n");
return;
}
for (size_t i = 0; i < stack_size(T->stack); ++i) {
VALUE a;
stack_at(T->stack, (int32_t) i, &a);
debug_value(T, a);
printf(" ");
}
printf("\n");
}
void tyc_assembly_decompile(TycheVM* T)
{
code_decompile(T->code);
}
void tyc_print_bytecode(TycheVM* T)
{
code_debug_bytecode(T->code);
}
#endif
//
// LOCATION STACK
//
static void push_location(TycheVM* T, uint32_t function_id, uint32_t pc)
{
if (T->location_stack.sz == T->location_stack.cap) {
T->location_stack.cap *= 2;
T->location_stack.locations = xrealloc(T->location_stack.locations, T->location_stack.cap * sizeof(Location));
}
T->location_stack.locations[T->location_stack.sz] = (Location) {
.function_id = function_id,
.pc = pc,
};
++T->location_stack.sz;
}
static Location* location_top(TycheVM* T)
{
if (T->location_stack.sz == 0)
abort();
return &T->location_stack.locations[T->location_stack.sz - 1];
}
static void location_pop(TycheVM* T)
{
if (T->location_stack.sz == 0)
abort();
--T->location_stack.sz;
}
//
// CODE LOADING AND EXECUTION
//
TYC_RESULT tyc_load_bytecode(TycheVM* T, uint8_t const* bytecode, size_t bytecode_sz)
{
TYC_RESULT r;
TRY(code_load_bytecode(T->code, bytecode, bytecode_sz))
TRY(stack_push(T->stack, create_value_idx(TT_FUNCTION, 0 /* main */)))
return T_OK;
}
static TYC_RESULT enter_function(TycheVM* T, uint16_t n_pars)
{
TYC_RESULT r;
// get parameters
VALUE* params = xcalloc(n_pars + 1, sizeof(VALUE));
for (uint16_t i = 0; i < n_pars; ++i)
TRY(stack_pop(T->stack, &params[i]))
// get function
VALUE function;
TRY(stack_pop(T->stack, &function))
if (value_type(function) != TT_FUNCTION)
return T_ERR_TYPE_UNEXPECTED;
// enter function
push_location(T, value_idx(function), 0);
stack_push_fp(T->stack);
// pass parameters
for (int i = n_pars-1; i >= 0; --i)
TRY(stack_push(T->stack, params[i]))
free(params);
return T_OK;
}
static TYC_RESULT run_until_return(TycheVM* T)
{
TYC_RESULT r;
size_t level = stack_fp_level(T->stack);
while (stack_fp_level(T->stack) >= level)
TRY(step(T))
return T_OK;
}
TYC_RESULT tyc_call(TycheVM* T, uint16_t n_pars)
{
TYC_RESULT r;
TRY(enter_function(T, n_pars))
TRY(run_until_return(T))
return T_OK;
}
//
// STACK MANIPULATION AND QUERY
//
size_t tyc_stack_size(TycheVM* T)
{
return stack_size(T->stack);
}
void tyc_pushnil(TycheVM* T)
{
stack_push(T->stack, create_value_nil());
}
void tyc_pushinteger(TycheVM* T, int32_t value)
{
stack_push(T->stack, create_value_integer(value));
}
TYC_RESULT tyc_type(TycheVM* T, int idx, TYC_TYPE* type)
{
VALUE v;
TYC_RESULT r = stack_at(T->stack, idx, &v);
if (r == T_OK)
*type = v.type;
return r;
}
TYC_RESULT tyc_tointeger(TycheVM* T, int idx, int32_t* value)
{
VALUE v;
TYC_RESULT r;
TRY(stack_at(T->stack, idx, &v))
if (v.type != TT_INTEGER)
return T_ERR_TYPE_UNEXPECTED;
*value = value_integer(v);
return T_OK;
}
TYC_RESULT tyc_tostring(TycheVM* T, int idx, const char** str)
{
VALUE v;
TYC_RESULT r;
TRY(stack_at(T->stack, idx, &v))
if (v.type == TT_STRING)
return heap_get_string(T->heap, value_idx(v), str);
else if (v.type == TT_STRING_CONST)
*str = code_const_string(T->code, value_idx(v));
else
return T_ERR_TYPE_UNEXPECTED;
return T_OK;
}
TYC_RESULT tyc_expr(TycheVM* T, TYC_EXPR op)
{
TYC_RESULT r;
VALUE v1, v2, result;
stack_pop(T->stack, &v2);
stack_pop(T->stack, &v1);
TRY(binary_expr(op, v1, v2, &result))
stack_push(T->stack, result);
return T_OK;
}
//
// STEP
//
static TYC_RESULT step(TycheVM* T)
{
VALUE a;
TYC_RESULT r;
Location* loc = location_top(T);
Instruction inst = code_next_instruction(T->code, loc->function_id, loc->pc);
#ifdef DEBUG_ASSEMBLY
debug_instruction(T, loc, inst);
#endif
switch (inst.operator) {
//
// stack manipulation
//
case TO_PUSHN:
tyc_pushnil(T);
break;
case TO_PUSHI:
tyc_pushinteger(T, inst.operand);
break;
case TO_PUSHF:
if (inst.operand < 0 || inst.operand >= (int) code_n_functions(T->code))
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_push(T->stack, create_value_idx(TT_FUNCTION, (uint32_t) inst.operand)))
break;
case TO_PUSHC:
if (inst.operand < 0 || inst.operand >= (int) code_n_consts(T->code))
return T_ERR_VALUE_OUT_OF_RANGE;
if (code_const_type(T->code, (size_t) inst.operand) == TC_STRING) {
TRY(stack_push(T->stack, create_value_idx(TT_STRING_CONST, inst.operand)))
} else {
abort(); // REAL consts not supported for now
}
break;
case TO_POP:
TRY(stack_pop(T->stack, NULL))
break;
//
// local variables
//
case TO_PUSHV:
if (inst.operand <= 0)
return T_ERR_VALUE_OUT_OF_RANGE;
for (int i = 0; i < inst.operand; ++i)
tyc_pushnil(T);
break;
case TO_SET:
if (inst.operand < 0)
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_pop(T->stack, &a))
TRY(stack_set(T->stack, inst.operand, a))
break;
case TO_DUPV:
if (inst.operand < 0)
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_at(T->stack, inst.operand, &a))
stack_push(T->stack, a);
break;
//
// expressions
//
case TO_SUM: TRY(tyc_expr(T, TX_SUM)); break;
case TO_SUB: TRY(tyc_expr(T, TX_SUB)); break;
case TO_MUL: TRY(tyc_expr(T, TX_MUL)); break;
case TO_IDIV: TRY(tyc_expr(T, TX_IDIV)); break;
case TO_EQ: TRY(tyc_expr(T, TX_EQ)); break;
case TO_NEQ: TRY(tyc_expr(T, TX_NEQ)); break;
case TO_LT: TRY(tyc_expr(T, TX_LT)); break;
case TO_LTE: TRY(tyc_expr(T, TX_LTE)); break;
case TO_GT: TRY(tyc_expr(T, TX_GT)); break;
case TO_GTE: TRY(tyc_expr(T, TX_GTE)); break;
case TO_AND: TRY(tyc_expr(T, TX_AND)); break;
case TO_OR: TRY(tyc_expr(T, TX_OR)); break;
case TO_XOR: TRY(tyc_expr(T, TX_XOR)); break;
case TO_POW: TRY(tyc_expr(T, TX_POW)); break;
case TO_SHL: TRY(tyc_expr(T, TX_SHL)); break;
case TO_SHR: TRY(tyc_expr(T, TX_SHR)); break;
case TO_MOD: TRY(tyc_expr(T, TX_MOD)); break;
//
// function calls
//
case TO_CALL:
if (inst.operand < 0)
return T_ERR_VALUE_OUT_OF_RANGE;
enter_function(T, (uint16_t) inst.operand);
break;
case TO_RET:
TRY(stack_pop(T->stack, &a))
TRY(stack_pop_fp(T->stack))
TRY(stack_push(T->stack, a))
location_pop(T);
goto dont_update_pc;
//
// jumps/branching
//
case TO_JMP:
if (inst.operand < 0 || inst.operand >= code_function_sz(T->code, loc->function_id))
return T_ERR_VALUE_OUT_OF_RANGE;
loc->pc = (uint32_t) inst.operand;
goto dont_update_pc;
case TO_BZ:
if (inst.operand < 0 || inst.operand >= code_function_sz(T->code, loc->function_id))
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_pop(T->stack, &a))
if (value_is_zero(a)) {
loc->pc = (uint32_t) inst.operand;
goto dont_update_pc;
}
break;
case TO_BNZ:
if (inst.operand < 0 || inst.operand >= code_function_sz(T->code, loc->function_id))
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_pop(T->stack, &a))
if (!value_is_zero(a)) {
loc->pc = (uint32_t) inst.operand;
goto dont_update_pc;
}
break;
default:
return T_ERR_INVALID_OPCODE;
}
// TODO - print stack
loc->pc += inst.sz;
dont_update_pc:
#ifdef DEBUG_ASSEMBLY
debug_stack(T);
#endif
return T_OK;
}

View File

@@ -1,37 +0,0 @@
Progress of the Lua port:
- [x] Assembler
- [x] Basic VM execution
- [x] Logic/arithmetic expressions
- [x] Variables
- [x] Local variables
- [x] Functions
- [x] Calling functions
- [x] Calling functions with parameters
- [x] Control flow
- [x] Labels in Assembly
- [x] Recursion
- [x] Strings
- [x] From constants
- [x] Garbage collection
- [x] Arrays
- [x] Garbage collection
- [ ] Tables
- [ ] Garbage collection
- [ ] Metatables
- [ ] Real
- [ ] Globals
- [ ] Error handling
- [ ] Stack traces in case of errors
- [ ] Closures/upvalues
- [ ] Assembler generate bytecode
- [ ] VM interpret it
## C interface
- [ ] Error management (decision)
- [ ] Format for value and heap value
- [ ] Transparency and log levels

View File

@@ -1,35 +0,0 @@
Bytecode format
---------------
The bytecode file is composed of the following sections:
* HEADER: 16-byte header
[0:3]: Magic
[4]: VM format
[rest]: Reserved for future use
* TABLE_OF_CONTENTS: list of 8 records pointing to each one of the sections
Each record (6 bytes):
- Pointer to section: 4 bytes
- Number of records in section: 2 bytes
* [0x0] Constants indexes: pointers to each of the constant locations
* Table of 4-byte constant indexes with pointer to constant
(counter start at beginning of raw constants)
* [0x1] Functions indexes: Pointer to functions within the code
[0:3]: function pointer (counter start at the beginning of executable code)
[4:5]: number of parameters
[6:7]: number of local variables
[8:b]: function size
* [0x2] Constants raw data
* [0x3] Code: executable code
* [0x4] Debugging info
???
The max file size is 2 Gb.
## Values can be encoded in the following ways:
* The type is defined by the operator.
* Encoding varies according to the type:
int: use protobuf format
float: 4-bit floating point
string: int-defined length, followed by the string proper - no null terminator
* Constant indexes and function ids are encoded as ints

View File

@@ -1,96 +0,0 @@
Operations
----------
Operations take either 0 or 1 parameter. The ones that take a parameter, it can be either a int8, int16 or int32.
Instructions follow this logic:
00 ~ 9F : no parameter
A0 ~ BF : int8 (1 byte)
C0 ~ DF : int16 (2 bytes)
E0 ~ FF : int32 (4 bytes)
The operations of 1, 2 and 4 bytes are always interchangeable by adding/subtracting 0x20.
,----------- no parameter
| ,-------- int8
| | ,----- int16
| | | ,-- int32
NP I8 I16 I32 Opc Instruction Description
Stack operations:
a0 c0 e0 pushi [int] Push int
a1 c1 e1 pushc [index] Push constant
a2 c2 e2 pushf [function] Push function id
00 pushn Push nil
01 pushz Push zero (or false)
02 pusht Push true
03 newa Push (create) empty array
04 newt Push (create) empty table
05 pop
06 dup
Local variables:
a3 c3 e3 pushv [int] Push n nil values into the stack (used to init local vars)
ab cb eb set [index] Set value in stack position (set local variable)
a4 c4 e4 dupv [index] Duplicate stack value (load local variable)
a5 c5 e5 setg [int] Set global variable
a6 c6 e6 getg [int] Get global variable
Function operations:
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)
11 retn Leave a function (return nil)
Table and array operations:
16 getkv Get table's value based on key (pull 1 value, push 1 value)
17 setkv Set table's key and value (pull 2 values from stack)
a8 c8 e8 geti Get array's position value
a9 c9 e9 seti Set array's position value
18 appnd Add value to the end of array
19 next Push the next pair into the stack (for loops)
1a smt Set value metatable
1b mt Get value metatable
Logical/arithmetic:
20 sum Sum top 2 values in stack
21 sub Subtract top 2 values in stack
22 mul Multiply top 2 values in stack
23 div Float division
24 idiv Integer division
25 mod Modulo
26 eq Equality
27 neq Inequality
28 lt Less than
29 lte Less than or equals
2a gt Greater than
2b gte Greater than or equals
2c and Bitwise AND
2d or Bitwise OR
2e xor Bitwise XOR
2f pow Power
30 shl Shift left
31 shr Shift right
Other value operations:
40 len Get table, array or string size
41 type Get type from value at the top of the stack
aa cast [type] Cast type to another type
42 ver Return VM version
External code:
48 cmpl Compile code to assembly
49 asmbl Assemble code to bytecode format
4a load Load bytecode as function (will place function on stack)
Control flow (the destination is always a 16-bit field):
ca bz [pc] Branch if zero
cb bnz [pc] Branch if not zero
cc jmp [pc] Unconditional jump
* Jumps can only happen within the same function.
Memory management:
4b gc Call garbage collector
Error handling: (0xa0~0xaf)
???

View File

@@ -1,15 +0,0 @@
Internal handling of values
---------------------------
## Supported types
Nil 0
Integer 1
Float 2
String 3
Array 4
Table 5
Function 6
NativePointer 7
## Internal format
???

View File

@@ -1,584 +0,0 @@
local pprint = { VERSION = '0.1' }
local depth = 1
pprint.defaults = {
-- If set to number N, then limit table recursion to N deep.
depth_limit = false,
-- type display trigger, hide not useful datatypes by default
-- custom types are treated as table
show_nil = true,
show_boolean = true,
show_number = true,
show_string = true,
show_table = true,
show_function = false,
show_thread = false,
show_userdata = false,
-- additional display trigger
show_metatable = false, -- show metatable
show_all = false, -- override other show settings and show everything
use_tostring = false, -- use __tostring to print table if available
filter_function = nil, -- called like callback(value[,key, parent]), return truty value to hide
object_cache = 'local', -- cache blob and table to give it a id, 'local' cache per print, 'global' cache
-- per process, falsy value to disable (might cause infinite loop)
-- format settings
indent_size = 2, -- indent for each nested table level
level_width = 80, -- max width per indent level
wrap_string = true, -- wrap string when it's longer than level_width
wrap_array = false, -- wrap every array elements
string_is_utf8 = true, -- treat string as utf8, and count utf8 char when wrapping, if possible
sort_keys = true, -- sort table keys
}
local TYPES = {
['nil'] = 1, ['boolean'] = 2, ['number'] = 3, ['string'] = 4,
['table'] = 5, ['function'] = 6, ['thread'] = 7, ['userdata'] = 8
}
-- seems this is the only way to escape these, as lua don't know how to map char '\a' to 'a'
local ESCAPE_MAP = {
['\a'] = '\\a', ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r',
['\t'] = '\\t', ['\v'] = '\\v', ['\\'] = '\\\\',
}
-- generic utilities
local tokenize_string = function(s)
local t = {}
for i = 1, #s do
local c = s:sub(i, i)
local b = c:byte()
local e = ESCAPE_MAP[c]
if (b >= 0x20 and b < 0x80) or e then
local s = e or c
t[i] = { char = s, len = #s }
else
t[i] = { char = string.format('\\x%02x', b), len = 4 }
end
if c == '"' then
t.has_double_quote = true
elseif c == "'" then
t.has_single_quote = true
end
end
return t
end
local tokenize_utf8_string = tokenize_string
local has_lpeg, lpeg = pcall(require, 'lpeg')
if has_lpeg then
local function utf8_valid_char(c)
return { char = c, len = 1 }
end
local function utf8_invalid_char(c)
local b = c:byte()
local e = ESCAPE_MAP[c]
if (b >= 0x20 and b < 0x80) or e then
local s = e or c
return { char = s, len = #s }
else
return { char = string.format('\\x%02x', b), len = 4 }
end
end
local cont = lpeg.R('\x80\xbf')
local utf8_char =
lpeg.R('\x20\x7f') +
lpeg.R('\xc0\xdf') * cont +
lpeg.R('\xe0\xef') * cont * cont +
lpeg.R('\xf0\xf7') * cont * cont * cont
local utf8_capture = (((utf8_char / utf8_valid_char) + (lpeg.P(1) / utf8_invalid_char)) ^ 0) * -1
tokenize_utf8_string = function(s)
local dq = s:find('"')
local sq = s:find("'")
local t = table.pack(utf8_capture:match(s))
t.has_double_quote = not not dq
t.has_single_quote = not not sq
return t
end
end
local function is_plain_key(key)
return type(key) == 'string' and key:match('^[%a_][%a%d_]*$')
end
local CACHE_TYPES = {
['table'] = true, ['function'] = true, ['thread'] = true, ['userdata'] = true
}
-- cache would be populated to be like:
-- {
-- function = { `fun1` = 1, _cnt = 1 }, -- object id
-- table = { `table1` = 1, `table2` = 2, _cnt = 2 },
-- visited_tables = { `table1` = 7, `table2` = 8 }, -- visit count
-- }
-- use weakrefs to avoid accidentall adding refcount
local function cache_apperance(obj, cache, option)
if not cache.visited_tables then
cache.visited_tables = setmetatable({}, {__mode = 'k'})
end
local t = type(obj)
-- TODO can't test filter_function here as we don't have the ix and key,
-- might cause different results?
-- respect show_xxx and filter_function to be consistent with print results
if (not TYPES[t] and not option.show_table)
or (TYPES[t] and not option['show_'..t]) then
return
end
if CACHE_TYPES[t] or TYPES[t] == nil then
if not cache[t] then
cache[t] = setmetatable({}, {__mode = 'k'})
cache[t]._cnt = 0
end
if not cache[t][obj] then
cache[t]._cnt = cache[t]._cnt + 1
cache[t][obj] = cache[t]._cnt
end
end
if t == 'table' or TYPES[t] == nil then
if cache.visited_tables[obj] == false then
-- already printed, no need to mark this and its children anymore
return
elseif cache.visited_tables[obj] == nil then
cache.visited_tables[obj] = 1
else
-- visited already, increment and continue
cache.visited_tables[obj] = cache.visited_tables[obj] + 1
return
end
for k, v in pairs(obj) do
cache_apperance(k, cache, option)
cache_apperance(v, cache, option)
end
local mt = getmetatable(obj)
if mt and option.show_metatable then
cache_apperance(mt, cache, option)
end
end
end
-- makes 'foo2' < 'foo100000'. string.sub makes substring anyway, no need to use index based method
local function str_natural_cmp(lhs, rhs)
while #lhs > 0 and #rhs > 0 do
local lmid, lend = lhs:find('%d+')
local rmid, rend = rhs:find('%d+')
if not (lmid and rmid) then return lhs < rhs end
local lsub = lhs:sub(1, lmid-1)
local rsub = rhs:sub(1, rmid-1)
if lsub ~= rsub then
return lsub < rsub
end
local lnum = tonumber(lhs:sub(lmid, lend))
local rnum = tonumber(rhs:sub(rmid, rend))
if lnum ~= rnum then
return lnum < rnum
end
lhs = lhs:sub(lend+1)
rhs = rhs:sub(rend+1)
end
return lhs < rhs
end
local function cmp(lhs, rhs)
local tleft = type(lhs)
local tright = type(rhs)
if tleft == 'number' and tright == 'number' then return lhs < rhs end
if tleft == 'string' and tright == 'string' then return str_natural_cmp(lhs, rhs) end
if tleft == tright then return str_natural_cmp(tostring(lhs), tostring(rhs)) end
-- allow custom types
local oleft = TYPES[tleft] or 9
local oright = TYPES[tright] or 9
return oleft < oright
end
-- setup option with default
local function make_option(option)
if option == nil then
option = {}
end
for k, v in pairs(pprint.defaults) do
if option[k] == nil then
option[k] = v
end
if option.show_all then
for t, _ in pairs(TYPES) do
option['show_'..t] = true
end
option.show_metatable = true
end
end
return option
end
-- override defaults and take effects for all following calls
function pprint.setup(option)
pprint.defaults = make_option(option)
end
-- format lua object into a string
function pprint.pformat(obj, option, printer)
option = make_option(option)
local buf = {}
local function default_printer(s)
table.insert(buf, s)
end
printer = printer or default_printer
local cache
if option.object_cache == 'global' then
-- steal the cache into a local var so it's not visible from _G or anywhere
-- still can't avoid user explicitly referentce pprint._cache but it shouldn't happen anyway
cache = pprint._cache or {}
pprint._cache = nil
elseif option.object_cache == 'local' then
cache = {}
end
local last = '' -- used for look back and remove trailing comma
local status = {
indent = '', -- current indent
len = 0, -- current line length
printed_something = false, -- used to remove leading new lines
}
local wrapped_printer = function(s)
status.printed_something = true
printer(last)
last = s
end
local function _indent(d)
status.indent = string.rep(' ', d + #(status.indent))
end
local function _n(d)
if not status.printed_something then return end
wrapped_printer('\n')
wrapped_printer(status.indent)
if d then
_indent(d)
end
status.len = 0
return true -- used to close bracket correctly
end
local function _p(s, nowrap)
status.len = status.len + #s
if not nowrap and status.len > option.level_width then
_n()
wrapped_printer(s)
status.len = #s
else
wrapped_printer(s)
end
end
local formatter = {}
local function format(v)
local f = formatter[type(v)]
f = f or formatter.table -- allow patched type()
if option.filter_function and option.filter_function(v, nil, nil) then
return ''
else
return f(v)
end
end
local function tostring_formatter(v)
return tostring(v)
end
local function number_formatter(n)
return n == math.huge and '[[math.huge]]' or tostring(n)
end
local function nop_formatter(v)
return ''
end
local function make_fixed_formatter(t, has_cache)
if has_cache then
return function (v)
return string.format('[[%s %d]]', t, cache[t][v])
end
else
return function (v)
return '[['..t..']]'
end
end
end
local function string_formatter(s, force_long_quote)
local tokens = option.string_is_utf8 and tokenize_utf8_string(s) or tokenize_string(s)
local string_len = 0
local escape_quotes = tokens.has_double_quote and tokens.has_single_quote
for _, token in ipairs(tokens) do
if escape_quotes and token.char == '"' then
string_len = string_len + 2
else
string_len = string_len + token.len
end
end
local quote_len = 2
local long_quote_dashes = 0
local function compute_long_quote_dashes()
local keep_looking = true
while keep_looking do
if s:find('%]' .. string.rep('=', long_quote_dashes) .. '%]') then
long_quote_dashes = long_quote_dashes + 1
else
keep_looking = false
end
end
end
if force_long_quote then
compute_long_quote_dashes()
quote_len = 2 + long_quote_dashes
end
if quote_len + string_len + status.len > option.level_width then
_n()
-- only wrap string when is longer than level_width
if option.wrap_string and string_len + quote_len > option.level_width then
if not force_long_quote then
compute_long_quote_dashes()
quote_len = 2 + long_quote_dashes
end
-- keep the quotes together
local dashes = string.rep('=', long_quote_dashes)
_p('[' .. dashes .. '[', true)
local status_len = status.len
local line_len = 0
local line = ''
for _, token in ipairs(tokens) do
if line_len + token.len + status_len > option.level_width then
_n()
_p(line, true)
line_len = token.len
line = token.char
else
line_len = line_len + token.len
line = line .. token.char
end
end
return line .. ']' .. dashes .. ']'
end
end
if tokens.has_double_quote and tokens.has_single_quote and not force_long_quote then
for i, token in ipairs(tokens) do
if token.char == '"' then
tokens[i].char = '\\"'
end
end
end
local flat_table = {}
for _, token in ipairs(tokens) do
table.insert(flat_table, token.char)
end
local concat = table.concat(flat_table)
if force_long_quote then
local dashes = string.rep('=', long_quote_dashes)
return '[' .. dashes .. '[' .. concat .. ']' .. dashes .. ']'
elseif tokens.has_single_quote then
-- use double quote
return '"' .. concat .. '"'
else
-- use single quote
return "'" .. concat .. "'"
end
end
local function table_formatter(t)
if option.use_tostring then
local mt = getmetatable(t)
if mt and mt.__tostring then
return string_formatter(tostring(t), true)
end
end
local print_header_ix = nil
local ttype = type(t)
if option.object_cache then
local cache_state = cache.visited_tables[t]
local tix = cache[ttype][t]
-- FIXME should really handle `cache_state == nil`
-- as user might add things through filter_function
if cache_state == false then
-- already printed, just print the the number
return string_formatter(string.format('%s %d', ttype, tix), true)
elseif cache_state > 1 then
-- appeared more than once, print table header with number
print_header_ix = tix
cache.visited_tables[t] = false
else
-- appeared exactly once, print like a normal table
end
end
local limit = tonumber(option.depth_limit)
if limit and depth > limit then
if print_header_ix then
return string.format('[[%s %d]]...', ttype, print_header_ix)
end
return string_formatter(tostring(t), true)
end
local tlen = #t
local wrapped = false
_p('{')
_indent(option.indent_size)
_p(string.rep(' ', option.indent_size - 1))
if print_header_ix then
_p(string.format('--[[%s %d]] ', ttype, print_header_ix))
end
for ix = 1,tlen do
local v = t[ix]
if formatter[type(v)] == nop_formatter or
(option.filter_function and option.filter_function(v, ix, t)) then
-- pass
else
if option.wrap_array then
wrapped = _n()
end
depth = depth+1
_p(format(v)..', ')
depth = depth-1
end
end
-- hashmap part of the table, in contrast to array part
local function is_hash_key(k)
if type(k) ~= 'number' then
return true
end
local numkey = math.floor(tonumber(k))
if numkey ~= k or numkey > tlen or numkey <= 0 then
return true
end
end
local function print_kv(k, v, t)
-- can't use option.show_x as obj may contain custom type
if formatter[type(v)] == nop_formatter or
formatter[type(k)] == nop_formatter or
(option.filter_function and option.filter_function(v, k, t)) then
return
end
wrapped = _n()
if is_plain_key(k) then
_p(k, true)
else
_p('[')
-- [[]] type string in key is illegal, needs to add spaces inbetween
local k = format(k)
if string.match(k, '%[%[') then
_p(' '..k..' ', true)
else
_p(k, true)
end
_p(']')
end
_p(' = ', true)
depth = depth+1
_p(format(v), true)
depth = depth-1
_p(',', true)
end
if option.sort_keys then
local keys = {}
for k, _ in pairs(t) do
if is_hash_key(k) then
table.insert(keys, k)
end
end
table.sort(keys, cmp)
for _, k in ipairs(keys) do
print_kv(k, t[k], t)
end
else
for k, v in pairs(t) do
if is_hash_key(k) then
print_kv(k, v, t)
end
end
end
if option.show_metatable then
local mt = getmetatable(t)
if mt then
print_kv('__metatable', mt, t)
end
end
_indent(-option.indent_size)
-- make { } into {}
last = string.gsub(last, '^ +$', '')
-- peek last to remove trailing comma
last = string.gsub(last, ',%s*$', ' ')
if wrapped then
_n()
end
_p('}')
return ''
end
-- set formatters
formatter['nil'] = option.show_nil and tostring_formatter or nop_formatter
formatter['boolean'] = option.show_boolean and tostring_formatter or nop_formatter
formatter['number'] = option.show_number and number_formatter or nop_formatter -- need to handle math.huge
formatter['function'] = option.show_function and make_fixed_formatter('function', option.object_cache) or nop_formatter
formatter['thread'] = option.show_thread and make_fixed_formatter('thread', option.object_cache) or nop_formatter
formatter['userdata'] = option.show_userdata and make_fixed_formatter('userdata', option.object_cache) or nop_formatter
formatter['string'] = option.show_string and string_formatter or nop_formatter
formatter['table'] = option.show_table and table_formatter or nop_formatter
if option.object_cache then
-- needs to visit the table before start printing
cache_apperance(obj, cache, option)
end
_p(format(obj))
printer(last) -- close the buffered one
-- put cache back if global
if option.object_cache == 'global' then
pprint._cache = cache
end
return table.concat(buf)
end
-- pprint all the arguments
function pprint.pprint( ... )
local args = {...}
-- select will get an accurate count of array len, counting trailing nils
local len = select('#', ...)
for ix = 1,len do
pprint.pformat(args[ix], nil, io.write)
io.write('\n')
end
end
setmetatable(pprint, {
__call = function (_, ...)
pprint.pprint(...)
end
})
return pprint

View File

@@ -1,461 +0,0 @@
#!/usr/bin/env lua
local pprint = require('pprint')
local assemble = require('tyche-as')
local VM = require('tyche-vm')
----------------------
-- --
-- SUPPORT --
-- --
----------------------
function TEST(name)
print("### " .. name)
end
function assert_eq(found, expected, key)
assert(type(found) == type(expected), 'Types not matching , expected "' .. pprint.pformat(expected) .. '", found "' .. pprint.pformat(found) .. '".' .. ((key ~= nil) and ('(key: ' .. key .. ')') or ''))
if type(found) == 'table' then
assert(#found == #expected, "Tables are of different sizes " .. ((key ~= nil) and ('(key: ' .. key .. ')') or ''))
for k,v in pairs(found) do
assert_eq(v, expected[k], k)
end
for k,v in pairs(expected) do
assert_eq(v, found[k], k)
end
else
assert(found == expected, 'Assertion failed, expected "' .. pprint.pformat(expected) .. '", found "' .. pprint.pformat(found) .. '".')
end
end
----------------------
-- --
-- PARSER --
-- --
----------------------
do TEST "Parser"
local source = [[
.const
0: 3.14
1: "Hello world"
.func 0
pushi 2 ; this is a comment
pushi 3
sum
ret
.func 1
pushi 5000
ret ]]
local expected = {
constants = { [0] = 3.14, [1] = "Hello world" },
functions = {
[0] = {
{ "pushi", 2 },
{ "pushi", 3 },
{ "sum" },
{ "ret" },
},
[1] = {
{ "pushi", 5000 },
{ "ret" },
}
}
}
local found = assemble(source)
-- pprint(expected)
-- pprint(found)
assert_eq(found, expected)
end
do TEST "Parser: labels"
local source = [[
.func 0
jmp @my_label
pushi 3
@my_label:
ret ]]
local expected = {
constants = {},
functions = {
[0] = {
{ "jmp", "@my_label" },
{ "pushi", 3 },
{ "ret", labels = { "@my_label" } },
}
}
}
local found = assemble(source)
assert_eq(found, expected)
end
----------------------
-- --
-- STACK --
-- --
----------------------
do TEST "Stack"
local stack = VM.new().stack
stack:push({ type='integer', value=10 })
stack:push({ type='integer', value=20 })
stack:push({ type='integer', value=30 })
assert_eq(#stack, 3)
assert_eq(stack[0].value, 10)
assert_eq(stack[1].value, 20)
assert_eq(stack[-1].value, 30)
assert_eq(stack[-2].value, 20)
stack:pop()
stack:pop()
assert_eq(stack[-1].value, 10)
stack:pop()
assert_eq(#stack, 0)
end
do TEST "Stack with frame pointer"
local stack = VM.new().stack
stack:push({ type='integer', value=10 })
stack:push({ type='integer', value=20 })
stack:push_fp()
stack:push({ type='integer', value=30 })
stack:push({ type='integer', value=40 })
stack:push({ type='integer', value=50 })
assert_eq(#stack, 3)
assert_eq(stack[0].value, 30)
assert_eq(stack[1].value, 40)
assert_eq(stack[-1].value, 50)
assert_eq(stack[-2].value, 40)
stack:pop_fp()
assert_eq(#stack, 2)
assert_eq(stack[0].value, 10)
assert_eq(stack[1].value, 20)
assert_eq(stack[-1].value, 20)
assert_eq(stack[-2].value, 10)
end
----------------------
-- --
-- VM --
-- --
----------------------
local function arith(a, b, op)
return VM.new():load(assemble(string.format([[
.func 0
pushi %d
pushi %d
%s
ret
]], a, b, op))):call(0)
end
do TEST "VM: basic"
local vm = VM.new()
-- vm.debug = true
local bytecode = assemble [[
.func 0
pushi 2
pushi 3
sum
ret
]]
vm:load(bytecode)
assert_eq(vm:stack_sz(), 1)
assert_eq(vm:is(-1, 'function'), true)
vm:call(0)
assert_eq(vm:stack_sz(), 1)
assert_eq(vm:is(-1, 'integer'), true)
assert_eq(vm:to_integer(-1), 5)
end
do TEST "VM: logic/arithmetic"
assert_eq(arith(2, 5, 'sum'):to_integer(-1), 7)
assert_eq(arith(2, 5, 'sub'):to_integer(-1), -3)
assert_eq(arith(2, 5, 'mul'):to_integer(-1), 10)
assert_eq(arith(20, 3, 'idiv'):to_integer(-1), 6)
assert_eq(arith(5, 5, 'eq'):to_integer(-1), 1)
assert_eq(arith(5, 5, 'neq'):to_integer(-1), 0)
assert_eq(arith(4, 5, 'lt'):to_integer(-1), 1)
assert_eq(arith(5, 5, 'lt'):to_integer(-1), 0)
assert_eq(arith(4, 5, 'lte'):to_integer(-1), 1)
assert_eq(arith(5, 5, 'lte'):to_integer(-1), 1)
assert_eq(arith(5, 5, 'gt'):to_integer(-1), 0)
assert_eq(arith(5, 5, 'gte'):to_integer(-1), 1)
assert_eq(arith(20, 5, 'and'):to_integer(-1), 4)
assert_eq(arith(20, 5, 'or'):to_integer(-1), 21)
assert_eq(arith(20, 5, 'xor'):to_integer(-1), 17)
assert_eq(arith(2, 5, 'pow'):to_integer(-1), 32)
assert_eq(arith(2, 5, 'shl'):to_integer(-1), 64)
assert_eq(arith(20, 3, 'shr'):to_integer(-1), 2)
assert_eq(arith(20, 3, 'mod'):to_integer(-1), 2)
end
do TEST "VM: local variables"
local vm = VM.new():load(assemble([[
.func 0
pushv 2 ; local a, b
pushi 3 ; a = 3
set 0
pushi 4 ; b = 4
set 1
dupv 0 ; return a
ret
]])):call(0)
assert_eq(vm:stack_sz(), 1)
assert_eq(vm:to_integer(-1), 3)
end
do TEST "VM: functions"
local vm = VM.new():load(assemble([[
.func 0
pushf 1
pushi 2
pushi 3
call 2
ret
.func 1
dupv 0
dupv 1
sub
ret
]])):call(0)
assert_eq(vm:stack_sz(), 1)
assert_eq(vm:to_integer(-1), -1)
end
do TEST "VM: jumps (jmp + bnz)"
local vm = VM.new():load(assemble [[
.func 0
jmp @x1
pushi 5
@x1:
pushi 1
bnz @x2
pushi 1
bz @x3
@x2:
pushi 6
ret
@x3:
pushi 7
ret
]]):call(0)
assert_eq(vm:to_integer(-1), 6)
end
do TEST "VM: jumps (bz)"
local vm = VM.new():load(assemble [[
.func 0
jmp @x1
pushi 5
@x1:
pushi 0
bnz @x2
pushi 0
bz @x3
@x2:
pushi 6
ret
@x3:
pushi 7
ret
]]):call(0)
assert_eq(vm:to_integer(-1), 7)
end
do TEST "VM: string from const"
local vm = VM.new():load(assemble [[
.const
0: "Hello"
.func 0
pushc 0
ret
]]):call(0)
assert_eq(vm:to_string(-1), "Hello")
end
do TEST "VM: managed strings"
local vm = VM.new():push_string("Hello")
-- print(vm:debug_heap())
assert_eq(vm:to_string(-1), "Hello")
end
do TEST "VM: concatenate strings (GC won't delete)"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushc 0
pushc 1
sum
gc
ret
]]):call(0)
-- print(vm:debug_heap())
assert_eq(vm:to_string(-1), "Hello world")
assert_eq(vm.heap:size(), 1)
end
do TEST "VM: GC strings"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
pushc 0
pushc 1
sum
pop
gc
ret
]]):call(0)
-- print(vm:debug_heap())
assert_eq(vm:is(-1, 'nil'), true)
assert_eq(vm.heap:size(), 0)
end
do TEST "VM: arrays"
local vm = VM.new():load(assemble [[
.func 0
newa
pushi 10
seti 0
pushi 20
seti 1
pushi 30
seti 2
geti 1
ret
]]):call(0)
-- print(vm:debug_heap())
assert_eq(vm:to_integer(-1), 20)
assert_eq(vm.heap:size(), 1)
end
do TEST "VM: arrays GC"
local vm = VM.new():load(assemble [[
.func 0
pushn
newa
pushi 10
seti 0
pop
gc
ret
]]):call(0)
assert_eq(vm.heap:size(), 0)
end
do TEST "VM: GC items (1st level) - no items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
pushc 0
pushc 1
sum
seti 0
gc
pop
ret
]]):call(0)
assert_eq(vm.heap:size(), 2)
end
do TEST "VM: GC items (1st level) - all items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
pushc 0
pushc 1
sum
seti 0
pop
gc
ret
]]):call(0)
assert_eq(vm.heap:size(), 0)
end
do TEST "VM: GC items (2nd level) - no items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
newa
pushc 0
pushc 1
sum
seti 0
seti 0
gc
pop
ret
]]):call(0)
assert_eq(vm.heap:size(), 3)
end
do TEST "VM: GC items (1st level) - all items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
newa
pushc 0
pushc 1
sum
seti 0
seti 0
pop
gc
ret
]]):call(0)
assert_eq(vm.heap:size(), 0)
end
print('End.')

View File

@@ -1,87 +0,0 @@
----------------------
-- --
-- PARSER --
-- --
----------------------
local function assemble(source)
local proto = {
constants = {},
functions = {},
}
local section = ''
local current_f_id = 0
local next_label = nil
for line in source:gmatch("([^\n]+)") do
local line = line:gsub("%s*;.*$", "") -- remove comments
line = line:match("^%s*(.-)%s*$") -- trim
if #line == 0 then goto continue end
if line == ".const" then
section = 'const'
elseif line:match("%.func%s+%d+") then
section = 'function'
local f_id = tonumber(line:match("%.func%s+(%d+)"))
proto.functions[f_id] = {}
current_f_id = f_id
elseif section == 'const' then
local k, v = line:match("^%s*(%d+)%s*:%s*(.+)$")
if not k then error("Invalid row for constant: " .. line) end
if v:sub(1, 1) == '"' then
proto.constants[tonumber(k)] = line:match('"(.*)"')
else
proto.constants[tonumber(k)] = tonumber(v)
end
elseif section == 'function' then
local regexes = {
"^%s*(%a+)%s+(%d+)%s*$", -- instruction + parameter
"^%s*(%a+)%s+(@[%a_][%a%d_]*)%s*$", -- instruction + label
"^%s*(%a+)%s*$", -- instruction only
"^(@[%a_][%a%d_]*):%s*$", -- label
}
local match = false
for i,regex in ipairs(regexes) do
local inst, par = line:match(regex)
if inst then
match = true
if i == 1 then -- instruction + parameter
table.insert(proto.functions[current_f_id], { inst, tonumber(par), labels = next_label })
elseif i == 2 then -- instruction + label
table.insert(proto.functions[current_f_id], { inst, par, labels = next_label })
elseif i == 3 then -- instruction only
table.insert(proto.functions[current_f_id], { inst, labels = next_label })
elseif i == 4 then -- label
if not next_label then
next_label = { inst }
else
table.insert(next_label, inst)
end
end
if i ~= 4 then
next_label = nil
end
end
end
if not match then error("Invalid instruction: " .. line) end
end
::continue::
end
return proto
end
----------------------
-- --
-- MAIN --
-- --
----------------------
if ... then
return assemble
else
error("Running assembler directly not supported yet")
end

View File

@@ -1,620 +0,0 @@
local pprint = require('pprint')
local TYPES = { 'nil', 'integer', 'float', 'string', 'array', 'table', 'function', 'native_pointer' }
local TYPE_MAP = {}; for _,v in ipairs(TYPES) do TYPE_MAP[v] = true end
local ARITH_LOGIC_OPS = {
sum=true, sub=true, mul=true, div=true, idiv=true, eq=true, neq=true, lt=true, lte=true, gt=true, gte=true,
['and']=true, ['or']=true, xor=true, pow=true, shl=true, shr=true, mod=true
}
math.randomseed(os.time())
----------------------
-- --
-- UTIL --
-- --
----------------------
local function validate_value(v)
assert(v, "value cannot be nil")
assert(type(v) == 'table',
"invalid value format (expected { type='...', value=... }), received: " .. pprint.pformat(v))
assert(TYPE_MAP[v.type], "missing field 'type' in value")
if v.type == 'nil' then
assert(v.value == nil)
elseif v.type == 'number' then
assert(type(v.value) == 'number')
elseif v.type == 'function' then
assert(type(v.value) == 'number' and v.value >= 0, "function must be a positive number")
elseif v.type == 'string' then
assert(type(v.ref) == 'number' or type(v.const_ref) == 'number')
elseif v.type == 'array' then
assert(type(v.ref) == 'number')
end
end
function is_zero(v)
if v.type == 'nil' then return true end
if v.type == 'integer' and v.value == 0 then return true end
return false
end
----------------------
-- --
-- STACK --
-- --
----------------------
local Stack = {}
Stack.__index = Stack
function Stack.new()
local self = setmetatable({
stack = {},
fps = {},
}, Stack)
self:push_fp()
return self
end
function Stack:top_fps()
return self.fps[#self.fps]
end
function Stack:push(value)
validate_value(value)
table.insert(self.stack, value)
end
function Stack:pop()
if #self.stack < self:top_fps() then error("Stack underflow") end
local v = self.stack[#self.stack]
self.stack[#self.stack] = nil
return v
end
function Stack:peek()
if #self.stack < self:top_fps() then error("Stack underflow") end
return self.stack[#self.stack]
end
Stack.__len = function(self)
return #self.stack - self:top_fps() + 1
end
Stack.__index = function(self, key)
local idx = tonumber(key)
if idx then
if idx >= 0 then
return self.stack[self:top_fps() + idx]
else
if self:top_fps() + #self.stack + idx < 0 then error("Stack access out of range") end
return self.stack[#self.stack + idx + 1]
end
else
return Stack[key] -- other methods
end
end
Stack.__newindex = function(self, key, value)
validate_value(value)
local idx = tonumber(key)
if idx then
if idx >= 0 then
self.stack[self:top_fps() + idx] = value
else
if self:top_fps() + #self.stack + idx < 0 then error("Stack access out of range") end
self.stack[#self.stack + idx + 1] = value
end
end
end
function Stack:push_fp()
table.insert(self.fps, #self.stack + 1)
end
function Stack:pop_fp()
if #self.fps == 1 then error("FPS queue underflow") end
for i=self:top_fps(),#self.stack,1 do
self.stack[i] = nil
end
table.remove(self.fps)
end
function Stack:fp_level()
return #self.fps
end
----------------------
-- --
-- CODE --
-- --
----------------------
local Code = {}
Code.__index = Code
function Code.new()
return setmetatable({
bytecode = nil
}, Code)
end
function Code:load(bytecode)
-- TODO - what if there's code already loaded?
self.bytecode = bytecode
return 0 -- main function
end
function Code:next_instruction(function_id, pc)
return {
operator = self.bytecode.functions[function_id][pc][1],
operand = self.bytecode.functions[function_id][pc][2],
instruction_size = 1,
}
end
function Code:find_label(function_id, label)
for pc, op in ipairs(self.bytecode.functions[function_id]) do
if op.labels then
for _,lbl in ipairs(op.labels) do
if lbl == label then
return pc
end
end
end
end
end
----------------------
-- --
-- EXPR --
-- --
----------------------
local EXPR = {}
-- initialize default
for op,_ in pairs(ARITH_LOGIC_OPS) do
EXPR[op] = {}
for _,type1 in ipairs(TYPES) do
EXPR[op][type1] = {}
for _,type2 in ipairs(TYPES) do
EXPR[op][type1][type2] = function(_, _, _) error(string.format("Type mismatch for operation '%s': types '%s' and '%s'", op, type1, type2)) end
end
end
end
EXPR.sum.integer.integer = function(vm, b, a) vm:push_integer(a.value + b.value) end
EXPR.sum.string.string = function(vm, b, a) vm:push_string(vm:_extract_string(a) ..vm:_extract_string(b)) end
EXPR.sub.integer.integer = function(vm, b, a) vm:push_integer(a.value - b.value) end
EXPR.mul.integer.integer = function(vm, b, a) vm:push_integer(a.value * b.value) end
-- TODO - div
EXPR.idiv.integer.integer = function(vm, b, a) vm:push_integer(math.floor(a.value / b.value)) end
EXPR.mod.integer.integer = function(vm, b, a) vm:push_integer(a.value % b.value) end
EXPR.eq.integer.integer = function(vm, b, a) vm:push_integer((a.value == b.value) and 1 or 0) end
EXPR.neq.integer.integer = function(vm, b, a) vm:push_integer((a.value ~= b.value) and 1 or 0) end
EXPR.lt.integer.integer = function(vm, b, a) vm:push_integer((a.value < b.value) and 1 or 0) end
EXPR.lte.integer.integer = function(vm, b, a) vm:push_integer((a.value <= b.value) and 1 or 0) end
EXPR.gt.integer.integer = function(vm, b, a) vm:push_integer((a.value > b.value) and 1 or 0) end
EXPR.gte.integer.integer = function(vm, b, a) vm:push_integer((a.value >= b.value) and 1 or 0) end
EXPR['and'].integer.integer = function(vm, b, a) vm:push_integer(a.value & b.value) end
EXPR['or'].integer.integer = function(vm, b, a) vm:push_integer(a.value | b.value) end
EXPR.xor.integer.integer = function(vm, b, a) vm:push_integer(a.value ~ b.value) end
EXPR.pow.integer.integer = function(vm, b, a) vm:push_integer(a.value ^ b.value) end
EXPR.shl.integer.integer = function(vm, b, a) vm:push_integer(a.value << b.value) end
EXPR.shr.integer.integer = function(vm, b, a) vm:push_integer(a.value >> b.value) end
----------------------
-- --
-- HEAP --
-- --
----------------------
local Heap = {}
Heap.__index = Heap
function Heap.new()
return setmetatable({
items = {}
}, Heap)
end
function Heap:add_value(value)
assert(value.type and (value.type == 'string' or value.type == 'array' or value.type == 'table'))
assert(value.value)
local key = math.random(1, math.maxinteger)
while self.items[key] do key = math.random(1, math.maxinteger) end
self.items[key] = value
return key
end
function Heap:get_value(key)
assert(type(key) == 'number')
return self.items[key]
end
function Heap:size()
local n = 0
for _ in pairs(self.items) do n = n + 1 end
return n
end
function Heap:call_gc(roots)
-- mark
local marked = {}
local function mark(v)
if v.type == 'string' then
if v.ref then marked[v.ref] = true end
elseif v.type == 'array' then
marked[v.ref] = true
for _,vv in ipairs(self.items[v.ref].value) do mark(vv) end
end
end
for _,v in ipairs(roots) do -- TODO - recursive, add support to array
mark(v)
end
-- sweep
for key,_ in pairs(self.items) do
if not marked[key] then
self.items[key] = nil
end
end
end
----------------------
-- --
-- VM --
-- --
----------------------
local VM = {}
VM.__index = VM
function VM.new()
return setmetatable({
stack = Stack.new(),
heap = Heap.new(),
code = Code.new(),
loc = {},
debug = false,
}, VM)
end
function VM:set_debug(b)
self.debug = b
return self
end
--
-- code management
--
function VM:load(bytecode)
local f_id = self.code:load(bytecode)
self.stack:push({ type = 'function', value = f_id })
return self
end
--
-- stack management
--
function VM:push_integer(n)
self.stack:push({ type = 'integer', value = n })
return self
end
function VM:push_string(str)
self.stack:push({ type = 'string', ref = self.heap:add_value({ type='string', value=str }) })
return self
end
function VM:push_nil()
self.stack:push({ type = 'nil' })
return self
end
function VM:new_array()
self.stack:push({ type = 'array', ref = self.heap:add_value({ type='array', value={} }) })
return self
end
--
-- information
--
function VM:stack_sz()
return #self.stack
end
function VM:is(idx, type_)
assert(type(idx) == "number")
assert(TYPE_MAP[type_])
return self.stack[idx].type == type_
end
function VM:to_integer(idx)
local value = self.stack[idx]
if value.type ~= 'integer' then error("Type error: not an integer") end
return value.value
end
function VM:_extract_string(value)
assert(value)
assert(value.type == 'string')
if value.const_ref then
return self.code.bytecode.constants[value.const_ref]
elseif value.ref then
return self.heap:get_value(value.ref).value
else
error("Incorrect string value (nor 'const_ref' or 'ref')")
end
end
function VM:_extract_array(value)
assert(value)
assert(value.type == 'array')
local array = self.heap:get_value(value.ref)
if type(array) ~= 'table' then error('Expected array') end
return self.heap:get_value(value.ref).value
end
function VM:to_string(idx)
local value = self.stack[idx]
if value.type ~= 'string' then error("Type error: not a string") end
return self:_extract_string(value)
end
function VM:format_value(v)
if v.type == 'integer' or v.type == 'real' then
return tostring(v.value)
elseif v.type == 'string' then
return '"' .. self:_extract_string(v) .. '"'
elseif v.type == 'array' then
local array = self:_extract_array(v)
local tbl = {}
for _,vv in ipairs(array) do table.insert(tbl, self:format_value(vv)) end
return "[" .. table.concat(tbl, ', ') .. "]"
elseif v.type == 'function' then
return '@' .. tostring(v.value)
elseif v.type == 'nil' then
return 'nil'
else
print('warning: cannot convert from type ' .. tostring(v.type))
return pprint.pformat(v)
end
end
function VM:debug_stack()
if #self.stack.stack == 0 then return "empty" end
local ss = {}
for i,v in ipairs(self.stack.stack) do
for _,fp in pairs(self.stack.fps) do
if i == fp then table.insert(ss, '^ ') end
end
table.insert(ss, self:format_value(v) .. ' ')
end
return table.concat(ss)
end
function VM:debug_heap()
local ss = { "Heap:\n" }
for k,v in pairs(self.heap.items) do
if v.type == 'string' then
table.insert(ss, string.format(' [%X] = "%s"', k, v.value))
elseif v.type == 'array' then
table.insert(ss, string.format(' [%X] = [', k))
local t = {}; for _,vv in ipairs(v.value) do t[#t+1] = self:format_value(vv) end
table.insert(ss, table.concat(t, ", ") .. ']')
else
error('Unsupported type in heap')
end
table.insert(ss, "\n")
end
return table.concat(ss)
end
--
-- code execution
--
function VM:_enter_function(n_pars)
-- get parameters
local vars = {}
for i=1,n_pars do
vars[i] = self.stack:pop()
end
-- get function
local f = self.stack:pop()
if f.type ~= 'function' then error("Type error: expected function") end
-- enter function
table.insert(self.loc, {
f_id = f.value,
pc = 1
})
self.stack:push_fp()
-- pass parameters
for i=1,n_pars do
self.stack:push(vars[#vars-i+1])
end
end
function VM:call(n_pars)
self:_enter_function(n_pars)
self:_run_until_return()
return self
end
function VM:_run_until_return()
local level = self.stack:fp_level()
while self.stack:fp_level() >= level do
self:_step()
end
end
function VM:_print_stack()
if self.debug then
print(self:debug_stack())
end
end
function VM:_step()
local loc = self.loc[#self.loc]
local op = self.code:next_instruction(loc.f_id, loc.pc)
if self.debug then print('## ' .. loc.f_id .. ':' .. loc.pc .. ' ' .. op.operator .. ' ' .. (op.operand and op.operand or '')) end
--
-- stack operations
--
if op.operator == 'pushn' then
self:push_nil()
elseif op.operator == 'pushi' then
self:push_integer(op.operand)
elseif op.operator == 'pushf' then
assert(op.operand >= 0)
self.stack:push({ type = 'function', value = op.operand })
elseif op.operator == 'pushc' then
local c = self.code.bytecode.constants[op.operand]
if type(c) == 'string' then
self.stack:push({ type = 'string', const_ref = op.operand })
elseif type(c) == 'number' then
error('REAL consts not supported for now.')
end
elseif op.operator == 'newa' then
self:new_array()
elseif op.operator == 'pop' then
self.stack:pop()
elseif op.operator == 'dup' then
self.stack:push(self.stack:peek())
--
-- local variables
--
elseif op.operator == 'pushv' then
assert(op.operand >= 0)
for _=1,op.operand do
self:push_nil()
end
elseif op.operator == 'set' then
assert(op.operand >= 0)
local a = self.stack:pop()
self.stack[op.operand] = a
elseif op.operator == 'dupv' then
assert(op.operand >= 0)
local a = self.stack[op.operand]
self.stack:push(a)
--
-- table and array operations
--
elseif op.operator == 'seti' then
local array_ref = self.stack[-2]
local array = self:_extract_array(array_ref)
array[op.operand+1] = self.stack:pop()
elseif op.operator == 'geti' then
local array_ref = self.stack[-1]
local array = self:_extract_array(array_ref)
self.stack:push(array[op.operand+1])
--
-- logic/arithmetic operations
--
elseif ARITH_LOGIC_OPS[op.operator] then
local a = self.stack:pop()
local b = self.stack:pop()
EXPR[op.operator][a.type][b.type](self, a, b)
--
-- function management
---
elseif op.operator == 'call' then
assert(op.operand >= 0)
self:_enter_function(op.operand)
elseif op.operator == 'ret' then
local v = self.stack:pop()
self.stack:pop_fp()
self.stack:push(v)
table.remove(self.loc)
self:_print_stack()
return
--
-- jumps/branching
--
elseif op.operator == 'jmp' then
loc.pc = self.code:find_label(loc.f_id, op.operand)
self:_print_stack()
return
elseif op.operator == 'bz' then
local v = self.stack:pop()
if is_zero(v) then
loc.pc = self.code:find_label(loc.f_id, op.operand)
self:_print_stack()
return
end
elseif op.operator == 'bnz' then
local v = self.stack:pop()
if not is_zero(v) then
loc.pc = self.code:find_label(loc.f_id, op.operand)
self:_print_stack()
return
end
--
-- memory management
--
elseif op.operator == 'gc' then
-- if self.debug then
-- print('About to run GC, current heap:')
-- print(self:debug_heap())
-- end
self.heap:call_gc(self.stack.stack)
-- if self.debug then
-- print('GC executed, this is the heap:')
-- print(self:debug_heap())
-- end
--
-- instruction not found
--
else
error("Unknown operator '" .. tostring(op.operator) .. "'")
end
self:_print_stack()
loc.pc = loc.pc + op.instruction_size
end
return VM

View File

@@ -1,6 +0,0 @@
- name: Basic test
assembly: |
.func 0
pushi 2
ret
expected: 2

View File

@@ -1,7 +0,0 @@
#include <stdio.h>
int main()
{
printf("This is not implemented yet.\n");
return 1;
}

View File

@@ -1,132 +0,0 @@
return {
{
name = "VM: basic",
code = [[
.func 0
pushi 2
pushi 3
sum
ret
]],
expected_stack_size = 1,
expected_stack_top = 5,
},
{
name = "VM: integer expressions",
template = [[
.func 0
pushi %d
pushi %d
%s
ret
]],
scenarios = {
{ parameters = { 2, 5, 'sum' }, name = "Sum", expected_stack_top = 7 },
{ parameters = { 2, 5, 'sub' }, name = "Subtraction", expected_stack_top = -3 },
{ parameters = { 2, 5, 'mul' }, name = "Multiplication", expected_stack_top = 10 },
{ parameters = { 20, 3, 'idiv' }, name = "Integer division", expected_stack_top = 6 },
{ parameters = { 5, 5, 'eq' }, name = "Equality", expected_stack_top = 1 },
{ parameters = { 5, 5, 'neq' }, name = "Inequality", expected_stack_top = 0 },
{ parameters = { 4, 5, 'lt' }, name = "Less than", expected_stack_top = 1 },
{ parameters = { 5, 5, 'lt' }, name = "Less than", expected_stack_top = 0 },
{ parameters = { 4, 5, 'lte' }, name = "Less than or equal", expected_stack_top = 1 },
{ parameters = { 5, 5, 'lte' }, name = "Less than or equal", expected_stack_top = 1 },
{ parameters = { 5, 5, 'gt' }, name = "Greater than", expected_stack_top = 0 },
{ parameters = { 5, 5, 'gte' }, name = "Greater than or equal", expected_stack_top = 1 },
{ parameters = { 20, 5, 'and' }, name = "Logical AND", expected_stack_top = 4 },
{ parameters = { 20, 5, 'or' }, name = "Logical OR", expected_stack_top = 21 },
{ parameters = { 20, 5, 'xor' }, name = "Logical XOR", expected_stack_top = 17 },
{ parameters = { 2, 5, 'pow' }, name = "Power", expected_stack_top = 32 },
{ parameters = { 2, 5, 'shl' }, name = "Shift left", expected_stack_top = 64 },
{ parameters = { 20, 3, 'shr' }, name = "Shift right", expected_stack_top = 2},
{ parameters = { 20, 3, 'mod' }, name = "Modulo", expected_stack_top = 2 },
},
},
{
name = "VM: local variables",
code = [[
.func 0
pushv 2 ; local a, b
pushi 3 ; a = 3
set 0
pushi 4 ; b = 4
set 1
dupv 0 ; return a
ret
]],
expected_stack_size = 1,
expected_stack_top = 3,
},
{
name = "VM: functions",
code = [[
.func 0
pushf 1
pushi 2
pushi 3
call 2
ret
.func 1
dupv 0
dupv 1
sub
ret
]],
expected_stack_size = 1,
expected_stack_top = -1,
},
{
name = "VM: jumps (jmp + bnz)",
code = [[
.func 0
jmp @x1 ; 0
pushi 5 ; 3
@x1:
pushi 1 ; 5
bnz @x2 ; 7
pushi 1 ; 10
bz @x3 ; 12
@x2:
pushi 6 ; 15
ret ; 17
@x3:
pushi 7 ; 18
ret ; 20
]],
--debug_bytecode = true,
--decompile = true,
--debug = true,
expected_stack_top = 6,
},
{
name = "VM: jumps (bz)",
code = [[
.func 0
jmp @x1
pushi 5
@x1:
pushi 0
bnz @x2
pushi 0
bz @x3
@x2:
pushi 6
ret
@x3:
pushi 7
ret
]],
expected_stack_top = 7,
},
{
name = "VM: string from const",
code = [[
.const
0: "Hello"
.func 0
pushc 0
ret
]],
expected_stack_top = "Hello"
}
}

View File

@@ -1,496 +0,0 @@
#include "../lib/priv.h"
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#define EQ(a, b) (memcmp(a, b) == 0)
static void run_assembly_tests(void);
static void run_assembly_test(lua_State* L);
static void run_assembly_test_code(lua_State* L, bool debug, bool decompile, bool debug_bytecode);
static void run_assembly_test_template(lua_State* L, bool debug, bool decompile, bool debug_bytecode);
int main(void)
{
{
printf("## Values\n");
assert(value_type(create_value_integer(42)) == TT_INTEGER);
assert(value_integer(create_value_integer(-42)) == -42);
assert(fabsf(value_real(create_value_real(42.4f)) - 42.4f) < 0.00001f);
assert(value_idx(create_value_idx(TT_FUNCTION, 42)) == 42);
}
{
printf("## Stack\n");
Stack* s = stack_new();
stack_push(s, create_value_integer(10));
stack_push(s, create_value_integer(20));
stack_push(s, create_value_integer(30));
VALUE v;
assert(stack_size(s) == 3);
assert(stack_at(s, 0, &v) == T_OK); assert(value_integer(v) == 10);
assert(stack_at(s, 1, &v) == T_OK); assert(value_integer(v) == 20);
assert(stack_at(s, -1, &v) == T_OK); assert(value_integer(v) == 30);
assert(stack_at(s, -2, &v) == T_OK); assert(value_integer(v) == 20);
assert(stack_at(s, 3, &v) == T_ERR_STACK_ACCESS_OUT_OF_RANGE);
assert(stack_at(s, -4, &v) == T_ERR_STACK_ACCESS_OUT_OF_RANGE);
assert(stack_set(s, 1, create_value_integer(99)) == T_OK);
assert(stack_at(s, 1, &v) == T_OK); assert(value_integer(v) == 99);
assert(stack_at(s, -2, &v) == T_OK); assert(value_integer(v) == 99);
assert(stack_pop(s, NULL) == T_OK);
assert(stack_pop(s, NULL) == T_OK);
assert(stack_at(s, -1, &v) == T_OK); assert(value_integer(v) == 10);
assert(stack_pop(s, NULL) == T_OK);
assert(stack_size(s) == 0);
assert(stack_pop(s, NULL) == T_ERR_STACK_UNDERFLOW);
stack_destroy(s);
}
{
printf("## Stack with frame pointer\n");
Stack* s = stack_new();
stack_push(s, create_value_integer(10));
stack_push(s, create_value_integer(20));
stack_push_fp(s);
stack_push(s, create_value_integer(30));
stack_push(s, create_value_integer(40));
stack_push(s, create_value_integer(50));
VALUE v;
assert(stack_size(s) == 3);
assert(stack_at(s, 0, &v) == T_OK); assert(value_integer(v) == 30);
assert(stack_at(s, 1, &v) == T_OK); assert(value_integer(v) == 40);
assert(stack_at(s, -1, &v) == T_OK); assert(value_integer(v) == 50);
assert(stack_at(s, -2, &v) == T_OK); assert(value_integer(v) == 40);
assert(stack_set(s, -2, create_value_integer(99)) == T_OK);
assert(stack_at(s, -2, &v) == T_OK); assert(value_integer(v) == 99);
assert(stack_at(s, 3, &v) == T_ERR_STACK_ACCESS_OUT_OF_RANGE);
assert(stack_at(s, -4, &v) == T_ERR_STACK_ACCESS_OUT_OF_RANGE);
stack_pop_fp(s);
assert(stack_size(s) == 2);
assert(stack_at(s, 0, &v) == T_OK); assert(value_integer(v) == 10);
assert(stack_at(s, 1, &v) == T_OK); assert(value_integer(v) == 20);
assert(stack_at(s, -1, &v) == T_OK); assert(value_integer(v) == 20);
assert(stack_at(s, -2, &v) == T_OK); assert(value_integer(v) == 10);
assert(stack_at(s, 2, &v) == T_ERR_STACK_ACCESS_OUT_OF_RANGE);
assert(stack_at(s, -3, &v) == T_ERR_STACK_ACCESS_OUT_OF_RANGE);
stack_destroy(s);
}
{
printf("## Arrays\n");
Array* a = array_new();
assert(array_len(a) == 0);
array_set(a, 1, create_value_integer(40));
assert(array_len(a) == 2);
assert(value_type(array_get(a, 0)) == TT_NIL);
assert(value_type(array_get(a, 1)) == TT_INTEGER);
array_append(a, create_value_integer(50));
assert(array_len(a) == 3);
assert(value_integer(array_get(a, 2)) == 50);
array_set(a, 2, create_value_integer(60));
assert(array_len(a) == 3);
assert(value_integer(array_get(a, 2)) == 60);
array_destroy(a);
}
{
printf("## Table - integer index\n");
Heap* h = heap_new();
Table* t = table_new(h);
table_set(t, create_value_integer(10), create_value_integer(100));
table_set(t, create_value_integer(20), create_value_integer(200));
VALUE v;
assert(table_get(t, create_value_integer(10), &v) == T_OK); assert(value_integer(v) == 100);
assert(table_get(t, create_value_integer(20), &v) == T_OK); assert(value_integer(v) == 200);
table_del(t, create_value_integer(20));
assert(table_get(t, create_value_integer(10), &v) == T_OK);
assert(table_get(t, create_value_integer(20), &v) == T_ERR_TABLE_KEY_NOT_FOUND);
table_destroy(t);
heap_destroy(h);
}
{
printf("## Table - string index\n");
Heap* h = heap_new();
Table* t = table_new(h);
VALUE key1 = create_value_idx(TT_STRING, heap_add_string(h, "key1"));
VALUE key2 = create_value_idx(TT_STRING, heap_add_string(h, "key2"));
table_set(t, key1, create_value_integer(100));
table_set(t, key2, create_value_integer(200));
VALUE key1b = create_value_idx(TT_STRING, heap_add_string(h, "key1"));
VALUE key2b = create_value_idx(TT_STRING, heap_add_string(h, "key2"));
VALUE v;
assert(table_get(t, key1b, &v) == T_OK); assert(value_integer(v) == 100);
assert(table_get(t, key2b, &v) == T_OK); assert(value_integer(v) == 200);
table_del(t, key2b);
assert(table_get(t, key1b, &v) == T_OK);
assert(table_get(t, key2b, &v) == T_ERR_TABLE_KEY_NOT_FOUND);
table_destroy(t);
heap_destroy(h);
}
{
printf("## Heap - strings\n");
Heap* h = heap_new();
HEAP_KEY key1 = heap_add_string(h, "hello");
HEAP_KEY key2 = heap_add_string(h, "world");
const char* value;
assert(heap_get_string(h, key1, &value) == T_OK); assert(strcmp(value, "hello") == 0);
assert(heap_get_string(h, key2, &value) == T_OK); assert(strcmp(value, "world") == 0);
assert(heap_get_string(h, 1000, &value) == T_ERR_HEAP_KEY_NOT_FOUND);
heap_destroy(h);
}
{
printf("## Heap - string GC\n");
Stack* s = stack_new();
Heap* h = heap_new();
stack_push(s, create_value_idx(TT_STRING, heap_add_string(h, "item1")));
stack_push(s, create_value_idx(TT_STRING, heap_add_string(h, "item2")));
stack_push(s, create_value_idx(TT_STRING, heap_add_string(h, "item3")));
size_t v_sz;
VALUE* v_idx;
assert(heap_size(h) == 3);
v_sz = stack_collectable_array(s, &v_idx);
heap_gc(h, v_idx, v_sz);
free(v_idx);
assert(heap_size(h) == 3);
stack_pop(s, NULL);
assert(heap_size(h) == 3);
v_sz = stack_collectable_array(s, &v_idx);
heap_gc(h, v_idx, v_sz);
free(v_idx);
assert(heap_size(h) == 2);
stack_pop(s, NULL);
v_sz = stack_collectable_array(s, &v_idx);
heap_gc(h, v_idx, v_sz);
free(v_idx);
assert(heap_size(h) == 1);
heap_destroy(h);
stack_destroy(s);
}
{
printf("## Bytecode\n");
const char* assembly_code =
".const\n"
" 0: 3.14\n"
" 1: \"Hello world\"\n"
"\n"
".func 0\n"
" pushi 2 ; this is a comment\n"
" pushi -3\n"
" sum\n"
" ret\n"
".func 1\n"
" pushi 5000\n"
" ret";
uint8_t* bytecode; size_t bytecode_sz;
assert(code_assemble(assembly_code, &bytecode, &bytecode_sz) == T_OK);
Code* code = code_new();
assert(code_load_bytecode(code, bytecode, bytecode_sz) == T_OK);
assert(code_n_consts(code) == 2);
assert(code_const_type(code, 0) == TC_REAL);
assert(code_const_type(code, 1) == TC_STRING);
assert(code_const_real(code, 0) > 3.13f && code_const_real(code, 0) < 3.15f);
assert(strcmp(code_const_string(code, 1), "Hello world") == 0);
assert(code_n_functions(code) == 2);
assert(code_function_sz(code, 0) == 6);
assert(code_function_sz(code, 1) == 4);
uint32_t addr = 0;
Instruction inst = code_next_instruction(code, 0, addr);
assert(inst.operator == TO_PUSHI);
assert(inst.operand == 2);
assert(inst.sz == 2);
addr += inst.sz;
inst = code_next_instruction(code, 0, addr);
assert(inst.operator == TO_PUSHI);
assert(inst.operand == -3);
addr += inst.sz;
inst = code_next_instruction(code, 0, addr);
assert(inst.operator == TO_SUM);
assert(inst.operand == 0);
addr += inst.sz;
inst = code_next_instruction(code, 1, 0);
assert(inst.operator == TO_PUSHI);
assert(inst.operand == 5000);
assert(inst.sz == 3);
code_destroy(code);
free(bytecode);
}
{
printf("## Bytecode - labels\n");
const char* assembly_code =
".func 0\n"
" jmp @my_label\n"
" pushi \n"
"@my_label:\n"
" ret";
uint8_t* bytecode; size_t bytecode_sz;
assert(code_assemble(assembly_code, &bytecode, &bytecode_sz) == T_OK);
Code* code = code_new();
assert(code_load_bytecode(code, bytecode, bytecode_sz) == T_OK);
Instruction inst = code_next_instruction(code, 0, 0);
assert(inst.operator == TO_JMP);
assert(inst.operand == 4);
assert(inst.sz == 3);
code_destroy(code);
free(bytecode);
}
{
printf("## VM - Basic\n");
TycheVM* T = tyc_new();
tyc_pushinteger(T, 2);
tyc_pushinteger(T, 3);
assert(tyc_expr(T, TX_SUM) == T_OK);
int32_t result; assert(tyc_tointeger(T, -1, &result) == T_OK);
assert(result == 5);
tyc_destroy(T);
}
{
printf("## Assembly tests\n");
run_assembly_tests();
}
}
static void run_assembly_tests(void)
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
int r = luaL_loadfile(L, "./test/code-tests.lua");
assert(r == LUA_OK);
lua_call(L, 0, 1);
assert(lua_istable(L, -1));
size_t len = (size_t) luaL_len(L, -1);
for (size_t i = 0; i < len; ++i) {
lua_geti(L, -1, (int)i + 1);
run_assembly_test(L);
lua_pop(L, 1);
}
lua_close(L);
}
static void run_assembly_test(lua_State* L)
{
// print test name
lua_getfield(L, -1, "name");
printf(" - %s\n", lua_tostring(L, -1));
lua_pop(L, 1);
// debug?
lua_getfield(L, -1, "debug");
bool debug = lua_isboolean(L, -1) && lua_toboolean(L, -1);
lua_pop(L, 1);
// decompile?
lua_getfield(L, -1, "decompile");
bool decompile = lua_isboolean(L, -1) && lua_toboolean(L, -1);
lua_pop(L, 1);
// decompile?
lua_getfield(L, -1, "debug_bytecode");
bool debug_bytecode = lua_isboolean(L, -1) && lua_toboolean(L, -1);
lua_pop(L, 1);
// has code?
lua_getfield(L, -1, "code");
if (!lua_isnil(L, -1)) {
lua_pop(L, 1);
run_assembly_test_code(L, debug, decompile, debug_bytecode);
return;
} else {
lua_pop(L, 1);
}
// has template
lua_getfield(L, -1, "template");
if (!lua_isnil(L, -1)) {
lua_pop(L, 1);
run_assembly_test_template(L, debug, decompile, debug_bytecode);
} else {
lua_pop(L, 1);
}
}
static void check_expected_top(lua_State* L, TycheVM* T)
{
// check stack size
lua_getfield(L, -1, "expected_stack_size");
if (!lua_isnil(L, -1))
assert(tyc_stack_size(T) == (size_t) lua_tointeger(L, -1));
lua_pop(L, 1);
// check stack top
lua_getfield(L, -1, "expected_stack_top");
if (lua_isinteger(L, -1)) {
TYC_TYPE type; assert(tyc_type(T, -1, &type) == T_OK); assert(type == TT_INTEGER);
int32_t v; assert(tyc_tointeger(T, -1, &v) == T_OK); assert(v == lua_tointeger(L, -1));
} else if (lua_isstring(L, -1)) {
TYC_TYPE type; assert(tyc_type(T, -1, &type) == T_OK); assert(type == TT_STRING || type == TT_STRING_CONST);
const char* str; assert(tyc_tostring(T, -1, &str) == T_OK); assert(strcmp(str, lua_tostring(L, -1)) == 0);
} else if (!lua_isnil(L, -1)) {
abort();
}
lua_pop(L, 1);
}
static void run_assembly_test_code(lua_State* L, bool debug, bool decompile, bool debug_bytecode)
{
TycheVM* T = tyc_new();
tyc_debug_to_console(T, debug);
// load code
uint8_t* bytecode; size_t bytecode_sz;
lua_getfield(L, -1, "code");
assert(code_assemble(lua_tostring(L, -1), &bytecode, &bytecode_sz) == T_OK);
lua_pop(L, 1);
// run code
assert(tyc_load_bytecode(T, bytecode, bytecode_sz) == T_OK);
if (debug_bytecode)
tyc_print_bytecode(T);
if (decompile)
tyc_assembly_decompile(T);
assert(tyc_call(T, 0) == T_OK);
// assert
check_expected_top(L, T);
// cleanup
free(bytecode);
tyc_destroy(T);
}
static void run_assembly_test_template(lua_State* L, bool debug, bool decompile, bool debug_bytecode)
{
lua_getfield(L, -1, "template");
char* template = strdup(lua_tostring(L, -1));
lua_pop(L, 1);
lua_getfield(L, -1, "scenarios");
assert(!lua_isnil(L, -1));
long n_scenarios = luaL_len(L, -1);
for (long i = 0; i < n_scenarios; ++i) {
lua_geti(L, -1, (int)i + 1);
lua_getfield(L, -1, "name");
printf(" .. %s\n", lua_tostring(L, -1));
lua_pop(L, 1);
// format code
luaL_dostring(L, "return string.format");
assert(lua_isfunction(L, -1));
lua_pushstring(L, template);
lua_getfield(L, -3, "parameters");
assert(!lua_isnil(L, -1));
int n_params = (int) luaL_len(L, -1);
for (int j = 0; j < n_params; ++j)
lua_geti(L, -(j + 1), j + 1);
lua_remove(L, -(n_params + 1));
lua_call(L, n_params + 1, 1);
char* formatted_code = strdup(lua_tostring(L, -1));
lua_pop(L, 1);
// run code
TycheVM* T = tyc_new();
tyc_debug_to_console(T, debug);
uint8_t* bytecode; size_t bytecode_sz;
assert(code_assemble(formatted_code, &bytecode, &bytecode_sz) == T_OK);
assert(tyc_load_bytecode(T, bytecode, bytecode_sz) == T_OK);
if (debug_bytecode)
tyc_print_bytecode(T);
if (decompile)
tyc_assembly_decompile(T);
assert(tyc_call(T, 0) == T_OK);
// assert
check_expected_top(L, T);
// cleanup
free(bytecode);
tyc_destroy(T);
free(formatted_code);
lua_pop(L, 1);
}
lua_pop(L, 1);
free(template);
}