15 Commits

Author SHA1 Message Date
9561d5cacd . 2026-05-10 07:43:04 -05:00
0116a214f6 . 2026-05-09 15:07:36 -05:00
a70abe76ad . 2026-05-09 15:05:55 -05:00
e2f930641e . 2026-05-09 15:04:44 -05:00
85443ded9d . 2026-05-09 14:23:01 -05:00
6762c49ed3 . 2026-05-09 13:50:16 -05:00
610491c1d7 . 2026-05-09 13:26:13 -05:00
554a7b55c5 . 2026-05-09 11:34:51 -05:00
8a26ba5351 . 2026-05-09 10:37:02 -05:00
83b80f6e7d . 2026-05-09 10:24:09 -05:00
a6adb9b723 . 2026-05-09 09:24:46 -05:00
19b51fcaa0 . 2026-05-09 08:53:11 -05:00
27164aaac3 . 2026-05-08 20:36:21 -05:00
04b9821662 Merge remote-tracking branch 'origin/lua-temp' into lua-temp 2026-05-08 16:32:10 -05:00
b2a829d6e5 . 2026-05-08 16:32:06 -05:00
6 changed files with 550 additions and 84 deletions

4
.idea/misc.xml generated
View File

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

View File

@@ -11,11 +11,11 @@ Progress of the Lua port:
- [x] Control flow
- [x] Labels in Assembly
- [x] Recursion
- [ ] Strings
- [ ] From constants
- [ ] Garbage collection
- [ ] Arrays
- [ ] Garbage collection
- [x] Strings
- [x] From constants
- [x] Garbage collection
- [x] Arrays
- [x] Garbage collection
- [ ] Tables
- [ ] Garbage collection
- [ ] Metatables
@@ -28,3 +28,10 @@ Progress of the Lua port:
- [ ] Assembler generate bytecode
- [ ] VM interpret it
## C interface
- [ ] Error management (decision)
- [ ] Format for value and heap value
- [ ] Transparency and log levels

View File

@@ -22,12 +22,13 @@ Stack operations:
a0 c0 e0 pushi [int] Push int
a1 c1 e1 pushc [index] Push constant
a2 c2 e2 pushf [function] Push function id
00 pushz Push zero (or false)
01 pusht Push true
02 newa Push (create) empty array
03 newt Push (create) empty table
04 pop
05 dup
00 pushn Push nil
01 pushz Push zero (or false)
02 pusht Push true
03 newa Push (create) empty array
04 newt Push (create) empty table
05 pop
06 dup
Local variables:
a3 c3 e3 pushv [int] Push n nil values into the stack (used to init local vars)
@@ -44,12 +45,12 @@ Function operations:
Table and array operations:
16 getkv Get table's value based on key (pull 1 value, push 1 value)
17 setkv Set table's key and value (pull 2 values from stack)
18 geta Get array's position value
19 seta Set array's position value (pull 2 values from stack)
1a appnd Add value to the end of array
1b next Push the next pair into the stack (for loops)
1c smt Set value metatable
1d mt Get value metatable
a8 c8 e8 geti Get array's position value
a9 c9 e9 seti Set array's position value
18 appnd Add value to the end of array
19 next Push the next pair into the stack (for loops)
1a smt Set value metatable
1b mt Get value metatable
Logical/arithmetic:
20 sum Sum top 2 values in stack
@@ -74,7 +75,7 @@ Logical/arithmetic:
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
aa cast [type] Cast type to another type
42 ver Return VM version
External code:
@@ -83,11 +84,13 @@ External code:
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
ca bz [pc] Branch if zero
cb bnz [pc] Branch if not zero
cc jmp [pc] Unconditional jump
* Jumps can only happen within the same function.
Memory management:
4b gc Call garbage collector
Error handling: (0xa0~0xaf)
???

228
lua-temp/tests.lua Normal file → Executable file
View File

