99 Commits
master ... c

Author SHA1 Message Date
5001302855 . 2026-05-17 10:03:02 -05:00
6f0c3a729b . 2026-05-17 09:36:15 -05:00
e2692a589a . 2026-05-17 06:29:25 -05:00
Andre Wagner
f447b6cb98 . 2026-05-16 15:41:20 -05:00
Andre Wagner
5c885654af . 2026-05-16 15:31:33 -05:00
Andre Wagner
df8edb549d . 2026-05-16 15:00:00 -05:00
Andre Wagner
d4aa83869c . 2026-05-16 12:14:11 -05:00
Andre Wagner
4d7282a30b . 2026-05-16 11:41:10 -05:00
Andre Wagner
5f88d862b0 . 2026-05-16 11:40:08 -05:00
Andre Wagner
38020b3cad . 2026-05-16 09:02:40 -05:00
Andre Wagner
32af8eae4a . 2026-05-16 08:49:58 -05:00
Andre Wagner
cd21d0ab9d . 2026-05-15 16:45:04 -05:00
Andre Wagner
0bfff6527a . 2026-05-15 16:44:41 -05:00
Andre Wagner
5adfa566a0 . 2026-05-15 16:10:34 -05:00
Andre Wagner
51852887ec . 2026-05-15 14:41:46 -05:00
Andre Wagner
886566a27b . 2026-05-15 14:04:03 -05:00
f7e474f819 . 2026-05-15 13:57:19 -05:00
a1c1aa0591 . 2026-05-15 13:14:06 -05:00
d3a876ca7d . 2026-05-15 11:19:33 -05:00
88ad6a5c02 . 2026-05-15 10:43:50 -05:00
d792d2d8c2 . 2026-05-15 10:43:21 -05:00
d2463fd163 . 2026-05-15 10:42:59 -05:00
a0881e3c22 . 2026-05-15 08:04:30 -05:00
06e5be5c89 . 2026-05-15 07:57:17 -05:00
c8a6db0e4e . 2026-05-15 07:22:58 -05:00
89caa700cf . 2026-05-14 23:28:16 -05:00
8a4cce0da4 . 2026-05-14 20:45:58 -05:00
07dfbc99a2 . 2026-05-14 16:48:24 -05:00
874997995c . 2026-05-14 15:49:31 -05:00
a38b2736c6 . 2026-05-14 14:39:34 -05:00
3e47163ee5 . 2026-05-14 13:02:43 -05:00
2265b8cf08 . 2026-05-13 20:30:14 -05:00
c1d23a00b8 . 2026-05-13 17:58:54 -05:00
bb8d5bfe34 . 2026-05-13 11:40:19 -05:00
aefc2ff9ad . 2026-05-13 11:24:33 -05:00
f61348456b . 2026-05-13 11:01:18 -05:00
9607c77939 . 2026-05-13 08:35:55 -05:00
d1c0052918 . 2026-05-13 08:14:51 -05:00
86858ef26f . 2026-05-13 08:13:07 -05:00
7501c74712 . 2026-05-13 08:07:13 -05:00
19bff9b32f . 2026-05-13 07:19:33 -05:00
15f2794133 . 2026-05-12 20:10:10 -05:00
3c9fafb11a . 2026-05-12 20:09:40 -05:00
e9da5d0cd5 . 2026-05-12 16:03:22 -05:00
be28a6df79 . 2026-05-12 15:45:07 -05:00
74d860976a . 2026-05-12 15:44:24 -05:00
dc4d588675 . 2026-05-12 11:25:37 -05:00
77434a4c01 . 2026-05-12 10:55:08 -05:00
bf8eb83575 . 2026-05-12 10:47:26 -05:00
455b24153c . 2026-05-12 10:47:14 -05:00
Andre Wagner
501f99f28f . 2026-05-12 10:37:41 -05:00
Andre Wagner
968bf9dd8d . 2026-05-12 10:21:54 -05:00
Andre Wagner
7d3ef6a7d6 . 2026-05-12 10:17:01 -05:00
Andre Wagner
b3b543d31d . 2026-05-12 09:57:54 -05:00
3a35a1e125 . 2026-05-12 09:23:43 -05:00
Andre Wagner
ad970577b0 . 2026-05-12 09:21:04 -05:00
Andre Wagner
2fd11f879c . 2026-05-12 07:58:04 -05:00
2ffe0bcaa0 . 2026-05-10 22:04:38 -05:00
f12d1f01da . 2026-05-10 16:10:44 -05:00
3a40eda575 . 2026-05-10 15:55:25 -05:00
65aedb5a49 . 2026-05-10 13:08:29 -05:00
6a4d9eb544 . 2026-05-10 12:57:16 -05:00
7e9f73bfa7 . 2026-05-10 08:55:46 -05:00
1ad732ea4b . 2026-05-10 08:10:50 -05:00
828ea58b35 . 2026-05-10 08:09:43 -05:00
1b53c813b4 . 2026-05-10 07:53:28 -05:00
9561d5cacd . 2026-05-10 07:43:04 -05:00
0116a214f6 . 2026-05-09 15:07:36 -05:00
a70abe76ad . 2026-05-09 15:05:55 -05:00
e2f930641e . 2026-05-09 15:04:44 -05:00
85443ded9d . 2026-05-09 14:23:01 -05:00
6762c49ed3 . 2026-05-09 13:50:16 -05:00
610491c1d7 . 2026-05-09 13:26:13 -05:00
554a7b55c5 . 2026-05-09 11:34:51 -05:00
8a26ba5351 . 2026-05-09 10:37:02 -05:00
83b80f6e7d . 2026-05-09 10:24:09 -05:00
a6adb9b723 . 2026-05-09 09:24:46 -05:00
19b51fcaa0 . 2026-05-09 08:53:11 -05:00
27164aaac3 . 2026-05-08 20:36:21 -05:00
04b9821662 Merge remote-tracking branch 'origin/lua-temp' into lua-temp 2026-05-08 16:32:10 -05:00
b2a829d6e5 . 2026-05-08 16:32:06 -05:00
Andre Wagner
2634ddd1ca . 2026-05-08 14:02:06 -05:00
Andre Wagner
9ff38cd4c0 . 2026-05-07 20:26:03 -05:00
Andre Wagner
4a23c52781 . 2026-05-07 09:20:22 -05:00
Andre Wagner
8f851a330e . 2026-05-06 12:00:53 -05:00
Andre Wagner
7ecffcfdda . 2026-05-06 11:58:15 -05:00
Andre Wagner
88f9ce0ea6 . 2026-05-06 11:12:41 -05:00
Andre Wagner
0fae9a0b37 . 2026-05-06 10:49:45 -05:00
Andre Wagner
2725dc8d33 . 2026-05-05 21:16:21 -05:00
Andre Wagner
516ee9f406 . 2026-05-05 21:11:41 -05:00
Andre Wagner
6428c6cf7f . 2026-05-05 16:26:34 -05:00
43dea6ee8f . 2026-05-05 14:06:44 -05:00
Andre Wagner
566990318b . 2026-05-05 14:04:51 -05:00
Andre Wagner
8c36fb07c0 . 2026-05-05 14:04:30 -05:00
Andre Wagner
60c55304b2 . 2026-05-05 09:54:42 -05:00
Andre Wagner
8614f978ea . 2026-05-05 09:40:22 -05:00
Andre Wagner
0e9c8f6e63 . 2026-05-04 15:56:46 -05:00
Andre Wagner
299984cd4b . 2026-05-04 14:05:42 -05:00
Andre Wagner
8a685ebbc8 . 2026-05-04 11:24:35 -05:00
68 changed files with 5814 additions and 221 deletions

