Compare commits
20 Commits
rethink-me
...
04b9821662
| Author | SHA1 | Date | |
|---|---|---|---|
| 04b9821662 | |||
| b2a829d6e5 | |||
|
|
2634ddd1ca | ||
|
|
9ff38cd4c0 | ||
|
|
4a23c52781 | ||
|
|
8f851a330e | ||
|
|
7ecffcfdda | ||
|
|
88f9ce0ea6 | ||
|
|
0fae9a0b37 | ||
|
|
2725dc8d33 | ||
|
|
516ee9f406 | ||
|
|
6428c6cf7f | ||
| 43dea6ee8f | |||
|
|
566990318b | ||
|
|
8c36fb07c0 | ||
|
|
60c55304b2 | ||
|
|
8614f978ea | ||
|
|
0e9c8f6e63 | ||
|
|
299984cd4b | ||
|
|
8a685ebbc8 |
11
.idea/ctestState.xml
generated
11
.idea/ctestState.xml
generated
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CidrCTestProjectState">
|
||||
<ctestState>
|
||||
<lineNumber testId="path:CMakeLists.txt test:tyche_as_test" value="116" />
|
||||
<lineNumber testId="path:CMakeLists.txt test:tyche_bytearray_test" value="102" />
|
||||
<lineNumber testId="path:CMakeLists.txt test:tyche_bytecode_test" value="106" />
|
||||
<lineNumber testId="path:CMakeLists.txt test:tyche_vm_test" value="110" />
|
||||
</ctestState>
|
||||
</component>
|
||||
</project>
|
||||
4
.idea/misc.xml
generated
4
.idea/misc.xml
generated
@@ -3,5 +3,7 @@
|
||||
<component name="CMakePythonSetting">
|
||||
<option name="pythonIntegrationState" value="YES" />
|
||||
</component>
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
<component name="CMakeWorkspace">
|
||||
<contentRoot DIR="$PROJECT_DIR$" />
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/tyche.iml
generated
2
.idea/tyche.iml
generated
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CIDR" type="CPP_MODULE" version="4" />
|
||||
@@ -60,34 +60,32 @@ FetchContent_MakeAvailable(googletest)
|
||||
|
||||
add_library(lib${PROJECT_NAME} SHARED
|
||||
src/common/overloaded.hh
|
||||
src/bytearray/bytearray.hh
|
||||
src/bytearray/bytearray.cc
|
||||
src/bytearray/bytearraybuilder.hh
|
||||
src/bytearray/bytearraybuilder.cc
|
||||
#src/bytecode/bytecode.cc
|
||||
#src/bytecode/bytecode.hh
|
||||
#src/bytecode/bytecodeprototype.hh
|
||||
#src/bytecode/constant.hh
|
||||
#src/bytecode/bc_exceptions.hh
|
||||
#src/assembler/lexer.cc
|
||||
#src/assembler/lexer.hh
|
||||
#src/assembler/assembler.cc
|
||||
#src/assembler/assembler.hh
|
||||
#src/assembler/as_exceptions.hh
|
||||
#src/instructions/instruction.hh
|
||||
#src/instructions/instruction.cc
|
||||
#src/vm/code.cc
|
||||
#src/vm/code.hh
|
||||
#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/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})
|
||||
@@ -98,10 +96,6 @@ target_compile_options(lib${PROJECT_NAME} PRIVATE ${warnings})
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(${PROJECT_NAME}-bytearray-test src/bytearray/tests.cc)
|
||||
target_link_libraries(${PROJECT_NAME}-bytearray-test lib${PROJECT_NAME} gtest_main)
|
||||
add_test(NAME tyche_bytearray_test COMMAND ${PROJECT_NAME}-bytearray-test)
|
||||
|
||||
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)
|
||||
@@ -110,9 +104,7 @@ 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
|
||||
src/bytearray/bytearraybuilder.cc
|
||||
src/bytearray/bytearraybuilder.hh)
|
||||
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)
|
||||
|
||||
|
||||
30
lua-temp/TODO.md
Normal file
30
lua-temp/TODO.md
Normal file
@@ -0,0 +1,30 @@
|
||||
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
|
||||
- [ ] Strings
|
||||
- [ ] From constants
|
||||
- [ ] Garbage collection
|
||||
- [ ] Arrays
|
||||
- [ ] Garbage collection
|
||||
- [ ] Tables
|
||||
- [ ] Garbage collection
|
||||
- [ ] Metatables
|
||||
- [ ] Real
|
||||
- [ ] Globals
|
||||
- [ ] Error handling
|
||||
- [ ] Stack traces in case of errors
|
||||
- [ ] Closures/upvalues
|
||||
|
||||
|
||||
- [ ] Assembler generate bytecode
|
||||
- [ ] VM interpret it
|
||||
35
lua-temp/doc/BYTECODE
Normal file
35
lua-temp/doc/BYTECODE
Normal file
@@ -0,0 +1,35 @@
|
||||
Bytecode format
|
||||
---------------
|
||||
|
||||
The bytecode file is composed of the following sections:
|
||||
|
||||
* HEADER: 16-byte header
|
||||
[0:3]: Magic
|
||||
[4]: VM format
|
||||
[rest]: Reserved for future use
|
||||
* TABLE_OF_CONTENTS: list of 8 records pointing to each one of the sections
|
||||
Each record (6 bytes):
|
||||
- Pointer to section: 4 bytes
|
||||
- Number of records in section: 2 bytes
|
||||
* [0x0] Constants indexes: pointers to each of the constant locations
|
||||
* Table of 4-byte constant indexes with pointer to constant
|
||||
(counter start at beginning of raw constants)
|
||||
* [0x1] Functions indexes: Pointer to functions within the code
|
||||
[0:3]: function pointer (counter start at the beginning of executable code)
|
||||
[4:5]: number of parameters
|
||||
[6:7]: number of local variables
|
||||
[8:b]: function size
|
||||
* [0x2] Constants raw data
|
||||
* [0x3] Code: executable code
|
||||
* [0x4] Debugging info
|
||||
???
|
||||
|
||||
The max file size is 2 Gb.
|
||||
|
||||
## Values can be encoded in the following ways:
|
||||
* The type is defined by the operator.
|
||||
* Encoding varies according to the type:
|
||||
int: use protobuf format
|
||||
float: 4-bit floating point
|
||||
string: int-defined length, followed by the string proper - no null terminator
|
||||
* Constant indexes and function ids are encoded as ints
|
||||
93
lua-temp/doc/OPCODES
Normal file
93
lua-temp/doc/OPCODES
Normal file
@@ -0,0 +1,93 @@
|
||||
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 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
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
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
|
||||
b0 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):
|
||||
c8 bz [pc] Branch if zero
|
||||
c9 bnz [pc] Branch if not zero
|
||||
ca jmp [pc] Unconditional jump
|
||||
* Jumps can only happen within the same function.
|
||||
|
||||
|
||||
Error handling: (0xa0~0xaf)
|
||||
???
|
||||
15
lua-temp/doc/VM
Normal file
15
lua-temp/doc/VM
Normal file
@@ -0,0 +1,15 @@
|
||||
Internal handling of values
|
||||
---------------------------
|
||||
|
||||
## Supported types
|
||||
Nil 0
|
||||
Integer 1
|
||||
Float 2
|
||||
String 3
|
||||
Array 4
|
||||
Table 5
|
||||
Function 6
|
||||
NativePointer 7
|
||||
|
||||
## Internal format
|
||||
???
|
||||
584
lua-temp/pprint.lua
Normal file
584
lua-temp/pprint.lua
Normal file
@@ -0,0 +1,584 @@
|
||||
local pprint = { VERSION = '0.1' }
|
||||
|
||||
local depth = 1
|
||||
|
||||
pprint.defaults = {
|
||||
-- If set to number N, then limit table recursion to N deep.
|
||||
depth_limit = false,
|
||||
-- type display trigger, hide not useful datatypes by default
|
||||
-- custom types are treated as table
|
||||
show_nil = true,
|
||||
show_boolean = true,
|
||||
show_number = true,
|
||||
show_string = true,
|
||||
show_table = true,
|
||||
show_function = false,
|
||||
show_thread = false,
|
||||
show_userdata = false,
|
||||
-- additional display trigger
|
||||
show_metatable = false, -- show metatable
|
||||
show_all = false, -- override other show settings and show everything
|
||||
use_tostring = false, -- use __tostring to print table if available
|
||||
filter_function = nil, -- called like callback(value[,key, parent]), return truty value to hide
|
||||
object_cache = 'local', -- cache blob and table to give it a id, 'local' cache per print, 'global' cache
|
||||
-- per process, falsy value to disable (might cause infinite loop)
|
||||
-- format settings
|
||||
indent_size = 2, -- indent for each nested table level
|
||||
level_width = 80, -- max width per indent level
|
||||
wrap_string = true, -- wrap string when it's longer than level_width
|
||||
wrap_array = false, -- wrap every array elements
|
||||
string_is_utf8 = true, -- treat string as utf8, and count utf8 char when wrapping, if possible
|
||||
sort_keys = true, -- sort table keys
|
||||
}
|
||||
|
||||
local TYPES = {
|
||||
['nil'] = 1, ['boolean'] = 2, ['number'] = 3, ['string'] = 4,
|
||||
['table'] = 5, ['function'] = 6, ['thread'] = 7, ['userdata'] = 8
|
||||
}
|
||||
|
||||
-- seems this is the only way to escape these, as lua don't know how to map char '\a' to 'a'
|
||||
local ESCAPE_MAP = {
|
||||
['\a'] = '\\a', ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r',
|
||||
['\t'] = '\\t', ['\v'] = '\\v', ['\\'] = '\\\\',
|
||||
}
|
||||
|
||||
-- generic utilities
|
||||
local tokenize_string = function(s)
|
||||
local t = {}
|
||||
for i = 1, #s do
|
||||
local c = s:sub(i, i)
|
||||
local b = c:byte()
|
||||
local e = ESCAPE_MAP[c]
|
||||
if (b >= 0x20 and b < 0x80) or e then
|
||||
local s = e or c
|
||||
t[i] = { char = s, len = #s }
|
||||
else
|
||||
t[i] = { char = string.format('\\x%02x', b), len = 4 }
|
||||
end
|
||||
if c == '"' then
|
||||
t.has_double_quote = true
|
||||
elseif c == "'" then
|
||||
t.has_single_quote = true
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
local tokenize_utf8_string = tokenize_string
|
||||
|
||||
local has_lpeg, lpeg = pcall(require, 'lpeg')
|
||||
|
||||
if has_lpeg then
|
||||
local function utf8_valid_char(c)
|
||||
return { char = c, len = 1 }
|
||||
end
|
||||
|
||||
local function utf8_invalid_char(c)
|
||||
local b = c:byte()
|
||||
local e = ESCAPE_MAP[c]
|
||||
if (b >= 0x20 and b < 0x80) or e then
|
||||
local s = e or c
|
||||
return { char = s, len = #s }
|
||||
else
|
||||
return { char = string.format('\\x%02x', b), len = 4 }
|
||||
end
|
||||
end
|
||||
|
||||
local cont = lpeg.R('\x80\xbf')
|
||||
local utf8_char =
|
||||
lpeg.R('\x20\x7f') +
|
||||
lpeg.R('\xc0\xdf') * cont +
|
||||
lpeg.R('\xe0\xef') * cont * cont +
|
||||
lpeg.R('\xf0\xf7') * cont * cont * cont
|
||||
|
||||
local utf8_capture = (((utf8_char / utf8_valid_char) + (lpeg.P(1) / utf8_invalid_char)) ^ 0) * -1
|
||||
|
||||
tokenize_utf8_string = function(s)
|
||||
local dq = s:find('"')
|
||||
local sq = s:find("'")
|
||||
local t = table.pack(utf8_capture:match(s))
|
||||
t.has_double_quote = not not dq
|
||||
t.has_single_quote = not not sq
|
||||
return t
|
||||
end
|
||||
end
|
||||
|
||||
local function is_plain_key(key)
|
||||
return type(key) == 'string' and key:match('^[%a_][%a%d_]*$')
|
||||
end
|
||||
|
||||
local CACHE_TYPES = {
|
||||
['table'] = true, ['function'] = true, ['thread'] = true, ['userdata'] = true
|
||||
}
|
||||
|
||||
-- cache would be populated to be like:
|
||||
-- {
|
||||
-- function = { `fun1` = 1, _cnt = 1 }, -- object id
|
||||
-- table = { `table1` = 1, `table2` = 2, _cnt = 2 },
|
||||
-- visited_tables = { `table1` = 7, `table2` = 8 }, -- visit count
|
||||
-- }
|
||||
-- use weakrefs to avoid accidentall adding refcount
|
||||
local function cache_apperance(obj, cache, option)
|
||||
if not cache.visited_tables then
|
||||
cache.visited_tables = setmetatable({}, {__mode = 'k'})
|
||||
end
|
||||
local t = type(obj)
|
||||
|
||||
-- TODO can't test filter_function here as we don't have the ix and key,
|
||||
-- might cause different results?
|
||||
-- respect show_xxx and filter_function to be consistent with print results
|
||||
if (not TYPES[t] and not option.show_table)
|
||||
or (TYPES[t] and not option['show_'..t]) then
|
||||
return
|
||||
end
|
||||
|
||||
if CACHE_TYPES[t] or TYPES[t] == nil then
|
||||
if not cache[t] then
|
||||
cache[t] = setmetatable({}, {__mode = 'k'})
|
||||
cache[t]._cnt = 0
|
||||
end
|
||||
if not cache[t][obj] then
|
||||
cache[t]._cnt = cache[t]._cnt + 1
|
||||
cache[t][obj] = cache[t]._cnt
|
||||
end
|
||||
end
|
||||
if t == 'table' or TYPES[t] == nil then
|
||||
if cache.visited_tables[obj] == false then
|
||||
-- already printed, no need to mark this and its children anymore
|
||||
return
|
||||
elseif cache.visited_tables[obj] == nil then
|
||||
cache.visited_tables[obj] = 1
|
||||
else
|
||||
-- visited already, increment and continue
|
||||
cache.visited_tables[obj] = cache.visited_tables[obj] + 1
|
||||
return
|
||||
end
|
||||
for k, v in pairs(obj) do
|
||||
cache_apperance(k, cache, option)
|
||||
cache_apperance(v, cache, option)
|
||||
end
|
||||
local mt = getmetatable(obj)
|
||||
if mt and option.show_metatable then
|
||||
cache_apperance(mt, cache, option)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- makes 'foo2' < 'foo100000'. string.sub makes substring anyway, no need to use index based method
|
||||
local function str_natural_cmp(lhs, rhs)
|
||||
while #lhs > 0 and #rhs > 0 do
|
||||
local lmid, lend = lhs:find('%d+')
|
||||
local rmid, rend = rhs:find('%d+')
|
||||
if not (lmid and rmid) then return lhs < rhs end
|
||||
|
||||
local lsub = lhs:sub(1, lmid-1)
|
||||
local rsub = rhs:sub(1, rmid-1)
|
||||
if lsub ~= rsub then
|
||||
return lsub < rsub
|
||||
end
|
||||
|
||||
local lnum = tonumber(lhs:sub(lmid, lend))
|
||||
local rnum = tonumber(rhs:sub(rmid, rend))
|
||||
if lnum ~= rnum then
|
||||
return lnum < rnum
|
||||
end
|
||||
|
||||
lhs = lhs:sub(lend+1)
|
||||
rhs = rhs:sub(rend+1)
|
||||
end
|
||||
return lhs < rhs
|
||||
end
|
||||
|
||||
local function cmp(lhs, rhs)
|
||||
local tleft = type(lhs)
|
||||
local tright = type(rhs)
|
||||
if tleft == 'number' and tright == 'number' then return lhs < rhs end
|
||||
if tleft == 'string' and tright == 'string' then return str_natural_cmp(lhs, rhs) end
|
||||
if tleft == tright then return str_natural_cmp(tostring(lhs), tostring(rhs)) end
|
||||
|
||||
-- allow custom types
|
||||
local oleft = TYPES[tleft] or 9
|
||||
local oright = TYPES[tright] or 9
|
||||
return oleft < oright
|
||||
end
|
||||
|
||||
-- setup option with default
|
||||
local function make_option(option)
|
||||
if option == nil then
|
||||
option = {}
|
||||
end
|
||||
for k, v in pairs(pprint.defaults) do
|
||||
if option[k] == nil then
|
||||
option[k] = v
|
||||
end
|
||||
if option.show_all then
|
||||
for t, _ in pairs(TYPES) do
|
||||
option['show_'..t] = true
|
||||
end
|
||||
option.show_metatable = true
|
||||
end
|
||||
end
|
||||
return option
|
||||
end
|
||||
|
||||
-- override defaults and take effects for all following calls
|
||||
function pprint.setup(option)
|
||||
pprint.defaults = make_option(option)
|
||||
end
|
||||
|
||||
-- format lua object into a string
|
||||
function pprint.pformat(obj, option, printer)
|
||||
option = make_option(option)
|
||||
local buf = {}
|
||||
local function default_printer(s)
|
||||
table.insert(buf, s)
|
||||
end
|
||||
printer = printer or default_printer
|
||||
|
||||
local cache
|
||||
if option.object_cache == 'global' then
|
||||
-- steal the cache into a local var so it's not visible from _G or anywhere
|
||||
-- still can't avoid user explicitly referentce pprint._cache but it shouldn't happen anyway
|
||||
cache = pprint._cache or {}
|
||||
pprint._cache = nil
|
||||
elseif option.object_cache == 'local' then
|
||||
cache = {}
|
||||
end
|
||||
|
||||
local last = '' -- used for look back and remove trailing comma
|
||||
local status = {
|
||||
indent = '', -- current indent
|
||||
len = 0, -- current line length
|
||||
printed_something = false, -- used to remove leading new lines
|
||||
}
|
||||
|
||||
local wrapped_printer = function(s)
|
||||
status.printed_something = true
|
||||
printer(last)
|
||||
last = s
|
||||
end
|
||||
|
||||
local function _indent(d)
|
||||
status.indent = string.rep(' ', d + #(status.indent))
|
||||
end
|
||||
|
||||
local function _n(d)
|
||||
if not status.printed_something then return end
|
||||
wrapped_printer('\n')
|
||||
wrapped_printer(status.indent)
|
||||
if d then
|
||||
_indent(d)
|
||||
end
|
||||
status.len = 0
|
||||
return true -- used to close bracket correctly
|
||||
end
|
||||
|
||||
local function _p(s, nowrap)
|
||||
status.len = status.len + #s
|
||||
if not nowrap and status.len > option.level_width then
|
||||
_n()
|
||||
wrapped_printer(s)
|
||||
status.len = #s
|
||||
else
|
||||
wrapped_printer(s)
|
||||
end
|
||||
end
|
||||
|
||||
local formatter = {}
|
||||
local function format(v)
|
||||
local f = formatter[type(v)]
|
||||
f = f or formatter.table -- allow patched type()
|
||||
if option.filter_function and option.filter_function(v, nil, nil) then
|
||||
return ''
|
||||
else
|
||||
return f(v)
|
||||
end
|
||||
end
|
||||
|
||||
local function tostring_formatter(v)
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
local function number_formatter(n)
|
||||
return n == math.huge and '[[math.huge]]' or tostring(n)
|
||||
end
|
||||
|
||||
local function nop_formatter(v)
|
||||
return ''
|
||||
end
|
||||
|
||||
local function make_fixed_formatter(t, has_cache)
|
||||
if has_cache then
|
||||
return function (v)
|
||||
return string.format('[[%s %d]]', t, cache[t][v])
|
||||
end
|
||||
else
|
||||
return function (v)
|
||||
return '[['..t..']]'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function string_formatter(s, force_long_quote)
|
||||
local tokens = option.string_is_utf8 and tokenize_utf8_string(s) or tokenize_string(s)
|
||||
local string_len = 0
|
||||
local escape_quotes = tokens.has_double_quote and tokens.has_single_quote
|
||||
for _, token in ipairs(tokens) do
|
||||
if escape_quotes and token.char == '"' then
|
||||
string_len = string_len + 2
|
||||
else
|
||||
string_len = string_len + token.len
|
||||
end
|
||||
end
|
||||
local quote_len = 2
|
||||
local long_quote_dashes = 0
|
||||
local function compute_long_quote_dashes()
|
||||
local keep_looking = true
|
||||
while keep_looking do
|
||||
if s:find('%]' .. string.rep('=', long_quote_dashes) .. '%]') then
|
||||
long_quote_dashes = long_quote_dashes + 1
|
||||
else
|
||||
keep_looking = false
|
||||
end
|
||||
end
|
||||
end
|
||||
if force_long_quote then
|
||||
compute_long_quote_dashes()
|
||||
quote_len = 2 + long_quote_dashes
|
||||
end
|
||||
if quote_len + string_len + status.len > option.level_width then
|
||||
_n()
|
||||
-- only wrap string when is longer than level_width
|
||||
if option.wrap_string and string_len + quote_len > option.level_width then
|
||||
if not force_long_quote then
|
||||
compute_long_quote_dashes()
|
||||
quote_len = 2 + long_quote_dashes
|
||||
end
|
||||
-- keep the quotes together
|
||||
local dashes = string.rep('=', long_quote_dashes)
|
||||
_p('[' .. dashes .. '[', true)
|
||||
local status_len = status.len
|
||||
local line_len = 0
|
||||
local line = ''
|
||||
for _, token in ipairs(tokens) do
|
||||
if line_len + token.len + status_len > option.level_width then
|
||||
_n()
|
||||
_p(line, true)
|
||||
line_len = token.len
|
||||
line = token.char
|
||||
else
|
||||
line_len = line_len + token.len
|
||||
line = line .. token.char
|
||||
end
|
||||
end
|
||||
|
||||
return line .. ']' .. dashes .. ']'
|
||||
end
|
||||
end
|
||||
|
||||
if tokens.has_double_quote and tokens.has_single_quote and not force_long_quote then
|
||||
for i, token in ipairs(tokens) do
|
||||
if token.char == '"' then
|
||||
tokens[i].char = '\\"'
|
||||
end
|
||||
end
|
||||
end
|
||||
local flat_table = {}
|
||||
for _, token in ipairs(tokens) do
|
||||
table.insert(flat_table, token.char)
|
||||
end
|
||||
local concat = table.concat(flat_table)
|
||||
|
||||
if force_long_quote then
|
||||
local dashes = string.rep('=', long_quote_dashes)
|
||||
return '[' .. dashes .. '[' .. concat .. ']' .. dashes .. ']'
|
||||
elseif tokens.has_single_quote then
|
||||
-- use double quote
|
||||
return '"' .. concat .. '"'
|
||||
else
|
||||
-- use single quote
|
||||
return "'" .. concat .. "'"
|
||||
end
|
||||
end
|
||||
|
||||
local function table_formatter(t)
|
||||
if option.use_tostring then
|
||||
local mt = getmetatable(t)
|
||||
if mt and mt.__tostring then
|
||||
return string_formatter(tostring(t), true)
|
||||
end
|
||||
end
|
||||
|
||||
local print_header_ix = nil
|
||||
local ttype = type(t)
|
||||
if option.object_cache then
|
||||
local cache_state = cache.visited_tables[t]
|
||||
local tix = cache[ttype][t]
|
||||
-- FIXME should really handle `cache_state == nil`
|
||||
-- as user might add things through filter_function
|
||||
if cache_state == false then
|
||||
-- already printed, just print the the number
|
||||
return string_formatter(string.format('%s %d', ttype, tix), true)
|
||||
elseif cache_state > 1 then
|
||||
-- appeared more than once, print table header with number
|
||||
print_header_ix = tix
|
||||
cache.visited_tables[t] = false
|
||||
else
|
||||
-- appeared exactly once, print like a normal table
|
||||
end
|
||||
end
|
||||
|
||||
local limit = tonumber(option.depth_limit)
|
||||
if limit and depth > limit then
|
||||
if print_header_ix then
|
||||
return string.format('[[%s %d]]...', ttype, print_header_ix)
|
||||
end
|
||||
return string_formatter(tostring(t), true)
|
||||
end
|
||||
|
||||
local tlen = #t
|
||||
local wrapped = false
|
||||
_p('{')
|
||||
_indent(option.indent_size)
|
||||
_p(string.rep(' ', option.indent_size - 1))
|
||||
if print_header_ix then
|
||||
_p(string.format('--[[%s %d]] ', ttype, print_header_ix))
|
||||
end
|
||||
for ix = 1,tlen do
|
||||
local v = t[ix]
|
||||
if formatter[type(v)] == nop_formatter or
|
||||
(option.filter_function and option.filter_function(v, ix, t)) then
|
||||
-- pass
|
||||
else
|
||||
if option.wrap_array then
|
||||
wrapped = _n()
|
||||
end
|
||||
depth = depth+1
|
||||
_p(format(v)..', ')
|
||||
depth = depth-1
|
||||
end
|
||||
end
|
||||
|
||||
-- hashmap part of the table, in contrast to array part
|
||||
local function is_hash_key(k)
|
||||
if type(k) ~= 'number' then
|
||||
return true
|
||||
end
|
||||
|
||||
local numkey = math.floor(tonumber(k))
|
||||
if numkey ~= k or numkey > tlen or numkey <= 0 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local function print_kv(k, v, t)
|
||||
-- can't use option.show_x as obj may contain custom type
|
||||
if formatter[type(v)] == nop_formatter or
|
||||
formatter[type(k)] == nop_formatter or
|
||||
(option.filter_function and option.filter_function(v, k, t)) then
|
||||
return
|
||||
end
|
||||
wrapped = _n()
|
||||
if is_plain_key(k) then
|
||||
_p(k, true)
|
||||
else
|
||||
_p('[')
|
||||
-- [[]] type string in key is illegal, needs to add spaces inbetween
|
||||
local k = format(k)
|
||||
if string.match(k, '%[%[') then
|
||||
_p(' '..k..' ', true)
|
||||
else
|
||||
_p(k, true)
|
||||
end
|
||||
_p(']')
|
||||
end
|
||||
_p(' = ', true)
|
||||
depth = depth+1
|
||||
_p(format(v), true)
|
||||
depth = depth-1
|
||||
_p(',', true)
|
||||
end
|
||||
|
||||
if option.sort_keys then
|
||||
local keys = {}
|
||||
for k, _ in pairs(t) do
|
||||
if is_hash_key(k) then
|
||||
table.insert(keys, k)
|
||||
end
|
||||
end
|
||||
table.sort(keys, cmp)
|
||||
for _, k in ipairs(keys) do
|
||||
print_kv(k, t[k], t)
|
||||
end
|
||||
else
|
||||
for k, v in pairs(t) do
|
||||
if is_hash_key(k) then
|
||||
print_kv(k, v, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if option.show_metatable then
|
||||
local mt = getmetatable(t)
|
||||
if mt then
|
||||
print_kv('__metatable', mt, t)
|
||||
end
|
||||
end
|
||||
|
||||
_indent(-option.indent_size)
|
||||
-- make { } into {}
|
||||
last = string.gsub(last, '^ +$', '')
|
||||
-- peek last to remove trailing comma
|
||||
last = string.gsub(last, ',%s*$', ' ')
|
||||
if wrapped then
|
||||
_n()
|
||||
end
|
||||
_p('}')
|
||||
|
||||
return ''
|
||||
end
|
||||
|
||||
-- set formatters
|
||||
formatter['nil'] = option.show_nil and tostring_formatter or nop_formatter
|
||||
formatter['boolean'] = option.show_boolean and tostring_formatter or nop_formatter
|
||||
formatter['number'] = option.show_number and number_formatter or nop_formatter -- need to handle math.huge
|
||||
formatter['function'] = option.show_function and make_fixed_formatter('function', option.object_cache) or nop_formatter
|
||||
formatter['thread'] = option.show_thread and make_fixed_formatter('thread', option.object_cache) or nop_formatter
|
||||
formatter['userdata'] = option.show_userdata and make_fixed_formatter('userdata', option.object_cache) or nop_formatter
|
||||
formatter['string'] = option.show_string and string_formatter or nop_formatter
|
||||
formatter['table'] = option.show_table and table_formatter or nop_formatter
|
||||
|
||||
if option.object_cache then
|
||||
-- needs to visit the table before start printing
|
||||
cache_apperance(obj, cache, option)
|
||||
end
|
||||
|
||||
_p(format(obj))
|
||||
printer(last) -- close the buffered one
|
||||
|
||||
-- put cache back if global
|
||||
if option.object_cache == 'global' then
|
||||
pprint._cache = cache
|
||||
end
|
||||
|
||||
return table.concat(buf)
|
||||
end
|
||||
|
||||
-- pprint all the arguments
|
||||
function pprint.pprint( ... )
|
||||
local args = {...}
|
||||
-- select will get an accurate count of array len, counting trailing nils
|
||||
local len = select('#', ...)
|
||||
for ix = 1,len do
|
||||
pprint.pformat(args[ix], nil, io.write)
|
||||
io.write('\n')
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(pprint, {
|
||||
__call = function (_, ...)
|
||||
pprint.pprint(...)
|
||||
end
|
||||
})
|
||||
|
||||
return pprint
|
||||
|
||||
302
lua-temp/tests.lua
Normal file
302
lua-temp/tests.lua
Normal file
@@ -0,0 +1,302 @@
|
||||
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 ARITH --
|
||||
-- --
|
||||
----------------------
|
||||
|
||||
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
|
||||
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)"
|
||||
pprint(assemble [[
|
||||
.func 0
|
||||
jmp @x1
|
||||
pushi 5
|
||||
@x1:
|
||||
pushi 0
|
||||
bnz @x2
|
||||
pushi 0
|
||||
bz @x3
|
||||
@x2:
|
||||
pushi 6
|
||||
ret
|
||||
@x3:
|
||||
pushi 7
|
||||
ret
|
||||
]])
|
||||
local vm = VM.new():set_debug(true):load(assemble [[
|
||||
.func 0
|
||||
jmp @x1
|
||||
pushi 5
|
||||
@x1:
|
||||
pushi 0
|
||||
bnz @x2
|
||||
bz @x3
|
||||
@x2:
|
||||
pushi 6
|
||||
ret
|
||||
@x3:
|
||||
pushi 7
|
||||
ret
|
||||
]]):call(0)
|
||||
|
||||
assert_eq(vm:to_integer(-1), 7)
|
||||
end
|
||||
|
||||
|
||||
print('End.')
|
||||
87
lua-temp/tyche-as.lua
Normal file
87
lua-temp/tyche-as.lua
Normal file
@@ -0,0 +1,87 @@
|
||||
----------------------
|
||||
-- --
|
||||
-- PARSER --
|
||||
-- --
|
||||
----------------------
|
||||
|
||||
local function assemble(source)
|
||||
local proto = {
|
||||
constants = {},
|
||||
functions = {},
|
||||
}
|
||||
|
||||
local section = ''
|
||||
local current_f_id = 0
|
||||
|
||||
local next_label = nil
|
||||
for line in source:gmatch("([^\n]+)") do
|
||||
local line = line:gsub("%s*;.*$", "") -- remove comments
|
||||
line = line:match("^%s*(.-)%s*$") -- trim
|
||||
|
||||
if #line == 0 then goto continue end
|
||||
|
||||
if line == ".const" then
|
||||
section = 'const'
|
||||
elseif line:match("%.func%s+%d+") then
|
||||
section = 'function'
|
||||
local f_id = tonumber(line:match("%.func%s+(%d+)"))
|
||||
proto.functions[f_id] = {}
|
||||
current_f_id = f_id
|
||||
elseif section == 'const' then
|
||||
local k, v = line:match("^%s*(%d+)%s*:%s*(.+)$")
|
||||
if not k then error("Invalid row for constant: " .. line) end
|
||||
if v:sub(1, 1) == '"' then
|
||||
proto.constants[tonumber(k)] = line:match('"(.*)"')
|
||||
else
|
||||
proto.constants[tonumber(k)] = tonumber(v)
|
||||
end
|
||||
elseif section == 'function' then
|
||||
local regexes = {
|
||||
"^%s*(%a+)%s+(%d+)%s*$", -- instruction + parameter
|
||||
"^%s*(%a+)%s+(@[%a_][%a%d_]*)%s*$", -- instruction + label
|
||||
"^%s*(%a+)%s*$", -- instruction only
|
||||
"^(@[%a_][%a%d_]*):%s*$", -- label
|
||||
}
|
||||
local match = false
|
||||
for i,regex in ipairs(regexes) do
|
||||
local inst, par = line:match(regex)
|
||||
if inst then
|
||||
match = true
|
||||
if i == 1 then -- instruction + parameter
|
||||
table.insert(proto.functions[current_f_id], { inst, tonumber(par), labels = next_label })
|
||||
elseif i == 2 then -- instruction + label
|
||||
table.insert(proto.functions[current_f_id], { inst, par, labels = next_label })
|
||||
elseif i == 3 then -- instruction only
|
||||
table.insert(proto.functions[current_f_id], { inst, labels = next_label })
|
||||
elseif i == 4 then -- label
|
||||
if not next_label then
|
||||
next_label = { inst }
|
||||
else
|
||||
table.insert(next_label, inst)
|
||||
end
|
||||
end
|
||||
if i ~= 4 then
|
||||
next_label = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
if not match then error("Invalid instruction: " .. line) end
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
return proto
|
||||
end
|
||||
|
||||
----------------------
|
||||
-- --
|
||||
-- MAIN --
|
||||
-- --
|
||||
----------------------
|
||||
|
||||
if ... then
|
||||
return assemble
|
||||
else
|
||||
error("Running assembler directly not supported yet")
|
||||
end
|
||||
441
lua-temp/tyche-vm.lua
Normal file
441
lua-temp/tyche-vm.lua
Normal file
@@ -0,0 +1,441 @@
|
||||
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
|
||||
}
|
||||
|
||||
----------------------
|
||||
-- --
|
||||
-- UTIL --
|
||||
-- --
|
||||
----------------------
|
||||
|
||||
local function format_value(v)
|
||||
if v.type == 'integer' or v.type == 'real' then
|
||||
return tostring(v.value)
|
||||
elseif v.type == 'string' then
|
||||
return '"' .. v.value .. '"'
|
||||
elseif v.type == 'function' then
|
||||
return '@' .. tostring(v.value)
|
||||
elseif v.type == 'nil' then
|
||||
return 'nil'
|
||||
else
|
||||
return pprint.pformat(v)
|
||||
end
|
||||
end
|
||||
|
||||
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")
|
||||
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
|
||||
|
||||
function Stack:debug()
|
||||
if #self.stack == 0 then return "empty" end
|
||||
local ss = {}
|
||||
for i,v in ipairs(self.stack) do
|
||||
for _,fp in pairs(self.fps) do
|
||||
if i == fp then table.insert(ss, '^ ') end
|
||||
end
|
||||
table.insert(ss, '[' .. format_value(v) .. '] ')
|
||||
end
|
||||
return table.concat(ss)
|
||||
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 + b) end
|
||||
EXPR.sub.integer.integer = function(vm, b, a) vm:push_integer(a - b) end
|
||||
EXPR.mul.integer.integer = function(vm, b, a) vm:push_integer(a * b) end
|
||||
-- TODO - div
|
||||
EXPR.idiv.integer.integer = function(vm, b, a) vm:push_integer(math.floor(a / b)) end
|
||||
EXPR.mod.integer.integer = function(vm, b, a) vm:push_integer(a % b) end
|
||||
EXPR.eq.integer.integer = function(vm, b, a) vm:push_integer((a == b) and 1 or 0) end
|
||||
EXPR.neq.integer.integer = function(vm, b, a) vm:push_integer((a ~= b) and 1 or 0) end
|
||||
EXPR.lt.integer.integer = function(vm, b, a) vm:push_integer((a < b) and 1 or 0) end
|
||||
EXPR.lte.integer.integer = function(vm, b, a) vm:push_integer((a <= b) and 1 or 0) end
|
||||
EXPR.gt.integer.integer = function(vm, b, a) vm:push_integer((a > b) and 1 or 0) end
|
||||
EXPR.gte.integer.integer = function(vm, b, a) vm:push_integer((a >= b) and 1 or 0) end
|
||||
EXPR['and'].integer.integer = function(vm, b, a) vm:push_integer(a & b) end
|
||||
EXPR['or'].integer.integer = function(vm, b, a) vm:push_integer(a | b) end
|
||||
EXPR.xor.integer.integer = function(vm, b, a) vm:push_integer(a ~ b) end
|
||||
EXPR.pow.integer.integer = function(vm, b, a) vm:push_integer(a ^ b) end
|
||||
EXPR.shl.integer.integer = function(vm, b, a) vm:push_integer(a << b) end
|
||||
EXPR.shr.integer.integer = function(vm, b, a) vm:push_integer(a >> b) end
|
||||
|
||||
----------------------
|
||||
-- --
|
||||
-- VM --
|
||||
-- --
|
||||
----------------------
|
||||
|
||||
local VM = {}
|
||||
VM.__index = VM
|
||||
|
||||
function VM.new()
|
||||
return setmetatable({
|
||||
stack = Stack.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 })
|
||||
end
|
||||
|
||||
function VM:push_nil()
|
||||
self.stack:push({ type = 'nil' })
|
||||
end
|
||||
|
||||
--
|
||||
-- information
|
||||
--
|
||||
|
||||
function VM:stack_sz()
|
||||
return #self.stack
|
||||
end
|
||||
|
||||
function VM:is(idx, 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
|
||||
|
||||
--
|
||||
-- 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:_debug_stack()
|
||||
if self.debug then
|
||||
print(self.stack:debug())
|
||||
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 == '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 == '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)
|
||||
|
||||
--
|
||||
-- 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.value, b.value)
|
||||
|
||||
--
|
||||
-- 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:_debug_stack()
|
||||
return
|
||||
|
||||
--
|
||||
-- jumps/branching
|
||||
--
|
||||
|
||||
elseif op.operator == 'jmp' then
|
||||
loc.pc = self.code:find_label(loc.f_id, op.operand)
|
||||
self:_debug_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:_debug_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:_debug_stack()
|
||||
return
|
||||
end
|
||||
|
||||
--
|
||||
-- instruction not found
|
||||
--
|
||||
|
||||
else
|
||||
error("Unknown operator '" .. tostring(op.operator) .. "'")
|
||||
end
|
||||
|
||||
self:_debug_stack()
|
||||
|
||||
loc.pc = loc.pc + op.instruction_size
|
||||
end
|
||||
|
||||
return VM
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
#include "as_exceptions.hh"
|
||||
#include "../bytecode/bytecode.hh"
|
||||
#include "../instructions/instruction.hh"
|
||||
#include "../vm/instruction.hh"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
namespace tyche::as {
|
||||
|
||||
StaticByteArray Assembler::assemble()
|
||||
ByteArray Assembler::assemble()
|
||||
{
|
||||
bc::BytecodePrototype bp;
|
||||
|
||||
@@ -61,16 +61,16 @@ StaticByteArray Assembler::assemble()
|
||||
tt = lexer_.ingest();
|
||||
}
|
||||
|
||||
auto oinst = translate_instruction(instruction, oper);
|
||||
auto oinst = vm::translate_instruction(instruction, oper);
|
||||
if (!oinst)
|
||||
throw AssemblyError("Invalid or misused instruction '" + instruction + "'", tt.line, tt.column);
|
||||
|
||||
bp.functions.at(function_id).code.append_byte((uint8_t) *oinst);
|
||||
switch (instruction_operand_type(*oinst)) {
|
||||
case OperandType::Int8: bp.functions.at(function_id).code.append_int8((int8_t) *oper); break;
|
||||
case OperandType::Int16: bp.functions.at(function_id).code.append_int16((int16_t) *oper); break;
|
||||
case OperandType::Int32: bp.functions.at(function_id).code.append_int32(*oper); break;
|
||||
case OperandType::NoOperand: default: break;
|
||||
switch (vm::instruction_operand_type(*oinst)) {
|
||||
case vm::OperandType::Int8: bp.functions.at(function_id).code.append_int8((int8_t) *oper); break;
|
||||
case vm::OperandType::Int16: bp.functions.at(function_id).code.append_int16((int16_t) *oper); break;
|
||||
case vm::OperandType::Int32: bp.functions.at(function_id).code.append_int32(*oper); break;
|
||||
case vm::OperandType::NoOperand: default: break;
|
||||
}
|
||||
|
||||
if (tt.type != TokenType::Enter)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "lexer.hh"
|
||||
#include "../bytearray/bytearray.hh"
|
||||
#include "../common/bytearray.hh"
|
||||
#include "../bytecode/bytecodeprototype.hh"
|
||||
|
||||
namespace tyche::as {
|
||||
@@ -14,7 +14,7 @@ class Assembler {
|
||||
public:
|
||||
explicit Assembler(std::string source) : lexer_(std::move(source) + "\n") {}
|
||||
|
||||
[[nodiscard]] StaticByteArray assemble();
|
||||
[[nodiscard]] ByteArray assemble();
|
||||
|
||||
private:
|
||||
Lexer lexer_;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#include "lexer.hh"
|
||||
|
||||
#include <iostream>
|
||||
using namespace std::string_literals;
|
||||
|
||||
#include "as_exceptions.hh"
|
||||
|
||||
namespace tyche::as {
|
||||
@@ -122,21 +119,4 @@ void Lexer::ingest_next_token()
|
||||
current_token_ = { .type = type, .token = value, .line = current_line, .column = pos_ - current_line_pos };
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Token const& t)
|
||||
{
|
||||
switch (t.type) {
|
||||
case TokenType::BOF: os << "BOF"s; break;
|
||||
case TokenType::Directive: os << "Directive ("s << std::get<std::string>(t.token) << ")"s;
|
||||
case TokenType::Instruction: os << "Instruction ("s << std::get<std::string>(t.token) << ")"s;
|
||||
case TokenType::Integer: os << "Integer ("s << std::to_string(std::get<int>(t.token)) << ")"s;
|
||||
case TokenType::Float: os << "Float ("s << std::to_string(std::get<float>(t.token)) << ")"s;
|
||||
case TokenType::String: os << "String ("s << std::get<std::string>(t.token) << ")"s;
|
||||
case TokenType::Enter: os << "Enter"s;
|
||||
case TokenType::Colon: os << "Colon"s;
|
||||
case TokenType::EOF_: os << "EOF"s;
|
||||
default: os << "???"s;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
} // tyche
|
||||
|
||||
@@ -13,7 +13,6 @@ enum class TokenType {
|
||||
|
||||
using TokenValue = std::variant<std::monostate, int, float, std::string>;
|
||||
|
||||
|
||||
struct Token {
|
||||
TokenType type;
|
||||
TokenValue token = std::monostate();
|
||||
@@ -22,7 +21,6 @@ struct Token {
|
||||
|
||||
friend bool operator==(Token const& lhs, Token const& rhs) { return std::tie(lhs.type, lhs.token) == std::tie(rhs.type, rhs.token); }
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& os, Token const& t);
|
||||
|
||||
std::string token_type_name(TokenType type);
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
#include "../bytecode/bytecodeprototype.hh"
|
||||
#include "../bytecode/bytecode.hh"
|
||||
#include "../instructions/instruction.hh"
|
||||
#include "../vm/instruction.hh"
|
||||
|
||||
using namespace tyche;
|
||||
using namespace tyche::as;
|
||||
using namespace tyche::bc;
|
||||
using namespace tyche::vm;
|
||||
|
||||
TEST(Lexer, Lexer)
|
||||
{
|
||||
@@ -47,7 +48,7 @@ TEST(Assember, Assembler)
|
||||
bp.functions.at(1).code.append_byte((uint8_t) Instruction::PushInt16);
|
||||
bp.functions.at(1).code.append_int16(5000);
|
||||
bp.functions.at(1).code.append_byte((uint8_t) Instruction::Return);
|
||||
StaticByteArray expected = Bytecode::generate(bp);
|
||||
ByteArray expected = Bytecode::generate(bp);
|
||||
|
||||
std::string src = R"(
|
||||
.const
|
||||
@@ -64,7 +65,7 @@ TEST(Assember, Assembler)
|
||||
ret
|
||||
)";
|
||||
|
||||
StaticByteArray actual = Assembler(src).assemble();
|
||||
ByteArray actual = Assembler(src).assemble();
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
#include "bytearray.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
namespace tyche {
|
||||
|
||||
uint8_t StaticByteArray::get_byte(uint32_t addr) const
|
||||
{
|
||||
return data_.at(addr);
|
||||
}
|
||||
|
||||
uint16_t StaticByteArray::get_uint16(uint32_t addr) const
|
||||
{
|
||||
return (uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8;
|
||||
}
|
||||
|
||||
uint32_t StaticByteArray::get_uint32(uint32_t addr) const
|
||||
{
|
||||
return (uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8
|
||||
| (uint32_t) get_byte(addr+2) << 16
|
||||
| (uint32_t) get_byte(addr+3) << 24;
|
||||
}
|
||||
|
||||
int8_t StaticByteArray::get_int8(uint32_t addr) const
|
||||
{
|
||||
return std::bit_cast<int8_t>(get_byte(addr));
|
||||
}
|
||||
|
||||
int16_t StaticByteArray::get_int16(uint32_t addr) const
|
||||
{
|
||||
return (uint16_t) get_byte(addr)
|
||||
| (uint16_t) get_byte(addr+1) << 8;
|
||||
}
|
||||
|
||||
int32_t StaticByteArray::get_int32(uint32_t addr) const
|
||||
{
|
||||
return std::bit_cast<int32_t>((uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8
|
||||
| (uint32_t) get_byte(addr+2) << 16
|
||||
| (uint32_t) get_byte(addr+3) << 24);
|
||||
}
|
||||
|
||||
float StaticByteArray::get_float(uint32_t addr) const
|
||||
{
|
||||
uint32_t bits = (uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8
|
||||
| (uint32_t) get_byte(addr+2) << 16
|
||||
| (uint32_t) get_byte(addr+3) << 24;
|
||||
float value;
|
||||
std::memcpy(&value, &bits, 4);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::pair<const char*, size_t> StaticByteArray::get_string_ptr(uint32_t addr) const
|
||||
{
|
||||
return { (const char *) &data_.at(addr), strlen((const char *) &data_.at(addr)) + 1 };
|
||||
}
|
||||
|
||||
std::string StaticByteArray::hexdump() const
|
||||
{
|
||||
auto to_hex = [](uint32_t value, size_t n_chars) -> std::string {
|
||||
char buf[15];
|
||||
snprintf(buf, sizeof buf, "%0*X", (int) n_chars, value);
|
||||
return { buf };
|
||||
};
|
||||
|
||||
std::string out;
|
||||
for (size_t i = 0; i < data_.size(); ++i) {
|
||||
if (i % 16 == 0)
|
||||
out += to_hex(i, 4) + " | ";
|
||||
out += to_hex(data_.at(i), 2) + " ";
|
||||
if (i % 16 == 15)
|
||||
out += "\n";
|
||||
}
|
||||
return out + "\n";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifndef TYCHE_BYTEARRAY_HH
|
||||
#define TYCHE_BYTEARRAY_HH
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace tyche {
|
||||
|
||||
class StaticByteArray {
|
||||
public:
|
||||
explicit StaticByteArray(std::vector<uint8_t> const& data) : data_(data) {}
|
||||
|
||||
explicit StaticByteArray(StaticByteArray const& ba) : data_(ba.data()) {}
|
||||
|
||||
// not assignable or moveable
|
||||
StaticByteArray(StaticByteArray&&) = delete;
|
||||
StaticByteArray& operator=(StaticByteArray const&) = delete;
|
||||
StaticByteArray& operator=(StaticByteArray&&) = delete;
|
||||
|
||||
[[nodiscard]] uint8_t get_byte(uint32_t addr) const;
|
||||
[[nodiscard]] uint16_t get_uint16(uint32_t addr) const;
|
||||
[[nodiscard]] uint32_t get_uint32(uint32_t addr) const;
|
||||
[[nodiscard]] int8_t get_int8(uint32_t addr) const;
|
||||
[[nodiscard]] int16_t get_int16(uint32_t addr) const;
|
||||
[[nodiscard]] int32_t get_int32(uint32_t addr) const;
|
||||
[[nodiscard]] float get_float(uint32_t addr) const;
|
||||
[[nodiscard]] std::pair<const char*, size_t> get_string_ptr(uint32_t addr) const;
|
||||
|
||||
[[nodiscard]] std::vector<uint8_t> const& data() const { return data_; }
|
||||
[[nodiscard]] size_t size() const { return data_.size(); }
|
||||
|
||||
[[nodiscard]] std::string hexdump() const;
|
||||
|
||||
friend bool operator==(StaticByteArray const& lhs, StaticByteArray const& rhs) { return lhs.data_ == rhs.data_; }
|
||||
|
||||
private:
|
||||
const std::vector<uint8_t> data_ {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_BYTEARRAY_HH
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "bytearraybuilder.hh"
|
||||
|
||||
namespace tyche {
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::set_byte(uint32_t addr, uint8_t byte)
|
||||
{
|
||||
if (data_.size() < (addr + 1))
|
||||
data_.resize(addr + 1, 0);
|
||||
data_.at(addr) = byte;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::set_int8(uint32_t addr, int8_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::set_int16(uint32_t addr, int16_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::set_int32(uint32_t addr, int32_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
set_byte(addr+2, (uint8_t) (value >> 16));
|
||||
set_byte(addr+3, (uint8_t) (value >> 24));
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::set_uint16(uint32_t addr, uint16_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::set_uint32(uint32_t addr, uint32_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
set_byte(addr+2, (uint8_t) (value >> 16));
|
||||
set_byte(addr+3, (uint8_t) (value >> 24));
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::set_float(uint32_t addr, float value)
|
||||
{
|
||||
uint32_t bits;
|
||||
std::memcpy(&bits, &value, 4);
|
||||
set_byte(addr, (uint8_t) (bits));
|
||||
set_byte(addr+1, (uint8_t) (bits >> 8));
|
||||
set_byte(addr+2, (uint8_t) (bits >> 16));
|
||||
set_byte(addr+3, (uint8_t) (bits >> 24));
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::set_string(uint32_t addr, std::string const& str)
|
||||
{
|
||||
for (uint8_t c: str)
|
||||
set_byte(addr++, c);
|
||||
set_byte(addr, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::set_bytearray(uint32_t addr, ByteArrayBuilder const& bytearray)
|
||||
{
|
||||
for (uint8_t byte: bytearray.data_)
|
||||
set_byte(addr++, byte);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArrayBuilder& ByteArrayBuilder::append_bytearray(ByteArrayBuilder const& bytearray)
|
||||
{
|
||||
data_.insert(data_.end(), bytearray.data_.begin(), bytearray.data_.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
StaticByteArray ByteArrayBuilder::build() const
|
||||
{
|
||||
return StaticByteArray(data_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#ifndef TYCHE_BYTEARRAYBUILDER_HH
|
||||
#define TYCHE_BYTEARRAYBUILDER_HH
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "bytearray.hh"
|
||||
|
||||
namespace tyche {
|
||||
|
||||
class ByteArrayBuilder {
|
||||
public:
|
||||
ByteArrayBuilder& set_byte(uint32_t addr, uint8_t byte);
|
||||
ByteArrayBuilder& set_uint16(uint32_t addr, uint16_t value);
|
||||
ByteArrayBuilder& set_uint32(uint32_t addr, uint32_t value);
|
||||
ByteArrayBuilder& set_int8(uint32_t addr, int8_t value);
|
||||
ByteArrayBuilder& set_int16(uint32_t addr, int16_t value);
|
||||
ByteArrayBuilder& set_int32(uint32_t addr, int32_t value);
|
||||
ByteArrayBuilder& set_float(uint32_t addr, float value);
|
||||
ByteArrayBuilder& set_string(uint32_t addr, std::string const& str);
|
||||
ByteArrayBuilder& set_bytearray(uint32_t addr, ByteArrayBuilder const& bytearray);
|
||||
|
||||
ByteArrayBuilder& append_byte(uint8_t byte) { set_byte(data_.size(), byte); return *this; }
|
||||
ByteArrayBuilder& append_uint16(uint16_t value) { set_uint16(data_.size(), value); return *this; }
|
||||
ByteArrayBuilder& append_uint32(uint32_t value) { set_uint32(data_.size(), value); return *this; }
|
||||
ByteArrayBuilder& append_int8(int8_t value) { set_int8(data_.size(), value); return *this; }
|
||||
ByteArrayBuilder& append_int16(int16_t value) { set_int16(data_.size(), value); return *this; }
|
||||
ByteArrayBuilder& append_int32(int32_t value) { set_int32(data_.size(), value); return *this; }
|
||||
ByteArrayBuilder& append_float(float value) { set_float(data_.size(), value); return *this; }
|
||||
ByteArrayBuilder& append_string(std::string const& str) { set_string(data_.size(), str); return *this; }
|
||||
ByteArrayBuilder& append_bytearray(ByteArrayBuilder const& bytearray);
|
||||
|
||||
[[nodiscard]] std::vector<uint8_t> const& data() const { return data_; }
|
||||
[[nodiscard]] size_t size() const { return data_.size(); }
|
||||
|
||||
[[nodiscard]] StaticByteArray build() const;
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> data_ {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_BYTEARRAYBUILDER_HH
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
|
||||
#include "bytearray.hh"
|
||||
#include "bytearraybuilder.hh"
|
||||
|
||||
using namespace tyche;
|
||||
|
||||
TEST(StaticByteArray, StaticByteArray)
|
||||
{
|
||||
auto test = [](std::function<void(ByteArrayBuilder&)> const& f, std::vector<uint8_t> const& expected) {
|
||||
ByteArrayBuilder ba;
|
||||
f(ba);
|
||||
ASSERT_EQ(ba.data().size(), expected.size());
|
||||
ASSERT_EQ(std::memcmp(ba.data().data(), expected.data(), ba.data().size()), 0);
|
||||
};
|
||||
|
||||
#define TESTX(a, ...) test([](ByteArrayBuilder& ba) { a; }, std::vector<uint8_t>({ __VA_ARGS__ }));
|
||||
|
||||
TESTX(ba.set_byte(1, 0xab), 0x00, 0xab)
|
||||
|
||||
ByteArrayBuilder ba;
|
||||
{ auto b = ba.set_byte(1, 0xab).build(); ASSERT_EQ(b.get_byte(1), 0xab); }
|
||||
|
||||
{ auto b = ba.set_int8(1, 12).build(); ASSERT_EQ(b.get_int8(1), 12); }
|
||||
{ auto b = ba.set_int8(1, -12).build(); ASSERT_EQ(b.get_int8(1), -12); }
|
||||
{ auto b = ba.set_int16(1, 5000).build(); ASSERT_EQ(b.get_int16(1), 5000); }
|
||||
{ auto b = ba.set_int32(1, 5000300).build(); ASSERT_EQ(b.get_int32(1), 5000300); }
|
||||
{ auto b = ba.set_int32(1, -5000300).build(); ASSERT_EQ(b.get_int32(1), -5000300); }
|
||||
|
||||
{ auto b = ba.set_float(1, 3.14).build(); ASSERT_FLOAT_EQ(b.get_float(1), 3.14); }
|
||||
{ auto b = ba.set_float(1, -3.14).build(); ASSERT_FLOAT_EQ(b.get_float(1), -3.14); }
|
||||
{ auto b = ba.set_float(1, -5000300.1324).build(); ASSERT_FLOAT_EQ(b.get_float(1), -5000300.1324); }
|
||||
|
||||
{
|
||||
auto b = ba.set_string(1, "Hello world!").build();
|
||||
auto str = b.get_string_ptr(1);
|
||||
EXPECT_STREQ(str.first, "Hello world!");
|
||||
ASSERT_EQ(str.second, 13);
|
||||
}
|
||||
|
||||
#undef TESTX
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -5,29 +5,29 @@
|
||||
|
||||
namespace tyche::bc {
|
||||
|
||||
Bytecode::Bytecode(StaticByteArray const* ba)
|
||||
: byte_array_(ba)
|
||||
Bytecode::Bytecode(ByteArray ba)
|
||||
: byte_array_(std::move(ba))
|
||||
{
|
||||
// check file size
|
||||
if (byte_array_->size() < (TOC_START + TOC_SZ))
|
||||
if (byte_array_.size() < (TOC_START + TOC_SZ))
|
||||
throw BytecodeParsingError("Invalid bytecode format (file too short)");
|
||||
|
||||
// check magic number and version
|
||||
if (byte_array_->get_uint32(0) != MAGIC_NUMBER)
|
||||
if (byte_array_.get_uint32(0) != MAGIC_NUMBER)
|
||||
throw BytecodeParsingError("Invalid bytecode format (magic number not matching)");
|
||||
if (byte_array_->get_uint32(4) != BYTECODE_VERSION)
|
||||
if (byte_array_.get_uint32(4) != BYTECODE_VERSION)
|
||||
throw BytecodeParsingError("Unexpected bytecode format version");
|
||||
|
||||
// load cache
|
||||
cache_.constants_idx_addr = byte_array_->get_uint32(TOC_START);
|
||||
cache_.n_constants = byte_array_->get_uint16(TOC_START + 4);
|
||||
cache_.functions_idx_addr = byte_array_->get_uint32(TOC_START + (1 * TOC_RECORD_SZ));
|
||||
cache_.n_functions = byte_array_->get_uint16(TOC_START + (1 * TOC_RECORD_SZ) + 4);
|
||||
cache_.constants_start_addr = byte_array_->get_uint32(TOC_START + (2 * TOC_RECORD_SZ));
|
||||
uint32_t code_start = byte_array_->get_uint32(TOC_START + (3 * TOC_RECORD_SZ));
|
||||
cache_.constants_idx_addr = byte_array_.get_uint32(TOC_START);
|
||||
cache_.n_constants = byte_array_.get_uint16(TOC_START + 4);
|
||||
cache_.functions_idx_addr = byte_array_.get_uint32(TOC_START + (1 * TOC_RECORD_SZ));
|
||||
cache_.n_functions = byte_array_.get_uint16(TOC_START + (1 * TOC_RECORD_SZ) + 4);
|
||||
cache_.constants_start_addr = byte_array_.get_uint32(TOC_START + (2 * TOC_RECORD_SZ));
|
||||
uint32_t code_start = byte_array_.get_uint32(TOC_START + (3 * TOC_RECORD_SZ));
|
||||
for (uint32_t i = 0; i < cache_.n_functions; ++i) {
|
||||
cache_.function_addr.emplace_back(code_start + byte_array_->get_uint32(cache_.functions_idx_addr + (i * FUNCTION_RECORD_SZ)));
|
||||
cache_.function_sz.emplace_back(byte_array_->get_uint32(cache_.functions_idx_addr + (i * FUNCTION_RECORD_SZ) + 8));
|
||||
cache_.function_addr.emplace_back(code_start + byte_array_.get_uint32(cache_.functions_idx_addr + (i * FUNCTION_RECORD_SZ)));
|
||||
cache_.function_sz.emplace_back(byte_array_.get_uint32(cache_.functions_idx_addr + (i * FUNCTION_RECORD_SZ) + 8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,12 +43,12 @@ uint32_t Bytecode::n_functions() const
|
||||
|
||||
ConstantValue Bytecode::get_constant(uint32_t idx) const
|
||||
{
|
||||
uint32_t constant_idx = byte_array_->get_uint32(cache_.constants_idx_addr + (idx * CONST_RECORD_SZ));
|
||||
switch ((ConstantType) byte_array_->get_byte(cache_.constants_start_addr + constant_idx)) {
|
||||
uint32_t constant_idx = byte_array_.get_uint32(cache_.constants_idx_addr + (idx * CONST_RECORD_SZ));
|
||||
switch ((ConstantType) byte_array_.get_byte(cache_.constants_start_addr + constant_idx)) {
|
||||
case CONST_TYPE_FLOAT:
|
||||
return byte_array_->get_float(cache_.constants_start_addr + constant_idx + 1);
|
||||
return byte_array_.get_float(cache_.constants_start_addr + constant_idx + 1);
|
||||
case CONST_TYPE_STRING:
|
||||
return byte_array_->get_string_ptr(cache_.constants_start_addr + constant_idx + 1).first;
|
||||
return byte_array_.get_string(cache_.constants_start_addr + constant_idx + 1).first;
|
||||
default:
|
||||
throw BytecodeParsingError("Invalid bytecode format (invalid constant type)");
|
||||
}
|
||||
@@ -58,8 +58,8 @@ Bytecode::FunctionDef Bytecode::get_function_def(uint32_t function_id) const
|
||||
{
|
||||
uint32_t idx = cache_.functions_idx_addr + (function_id * FUNCTION_RECORD_SZ);
|
||||
return {
|
||||
.n_params = byte_array_->get_uint16(idx + 4),
|
||||
.locals = byte_array_->get_uint16(idx + 6),
|
||||
.n_params = byte_array_.get_uint16(idx + 4),
|
||||
.locals = byte_array_.get_uint16(idx + 6),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,34 +70,34 @@ uint32_t Bytecode::get_function_sz(uint32_t function_id) const
|
||||
|
||||
uint8_t Bytecode::get_code_byte(uint32_t function_id, uint32_t idx) const
|
||||
{
|
||||
return byte_array_->get_byte(cache_.function_addr.at(function_id) + idx);
|
||||
return byte_array_.get_byte(cache_.function_addr.at(function_id) + idx);
|
||||
}
|
||||
|
||||
int8_t Bytecode::get_code_int8(uint32_t function_id, uint32_t idx) const
|
||||
{
|
||||
return byte_array_->get_int8(cache_.function_addr.at(function_id) + idx);
|
||||
return byte_array_.get_int8(cache_.function_addr.at(function_id) + idx);
|
||||
}
|
||||
|
||||
int16_t Bytecode::get_code_int16(uint32_t function_id, uint32_t idx) const
|
||||
{
|
||||
return byte_array_->get_int16(cache_.function_addr.at(function_id) + idx);
|
||||
return byte_array_.get_int16(cache_.function_addr.at(function_id) + idx);
|
||||
}
|
||||
|
||||
int32_t Bytecode::get_code_int32(uint32_t function_id, uint32_t idx) const
|
||||
{
|
||||
return byte_array_->get_int32(cache_.function_addr.at(function_id) + idx);
|
||||
return byte_array_.get_int32(cache_.function_addr.at(function_id) + idx);
|
||||
}
|
||||
|
||||
StaticByteArray Bytecode::generate(BytecodePrototype const& bp)
|
||||
ByteArray Bytecode::generate(BytecodePrototype const& bp)
|
||||
{
|
||||
// header section
|
||||
ByteArrayBuilder header;
|
||||
ByteArray header;
|
||||
header.set_uint32(0, MAGIC_NUMBER);
|
||||
header.set_byte(4, BYTECODE_VERSION);
|
||||
|
||||
// constants
|
||||
ByteArrayBuilder constant_indexes;
|
||||
ByteArrayBuilder raw_constants;
|
||||
ByteArray constant_indexes;
|
||||
ByteArray raw_constants;
|
||||
|
||||
uint32_t idx = 0;
|
||||
for (auto const& constant: bp.constants) {
|
||||
@@ -116,8 +116,8 @@ StaticByteArray Bytecode::generate(BytecodePrototype const& bp)
|
||||
}
|
||||
|
||||
// functions
|
||||
ByteArrayBuilder functions_indexes;
|
||||
ByteArrayBuilder raw_code;
|
||||
ByteArray functions_indexes;
|
||||
ByteArray raw_code;
|
||||
|
||||
uint32_t idx_idx = 0, code_idx = 0;
|
||||
for (auto const& f: bp.functions) {
|
||||
@@ -135,7 +135,7 @@ StaticByteArray Bytecode::generate(BytecodePrototype const& bp)
|
||||
uint32_t raw_constant_start = function_idx_start + functions_indexes.size();
|
||||
uint32_t raw_code_start = raw_constant_start + raw_constants.size();
|
||||
|
||||
ByteArrayBuilder toc;
|
||||
ByteArray toc;
|
||||
if (!bp.constants.empty()) {
|
||||
toc.set_uint32(SEC_CONST_IDX * TOC_RECORD_SZ, CONST_IDX_START);
|
||||
toc.set_uint32(SEC_CONST_IDX * TOC_RECORD_SZ + 4, constant_indexes.size() / CONST_RECORD_SZ);
|
||||
@@ -153,14 +153,14 @@ StaticByteArray Bytecode::generate(BytecodePrototype const& bp)
|
||||
// assemble bytecode
|
||||
//
|
||||
|
||||
ByteArrayBuilder ba;
|
||||
ByteArray ba;
|
||||
ba.set_bytearray(0, header);
|
||||
ba.set_bytearray(TOC_START, toc);
|
||||
ba.set_bytearray(CONST_IDX_START, constant_indexes);
|
||||
ba.set_bytearray(function_idx_start, functions_indexes);
|
||||
ba.set_bytearray(raw_constant_start, raw_constants);
|
||||
ba.set_bytearray(raw_code_start, raw_code);
|
||||
return ba.build();
|
||||
return ba;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
#ifndef TYCHE_BYTECODE_HH
|
||||
#define TYCHE_BYTECODE_HH
|
||||
|
||||
#include "../bytearray/bytearray.hh"
|
||||
#include "../common/bytearray.hh"
|
||||
#include "bytecodeprototype.hh"
|
||||
#include "constant.hh"
|
||||
|
||||
namespace tyche::bc {
|
||||
|
||||
class Bytecode {
|
||||
public:
|
||||
explicit Bytecode(StaticByteArray const* ba);
|
||||
Bytecode() = default;
|
||||
explicit Bytecode(ByteArray ba);
|
||||
|
||||
[[nodiscard]] uint32_t n_constants() const;
|
||||
[[nodiscard]] uint32_t n_functions() const;
|
||||
@@ -27,10 +27,10 @@ public:
|
||||
|
||||
// TODO - debugging info
|
||||
|
||||
[[nodiscard]] static StaticByteArray generate(BytecodePrototype const& bp);
|
||||
[[nodiscard]] static ByteArray generate(BytecodePrototype const& bp);
|
||||
|
||||
private:
|
||||
StaticByteArray const* byte_array_; // the actual data
|
||||
ByteArray byte_array_; // the actual data
|
||||
|
||||
static constexpr uint8_t BYTECODE_VERSION = 1;
|
||||
static constexpr uint32_t MAGIC_NUMBER = 0x74b3c138;
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include "../bytearray/bytearraybuilder.hh"
|
||||
#include "constant.hh"
|
||||
#include "../common/bytearray.hh"
|
||||
|
||||
namespace tyche::bc {
|
||||
|
||||
@@ -13,12 +14,11 @@ struct BytecodePrototype {
|
||||
struct Function {
|
||||
uint16_t n_pars;
|
||||
uint16_t n_locals;
|
||||
ByteArrayBuilder code {};
|
||||
ByteArray code {};
|
||||
|
||||
Function(uint16_t n_pars_, uint16_t n_locals_) : n_pars(n_pars_), n_locals(n_locals_), code(ByteArrayBuilder {}) {}
|
||||
Function(uint16_t n_pars_, uint16_t n_locals_) : n_pars(n_pars_), n_locals(n_locals_), code(ByteArray {}) {}
|
||||
};
|
||||
|
||||
using ConstantValue = std::variant<float, std::string>;
|
||||
std::vector<ConstantValue> constants {};
|
||||
std::vector<Function> functions {};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace tyche::bc {
|
||||
|
||||
using ConstantValue = std::variant<float, const char*>;
|
||||
using ConstantValue = std::variant<float, std::string>;
|
||||
|
||||
enum ConstantType : uint8_t { CONST_TYPE_FLOAT = 1, CONST_TYPE_STRING = 2 };
|
||||
|
||||
|
||||
@@ -3,46 +3,40 @@
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
|
||||
#include "../bytearray/bytearray.hh"
|
||||
#include "../bytearray/bytearraybuilder.hh"
|
||||
#include "../common/bytearray.hh"
|
||||
#include "bytecodeprototype.hh"
|
||||
#include "bytecode.hh"
|
||||
|
||||
using namespace tyche;
|
||||
using namespace tyche::bc;
|
||||
|
||||
TEST(StaticByteArray, StaticByteArray)
|
||||
TEST(ByteArray, ByteArray)
|
||||
{
|
||||
auto test = [](std::function<void(ByteArrayBuilder&)> const& f, std::vector<uint8_t> const& expected) {
|
||||
ByteArrayBuilder ba;
|
||||
auto test = [](std::function<void(ByteArray&)> const& f, std::vector<uint8_t> const& expected) {
|
||||
ByteArray ba;
|
||||
f(ba);
|
||||
ASSERT_EQ(ba.data().size(), expected.size());
|
||||
ASSERT_EQ(std::memcmp(ba.data().data(), expected.data(), ba.data().size()), 0);
|
||||
};
|
||||
|
||||
#define TESTX(a, ...) test([](ByteArrayBuilder& ba) { a; }, std::vector<uint8_t>({ __VA_ARGS__ }));
|
||||
#define TESTX(a, ...) test([](ByteArray& ba) { a; }, std::vector<uint8_t>({ __VA_ARGS__ }));
|
||||
|
||||
TESTX(ba.set_byte(1, 0xab), 0x00, 0xab)
|
||||
|
||||
ByteArrayBuilder ba;
|
||||
{ auto b = ba.set_byte(1, 0xab).build(); ASSERT_EQ(b.get_byte(1), 0xab); }
|
||||
ByteArray ba;
|
||||
ba.set_byte(1, 0xab); ASSERT_EQ(ba.get_byte(1), 0xab);
|
||||
|
||||
{ auto b = ba.set_int8(1, 12).build(); ASSERT_EQ(b.get_int8(1), 12); }
|
||||
{ auto b = ba.set_int8(1, -12).build(); ASSERT_EQ(b.get_int8(1), -12); }
|
||||
{ auto b = ba.set_int16(1, 5000).build(); ASSERT_EQ(b.get_int16(1), 5000); }
|
||||
{ auto b = ba.set_int32(1, 5000300).build(); ASSERT_EQ(b.get_int32(1), 5000300); }
|
||||
{ auto b = ba.set_int32(1, -5000300).build(); ASSERT_EQ(b.get_int32(1), -5000300); }
|
||||
ba.set_int8(1, 12); ASSERT_EQ(ba.get_int8(1), 12);
|
||||
ba.set_int8(1, -12); ASSERT_EQ(ba.get_int8(1), -12);
|
||||
ba.set_int16(1, 5000); ASSERT_EQ(ba.get_int16(1), 5000);
|
||||
ba.set_int32(1, 5000300); ASSERT_EQ(ba.get_int32(1), 5000300);
|
||||
ba.set_int32(1, -5000300); ASSERT_EQ(ba.get_int32(1), -5000300);
|
||||
|
||||
{ auto b = ba.set_float(1, 3.14).build(); ASSERT_FLOAT_EQ(b.get_float(1), 3.14); }
|
||||
{ auto b = ba.set_float(1, -3.14).build(); ASSERT_FLOAT_EQ(b.get_float(1), -3.14); }
|
||||
{ auto b = ba.set_float(1, -5000300.1324).build(); ASSERT_FLOAT_EQ(b.get_float(1), -5000300.1324); }
|
||||
ba.set_float(1, 3.14); ASSERT_FLOAT_EQ(ba.get_float(1), 3.14);
|
||||
ba.set_float(1, -3.14); ASSERT_FLOAT_EQ(ba.get_float(1), -3.14);
|
||||
ba.set_float(1, -5000300.1324); ASSERT_FLOAT_EQ(ba.get_float(1), -5000300.1324);
|
||||
|
||||
{
|
||||
auto b = ba.set_string(1, "Hello world!").build();
|
||||
auto str = b.get_string_ptr(1);
|
||||
EXPECT_STREQ(str.first, "Hello world!");
|
||||
ASSERT_EQ(str.second, 13);
|
||||
}
|
||||
ba.set_string(1, "Hello world!"); ASSERT_EQ(ba.get_string(1), std::make_pair("Hello world!", 13));
|
||||
|
||||
#undef TESTX
|
||||
}
|
||||
@@ -79,7 +73,7 @@ TEST(Bytecode, Constants)
|
||||
CONST_TYPE_STRING, 'H', 'E', 'L', 'L', 'O', 0x00
|
||||
};
|
||||
|
||||
StaticByteArray ba = Bytecode::generate(bp);
|
||||
ByteArray ba = Bytecode::generate(bp);
|
||||
ASSERT_EQ(ba.data(), expected);
|
||||
}
|
||||
|
||||
@@ -118,7 +112,7 @@ TEST(Bytecode, Code)
|
||||
0x68, 42, 0x42,
|
||||
};
|
||||
|
||||
StaticByteArray ba = Bytecode::generate(bp);
|
||||
ByteArray ba = Bytecode::generate(bp);
|
||||
ASSERT_EQ(ba.data(), expected);
|
||||
}
|
||||
|
||||
@@ -138,12 +132,12 @@ TEST(Bytecode, Parsing)
|
||||
auto& ff = bp.functions.emplace_back(2, 1);
|
||||
ff.code.append_byte(0x42);
|
||||
|
||||
StaticByteArray ba = Bytecode::generate(bp);
|
||||
ByteArray ba = Bytecode::generate(bp);
|
||||
// print(ba.data());
|
||||
|
||||
// read bytecode
|
||||
|
||||
Bytecode bc(&ba);
|
||||
Bytecode bc(std::move(ba));
|
||||
|
||||
ASSERT_EQ(bc.n_constants(), 2);
|
||||
ASSERT_EQ(bc.n_functions(), 2);
|
||||
@@ -151,7 +145,7 @@ TEST(Bytecode, Parsing)
|
||||
ASSERT_EQ(bc.get_function_sz(1), 1);
|
||||
|
||||
ASSERT_FLOAT_EQ(std::get<float>(bc.get_constant(0)), 3.14f);
|
||||
EXPECT_STREQ(std::get<const char*>(bc.get_constant(1)), "HELLO");
|
||||
ASSERT_EQ(std::get<std::string>(bc.get_constant(1)), "HELLO");
|
||||
|
||||
Bytecode::FunctionDef f1 = bc.get_function_def(0);
|
||||
ASSERT_EQ(f1.n_params, 0);
|
||||
|
||||
152
src/common/bytearray.cc
Normal file
152
src/common/bytearray.cc
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "bytearray.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
namespace tyche {
|
||||
|
||||
void ByteArray::set_byte(uint32_t addr, uint8_t byte)
|
||||
{
|
||||
if (data_.size() < (addr + 1))
|
||||
data_.resize(addr + 1, 0);
|
||||
data_.at(addr) = byte;
|
||||
}
|
||||
|
||||
void ByteArray::set_int8(uint32_t addr, int8_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) value);
|
||||
}
|
||||
|
||||
void ByteArray::set_int16(uint32_t addr, int16_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
}
|
||||
|
||||
void ByteArray::set_int32(uint32_t addr, int32_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
set_byte(addr+2, (uint8_t) (value >> 16));
|
||||
set_byte(addr+3, (uint8_t) (value >> 24));
|
||||
}
|
||||
|
||||
void ByteArray::set_uint16(uint32_t addr, uint16_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
}
|
||||
|
||||
void ByteArray::set_uint32(uint32_t addr, uint32_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
set_byte(addr+2, (uint8_t) (value >> 16));
|
||||
set_byte(addr+3, (uint8_t) (value >> 24));
|
||||
}
|
||||
|
||||
void ByteArray::set_float(uint32_t addr, float value)
|
||||
{
|
||||
uint32_t bits;
|
||||
std::memcpy(&bits, &value, 4);
|
||||
set_byte(addr, (uint8_t) (bits));
|
||||
set_byte(addr+1, (uint8_t) (bits >> 8));
|
||||
set_byte(addr+2, (uint8_t) (bits >> 16));
|
||||
set_byte(addr+3, (uint8_t) (bits >> 24));
|
||||
}
|
||||
|
||||
void ByteArray::set_string(uint32_t addr, std::string const& str)
|
||||
{
|
||||
for (uint8_t c: str)
|
||||
set_byte(addr++, c);
|
||||
set_byte(addr, 0);
|
||||
}
|
||||
|
||||
void ByteArray::set_bytearray(uint32_t addr, ByteArray const& bytearray)
|
||||
{
|
||||
for (uint8_t byte: bytearray.data())
|
||||
set_byte(addr++, byte);
|
||||
}
|
||||
|
||||
uint8_t ByteArray::get_byte(uint32_t addr) const
|
||||
{
|
||||
return data_.at(addr);
|
||||
}
|
||||
|
||||
uint16_t ByteArray::get_uint16(uint32_t addr) const
|
||||
{
|
||||
return (uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8;
|
||||
}
|
||||
|
||||
uint32_t ByteArray::get_uint32(uint32_t addr) const
|
||||
{
|
||||
return (uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8
|
||||
| (uint32_t) get_byte(addr+2) << 16
|
||||
| (uint32_t) get_byte(addr+3) << 24;
|
||||
}
|
||||
|
||||
int8_t ByteArray::get_int8(uint32_t addr) const
|
||||
{
|
||||
return std::bit_cast<int8_t>(get_byte(addr));
|
||||
}
|
||||
|
||||
int16_t ByteArray::get_int16(uint32_t addr) const
|
||||
{
|
||||
return (uint16_t) get_byte(addr)
|
||||
| (uint16_t) get_byte(addr+1) << 8;
|
||||
}
|
||||
|
||||
int32_t ByteArray::get_int32(uint32_t addr) const
|
||||
{
|
||||
return std::bit_cast<int32_t>((uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8
|
||||
| (uint32_t) get_byte(addr+2) << 16
|
||||
| (uint32_t) get_byte(addr+3) << 24);
|
||||
}
|
||||
|
||||
float ByteArray::get_float(uint32_t addr) const
|
||||
{
|
||||
uint32_t bits = (uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8
|
||||
| (uint32_t) get_byte(addr+2) << 16
|
||||
| (uint32_t) get_byte(addr+3) << 24;
|
||||
float value;
|
||||
std::memcpy(&value, &bits, 4);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::pair<std::string, size_t> ByteArray::get_string(uint32_t addr) const
|
||||
{
|
||||
std::string data;
|
||||
while (char c = (char) get_byte(addr++))
|
||||
data += c;
|
||||
return { data, data.size() + 1 };
|
||||
}
|
||||
|
||||
void ByteArray::append_bytearray(ByteArray const& bytearray)
|
||||
{
|
||||
data_.insert(data_.end(), bytearray.data().begin(), bytearray.data().end());
|
||||
}
|
||||
|
||||
std::string ByteArray::hexdump() const
|
||||
{
|
||||
auto to_hex = [](uint32_t value, size_t n_chars) -> std::string {
|
||||
char buf[15];
|
||||
snprintf(buf, sizeof buf, "%0*X", (int) n_chars, value);
|
||||
return { buf };
|
||||
};
|
||||
|
||||
std::string out;
|
||||
for (size_t i = 0; i < data_.size(); ++i) {
|
||||
if (i % 16 == 0)
|
||||
out += to_hex(i, 4) + " | ";
|
||||
out += to_hex(data_.at(i), 2) + " ";
|
||||
if (i % 16 == 15)
|
||||
out += "\n";
|
||||
}
|
||||
return out + "\n";
|
||||
}
|
||||
|
||||
}
|
||||
58
src/common/bytearray.hh
Normal file
58
src/common/bytearray.hh
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef TYCHE_BYTEARRAY_HH
|
||||
#define TYCHE_BYTEARRAY_HH
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace tyche {
|
||||
|
||||
class ByteArray {
|
||||
public:
|
||||
ByteArray() = default;
|
||||
explicit ByteArray(std::vector<uint8_t> data) : data_(std::move(data)) {}
|
||||
|
||||
void set_byte(uint32_t addr, uint8_t byte);
|
||||
void set_uint16(uint32_t addr, uint16_t value);
|
||||
void set_uint32(uint32_t addr, uint32_t value);
|
||||
void set_int8(uint32_t addr, int8_t value);
|
||||
void set_int16(uint32_t addr, int16_t value);
|
||||
void set_int32(uint32_t addr, int32_t value);
|
||||
void set_float(uint32_t addr, float value);
|
||||
void set_string(uint32_t addr, std::string const& str);
|
||||
void set_bytearray(uint32_t addr, ByteArray const& bytearray);
|
||||
|
||||
void append_byte(uint8_t byte) { set_byte(data_.size(), byte); }
|
||||
void append_uint16(uint16_t value) { set_uint16(data_.size(), value); }
|
||||
void append_uint32(uint32_t value) { set_uint32(data_.size(), value); }
|
||||
void append_int8(int8_t value) { set_int8(data_.size(), value); }
|
||||
void append_int16(int16_t value) { set_int16(data_.size(), value); }
|
||||
void append_int32(int32_t value) { set_int32(data_.size(), value); }
|
||||
void append_float(float value) { set_float(data_.size(), value); }
|
||||
void append_string(std::string const& str) { set_string(data_.size(), str); }
|
||||
void append_bytearray(ByteArray const& bytearray);
|
||||
|
||||
[[nodiscard]] uint8_t get_byte(uint32_t addr) const;
|
||||
[[nodiscard]] uint16_t get_uint16(uint32_t addr) const;
|
||||
[[nodiscard]] uint32_t get_uint32(uint32_t addr) const;
|
||||
[[nodiscard]] int8_t get_int8(uint32_t addr) const;
|
||||
[[nodiscard]] int16_t get_int16(uint32_t addr) const;
|
||||
[[nodiscard]] int32_t get_int32(uint32_t addr) const;
|
||||
[[nodiscard]] float get_float(uint32_t addr) const;
|
||||
[[nodiscard]] std::pair<std::string, size_t> get_string(uint32_t addr) const;
|
||||
|
||||
[[nodiscard]] std::vector<uint8_t> const& data() const { return data_; }
|
||||
[[nodiscard]] size_t size() const { return data_.size(); }
|
||||
|
||||
[[nodiscard]] std::string hexdump() const;
|
||||
|
||||
friend bool operator==(ByteArray const& lhs, ByteArray const& rhs) { return lhs.data_ == rhs.data_; }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> data_ {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_BYTEARRAY_HH
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "code.hh"
|
||||
#include "../common/overloaded.hh"
|
||||
#include "../instructions/instruction.hh"
|
||||
#include "instruction.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
FunctionId Code::import_bytecode(StaticByteArray const* incoming)
|
||||
FunctionId Code::import_bytecode(ByteArray incoming)
|
||||
{
|
||||
bc::Bytecode bc(incoming);
|
||||
bc::Bytecode bc(std::move(incoming));
|
||||
// TODO - adjust function calls, constants
|
||||
|
||||
bytecode_ = std::move(bc);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef TYCHE_CODE_HH
|
||||
#define TYCHE_CODE_HH
|
||||
|
||||
#include "../instructions/instruction.hh"
|
||||
#include "instruction.hh"
|
||||
#include "location.hh"
|
||||
#include "value.hh"
|
||||
#include "../bytecode/bytecode.hh"
|
||||
@@ -17,7 +17,7 @@ struct Operation
|
||||
|
||||
class Code {
|
||||
public:
|
||||
FunctionId import_bytecode(StaticByteArray const* incoming);
|
||||
FunctionId import_bytecode(ByteArray incoming);
|
||||
|
||||
[[nodiscard]] std::string disassemble() const;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ static int init_ = []() {
|
||||
BIN_OP(Sum, Integer, Float) { return Value::createFloat((float) a.as_integer() + b.as_float()); };
|
||||
BIN_OP(Sum, Float, Integer) { return Value::createFloat(a.as_float() + (float) b.as_integer()); };
|
||||
BIN_OP(Sum, Float, Float) { return Value::createFloat(a.as_float() + b.as_float()); };
|
||||
BIN_OP(Sum, String, String) { return Value::createString(std::string(a.as_string_ptr()) + std::string(b.as_string_ptr())); };
|
||||
BIN_OP(Sum, String, String) { return Value::createString(a.as_string() + b.as_string()); };
|
||||
|
||||
BIN_OP(Subtraction, Integer, Integer) { return Value::createInteger(a.as_integer() - b.as_integer()); };
|
||||
BIN_OP(Subtraction, Integer, Float) { return Value::createFloat((float) a.as_integer() - b.as_float()); };
|
||||
@@ -61,13 +61,13 @@ static int init_ = []() {
|
||||
BIN_OP(Equality, Integer, Float) { return Value::createIntegerFromBool(std::abs((float) a.as_integer() - b.as_float()) < FLOAT_EPSILON); };
|
||||
BIN_OP(Equality, Float, Integer) { return Value::createIntegerFromBool(std::abs(a.as_float() - (float) b.as_integer()) < FLOAT_EPSILON); };
|
||||
BIN_OP(Equality, Float, Float) { return Value::createIntegerFromBool(std::abs(a.as_float() - b.as_float()) < FLOAT_EPSILON); };
|
||||
BIN_OP(Equality, String, String) { return Value::createIntegerFromBool(strcmp(a.as_string_ptr(), b.as_string_ptr()) == 0); };
|
||||
BIN_OP(Equality, String, String) { return Value::createIntegerFromBool(a.as_string() == b.as_string()); };
|
||||
|
||||
BIN_OP(Inequality, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() != b.as_integer()); };
|
||||
BIN_OP(Inequality, Integer, Float) { return Value::createIntegerFromBool(std::abs((float) a.as_integer() - b.as_float()) >= FLOAT_EPSILON); };
|
||||
BIN_OP(Inequality, Float, Integer) { return Value::createIntegerFromBool(std::abs(a.as_float() - (float) b.as_integer()) >= FLOAT_EPSILON); };
|
||||
BIN_OP(Inequality, Float, Float) { return Value::createIntegerFromBool(std::abs(a.as_float() - b.as_float()) >= FLOAT_EPSILON); };
|
||||
BIN_OP(Inequality, String, String) { return Value::createIntegerFromBool(strcmp(a.as_string_ptr(), b.as_string_ptr()) != 0); };
|
||||
BIN_OP(Inequality, String, String) { return Value::createIntegerFromBool(a.as_string() != b.as_string()); };
|
||||
|
||||
BIN_OP(LessThan, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() < b.as_integer()); };
|
||||
BIN_OP(LessThan, Integer, Float) { return Value::createIntegerFromBool((float) a.as_integer() < b.as_float()); };
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace tyche {
|
||||
namespace tyche::vm {
|
||||
|
||||
const std::unordered_map<std::string, Instruction> instruction_names = {
|
||||
{ "pushi", Instruction::PushInt8 },
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "../bytecode/bytecode.hh"
|
||||
|
||||
namespace tyche {
|
||||
namespace tyche::vm {
|
||||
|
||||
constexpr uint8_t OPCODE_NEXT_SIZE = 0x20;
|
||||
|
||||
@@ -110,7 +110,7 @@ TEST(VM, StackOperations)
|
||||
ASSERT_FLOAT_EQ(run("pushi 5000").to_float(-1), 5000.f);
|
||||
ASSERT_FLOAT_EQ(run("pushc 0").to_float(-1), 3.14f);
|
||||
ASSERT_EQ(run("pushc 0").to_integer(-1), 3);
|
||||
EXPECT_STREQ(run("pushc 1").to_string_ptr(-1), "Hello world");
|
||||
ASSERT_EQ(run("pushc 1").to_string(-1), "Hello world");
|
||||
ASSERT_TRUE(run("pushf 0").is_function(-1));
|
||||
ASSERT_EQ(run("pushi 2\n pushi 3\n pop").to_integer(-1), 2);
|
||||
}
|
||||
@@ -224,7 +224,7 @@ TEST(VM, FloatFloatOperations)
|
||||
|
||||
TEST(VM, StringString)
|
||||
{
|
||||
EXPECT_STREQ(run(R"(
|
||||
ASSERT_EQ(run(R"(
|
||||
.const
|
||||
0: "Hello"
|
||||
1: "World"
|
||||
@@ -233,7 +233,7 @@ TEST(VM, StringString)
|
||||
pushc 1
|
||||
sum
|
||||
ret
|
||||
)").to_string_ptr(-1), "HelloWorld");
|
||||
)").to_string(-1), "HelloWorld");
|
||||
|
||||
ASSERT_EQ(run(R"(
|
||||
.const
|
||||
|
||||
@@ -41,13 +41,4 @@ std::string Value::to_string() const
|
||||
}, value_);
|
||||
}
|
||||
|
||||
const char* Value::as_string_ptr() const
|
||||
{
|
||||
if (auto s = std::get_if<std::string>(&value_))
|
||||
return s->c_str();
|
||||
if (auto s = std::get_if<const char*>(&value_))
|
||||
return *s;
|
||||
throw std::logic_error("Shouldn't get here");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ public:
|
||||
static Value createInteger(int32_t v) { return Value(v); }
|
||||
static Value createFloat(float f) { return Value(f); }
|
||||
static Value createString(std::string const& str) { return Value(str); }
|
||||
static Value createStringFromConstant(const char* str) { return Value(str); }
|
||||
static Value createFunctionId(FunctionId f_id) { return Value(Function { f_id }); }
|
||||
|
||||
static Value createFalse() { return createInteger(0); }
|
||||
@@ -37,13 +36,13 @@ public:
|
||||
|
||||
[[nodiscard]] int32_t as_integer() const { return std::get<int32_t>(value_); }
|
||||
[[nodiscard]] float as_float() const { return std::get<float>(value_); }
|
||||
[[nodiscard]] const char* as_string_ptr() const;
|
||||
[[nodiscard]] std::string as_string() const { return std::get<std::string>(value_); }
|
||||
[[nodiscard]] FunctionId as_function_id() const { return std::get<Function>(value_).f_id; }
|
||||
|
||||
[[nodiscard]] std::string to_string() const;
|
||||
|
||||
private:
|
||||
using Internal = std::variant<std::monostate, int32_t, float, std::string, const char*, Function>;
|
||||
using Internal = std::variant<std::monostate, int32_t, float, std::string, Function>;
|
||||
Internal value_;
|
||||
|
||||
explicit Value(Internal const& internal) : value_(internal) {}
|
||||
|
||||
12
src/vm/vm.cc
12
src/vm/vm.cc
@@ -49,11 +49,11 @@ float VM::to_float(int index) const
|
||||
throw VMTypeError(Type::Float, f.type());
|
||||
}
|
||||
|
||||
const char* VM::to_string_ptr(int index) const
|
||||
std::string VM::to_string(int index) const
|
||||
{
|
||||
Value s = stack_.at(index);
|
||||
assert_type(s, Type::String);
|
||||
return s.as_string_ptr();
|
||||
Value i = stack_.at(index);
|
||||
assert_type(i, Type::String);
|
||||
return i.as_string();
|
||||
}
|
||||
|
||||
VM& VM::push_nil()
|
||||
@@ -110,8 +110,8 @@ void VM::step()
|
||||
auto cnst = code_.bytecode().get_constant(op.operator_);
|
||||
if (auto f = std::get_if<float>(&cnst))
|
||||
push_float(*f);
|
||||
else if (auto s = std::get_if<const char*>(&cnst))
|
||||
stack_.push(Value::createStringFromConstant(*s));
|
||||
else if (auto s = std::get_if<std::string>(&cnst))
|
||||
push_string(*s);
|
||||
else
|
||||
throw std::logic_error("Shouldn't get here");
|
||||
break;
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
|
||||
[[nodiscard]] int32_t to_integer(int index) const;
|
||||
[[nodiscard]] float to_float(int index) const;
|
||||
[[nodiscard]] const char* to_string_ptr(int index) const;
|
||||
[[nodiscard]] std::string to_string(int index) const;
|
||||
|
||||
[[nodiscard]] std::string debug_stack() const { return stack_.debug(); }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user