@@ -1,3 +1,5 @@
#!/usr/bin/env lua
local pprint = require('pprint')
local assemble = require('tyche-as')
local VM = require('tyche-vm')
@@ -146,12 +148,12 @@ end
----------------------
-- --
-- VM ARITH --
-- VM --
-- --
----------------------
local function arith(a, b, op)
return VM:new():load(assemble(string.format([[
return VM.new():load(assemble(string.format([[
.func 0
pushi %d
pushi %d
@@ -162,7 +164,7 @@ end
do TEST "VM: basic"
local vm = VM:new()
local vm = VM.new()
-- vm.debug = true
local bytecode = assemble [[
.func 0
@@ -206,7 +208,7 @@ do TEST "VM: logic/arithmetic"
end
do TEST "VM: local variables"
local vm = VM:new():load(assemble([[
local vm = VM.new():load(assemble([[
.func 0
pushv 2 ; local a, b
pushi 3 ; a = 3
@@ -222,7 +224,7 @@ do TEST "VM: local variables"
end
do TEST "VM: functions"
local vm = VM:new():load(assemble([[
local vm = VM.new():load(assemble([[
.func 0
pushf 1
pushi 2
@@ -240,4 +242,220 @@ do TEST "VM: functions"
assert_eq(vm:to_integer(-1), -1)
end
do TEST "VM: jumps (jmp + bnz)"
local vm = VM.new():load(assemble [[
.func 0
jmp @x1
pushi 5
@x1:
pushi 1
bnz @x2
pushi 1
bz @x3
@x2:
pushi 6
ret
@x3:
pushi 7
ret
]]):call(0)
assert_eq(vm:to_integer(-1), 6)
end
do TEST "VM: jumps (bz)"
local vm = VM.new():load(assemble [[
.func 0
jmp @x1
pushi 5
@x1:
pushi 0
bnz @x2
pushi 0
bz @x3
@x2:
pushi 6
ret
@x3:
pushi 7
ret
]]):call(0)
assert_eq(vm:to_integer(-1), 7)
end
do TEST "VM: string from const"
local vm = VM.new():load(assemble [[
.const
0: "Hello"
.func 0
pushc 0
ret
]]):call(0)
assert_eq(vm:to_string(-1), "Hello")
end
do TEST "VM: managed strings"
local vm = VM.new():push_string("Hello")
-- print(vm:debug_heap())
assert_eq(vm:to_string(-1), "Hello")
end
do TEST "VM: concatenate strings (GC won't delete)"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushc 0
pushc 1
sum
gc
ret
]]):call(0)
-- print(vm:debug_heap())
assert_eq(vm:to_string(-1), "Hello world")
assert_eq(vm.heap:size(), 1)
end
do TEST "VM: GC strings"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
pushc 0
pushc 1
sum
pop
gc
ret
]]):call(0)
-- print(vm:debug_heap())
assert_eq(vm:is(-1, 'nil'), true)
assert_eq(vm.heap:size(), 0)
end
do TEST "VM: arrays"
local vm = VM.new():load(assemble [[
.func 0
newa
pushi 10
seti 0
pushi 20
seti 1
pushi 30
seti 2
geti 1
ret
]]):call(0)
-- print(vm:debug_heap())
assert_eq(vm:to_integer(-1), 20)
assert_eq(vm.heap:size(), 1)
end
do TEST "VM: arrays GC"
local vm = VM.new():load(assemble [[
.func 0
pushn
newa
pushi 10
seti 0
pop
gc
ret
]]):call(0)
assert_eq(vm.heap:size(), 0)
end
do TEST "VM: GC items (1st level) - no items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
pushc 0
pushc 1
sum
seti 0
gc
pop
ret
]]):call(0)
assert_eq(vm.heap:size(), 2)
end
do TEST "VM: GC items (1st level) - all items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
pushc 0
pushc 1
sum
seti 0
pop
gc
ret
]]):call(0)
assert_eq(vm.heap:size(), 0)
end
do TEST "VM: GC items (2nd level) - no items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
newa
pushc 0
pushc 1
sum
seti 0
seti 0
gc
pop
ret
]]):call(0)
assert_eq(vm.heap:size(), 3)
end
do TEST "VM: GC items (1st level) - all items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
newa
pushc 0
pushc 1
sum
seti 0
seti 0
pop
gc
ret
]]):call(0)
assert_eq(vm.heap:size(), 0)
end
print('End.')

View File

@@ -38,9 +38,9 @@ local function assemble(source)
elseif section == 'function' then
local regexes = {
"^%s*(%a+)%s+(%d+)%s*$", -- instruction + parameter
"^%s*(%a+)%s+(@[%a_]+)%s*$", -- instruction + label
"^%s*(%a+)%s+(@[%a_][%a%d_]*)%s*$", -- instruction + label
"^%s*(%a+)%s*$", -- instruction only
"^(@[%a_]+):%s*$", -- label
"^(@[%a_][%a%d_]*):%s*$", -- label
}
local match = false
for i,regex in ipairs(regexes) do

View File

@@ -8,29 +8,18 @@ local ARITH_LOGIC_OPS = {
['and']=true, ['or']=true, xor=true, pow=true, shl=true, shr=true, mod=true
}
math.randomseed(os.time())
----------------------
-- --
-- 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)
local 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(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)
@@ -38,9 +27,19 @@ function validate_value(v)
assert(type(v.value) == 'number')
elseif v.type == 'function' then
assert(type(v.value) == 'number' and v.value >= 0, "function must be a positive number")
elseif v.type == 'string' then
assert(type(v.ref) == 'number' or type(v.const_ref) == 'number')
elseif v.type == 'array' then
assert(type(v.ref) == 'number')
end
end
function is_zero(v)
if v.type == 'nil' then return true end
if v.type == 'integer' and v.value == 0 then return true end
return false
end
----------------------
-- --
-- STACK --
@@ -127,17 +126,6 @@ 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
----------------------
-- --
@@ -168,6 +156,18 @@ function Code:next_instruction(function_id, pc)
}
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 --
@@ -182,29 +182,91 @@ for op,_ in pairs(ARITH_LOGIC_OPS) do
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
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
EXPR.sum.integer.integer = function(vm, b, a) vm:push_integer(a.value + b.value) end
EXPR.sum.string.string = function(vm, b, a) vm:push_string(vm:_extract_string(a) ..vm:_extract_string(b)) end
EXPR.sub.integer.integer = function(vm, b, a) vm:push_integer(a.value - b.value) end
EXPR.mul.integer.integer = function(vm, b, a) vm:push_integer(a.value * b.value) end
-- TODO - div
EXPR.idiv.integer.integer = function(vm, b, a) vm:push_integer(math.floor(a / 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
EXPR.idiv.integer.integer = function(vm, b, a) vm:push_integer(math.floor(a.value / b.value)) end
EXPR.mod.integer.integer = function(vm, b, a) vm:push_integer(a.value % b.value) end
EXPR.eq.integer.integer = function(vm, b, a) vm:push_integer((a.value == b.value) and 1 or 0) end
EXPR.neq.integer.integer = function(vm, b, a) vm:push_integer((a.value ~= b.value) and 1 or 0) end
EXPR.lt.integer.integer = function(vm, b, a) vm:push_integer((a.value < b.value) and 1 or 0) end
EXPR.lte.integer.integer = function(vm, b, a) vm:push_integer((a.value <= b.value) and 1 or 0) end
EXPR.gt.integer.integer = function(vm, b, a) vm:push_integer((a.value > b.value) and 1 or 0) end
EXPR.gte.integer.integer = function(vm, b, a) vm:push_integer((a.value >= b.value) and 1 or 0) end
EXPR['and'].integer.integer = function(vm, b, a) vm:push_integer(a.value & b.value) end
EXPR['or'].integer.integer = function(vm, b, a) vm:push_integer(a.value | b.value) end
EXPR.xor.integer.integer = function(vm, b, a) vm:push_integer(a.value ~ b.value) end
EXPR.pow.integer.integer = function(vm, b, a) vm:push_integer(a.value ^ b.value) end
EXPR.shl.integer.integer = function(vm, b, a) vm:push_integer(a.value << b.value) end
EXPR.shr.integer.integer = function(vm, b, a) vm:push_integer(a.value >> b.value) end
----------------------
-- --
-- HEAP --
-- --
----------------------
local Heap = {}
Heap.__index = Heap
function Heap.new()
return setmetatable({
items = {}
}, Heap)
end
function Heap:add_value(value)
assert(value.type and (value.type == 'string' or value.type == 'array' or value.type == 'table'))
assert(value.value)
local key = math.random(1, math.maxinteger)
while self.items[key] do key = math.random(1, math.maxinteger) end
self.items[key] = value
return key
end
function Heap:get_value(key)
assert(type(key) == 'number')
return self.items[key]
end
function Heap:size()
local n = 0
for _ in pairs(self.items) do n = n + 1 end
return n
end
function Heap:call_gc(roots)
-- mark
local marked = {}
local function mark(v)
if v.type == 'string' then
if v.ref then marked[v.ref] = true end
elseif v.type == 'array' then
marked[v.ref] = true
for _,vv in ipairs(self.items[v.ref].value) do mark(vv) end
end
end
for _,v in ipairs(roots) do -- TODO - recursive, add support to array
mark(v)
end
-- sweep
for key,_ in pairs(self.items) do
if not marked[key] then
self.items[key] = nil
end
end
end
----------------------
-- --
@@ -218,6 +280,7 @@ VM.__index = VM
function VM.new()
return setmetatable({
stack = Stack.new(),
heap = Heap.new(),
code = Code.new(),
loc = {},
debug = false,
@@ -245,10 +308,22 @@ end
function VM:push_integer(n)
self.stack:push({ type = 'integer', value = n })
return self
end
function VM:push_string(str)
self.stack:push({ type = 'string', ref = self.heap:add_value({ type='string', value=str }) })
return self
end
function VM:push_nil()
self.stack:push({ type = 'nil' })
return self
end
function VM:new_array()
self.stack:push({ type = 'array', ref = self.heap:add_value({ type='array', value={} }) })
return self
end
--
@@ -260,6 +335,8 @@ function VM:stack_sz()
end
function VM:is(idx, type_)
assert(type(idx) == "number")
assert(TYPE_MAP[type_])
return self.stack[idx].type == type_
end
@@ -269,6 +346,81 @@ function VM:to_integer(idx)
return value.value
end
function VM:_extract_string(value)
assert(value)
assert(value.type == 'string')
if value.const_ref then
return self.code.bytecode.constants[value.const_ref]
elseif value.ref then
return self.heap:get_value(value.ref).value
else
error("Incorrect string value (nor 'const_ref' or 'ref')")
end
end
function VM:_extract_array(value)
assert(value)
assert(value.type == 'array')
local array = self.heap:get_value(value.ref)
if type(array) ~= 'table' then error('Expected array') end
return self.heap:get_value(value.ref).value
end
function VM:to_string(idx)
local value = self.stack[idx]
if value.type ~= 'string' then error("Type error: not a string") end
return self:_extract_string(value)
end
function VM:format_value(v)
if v.type == 'integer' or v.type == 'real' then
return tostring(v.value)
elseif v.type == 'string' then
return '"' .. self:_extract_string(v) .. '"'
elseif v.type == 'array' then
local array = self:_extract_array(v)
local tbl = {}
for _,vv in ipairs(array) do table.insert(tbl, self:format_value(vv)) end
return "[" .. table.concat(tbl, ', ') .. "]"
elseif v.type == 'function' then
return '@' .. tostring(v.value)
elseif v.type == 'nil' then
return 'nil'
else
print('warning: cannot convert from type ' .. tostring(v.type))
return pprint.pformat(v)
end
end
function VM:debug_stack()
if #self.stack.stack == 0 then return "empty" end
local ss = {}
for i,v in ipairs(self.stack.stack) do
for _,fp in pairs(self.stack.fps) do
if i == fp then table.insert(ss, '^ ') end
end
table.insert(ss, self:format_value(v) .. ' ')
end
return table.concat(ss)
end
function VM:debug_heap()
local ss = { "Heap:\n" }
for k,v in pairs(self.heap.items) do
if v.type == 'string' then
table.insert(ss, string.format(' [%X] = "%s"', k, v.value))
elseif v.type == 'array' then
table.insert(ss, string.format(' [%X] = [', k))
local t = {}; for _,vv in ipairs(v.value) do t[#t+1] = self:format_value(vv) end
table.insert(ss, table.concat(t, ", ") .. ']')
else
error('Unsupported type in heap')
end
table.insert(ss, "\n")
end
return table.concat(ss)
end
--
-- code execution
--
@@ -310,6 +462,12 @@ function VM:_run_until_return()
end
end
function VM:_print_stack()
if self.debug then
print(self:debug_stack())
end
end
function VM:_step()
local loc = self.loc[#self.loc]
local op = self.code:next_instruction(loc.f_id, loc.pc)
@@ -320,20 +478,40 @@ function VM:_step()
-- stack operations
--
if op.operator == 'pushi' then
if op.operator == 'pushn' then
self:push_nil()
elseif op.operator == 'pushi' then
self:push_integer(op.operand)
elseif op.operator == 'pushf' then
assert(op.operand >= 0)
self.stack:push({ type = 'function', value = op.operand })
elseif op.operator == 'pushc' then
local c = self.code.bytecode.constants[op.operand]
if type(c) == 'string' then
self.stack:push({ type = 'string', const_ref = op.operand })
elseif type(c) == 'number' then
error('REAL consts not supported for now.')
end
elseif op.operator == 'newa' then
self:new_array()
elseif op.operator == 'pop' then
self.stack:pop()
elseif op.operator == 'dup' then
self.stack:push(self.stack:peek())
--
-- local variables
--
elseif op.operator == 'pushv' then
assert(op.operand >= 0)
for i=1,op.operand do
for _=1,op.operand do
self:push_nil()
end
@@ -347,6 +525,20 @@ function VM:_step()
local a = self.stack[op.operand]
self.stack:push(a)
--
-- table and array operations
--
elseif op.operator == 'seti' then
local array_ref = self.stack[-2]
local array = self:_extract_array(array_ref)
array[op.operand+1] = self.stack:pop()
elseif op.operator == 'geti' then
local array_ref = self.stack[-1]
local array = self:_extract_array(array_ref)
self.stack:push(array[op.operand+1])
--
-- logic/arithmetic operations
--
@@ -354,7 +546,7 @@ function VM:_step()
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)
EXPR[op.operator][a.type][b.type](self, a, b)
--
-- function management
@@ -369,14 +561,58 @@ function VM:_step()
self.stack:pop_fp()
self.stack:push(v)
table.remove(self.loc)
if self.debug then print(self.stack:debug()) end
self:_print_stack()
return
else
--
-- jumps/branching
--
elseif op.operator == 'jmp' then
loc.pc = self.code:find_label(loc.f_id, op.operand)
self:_print_stack()
return
elseif op.operator == 'bz' then
local v = self.stack:pop()
if is_zero(v) then
loc.pc = self.code:find_label(loc.f_id, op.operand)
self:_print_stack()
return
end
elseif op.operator == 'bnz' then
local v = self.stack:pop()
if not is_zero(v) then
loc.pc = self.code:find_label(loc.f_id, op.operand)
self:_print_stack()
return
end
--
-- memory management
--
elseif op.operator == 'gc' then
-- if self.debug then
-- print('About to run GC, current heap:')
-- print(self:debug_heap())
-- end
self.heap:call_gc(self.stack.stack)
-- if self.debug then
-- print('GC executed, this is the heap:')
-- print(self:debug_heap())
-- end
--
-- instruction not found
--
else
error("Unknown operator '" .. tostring(op.operator) .. "'")
end
if self.debug then print(self.stack:debug()) end
self:_print_stack()
loc.pc = loc.pc + op.instruction_size
end