6
.gitignore vendored
View File

@@ -34,3 +34,9 @@
cmake-build-*/
build/
tyche
tyche-test
libtyche.a
libtyche.so*
lib/compiler/compiler.lua.h

19
.idea/misc.xml generated
View File

@@ -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
View File

@@ -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
View 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)

View File

@@ -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
View 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
View File

@@ -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
View 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
View File

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

7
config/WARNINGS_GCC Normal file
View File

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

View File

@@ -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

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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, &params[i]))
// get function
VALUE function;
TRY(stack_pop(T->stack, &function))
if (value_type(function) != TT_FUNCTION)
return T_ERR_TYPE_UNEXPECTED;
// enter function
push_location(T, value_idx(function), 0);
stack_push_fp(T->stack);
// pass parameters
for (int i = n_pars-1; i >= 0; --i)
TRY(stack_push(T->stack, params[i]))
free(params);
return T_OK;
}
static TYC_RESULT run_until_return(TycheVM* T)
{
TYC_RESULT r;
size_t level = stack_fp_level(T->stack);
while (stack_fp_level(T->stack) >= level)
TRY(step(T))
return T_OK;
}
TYC_RESULT tyc_call(TycheVM* T, uint16_t n_pars)
{
TYC_RESULT r;
TRY(enter_function(T, n_pars))
TRY(run_until_return(T))
return T_OK;
}
//
// STACK MANIPULATION AND QUERY
//
size_t tyc_stack_size(TycheVM* T)
{
return stack_size(T->stack);
}
void tyc_pushnil(TycheVM* T)
{
stack_push(T->stack, create_value_nil());
}
void tyc_pushinteger(TycheVM* T, int32_t value)
{
stack_push(T->stack, create_value_integer(value));
}
TYC_RESULT tyc_type(TycheVM* T, int idx, TYC_TYPE* type)
{
VALUE v;
TYC_RESULT r = stack_at(T->stack, idx, &v);
if (r == T_OK)
*type = v.type;
return r;
}
TYC_RESULT tyc_tointeger(TycheVM* T, int idx, int32_t* value)
{
VALUE v;
TYC_RESULT r;
TRY(stack_at(T->stack, idx, &v))
if (v.type != TT_INTEGER)
return T_ERR_TYPE_UNEXPECTED;
*value = value_integer(v);
return T_OK;
}
TYC_RESULT tyc_tostring(TycheVM* T, int idx, const char** str)
{
VALUE v;
TYC_RESULT r;
TRY(stack_at(T->stack, idx, &v))
if (v.type == TT_STRING)
return heap_get_string(T->heap, value_idx(v), str);
else if (v.type == TT_STRING_CONST)
*str = code_const_string(T->code, value_idx(v));
else
return T_ERR_TYPE_UNEXPECTED;
return T_OK;
}
TYC_RESULT tyc_expr(TycheVM* T, TYC_EXPR op)
{
TYC_RESULT r;
VALUE v1, v2, result;
stack_pop(T->stack, &v2);
stack_pop(T->stack, &v1);
TRY(binary_expr(op, v1, v2, &result))
stack_push(T->stack, result);
return T_OK;
}
//
// STEP
//
static TYC_RESULT step(TycheVM* T)
{
VALUE a;
TYC_RESULT r;
Location* loc = location_top(T);
Instruction inst = code_next_instruction(T->code, loc->function_id, loc->pc);
#ifdef DEBUG_ASSEMBLY
debug_instruction(T, loc, inst);
#endif
switch (inst.operator) {
//
// stack manipulation
//
case TO_PUSHN:
tyc_pushnil(T);
break;
case TO_PUSHI:
tyc_pushinteger(T, inst.operand);
break;
case TO_PUSHF:
if (inst.operand < 0 || inst.operand >= (int) code_n_functions(T->code))
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_push(T->stack, create_value_idx(TT_FUNCTION, (uint32_t) inst.operand)))
break;
case TO_PUSHC:
if (inst.operand < 0 || inst.operand >= (int) code_n_consts(T->code))
return T_ERR_VALUE_OUT_OF_RANGE;
if (code_const_type(T->code, (size_t) inst.operand) == TC_STRING) {
TRY(stack_push(T->stack, create_value_idx(TT_STRING_CONST, inst.operand)))
} else {
abort(); // REAL consts not supported for now
}
break;
case TO_POP:
TRY(stack_pop(T->stack, NULL))
break;
//
// local variables
//
case TO_PUSHV:
if (inst.operand <= 0)
return T_ERR_VALUE_OUT_OF_RANGE;
for (int i = 0; i < inst.operand; ++i)
tyc_pushnil(T);
break;
case TO_SET:
if (inst.operand < 0)
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_pop(T->stack, &a))
TRY(stack_set(T->stack, inst.operand, a))
break;
case TO_DUPV:
if (inst.operand < 0)
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_at(T->stack, inst.operand, &a))
stack_push(T->stack, a);
break;
//
// expressions
//
case TO_SUM: TRY(tyc_expr(T, TX_SUM)); break;
case TO_SUB: TRY(tyc_expr(T, TX_SUB)); break;
case TO_MUL: TRY(tyc_expr(T, TX_MUL)); break;
case TO_IDIV: TRY(tyc_expr(T, TX_IDIV)); break;
case TO_EQ: TRY(tyc_expr(T, TX_EQ)); break;
case TO_NEQ: TRY(tyc_expr(T, TX_NEQ)); break;
case TO_LT: TRY(tyc_expr(T, TX_LT)); break;
case TO_LTE: TRY(tyc_expr(T, TX_LTE)); break;
case TO_GT: TRY(tyc_expr(T, TX_GT)); break;
case TO_GTE: TRY(tyc_expr(T, TX_GTE)); break;
case TO_AND: TRY(tyc_expr(T, TX_AND)); break;
case TO_OR: TRY(tyc_expr(T, TX_OR)); break;
case TO_XOR: TRY(tyc_expr(T, TX_XOR)); break;
case TO_POW: TRY(tyc_expr(T, TX_POW)); break;
case TO_SHL: TRY(tyc_expr(T, TX_SHL)); break;
case TO_SHR: TRY(tyc_expr(T, TX_SHR)); break;
case TO_MOD: TRY(tyc_expr(T, TX_MOD)); break;
//
// function calls
//
case TO_CALL:
if (inst.operand < 0)
return T_ERR_VALUE_OUT_OF_RANGE;
enter_function(T, (uint16_t) inst.operand);
break;
case TO_RET:
TRY(stack_pop(T->stack, &a))
TRY(stack_pop_fp(T->stack))
TRY(stack_push(T->stack, a))
location_pop(T);
goto dont_update_pc;
//
// jumps/branching
//
case TO_JMP:
if (inst.operand < 0 || inst.operand >= code_function_sz(T->code, loc->function_id))
return T_ERR_VALUE_OUT_OF_RANGE;
loc->pc = (uint32_t) inst.operand;
goto dont_update_pc;
case TO_BZ:
if (inst.operand < 0 || inst.operand >= code_function_sz(T->code, loc->function_id))
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_pop(T->stack, &a))
if (value_is_zero(a)) {
loc->pc = (uint32_t) inst.operand;
goto dont_update_pc;
}
break;
case TO_BNZ:
if (inst.operand < 0 || inst.operand >= code_function_sz(T->code, loc->function_id))
return T_ERR_VALUE_OUT_OF_RANGE;
TRY(stack_pop(T->stack, &a))
if (!value_is_zero(a)) {
loc->pc = (uint32_t) inst.operand;
goto dont_update_pc;
}
break;
default:
return T_ERR_INVALID_OPCODE;
}
// TODO - print stack
loc->pc += inst.sz;
dont_update_pc:
#ifdef DEBUG_ASSEMBLY
debug_stack(T);
#endif
return T_OK;
}

37
lua-temp/TODO.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

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

7
src/tyche.c Normal file
View 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
View 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
View 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);
}