Compare commits
99 Commits
rethink-me
...
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 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -34,3 +34,9 @@
|
||||
|
||||
cmake-build-*/
|
||||
build/
|
||||
|
||||
tyche
|
||||
tyche-test
|
||||
libtyche.a
|
||||
libtyche.so*
|
||||
lib/compiler/compiler.lua.h
|
||||
19
.idea/misc.xml
generated
19
.idea/misc.xml
generated
@@ -3,5 +3,22 @@
|
||||
<component name="CMakePythonSetting">
|
||||
<option name="pythonIntegrationState" value="YES" />
|
||||
</component>
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
<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>
|
||||
2
.idea/tyche.iml
generated
2
.idea/tyche.iml
generated
@@ -1,2 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CIDR" type="CPP_MODULE" version="4" />
|
||||
<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" />
|
||||
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)
|
||||
264
CMakeLists.txt
264
CMakeLists.txt
@@ -1,127 +1,175 @@
|
||||
cmake_minimum_required (VERSION 3.24)
|
||||
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(tyche
|
||||
VERSION 0.0.1
|
||||
DESCRIPTION "An embeddable/standalone programming language"
|
||||
LANGUAGES C CXX ASM)
|
||||
VERSION 0.1.0
|
||||
LANGUAGES C
|
||||
)
|
||||
|
||||
#
|
||||
# project options / configuration
|
||||
#
|
||||
# ---------------------------------------------------------------------------
|
||||
# Project-wide settings
|
||||
# ---------------------------------------------------------------------------
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
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)
|
||||
# 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()
|
||||
|
||||
# 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)
|
||||
# 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()
|
||||
|
||||
# try to use ccache, if available
|
||||
find_program(CCACHE_PROGRAM ccache)
|
||||
if(CCACHE_PROGRAM)
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
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)
|
||||
|
||||
# ignore warnings in imported files
|
||||
set_source_files_properties(${IMGUI_SRC} PROPERTIES COMPILE_FLAGS "-w")
|
||||
# Warnings apply to every build type
|
||||
target_compile_options(project_options INTERFACE ${PROJECT_WARNINGS})
|
||||
|
||||
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
|
||||
# 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>"
|
||||
)
|
||||
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_link_options(project_options INTERFACE
|
||||
"$<$<CONFIG:Debug>:-fsanitize=address>"
|
||||
"$<$<CONFIG:Debug>:-fsanitize=undefined>"
|
||||
)
|
||||
|
||||
target_compile_options(lib${PROJECT_NAME} PRIVATE ${warnings})
|
||||
# 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()
|
||||
|
||||
#
|
||||
# tests
|
||||
#
|
||||
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)
|
||||
|
||||
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
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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}
|
||||
)
|
||||
|
||||
#
|
||||
# installation
|
||||
#
|
||||
install(DIRECTORY include/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
FILES_MATCHING PATTERN "*.h"
|
||||
)
|
||||
|
||||
install(TARGETS lib${CMAKE_PROJECT_NAME} RUNTIME DESTINATION lib)
|
||||
# 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)
|
||||
104
TODO.md
104
TODO.md
@@ -1,65 +1,57 @@
|
||||
## Bytecode
|
||||
## C
|
||||
|
||||
- [x] Byte array
|
||||
- Auto-expand
|
||||
- Add/retrive byte/int/float/string
|
||||
- Should not be larger than the byte array itself
|
||||
- [x] Bytecode
|
||||
- Add/retrive all types of data
|
||||
- Keeps no memory except for caching
|
||||
- [x] Refactor bytecode code
|
||||
Decisions:
|
||||
- How to handle errors
|
||||
- How values and heap values will be represented
|
||||
- Transparency and log levels
|
||||
|
||||
Improvements:
|
||||
- [x] Fixed int type (based on opcode)
|
||||
- [x] Constant type (only floats and strings for now)
|
||||
|
||||
After some additional development:
|
||||
- [ ] Bytecode debugging info
|
||||
|
||||
|
||||
## VM
|
||||
|
||||
- [x] VM
|
||||
- [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] Simple bytecode loader
|
||||
- [x] Output bytecode format
|
||||
- [x] Value object
|
||||
- [x] Stack object
|
||||
- [x] External interface
|
||||
- [x] Code execution (except functions)
|
||||
- [x] Functions
|
||||
- [x] Print stack
|
||||
- [x] Assembler
|
||||
- [ ] VM execution
|
||||
- [x] Stack operations (nil, integer, float, string, function)
|
||||
- [x] Integer
|
||||
- [x] Float
|
||||
- [x] String
|
||||
- [x] Interpret bytecode (fast)
|
||||
- [x] Execution loop (fast)
|
||||
- [ ] VM operations
|
||||
- [x] Expressions
|
||||
- [x] Integer
|
||||
- [x] Float
|
||||
- [x] String
|
||||
- [ ] Local/global variables
|
||||
- [ ] Functions
|
||||
- [ ] Constants
|
||||
- [ ] Other operations
|
||||
- [x] Local variables
|
||||
- [x] Functions
|
||||
- [x] With parameters
|
||||
- [x] Debug VM execution
|
||||
- [x] Control flow
|
||||
- [x] Recursion
|
||||
- [ ] Strings
|
||||
- [ ] From constants
|
||||
- [ ] Garbage collection
|
||||
- [ ] Arrays
|
||||
- [ ] Iteration
|
||||
- [ ] Expressions
|
||||
- [ ] Garbage collection
|
||||
- [ ] Tables
|
||||
- [ ] Iteration
|
||||
- [ ] Garbage collection
|
||||
- [ ] Metatables
|
||||
- [ ] Expressions
|
||||
- [ ] Control flow
|
||||
- [ ] Compilation
|
||||
- [ ] Iteration
|
||||
- [ ] Floats (real)
|
||||
- [ ] Globals (?)
|
||||
- [ ] Error handling
|
||||
- [ ] C++ API
|
||||
- [ ] Run native code on VM
|
||||
- [ ] Run tyche code from C++
|
||||
- [ ] C API
|
||||
- [ ] Stack trace in case of errors
|
||||
- [ ] Closure/upvalues
|
||||
- [ ] Rest of opcodes
|
||||
- [ ] Prepare for release
|
||||
- [ ] Documentation and webpage
|
||||
|
||||
After some additional development:
|
||||
- [ ] Bytecode loader
|
||||
- Combine multiple chunks
|
||||
- Resolve function ids, constant ids, etc
|
||||
- [ ] Upvalues
|
||||
## 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
|
||||
45
doc/BYTECODE
45
doc/BYTECODE
@@ -3,33 +3,32 @@ Bytecode format
|
||||
|
||||
The bytecode file is composed of the following sections:
|
||||
|
||||
* HEADER: 16-byte header
|
||||
* HEADER: 8-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
|
||||
???
|
||||
[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.
|
||||
* 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
|
||||
|
||||
65
doc/OPCODES
65
doc/OPCODES
@@ -22,16 +22,17 @@ 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 pushz Push zero (or false)
|
||||
01 pusht Push true
|
||||
02 newa Push (create) empty array
|
||||
03 newt Push (create) empty table
|
||||
04 pop
|
||||
05 dup
|
||||
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)
|
||||
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
|
||||
@@ -44,12 +45,12 @@ Function operations:
|
||||
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)
|
||||
18 geta Get array's position value
|
||||
19 seta Set array's position value (pull 2 values from stack)
|
||||
1a appnd Add value to the end of array
|
||||
1b next Push the next pair into the stack (for loops)
|
||||
1c smt Set value metatable
|
||||
1d mt Get value metatable
|
||||
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
|
||||
@@ -57,24 +58,24 @@ Logical/arithmetic:
|
||||
22 mul Multiply top 2 values in stack
|
||||
23 div Float division
|
||||
24 idiv Integer division
|
||||
25 eq Equality
|
||||
26 neq Inequality
|
||||
27 lt Less than
|
||||
28 lte Less than or equals
|
||||
29 gt Greater than
|
||||
2a gte Greater than or equals
|
||||
2b and Bitwise AND
|
||||
2c or Bitwise OR
|
||||
2d xor Bitwise XOR
|
||||
2e pow Power
|
||||
2f shl Shift left
|
||||
30 shr Shift right
|
||||
31 mod Modulo
|
||||
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
|
||||
b0 cast [type] Cast type to another type
|
||||
ad cast [type] Cast type to another type
|
||||
42 ver Return VM version
|
||||
|
||||
External code:
|
||||
@@ -82,12 +83,14 @@ External code:
|
||||
49 asmbl Assemble code to bytecode format
|
||||
4a load Load bytecode as function (will place function on stack)
|
||||
|
||||
Control flow:
|
||||
a8 c8 e8 bz [pc] Branch if zero
|
||||
a9 c9 e9 bnz [pc] Branch if not zero
|
||||
aa ca ea jmp [pc] Unconditional jump
|
||||
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)
|
||||
???
|
||||
|
||||
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