Compare commits
18 Commits
master
...
2634ddd1ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2634ddd1ca | ||
|
|
9ff38cd4c0 | ||
|
|
4a23c52781 | ||
|
|
8f851a330e | ||
|
|
7ecffcfdda | ||
|
|
88f9ce0ea6 | ||
|
|
0fae9a0b37 | ||
|
|
2725dc8d33 | ||
|
|
516ee9f406 | ||
|
|
6428c6cf7f | ||
| 43dea6ee8f | |||
|
|
566990318b | ||
|
|
8c36fb07c0 | ||
|
|
60c55304b2 | ||
|
|
8614f978ea | ||
|
|
0e9c8f6e63 | ||
|
|
299984cd4b | ||
|
|
8a685ebbc8 |
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
|
||||||
|
|
||||||
243
lua-temp/tests.lua
Normal file
243
lua-temp/tests.lua
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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_]+)%s*$", -- instruction + label
|
||||||
|
"^%s*(%a+)%s*$", -- instruction only
|
||||||
|
"^(@[%a_]+):%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
|
||||||
384
lua-temp/tyche-vm.lua
Normal file
384
lua-temp/tyche-vm.lua
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
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 --
|
||||||
|
-- --
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function validate_value(v)
|
||||||
|
assert(v, "value cannot be nil")
|
||||||
|
assert(type(v) == 'table', "invalid value format (expected { type='...', value=... }), received: " .. pprint.pformat(value))
|
||||||
|
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
|
||||||
|
|
||||||
|
----------------------
|
||||||
|
-- --
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
----------------------
|
||||||
|
-- --
|
||||||
|
-- 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(vm, a, b) 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:_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 })
|
||||||
|
|
||||||
|
--
|
||||||
|
-- local variables
|
||||||
|
--
|
||||||
|
|
||||||
|
elseif op.operator == 'pushv' then
|
||||||
|
assert(op.operand >= 0)
|
||||||
|
for i=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)
|
||||||
|
if self.debug then print(self.stack:debug()) end
|
||||||
|
return
|
||||||
|
else
|
||||||
|
|
||||||
|
error("Unknown operator '" .. tostring(op.operator) .. "'")
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.debug then print(self.stack:debug()) end
|
||||||
|
|
||||||
|
loc.pc = loc.pc + op.instruction_size
|
||||||
|
end
|
||||||
|
|
||||||
|
return VM
|
||||||
Reference in New Issue
Block a user