Compare commits
10 Commits
79e9df852c
...
5c4a545369
Author | SHA1 | Date |
---|---|---|
_ | 5c4a545369 | |
_ | bebc96916b | |
_ | 5ab30ac5b4 | |
_ | 062c6e6a2d | |
_ | 2f676adc19 | |
_ | 9811d265f6 | |
_ | fcfd9397ff | |
_ | 0d5e1098bc | |
_ | 1d813b0f44 | |
_ | 9d29aeb43b |
|
@ -0,0 +1,14 @@
|
|||
LunarWaveVM is a Lua 5.4 virtual machine written in Rust.
|
||||
|
||||
![A star field with an orange crab-shaped nebula facing a dark purple moon with a bright magenta crescent.](pictures/lunar wave crab nebula.jpeg)
|
||||
|
||||
# Roadmap
|
||||
|
||||
- [x] Loading simple pre-compiled bytecode programs
|
||||
- [x] Hash tables
|
||||
- [x] Fizzbuzz
|
||||
- [ ] Closures
|
||||
- [ ] Garbage collection
|
||||
- [ ] Long strings
|
||||
- [ ] Using arrays internally for tables
|
||||
- [ ] Compiling Lua source code to bytecode
|
176
notes.md
176
notes.md
|
@ -1,176 +0,0 @@
|
|||
Lua source code
|
||||
|
||||
`hello.lua`
|
||||
|
||||
```lua
|
||||
print "Hello."
|
||||
```
|
||||
|
||||
`math.lua`
|
||||
|
||||
```lua
|
||||
local function add (a, b)
|
||||
return a + b
|
||||
end
|
||||
|
||||
print (("1 + 2 = %i"):format (add (1, 2)))
|
||||
```
|
||||
|
||||
luac5.4 listing
|
||||
|
||||
```
|
||||
main <hello.lua:0,0> (5 instructions at 0x564f4fd74cc0)
|
||||
0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
|
||||
1 [1] VARARGPREP 0
|
||||
2 [1] GETTABUP 0 0 0 ; _ENV "print"
|
||||
3 [1] LOADK 1 1 ; "Hello."
|
||||
4 [1] CALL 0 2 1 ; 1 in 0 out
|
||||
5 [1] RETURN 0 1 1 ; 0 out
|
||||
```
|
||||
|
||||
```
|
||||
main <math.lua:0,0> (12 instructions at 0x55ee2417acc0)
|
||||
0+ params, 7 slots, 1 upvalue, 1 local, 3 constants, 1 function
|
||||
1 [1] VARARGPREP 0
|
||||
2 [3] CLOSURE 0 0 ; 0x55ee2417b000
|
||||
3 [5] GETTABUP 1 0 0 ; _ENV "print"
|
||||
4 [5] LOADK 2 1 ; "1 + 2 = %i"
|
||||
5 [5] SELF 2 2 2k ; "format"
|
||||
6 [5] MOVE 4 0
|
||||
7 [5] LOADI 5 1
|
||||
8 [5] LOADI 6 2
|
||||
9 [5] CALL 4 3 0 ; 2 in all out
|
||||
10 [5] CALL 2 0 0 ; all in all out
|
||||
11 [5] CALL 1 0 1 ; all in 0 out
|
||||
12 [5] RETURN 1 1 1 ; 0 out
|
||||
|
||||
function <math.lua:1,3> (4 instructions at 0x55ee2417b000)
|
||||
2 params, 3 slots, 0 upvalues, 2 locals, 0 constants, 0 functions
|
||||
1 [2] ADD 2 0 1
|
||||
2 [2] MMBIN 0 1 6 ; __add
|
||||
3 [2] RETURN1 2
|
||||
4 [3] RETURN0
|
||||
```
|
||||
|
||||
```
|
||||
main <test_vectors/is_93.lua:0,0> (14 instructions at 0x559f55e1ecd0)
|
||||
0+ params, 2 slots, 1 upvalue, 1 local, 5 constants, 1 function
|
||||
1 [1] VARARGPREP 0
|
||||
2 [1] GETTABUP 0 0 0 ; _ENV "arg"
|
||||
3 [1] GETI 0 0 1
|
||||
4 [1] EQK 0 1 0 ; "93"
|
||||
5 [1] JMP 4 ; to 10
|
||||
6 [2] GETTABUP 0 0 2 ; _ENV "print"
|
||||
7 [2] LOADK 1 3 ; "it's 93"
|
||||
8 [2] CALL 0 2 1 ; 1 in 0 out
|
||||
9 [2] JMP 3 ; to 13
|
||||
10 [4] GETTABUP 0 0 2 ; _ENV "print"
|
||||
11 [4] LOADK 1 4 ; "it's not 93"
|
||||
12 [4] CALL 0 2 1 ; 1 in 0 out
|
||||
13 [9] CLOSURE 0 0 ; 0x559f55e1f3d0
|
||||
14 [9] RETURN 1 1 1 ; 0 out
|
||||
constants (5) for 0x559f55e1ecd0:
|
||||
0 S "arg"
|
||||
1 S "93"
|
||||
2 S "print"
|
||||
3 S "it's 93"
|
||||
4 S "it's not 93"
|
||||
locals (1) for 0x559f55e1ecd0:
|
||||
0 unused_fn 14 15
|
||||
upvalues (1) for 0x559f55e1ecd0:
|
||||
0 _ENV 1 0
|
||||
|
||||
function <test_vectors/is_93.lua:7,9> (4 instructions at 0x559f55e1f3d0)
|
||||
0 params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
|
||||
1 [8] GETTABUP 0 0 0 ; _ENV "print"
|
||||
2 [8] LOADK 1 1 ; "unused"
|
||||
3 [8] CALL 0 2 1 ; 1 in 0 out
|
||||
4 [9] RETURN0
|
||||
constants (2) for 0x559f55e1f3d0:
|
||||
0 S "print"
|
||||
1 S "unused"
|
||||
locals (0) for 0x559f55e1f3d0:
|
||||
upvalues (1) for 0x559f55e1f3d0:
|
||||
0 _ENV 0 0
|
||||
```
|
||||
|
||||
Octal dump of luac5.4 byte code
|
||||
|
||||
```
|
||||
0000000 1b 4c 75 61 54 00 19 93 0d 0a 1a 0a 04 08 08 78 >.LuaT..........x<
|
||||
0000020 56 00 00 00 00 00 00 00 00 00 00 00 28 77 40 01 >V...........(w@.<
|
||||
0000040 8b 40 68 65 6c 6c 6f 2e 6c 75 61 80 80 00 01 02 >.@hello.lua.....<
|
||||
0000060 85 51 00 00 00 0b 00 00 00 83 80 00 00 44 00 02 >.Q...........D..<
|
||||
0000100 01 46 00 01 01 82 04 86 70 72 69 6e 74 04 87 48 >.F......print..H<
|
||||
0000120 65 6c 6c 6f 2e 81 01 00 00 80 85 01 00 00 00 00 >ello............<
|
||||
0000140 80 80 81 85 5f 45 4e 56 >...._ENV<
|
||||
0000150
|
||||
```
|
||||
|
||||
```
|
||||
0000000 1b 4c 75 61 54 00 19 93 0d 0a 1a 0a 04 08 08 78 >.LuaT..........x<
|
||||
0000020 56 00 00 00 00 00 00 00 00 00 00 00 28 77 40 01 >V...........(w@.<
|
||||
0000040 8a 40 6d 61 74 68 2e 6c 75 61 80 80 00 01 07 8c >.@math.lua......<
|
||||
0000060 51 00 00 00 4f 00 00 00 8b 00 00 00 03 81 00 00 >Q...O...........<
|
||||
0000100 14 81 02 02 00 02 00 00 81 02 00 80 01 83 00 80 >................<
|
||||
0000120 44 02 03 00 44 01 00 00 c4 00 00 01 c6 00 01 01 >D...D...........<
|
||||
0000140 83 04 86 70 72 69 6e 74 04 8b 31 20 2b 20 32 20 >...print..1 + 2 <
|
||||
0000160 3d 20 25 69 04 87 66 6f 72 6d 61 74 81 01 00 00 >= %i..format....<
|
||||
0000200 81 80 81 83 02 00 03 84 22 01 00 01 2e 00 01 06 >........".......<
|
||||
0000220 48 01 02 00 47 01 01 00 80 80 80 84 01 00 00 01 >H...G...........<
|
||||
0000240 80 82 82 61 80 84 82 62 80 84 80 8c 01 02 02 00 >...a...b........<
|
||||
0000260 00 00 00 00 00 00 00 00 80 81 84 61 64 64 82 8c >...........add..<
|
||||
0000300 81 85 5f 45 4e 56 >.._ENV<
|
||||
0000306
|
||||
|
||||
```
|
||||
|
||||
# Interpretation of byte code
|
||||
|
||||
Overall structure
|
||||
|
||||
- Roughly 32 byte header with magic number, version number, etc.
|
||||
- File name
|
||||
- `80 80 00 01 02 85` header for main function
|
||||
- Packed 4-byte instructions for main function
|
||||
- `82` or `83` length prefix for string table
|
||||
- String table for main function
|
||||
- `81 01 00 00 81 80 81 83 02 00 03 84` header for "add" function
|
||||
- Packed 4-byte instructions for "add" function
|
||||
- `80 80 80 84 01 00 00 01 80` Header for file-scope debug symbols?
|
||||
- File-scope debug symbols?
|
||||
- String table for entire file?
|
||||
|
||||
## Bytecodes
|
||||
|
||||
Per lopcodes.h, instructions are 32 bits long, always.
|
||||
The opcode is encoded in the first (highest?) 7 bits.
|
||||
|
||||
```
|
||||
83 80 00 00 ; 0x03 = 3 = LOADK
|
||||
0b 00 00 00 ; 0x0b = 11 = GETTABUP
|
||||
22 01 00 01 ; 0x22 = 34 = ADD
|
||||
2e 00 01 06 ; 0x2e = 46 = MMBIN
|
||||
c4 00 00 01 ; 0xc4 = 68 = CALL
|
||||
44 00 02 01 ; 0x44 = 68 = CALL
|
||||
46 00 01 01 ; 0x46 = 70 = RETURN
|
||||
47 01 01 00 ; 0x47 = 71 = RETURN0
|
||||
48 01 02 00 ; 0x48 = 72 = RETURN1
|
||||
51 00 00 00 ; 0x51 = 81 = VARARGPREP
|
||||
```
|
||||
|
||||
## Strings
|
||||
|
||||
Filenames are encoded at the top, and there's a string table at the bottom.
|
||||
|
||||
Strings appear to be prefixed with a variable-length length prefix.
|
||||
There is an extra byte before each string which I can't account for,
|
||||
and the lengths seem to be off by one, e.g. 0x84 is a length of 3, not 4.
|
||||
|
||||
| --- |
|
||||
| "add" | 81 84 61 64 64
|
||||
| "_ENV" | 81 85 5f 45 4e 56
|
||||
| "print" | 04 86 70 72 69 6e 74
|
||||
| "format" | 04 87 66 6f 72 6d 61 74
|
||||
| "@math.lua" | 01 8a 40 6d 61 74 68 2e 6c 75 61
|
||||
| "1 + 2 = %i" | 04 8b 31 20 2b 20 32 20 3d 20 25 69
|
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
|
@ -5,14 +5,24 @@ pub enum Instruction {
|
|||
Call (u8, u8, u8),
|
||||
Closure (u8, u32),
|
||||
|
||||
EqI (u8, i8, bool),
|
||||
|
||||
// Equals Constant?
|
||||
EqK (u8, u8, bool),
|
||||
|
||||
ExtraArg (u32),
|
||||
|
||||
ForLoop (u8, u32),
|
||||
|
||||
ForPrep (u8, u32),
|
||||
|
||||
GetField (u8, u8, u8),
|
||||
|
||||
// Get Immediate?
|
||||
GetI (u8, u8, u8),
|
||||
|
||||
GetTable (u8, u8, u8),
|
||||
|
||||
// Get Table, Upvalue
|
||||
GetTabUp (u8, u8, u8),
|
||||
|
||||
|
@ -39,6 +49,10 @@ pub enum Instruction {
|
|||
// MetaMethod, Binary
|
||||
MmBin (u8, u8, u8),
|
||||
|
||||
MmBinK (u8, u8, u8, bool),
|
||||
|
||||
ModK (u8, u8, u8),
|
||||
|
||||
Move (u8, u8),
|
||||
|
||||
Mul (u8, u8, u8),
|
||||
|
@ -55,6 +69,12 @@ pub enum Instruction {
|
|||
// Return just one register
|
||||
Return1 (u8),
|
||||
|
||||
SetField (u8, u8, u8, bool),
|
||||
|
||||
SetI (u8, u8, u8, bool),
|
||||
|
||||
SetList (u8, u8, u8, bool),
|
||||
|
||||
SetTabUp (u8, u8, u8),
|
||||
|
||||
TailCall (u8, u8, u8, bool),
|
||||
|
|
|
@ -43,6 +43,11 @@ pub (crate) fn compile_bytecode (source: Vec <u8>) -> Vec <u8> {
|
|||
output.stdout.as_slice ().to_vec ()
|
||||
}
|
||||
|
||||
fn i_sb (buf: [u8; 4]) -> Option <i8> {
|
||||
let b = buf [2];
|
||||
i8::try_from (i32::try_from (b).ok ()? - 127).ok ()
|
||||
}
|
||||
|
||||
pub fn parse_inst (buf: [u8; 4]) -> Option <Inst>
|
||||
{
|
||||
let opcode = buf [0] & 0x7f;
|
||||
|
@ -69,14 +74,21 @@ pub fn parse_inst (buf: [u8; 4]) -> Option <Inst>
|
|||
0x08 => Inst::LoadNil (a),
|
||||
0x09 => Inst::GetUpVal (a, b),
|
||||
0x0b => Inst::GetTabUp (a, b, c),
|
||||
0x0c => Inst::GetTable (a, b, c),
|
||||
0x0d => Inst::GetI (a, b, c),
|
||||
0x0e => Inst::GetField (a, b, c),
|
||||
0x0f => Inst::SetTabUp (a, b, c),
|
||||
0x11 => Inst::SetI (a, b, c, k),
|
||||
0x12 => Inst::SetField (a, b, c, k),
|
||||
0x13 => Inst::NewTable (a),
|
||||
0x19 => Inst::ModK (a, b, c),
|
||||
0x22 => Inst::Add (a, b, c),
|
||||
0x24 => Inst::Mul (a, b, c),
|
||||
0x2e => Inst::MmBin (a, b, c),
|
||||
0x30 => Inst::MmBinK (a, b, c, k),
|
||||
0x33 => Inst::Not (a, b),
|
||||
0x3c => Inst::EqK (a, b, k),
|
||||
0x3d => Inst::EqI (a, i_sb (buf)?, k),
|
||||
0x38 => Inst::Jmp (s_j),
|
||||
0x42 => Inst::Test (a, k),
|
||||
0x44 => Inst::Call (a, b, c),
|
||||
|
@ -84,6 +96,9 @@ pub fn parse_inst (buf: [u8; 4]) -> Option <Inst>
|
|||
0x46 => Inst::Return (a, b, c, k),
|
||||
0x47 => Inst::Return0,
|
||||
0x48 => Inst::Return1 (a),
|
||||
0x49 => Inst::ForLoop (a, bx),
|
||||
0x4a => Inst::ForPrep (a, bx),
|
||||
0x4e => Inst::SetList (a, b, c, k),
|
||||
0x4f => Inst::Closure (a, bx),
|
||||
0x51 => Inst::VarArgPrep (a.into ()),
|
||||
0x52 => Inst::ExtraArg (ax),
|
||||
|
@ -118,11 +133,23 @@ fn parse_byte <R: Read> (rdr: &mut R) -> Option <u8>
|
|||
Some (buf [0])
|
||||
}
|
||||
|
||||
fn parse_float <R: Read> (rdr: &mut R) -> Option <f64> {
|
||||
let mut buf = [0u8; 8];
|
||||
rdr.read_exact (&mut buf).ok ()?;
|
||||
Some (f64::from_ne_bytes(buf))
|
||||
}
|
||||
|
||||
fn parse_int <R: Read> (rdr: &mut R) -> Option <u32>
|
||||
{
|
||||
Some ((parse_byte (rdr)? - 0x80) as u32)
|
||||
}
|
||||
|
||||
fn parse_i64 <R: Read> (rdr: &mut R) -> Option <i64> {
|
||||
let mut buf = [0u8; 8];
|
||||
rdr.read_exact (&mut buf).ok ()?;
|
||||
Some (i64::from_ne_bytes(buf))
|
||||
}
|
||||
|
||||
// I'm doing this recursively so it's easy to match with the PUC Lua
|
||||
// code, but I don't like recursion in general, and I don't know
|
||||
// why PUC wrote it that way.
|
||||
|
@ -132,6 +159,8 @@ pub fn parse_block <R: Read> (rdr: &mut R, blocks: &mut Vec <Block>)
|
|||
{
|
||||
// Ignore things I haven't implemented yet
|
||||
|
||||
use crate::value::Value;
|
||||
|
||||
parse_string (rdr)?; // function name
|
||||
parse_int (rdr)?; // start line in source code
|
||||
parse_int (rdr)?; // last line in source code
|
||||
|
@ -152,12 +181,18 @@ pub fn parse_block <R: Read> (rdr: &mut R, blocks: &mut Vec <Block>)
|
|||
|
||||
let mut constants = Vec::with_capacity (constant_count as usize);
|
||||
|
||||
for _ in 0..constant_count {
|
||||
for i in 0..constant_count {
|
||||
// LUA_TNIL and friends from `lua.h` in PUC Lua
|
||||
let const_type = parse_byte (rdr)?;
|
||||
assert_eq! (const_type, 0x04);
|
||||
|
||||
let s = parse_string (rdr)?;
|
||||
constants.push (s.into ());
|
||||
let val = match const_type {
|
||||
3 => Value::from (parse_i64 (rdr)?),
|
||||
4 => parse_string (rdr)?.into (),
|
||||
19 => Value::from (parse_float (rdr)?),
|
||||
x => panic! ("Constant {} has type {}", i, x),
|
||||
};
|
||||
|
||||
constants.push (val);
|
||||
}
|
||||
|
||||
let upvalue_count = parse_int (rdr)? as usize;
|
||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -1,3 +1,5 @@
|
|||
// cargo run -- --script test_vectors/fizz_buzz.lua
|
||||
|
||||
mod instruction;
|
||||
mod loader;
|
||||
mod state;
|
||||
|
@ -9,8 +11,21 @@ mod tests;
|
|||
fn main () {
|
||||
use state::State;
|
||||
|
||||
let mut script = String::from ("test_vectors/hello.lua");
|
||||
|
||||
let mut args = std::env::args ();
|
||||
let exe_name = args.next ().unwrap ();
|
||||
|
||||
while let Some (arg) = args.next () {
|
||||
match arg.as_str () {
|
||||
"--script" => script = args.next ().unwrap (),
|
||||
"--" => break,
|
||||
_ => panic! ("can't parse args"),
|
||||
}
|
||||
}
|
||||
|
||||
let lua_file = {
|
||||
let source = std::fs::read ("test_vectors/hello.lua").expect ("couldn't load Lua source code");
|
||||
let source = std::fs::read (script).expect ("couldn't load Lua source code");
|
||||
let bytecode = loader::compile_bytecode(source);
|
||||
let mut rdr = std::io::Cursor::new (bytecode);
|
||||
loader::parse_chunk (&mut rdr).unwrap ()
|
||||
|
@ -21,7 +36,7 @@ fn main () {
|
|||
vm.debug_print = true;
|
||||
}
|
||||
|
||||
let upvalues = State::upvalues_from_args (std::env::args ());
|
||||
let upvalues = State::upvalues_from_args ([exe_name].into_iter ().chain (args));
|
||||
|
||||
vm.breakpoints.push (state::Breakpoint {
|
||||
block_idx: 3,
|
||||
|
|
206
src/state.rs
206
src/state.rs
|
@ -1,10 +1,9 @@
|
|||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
instruction::Instruction,
|
||||
value::{
|
||||
BogusClosure,
|
||||
Table,
|
||||
Value,
|
||||
},
|
||||
};
|
||||
|
@ -41,6 +40,8 @@ pub struct Breakpoint {
|
|||
#[derive (Debug)]
|
||||
pub struct State {
|
||||
registers: Vec <Value>,
|
||||
// Currently only used for native function calls
|
||||
top: usize,
|
||||
stack: Vec <StackFrame>,
|
||||
|
||||
pub debug_print: bool,
|
||||
|
@ -52,6 +53,7 @@ impl Default for State {
|
|||
fn default () -> Self {
|
||||
Self {
|
||||
registers: vec! [Value::Nil; 16],
|
||||
top: 0,
|
||||
stack: vec! [
|
||||
StackFrame {
|
||||
program_counter: 0,
|
||||
|
@ -66,18 +68,35 @@ impl Default for State {
|
|||
}
|
||||
}
|
||||
|
||||
fn lw_print (l: &mut State) -> i32 {
|
||||
for i in 0..u8::try_from (l.get_top ()).unwrap () {
|
||||
let input = l.reg (i);
|
||||
|
||||
if i == 0 {
|
||||
print! ("{input}");
|
||||
}
|
||||
else {
|
||||
print! ("\t{input}");
|
||||
}
|
||||
}
|
||||
println! ("");
|
||||
*l.reg_mut (0) = Value::from (1993);
|
||||
1
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn upvalues_from_args <I: Iterator <Item = String>> (args: I) -> Vec <Value>
|
||||
{
|
||||
let arg: Vec <_> = args.map (|s| s.to_string ()).collect ();
|
||||
let arg = args.map (|s| Value::from (s)).enumerate ();
|
||||
let arg = Value::from_iter (arg.map (|(i, v)| (Value::from (i), v)));
|
||||
|
||||
let env = HashMap::from_iter ([
|
||||
("arg", Value::BogusArg (arg.into ())),
|
||||
("print", Value::BogusPrint),
|
||||
].map (|(k, v)| (k.to_string (), v)));
|
||||
let env = [
|
||||
("arg", arg),
|
||||
("print", Value::RsFunc (lw_print)),
|
||||
].into_iter ().map (|(k, v)| (k.to_string (), v));
|
||||
|
||||
vec! [
|
||||
Value::BogusEnv (env.into ()),
|
||||
Value::from_iter (env.into_iter ()),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -98,6 +117,11 @@ impl State {
|
|||
&mut self.registers [frame.register_offset + i as usize]
|
||||
}
|
||||
|
||||
// For native functions to check how many args they got
|
||||
pub fn get_top (&self) -> usize {
|
||||
self.top - self.stack.last ().unwrap ().register_offset
|
||||
}
|
||||
|
||||
pub fn execute_chunk (&mut self, chunk: &Chunk, upvalues: &[Value])
|
||||
-> Vec <Value> {
|
||||
let max_iters = 2000;
|
||||
|
@ -136,7 +160,9 @@ impl State {
|
|||
|
||||
*self.reg_mut (*a) = Value::from (v_b + v_c);
|
||||
},
|
||||
Instruction::Call (a, _b, c) => {
|
||||
Instruction::Call (a, b, _c) => {
|
||||
let b = usize::from (*b);
|
||||
|
||||
// Take arguments from registers [a + 1, a + b)
|
||||
// Call the function in register [a]
|
||||
// Return values in registers [a, a + c - 1)
|
||||
|
@ -145,9 +171,10 @@ impl State {
|
|||
//
|
||||
// e.g. CALL 0 2 1 mean "Call 0 with 1 argument, return 1 value", like for printing a constant
|
||||
|
||||
// TODO: Only implement printing values for now
|
||||
// Do a clone here to avoid a borow problem.
|
||||
// Should be fixable with more clever code.
|
||||
|
||||
let v_a = self.reg (*a);
|
||||
let v_a = self.reg (*a).clone ();
|
||||
|
||||
match v_a {
|
||||
Value::BogusClosure (rc) => {
|
||||
|
@ -173,17 +200,27 @@ impl State {
|
|||
// Skip the PC increment at the bottom of the loop
|
||||
continue;
|
||||
},
|
||||
Value::BogusPrint => {
|
||||
// In real Lua, print is a function inside
|
||||
// the runtime. Here it's bogus.
|
||||
Value::RsFunc (x) => {
|
||||
let current_frame = self.stack.last ().unwrap ();
|
||||
let new_offset = current_frame.register_offset + usize::from (*a) + 1;
|
||||
self.stack.push (StackFrame {
|
||||
program_counter: 65535, // Bogus for native functions
|
||||
block_idx: 65535, // Bogus
|
||||
register_offset: new_offset,
|
||||
});
|
||||
|
||||
// assert_eq! (*b, 2);
|
||||
assert_eq! (*c, 1);
|
||||
// No clue what the '1' is doing here
|
||||
self.top = new_offset + b - 1;
|
||||
|
||||
let value = self.reg (a + 1);
|
||||
println! ("{}", value);
|
||||
// Call
|
||||
let num_results = x (self);
|
||||
|
||||
*self.reg_mut (*a) = self.reg_mut (*a + 1).take ();
|
||||
let popped_frame = self.stack.pop ().unwrap ();
|
||||
let offset = popped_frame.register_offset - 1;
|
||||
|
||||
for i in (offset)..(offset + usize::try_from (num_results).unwrap ()) {
|
||||
self.registers [i] = self.registers [i + 1 + usize::from (*a)].take ();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let stack = &self.stack;
|
||||
|
@ -199,6 +236,12 @@ impl State {
|
|||
upvalues: vec! [],
|
||||
}.into ());
|
||||
},
|
||||
Instruction::EqI (a, sb, k_flag) => {
|
||||
if (self.reg (*a).as_int ().unwrap () == *sb as i64) != *k_flag
|
||||
{
|
||||
next_pc += 1;
|
||||
}
|
||||
},
|
||||
Instruction::EqK (a, b, k_flag) => {
|
||||
let b = usize::from (*b);
|
||||
|
||||
|
@ -212,30 +255,73 @@ impl State {
|
|||
|
||||
assert_eq! (*ax, 0, "implemented only for ax == 0");
|
||||
},
|
||||
Instruction::ForLoop (a, bx) => {
|
||||
let mut iter = self.reg (*a + 3).as_int ().unwrap ();
|
||||
iter += 1;
|
||||
*self.reg_mut (*a + 3) = iter.into ();
|
||||
|
||||
let stop = self.reg (*a + 1).as_int ().unwrap ();
|
||||
|
||||
if iter <= stop {
|
||||
next_pc -= i32::try_from (*bx).unwrap ();
|
||||
}
|
||||
},
|
||||
Instruction::ForPrep (a, bx) => {
|
||||
let start = self.reg (*a).as_int ().unwrap ();
|
||||
let stop = self.reg (*a + 1).as_int ().unwrap ();
|
||||
|
||||
if start > stop {
|
||||
next_pc += i32::try_from (*bx).unwrap () + 1;
|
||||
}
|
||||
|
||||
*self.reg_mut (*a + 3) = start.into ();
|
||||
},
|
||||
Instruction::GetField (a, b, c) => {
|
||||
let t = match self.reg (*b) {
|
||||
Value::Table (t) => t,
|
||||
_ => panic! ("R[B] must be a table"),
|
||||
};
|
||||
|
||||
let key = match &k [usize::from (*c)] {
|
||||
Value::String (s) => s,
|
||||
_ => panic! ("K[C] must be a string"),
|
||||
};
|
||||
|
||||
let val = t.borrow ().get (Value::String (Rc::clone (key)));
|
||||
|
||||
*self.reg_mut (*a) = val;
|
||||
},
|
||||
Instruction::GetTable (a, b, c) => {
|
||||
let t = match self.reg (*b) {
|
||||
Value::Table (t) => t,
|
||||
_ => panic! ("R[B] must be a table"),
|
||||
};
|
||||
|
||||
let key = self.reg (*c);
|
||||
|
||||
let val = t.borrow ().get (key.clone ());
|
||||
|
||||
*self.reg_mut (*a) = val;
|
||||
},
|
||||
Instruction::GetTabUp (a, b, c) => {
|
||||
let b = usize::try_from (*b).unwrap ();
|
||||
let c = usize::try_from (*c).unwrap ();
|
||||
|
||||
let env = match upvalues.get (b).unwrap () {
|
||||
Value::BogusEnv (x) => x,
|
||||
_ => panic! ("Only allowed upvalue is BogusEnv"),
|
||||
};
|
||||
let table = upvalues.get (b).unwrap ().as_table ().expect ("GetTabUp only works on tables").borrow ();
|
||||
|
||||
let key = match k.get (c).unwrap () {
|
||||
Value::String (s) => s.as_ref (),
|
||||
Value::String (s) => String::from (s.as_ref()),
|
||||
_ => panic! ("GetTabUp only supports string keys"),
|
||||
};
|
||||
|
||||
let value = env.get (key).unwrap ();
|
||||
*self.reg_mut (*a) = value.clone ();
|
||||
*self.reg_mut (*a) = table.get (key);
|
||||
},
|
||||
Instruction::GetI (a, b, c) => {
|
||||
let c = usize::try_from (*c).unwrap ();
|
||||
let key = i64::try_from (*c).unwrap ();
|
||||
|
||||
let table = self.reg (*b);
|
||||
let value = match table {
|
||||
Value::BogusArg (arg) => arg.get (c).map (|x| x.as_str().into ()).unwrap_or_default(),
|
||||
_ => unimplemented!(),
|
||||
let value = {
|
||||
let table = self.reg (*b).as_table ().expect ("GetI only works on tables").borrow ();
|
||||
table.get (key)
|
||||
};
|
||||
|
||||
*self.reg_mut (*a) = value;
|
||||
|
@ -283,6 +369,15 @@ impl State {
|
|||
panic! ("Not sure how to implememtn OP_MMBIN for these 2 values {a:?}, {b:?}");
|
||||
}
|
||||
},
|
||||
Instruction::MmBinK (_a, _b, _c, _k) => {
|
||||
// Ignore
|
||||
},
|
||||
Instruction::ModK (a, b, c) => {
|
||||
let b = self.reg (*b).as_int().unwrap ();
|
||||
let c = k [usize::from (*c)].as_int ().unwrap ();
|
||||
|
||||
*self.reg_mut (*a) = (b % c).into ();
|
||||
},
|
||||
Instruction::Move (a, b) => {
|
||||
*self.reg_mut (*a) = self.reg (*b).clone ();
|
||||
},
|
||||
|
@ -371,6 +466,55 @@ impl State {
|
|||
let offset = popped_frame.register_offset;
|
||||
self.registers [offset - 1] = self.registers [offset + a].take ();
|
||||
},
|
||||
Instruction::SetField (a, b, c, k_flag) => {
|
||||
let value = if *k_flag {
|
||||
&k [usize::from (*c)]
|
||||
}
|
||||
else {
|
||||
self.reg (*c)
|
||||
}
|
||||
.clone ();
|
||||
|
||||
let b = usize::try_from (*b).unwrap ();
|
||||
|
||||
let key = match k.get (b).unwrap () {
|
||||
Value::String (s) => s.as_ref (),
|
||||
_ => panic! ("GetTabUp only supports string keys"),
|
||||
};
|
||||
|
||||
let mut dst = self.reg (*a).as_table ()
|
||||
.expect ("SetField only works on tables").borrow_mut ();
|
||||
|
||||
dst.insert (Value::from (key.as_str ()), value);
|
||||
},
|
||||
Instruction::SetI (a, b, c, k_flag) => {
|
||||
let value = if *k_flag {
|
||||
&k [usize::from (*c)]
|
||||
}
|
||||
else {
|
||||
self.reg (*c)
|
||||
}
|
||||
.clone ();
|
||||
|
||||
let mut dst = self.reg_mut (*a).as_table ().expect ("SetI only works on tables").borrow_mut ();
|
||||
|
||||
dst.insert (i64::from (*b), value);
|
||||
},
|
||||
Instruction::SetList (a, b, c, k) => {
|
||||
if *b == 0 {
|
||||
panic! ("SetList with b == 0 not implemented");
|
||||
}
|
||||
if *k {
|
||||
panic! ("SetList with k = true not implemented");
|
||||
}
|
||||
|
||||
let mut dst = self.reg (*a).as_table ().expect ("SetList only works on tables").borrow_mut ();
|
||||
|
||||
for i in 1..=*b {
|
||||
let src = self.reg (*a + i);
|
||||
dst.insert (Value::from (i64::from (*c + i)), src.clone ());
|
||||
}
|
||||
},
|
||||
Instruction::SetTabUp (_a, _b, _c) => unimplemented! (),
|
||||
Instruction::TailCall (_a, _b, _c, _k) => unimplemented! (),
|
||||
Instruction::Test (a, _k) => {
|
||||
|
|
130
src/tests.rs
130
src/tests.rs
|
@ -214,6 +214,64 @@ fn fma () {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heap () {
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
};
|
||||
use crate::value::Table;
|
||||
|
||||
{
|
||||
let mut allocations = HashMap::new ();
|
||||
let mut ctr = 0;
|
||||
|
||||
let c = ctr;
|
||||
allocations.insert (ctr, Table::default ());
|
||||
ctr += 1;
|
||||
|
||||
let a = ctr;
|
||||
allocations.insert (ctr, Table::default ());
|
||||
ctr += 1;
|
||||
|
||||
allocations.get_mut (&a).unwrap ().insert (1, c);
|
||||
|
||||
allocations.get_mut (&c).unwrap ().insert (2, "eee");
|
||||
}
|
||||
|
||||
if true {
|
||||
#[derive (Clone, Debug, PartialEq)]
|
||||
enum Value {
|
||||
S (Rc <String>),
|
||||
T (Rc <RefCell <HashMap <i64, Value>>>),
|
||||
}
|
||||
|
||||
let c = Value::T (Default::default ());
|
||||
|
||||
let a = Value::T (Default::default ());
|
||||
match &a {
|
||||
Value::T (t) => t.borrow_mut ().insert (1, c.clone ()),
|
||||
_ => panic! ("impossible"),
|
||||
};
|
||||
|
||||
match &c {
|
||||
Value::T (t) => t.borrow_mut ().insert (2, Value::S (Rc::new (String::from ("eee")))),
|
||||
_ =>panic! ("impossible"),
|
||||
};
|
||||
|
||||
let actual = match &a {
|
||||
Value::T (t) => match t.borrow ().get (&1) {
|
||||
Some (Value::T (t)) => t.borrow ().get (&2).unwrap ().clone (),
|
||||
_ => panic! ("impossible"),
|
||||
},
|
||||
_ => panic! ("impossible"),
|
||||
};
|
||||
|
||||
assert_eq! (actual, Value::S (Rc::new ("eee".into ())));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_93 () {
|
||||
assert_eq! (Value::from ("93"), Value::from ("93"));
|
||||
|
@ -243,6 +301,74 @@ fn is_93 () {
|
|||
assert_eq! (run (&["", "94"], &chunk), vec! [Value::from (1)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_functions () {
|
||||
fn add (l: &mut State) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn multiply (l: &mut State) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn greet (l: &mut State) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
let _funcs = [
|
||||
add,
|
||||
multiply,
|
||||
greet,
|
||||
];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tables_1 () {
|
||||
// I don't capture stdout yet, but I can at least make sure basic table
|
||||
// operations don't crash
|
||||
|
||||
let src = r#"
|
||||
print " 1"
|
||||
|
||||
print (nil)
|
||||
print (false)
|
||||
print (true)
|
||||
print (1993)
|
||||
print (1993.00)
|
||||
print "Hello."
|
||||
|
||||
print " 2"
|
||||
|
||||
local t = {}
|
||||
print (t)
|
||||
print (t [1])
|
||||
t [1] = "asdf"
|
||||
print (t [1])
|
||||
t.x = 1993
|
||||
print (t.x)
|
||||
|
||||
t.t = { 3.14159 }
|
||||
print (t ["t"][1])
|
||||
"#;
|
||||
|
||||
run_source (&[], src);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tables_2 () {
|
||||
let src = r#"
|
||||
print " 3"
|
||||
|
||||
local c = {}
|
||||
local a = { c }
|
||||
print (a [1])
|
||||
c [2] = "eee"
|
||||
print (a [2])
|
||||
"#;
|
||||
|
||||
run_source (&[], src);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_size () {
|
||||
// Per https://www.lua.org/doc/jucs05.pdf,
|
||||
|
@ -254,9 +380,7 @@ fn value_size () {
|
|||
// It would be nice if LunarWaveVM is the same or better.
|
||||
// There are some exploratory things in this test, too
|
||||
|
||||
use std::{
|
||||
mem::size_of,
|
||||
};
|
||||
use std::mem::size_of;
|
||||
|
||||
assert! (size_of::<Box <()>> () <= 8);
|
||||
assert! (size_of::<std::rc::Rc <()>> () <= 8);
|
||||
|
|
69
src/value.rs
69
src/value.rs
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
cmp::{
|
||||
Eq,
|
||||
PartialEq,
|
||||
|
@ -24,16 +25,14 @@ pub enum Value {
|
|||
Float (f64),
|
||||
|
||||
Integer (i64),
|
||||
RsFunc (fn (&mut crate::state::State) -> i32),
|
||||
String (Rc <String>),
|
||||
Table (Rc <Table>),
|
||||
Table (Rc <RefCell <Table>>),
|
||||
|
||||
// These are all bogus, I haven't figured out how to implement
|
||||
// tables and function pointers yet
|
||||
// closures yet
|
||||
|
||||
BogusArg (Rc <Vec <String>>),
|
||||
BogusClosure (Rc <BogusClosure>),
|
||||
BogusEnv (Rc <HashMap <String, Value>>),
|
||||
BogusPrint,
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
|
@ -50,13 +49,11 @@ impl fmt::Display for Value {
|
|||
Value::Boolean (true) => write! (f, "true"),
|
||||
Value::Float (x) => write! (f, "{:?}", x),
|
||||
Value::Integer (x) => write! (f, "{}", x),
|
||||
Value::RsFunc (x) => write! (f, "function: {:?}", x),
|
||||
Value::String (s) => write! (f, "{}", s),
|
||||
Value::Table (t) => write! (f, "table: {:?}", std::rc::Rc::as_ptr (t)),
|
||||
|
||||
Value::BogusArg (_) => write! (f, "BogusArg"),
|
||||
Value::BogusClosure (_) => write! (f, "BogusClosure"),
|
||||
Value::BogusEnv (_) => write! (f, "BogusEnv"),
|
||||
Value::BogusPrint => write! (f, "BogusPrint"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,9 +94,34 @@ impl From <f64> for Value {
|
|||
}
|
||||
}
|
||||
|
||||
impl From <usize> for Value {
|
||||
fn from (x: usize) -> Self {
|
||||
Self::Integer (i64::try_from (x).unwrap ())
|
||||
}
|
||||
}
|
||||
|
||||
impl From <Table> for Value {
|
||||
fn from (x: Table) -> Self {
|
||||
Self::Table (x.into ())
|
||||
Self::Table (Rc::new (RefCell::new (x)))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator <(Value, Value)> for Value {
|
||||
fn from_iter <I: IntoIterator <Item=(Value, Value)>> (i: I) -> Self {
|
||||
let table = Table::from_iter (i);
|
||||
Self::from (table)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator <(String, Value)> for Value {
|
||||
fn from_iter <I: IntoIterator <Item=(String, Value)>> (i: I) -> Self {
|
||||
Self::from_iter (i.into_iter ().map (|(s, v)| (Value::from (s), v)))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator <Value> for Value {
|
||||
fn from_iter <I: IntoIterator <Item=Value>> (i: I) -> Self {
|
||||
Self::from_iter ((1..).zip (i.into_iter ()).map (|(i, v)| (Value::from (i), v)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,14 +139,12 @@ impl std::hash::Hash for Value {
|
|||
Self::Float (x) => x.to_ne_bytes ().hash (state),
|
||||
Self::Integer (x) => x.hash (state),
|
||||
|
||||
Self::RsFunc (x) => x.hash (state),
|
||||
// TODO: Implement string interning so we don't hash the whole string here
|
||||
Self::String (x) => x.hash (state),
|
||||
Self::Table (x) => Rc::as_ptr (&x).hash (state),
|
||||
|
||||
Self::BogusArg (_) => panic! ("can't hash Bogus values"),
|
||||
Self::BogusClosure (_) => panic! ("can't hash Bogus values"),
|
||||
Self::BogusEnv (_) => panic! ("can't hash Bogus values"),
|
||||
Self::BogusPrint => panic! ("can't hash Bogus values"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +165,20 @@ impl Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn as_int (&self) -> Option <i64> {
|
||||
match self {
|
||||
Self::Integer (x) => Some (*x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_table (&self) -> Option <&Rc <RefCell <Table>>> {
|
||||
match self {
|
||||
Self::Table (t) => Some (t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_truthy (&self) -> bool {
|
||||
// And this is something Lua does better than JS and Python.
|
||||
|
||||
|
@ -190,6 +224,17 @@ impl Table {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromIterator <(Value, Value)> for Table {
|
||||
fn from_iter<I: IntoIterator<Item = (Value, Value)>> (i: I) -> Self
|
||||
{
|
||||
let hash = i.into_iter ().collect ();
|
||||
Self {
|
||||
array: Default::default (),
|
||||
hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg (test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
for i = 1, 50 do
|
||||
if i % 3 == 0 and i % 5 == 0 then
|
||||
print (i, "FizzBuzz")
|
||||
elseif i % 3 == 0 then
|
||||
print (i, "Fizz")
|
||||
elseif i % 5 == 0 then
|
||||
print (i, "Buzz")
|
||||
else
|
||||
print (i)
|
||||
end
|
||||
end
|
|
@ -1,9 +1,4 @@
|
|||
print (nil)
|
||||
print (false)
|
||||
print (true)
|
||||
print (1993)
|
||||
print (1993.00)
|
||||
print "Hello."
|
||||
|
||||
local t = {}
|
||||
print (t)
|
||||
print ("Hi!", 2)
|
||||
print ()
|
||||
print ("Hi!")
|
||||
print ("You've _gotta_ admit this is **cool**!")
|
||||
|
|
Loading…
Reference in New Issue