Compare commits
113 Commits
9fc093e3fb
...
c
| Author | SHA1 | Date | |
|---|---|---|---|
| 5001302855 | |||
| 6f0c3a729b | |||
| e2692a589a | |||
|
|
f447b6cb98 | ||
|
|
5c885654af | ||
|
|
df8edb549d | ||
|
|
d4aa83869c | ||
|
|
4d7282a30b | ||
|
|
5f88d862b0 | ||
|
|
38020b3cad | ||
|
|
32af8eae4a | ||
|
|
cd21d0ab9d | ||
|
|
0bfff6527a | ||
|
|
5adfa566a0 | ||
|
|
51852887ec | ||
|
|
886566a27b | ||
| f7e474f819 | |||
| a1c1aa0591 | |||
| d3a876ca7d | |||
| 88ad6a5c02 | |||
| d792d2d8c2 | |||
| d2463fd163 | |||
| a0881e3c22 | |||
| 06e5be5c89 | |||
| c8a6db0e4e | |||
| 89caa700cf | |||
| 8a4cce0da4 | |||
| 07dfbc99a2 | |||
| 874997995c | |||
| a38b2736c6 | |||
| 3e47163ee5 | |||
| 2265b8cf08 | |||
| c1d23a00b8 | |||
| bb8d5bfe34 | |||
| aefc2ff9ad | |||
| f61348456b | |||
| 9607c77939 | |||
| d1c0052918 | |||
| 86858ef26f | |||
| 7501c74712 | |||
| 19bff9b32f | |||
| 15f2794133 | |||
| 3c9fafb11a | |||
| e9da5d0cd5 | |||
| be28a6df79 | |||
| 74d860976a | |||
| dc4d588675 | |||
| 77434a4c01 | |||
| bf8eb83575 | |||
| 455b24153c | |||
|
|
501f99f28f | ||
|
|
968bf9dd8d | ||
|
|
7d3ef6a7d6 | ||
|
|
b3b543d31d | ||
| 3a35a1e125 | |||
|
|
ad970577b0 | ||
|
|
2fd11f879c | ||
| 2ffe0bcaa0 | |||
| f12d1f01da | |||
| 3a40eda575 | |||
| 65aedb5a49 | |||
| 6a4d9eb544 | |||
| 7e9f73bfa7 | |||
| 1ad732ea4b | |||
| 828ea58b35 | |||
| 1b53c813b4 | |||
| 9561d5cacd | |||
| 0116a214f6 | |||
| a70abe76ad | |||
| e2f930641e | |||
| 85443ded9d | |||
| 6762c49ed3 | |||
| 610491c1d7 | |||
| 554a7b55c5 | |||
| 8a26ba5351 | |||
| 83b80f6e7d | |||
| a6adb9b723 | |||
| 19b51fcaa0 | |||
| 27164aaac3 | |||
| 04b9821662 | |||
| b2a829d6e5 | |||
|
|
2634ddd1ca | ||
|
|
9ff38cd4c0 | ||
|
|
4a23c52781 | ||
|
|
8f851a330e | ||
|
|
7ecffcfdda | ||
|
|
88f9ce0ea6 | ||
|
|
0fae9a0b37 | ||
|
|
2725dc8d33 | ||
|
|
516ee9f406 | ||
|
|
6428c6cf7f | ||
| 43dea6ee8f | |||
|
|
566990318b | ||
|
|
8c36fb07c0 | ||
|
|
60c55304b2 | ||
|
|
8614f978ea | ||
|
|
0e9c8f6e63 | ||
|
|
299984cd4b | ||
|
|
8a685ebbc8 | ||
| 9bc6ad1c92 | |||
| f9733f3b20 | |||
| a1aed4988a | |||
| b835dbb36e | |||
|
|
71390b0f84 | ||
|
|
b471726e0d | ||
|
|
feb272e545 | ||
|
|
03b61f4339 | ||
|
|
30bfb38e9a | ||
|
|
635596c31d | ||
| 148c98e642 | |||
| 54729c1e14 | |||
| d8130272a0 | |||
| 3f097b0ba8 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -32,3 +32,11 @@
|
|||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
|
||||||
|
cmake-build-*/
|
||||||
|
build/
|
||||||
|
|
||||||
|
tyche
|
||||||
|
tyche-test
|
||||||
|
libtyche.a
|
||||||
|
libtyche.so*
|
||||||
|
lib/compiler/compiler.lua.h
|
||||||
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
37
.idea/editor.xml
generated
Normal file
37
.idea/editor.xml
generated
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?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="<NamingElement Priority="1"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
|
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Concepts/@EntryIndexedValue" value="<NamingElement Priority="2"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="concept" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
|
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Enum_0020members/@EntryIndexedValue" value="<NamingElement Priority="14"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="scoped enumerator" /><type Name="unscoped enumerator" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
|
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Enums/@EntryIndexedValue" value="<NamingElement Priority="3"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="enum" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
|
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Other_0020constants/@EntryIndexedValue" value="<NamingElement Priority="15"><Descriptor Static="True" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="local variable" /><type Name="struct field" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></NamingElement>" type="string" />
|
||||||
|
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Unions/@EntryIndexedValue" value="<NamingElement Priority="4"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="union" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
24
.idea/misc.xml
generated
Normal file
24
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?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/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/tyche.iml" filepath="$PROJECT_DIR$/.idea/tyche.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
2
.idea/tyche.iml
generated
Normal file
2
.idea/tyche.iml
generated
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?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" />
|
||||||
7
.idea/vcs.xml
generated
Normal file
7
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
127
.old/CMakeLists.txt
Normal file
127
.old/CMakeLists.txt
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
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)
|
||||||
18
.old/src/assembler/as_exceptions.hh
Normal file
18
.old/src/assembler/as_exceptions.hh
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#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
|
||||||
98
.old/src/assembler/assembler.cc
Normal file
98
.old/src/assembler/assembler.cc
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#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
|
||||||
27
.old/src/assembler/assembler.hh
Normal file
27
.old/src/assembler/assembler.hh
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#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
|
||||||
122
.old/src/assembler/lexer.cc
Normal file
122
.old/src/assembler/lexer.cc
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#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
|
||||||
45
.old/src/assembler/lexer.hh
Normal file
45
.old/src/assembler/lexer.hh
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#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
|
||||||
76
.old/src/assembler/tests.cc
Normal file
76
.old/src/assembler/tests.cc
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#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();
|
||||||
|
}
|
||||||
15
.old/src/bytecode/bc_exceptions.hh
Normal file
15
.old/src/bytecode/bc_exceptions.hh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#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
|
||||||
166
.old/src/bytecode/bytecode.cc
Normal file
166
.old/src/bytecode/bytecode.cc
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
62
.old/src/bytecode/bytecode.hh
Normal file
62
.old/src/bytecode/bytecode.hh
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#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
|
||||||
30
.old/src/bytecode/bytecodeprototype.hh
Normal file
30
.old/src/bytecode/bytecodeprototype.hh
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#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
|
||||||
15
.old/src/bytecode/constant.hh
Normal file
15
.old/src/bytecode/constant.hh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#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
|
||||||
167
.old/src/bytecode/tests.cc
Normal file
167
.old/src/bytecode/tests.cc
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#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();
|
||||||
|
}
|
||||||
152
.old/src/common/bytearray.cc
Normal file
152
.old/src/common/bytearray.cc
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
58
.old/src/common/bytearray.hh
Normal file
58
.old/src/common/bytearray.hh
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#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
|
||||||
8
.old/src/common/overloaded.hh
Normal file
8
.old/src/common/overloaded.hh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#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
|
||||||
82
.old/src/vm/code.cc
Normal file
82
.old/src/vm/code.cc
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#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
|
||||||
34
.old/src/vm/code.hh
Normal file
34
.old/src/vm/code.hh
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#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
|
||||||
114
.old/src/vm/expr.cc
Normal file
114
.old/src/vm/expr.cc
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
.old/src/vm/expr.hh
Normal file
21
.old/src/vm/expr.hh
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#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
|
||||||
244
.old/src/vm/instruction.cc
Normal file
244
.old/src/vm/instruction.cc
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
#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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
121
.old/src/vm/instruction.hh
Normal file
121
.old/src/vm/instruction.hh
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#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
|
||||||
16
.old/src/vm/location.hh
Normal file
16
.old/src/vm/location.hh
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#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
|
||||||
95
.old/src/vm/stack.cc
Normal file
95
.old/src/vm/stack.cc
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#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
|
||||||
38
.old/src/vm/stack.hh
Normal file
38
.old/src/vm/stack.hh
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#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
|
||||||
292
.old/src/vm/tests.cc
Normal file
292
.old/src/vm/tests.cc
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
#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();
|
||||||
|
}
|
||||||
44
.old/src/vm/value.cc
Normal file
44
.old/src/vm/value.cc
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#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_);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
53
.old/src/vm/value.hh
Normal file
53
.old/src/vm/value.hh
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#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
|
||||||
208
.old/src/vm/vm.cc
Normal file
208
.old/src/vm/vm.cc
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
#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
|
||||||
51
.old/src/vm/vm.hh
Normal file
51
.old/src/vm/vm.hh
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#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
|
||||||
49
.old/src/vm/vm_exceptions.hh
Normal file
49
.old/src/vm/vm_exceptions.hh
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#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
|
||||||
175
CMakeLists.txt
Normal file
175
CMakeLists.txt
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
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
Normal file
117
Makefile
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#
|
||||||
|
# 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)
|
||||||
57
TODO.md
Normal file
57
TODO.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
## C
|
||||||
|
|
||||||
|
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
|
||||||
22
config/WARNINGS
Normal file
22
config/WARNINGS
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
-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
|
||||||
5
config/WARNINGS_CLANG
Normal file
5
config/WARNINGS_CLANG
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
-Wshadow-all
|
||||||
|
-Wcomma
|
||||||
|
-Wassign-enum
|
||||||
|
-Wno-newline-eof
|
||||||
|
-Wno-unused-command-line-argument
|
||||||
7
config/WARNINGS_GCC
Normal file
7
config/WARNINGS_GCC
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
-Wlogical-op
|
||||||
|
-Wjump-misses-init
|
||||||
|
-Wduplicated-cond
|
||||||
|
-Wduplicated-branches
|
||||||
|
-Wtrampolines
|
||||||
|
-Walloc-zero
|
||||||
|
-Walloca
|
||||||
34
doc/BYTECODE
Normal file
34
doc/BYTECODE
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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
|
||||||
96
doc/OPCODES
Normal file
96
doc/OPCODES
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
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)
|
||||||
|
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
|
||||||
|
* Jumps can only happen within the same function.
|
||||||
|
|
||||||
|
Memory management:
|
||||||
|
4b gc Call garbage collector
|
||||||
|
|
||||||
|
Error handling: (0xa0~0xaf)
|
||||||
|
???
|
||||||
15
doc/VM
Normal file
15
doc/VM
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Internal handling of values
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
## Supported types
|
||||||
|
Nil 0
|
||||||
|
Integer 1
|
||||||
|
Float 2
|
||||||
|
String 3
|
||||||
|
Array 4
|
||||||
|
Table 5
|
||||||
|
Function 6
|
||||||
|
NativePointer 7
|
||||||
|
|
||||||
|
## Internal format
|
||||||
|
???
|
||||||
58
lib/array.c
Normal file
58
lib/array.c
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
285
lib/code.c
Normal file
285
lib/code.c
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
#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
|
||||||
44
lib/compiler.c
Normal file
44
lib/compiler.c
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
302
lib/compiler/compiler.lua
Normal file
302
lib/compiler/compiler.lua
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
----------------------
|
||||||
|
-- --
|
||||||
|
-- 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
|
||||||
627
lib/contrib/khash.h
Normal file
627
lib/contrib/khash.h
Normal file
@@ -0,0 +1,627 @@
|
|||||||
|
/* 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 */
|
||||||
66
lib/expr.c
Normal file
66
lib/expr.c
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
141
lib/heap.c
Normal file
141
lib/heap.c
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
241
lib/priv.h
Normal file
241
lib/priv.h
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
#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
|
||||||
148
lib/stack.c
Normal file
148
lib/stack.c
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
132
lib/table.c
Normal file
132
lib/table.c
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
lib/tyche.h
Normal file
55
lib/tyche.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#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
|
||||||
31
lib/utils.c
Normal file
31
lib/utils.c
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
76
lib/value.c
Normal file
76
lib/value.c
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#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
Normal file
468
lib/vm.c
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
#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, ¶ms[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;
|
||||||
|
}
|
||||||
37
lua-temp/TODO.md
Normal file
37
lua-temp/TODO.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
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
|
||||||
35
lua-temp/doc/BYTECODE
Normal file
35
lua-temp/doc/BYTECODE
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
96
lua-temp/doc/OPCODES
Normal file
96
lua-temp/doc/OPCODES
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
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)
|
||||||
|
???
|
||||||
15
lua-temp/doc/VM
Normal file
15
lua-temp/doc/VM
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Internal handling of values
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
## Supported types
|
||||||
|
Nil 0
|
||||||
|
Integer 1
|
||||||
|
Float 2
|
||||||
|
String 3
|
||||||
|
Array 4
|
||||||
|
Table 5
|
||||||
|
Function 6
|
||||||
|
NativePointer 7
|
||||||
|
|
||||||
|
## Internal format
|
||||||
|
???
|
||||||
584
lua-temp/pprint.lua
Normal file
584
lua-temp/pprint.lua
Normal file
@@ -0,0 +1,584 @@
|
|||||||
|
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
|
||||||
|
|
||||||
461
lua-temp/tests.lua
Executable file
461
lua-temp/tests.lua
Executable file
@@ -0,0 +1,461 @@
|
|||||||
|
#!/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.')
|
||||||
87
lua-temp/tyche-as.lua
Normal file
87
lua-temp/tyche-as.lua
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
----------------------
|
||||||
|
-- --
|
||||||
|
-- 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
|
||||||
620
lua-temp/tyche-vm.lua
Normal file
620
lua-temp/tyche-vm.lua
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
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
|
||||||
6
src/tests.yaml
Normal file
6
src/tests.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
- name: Basic test
|
||||||
|
assembly: |
|
||||||
|
.func 0
|
||||||
|
pushi 2
|
||||||
|
ret
|
||||||
|
expected: 2
|
||||||
7
src/tyche.c
Normal file
7
src/tyche.c
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
printf("This is not implemented yet.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
132
test/code-tests.lua
Normal file
132
test/code-tests.lua
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
496
test/tests.c
Normal file
496
test/tests.c
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user