Compare commits

..

10 Commits

Author SHA1 Message Date
_ 5c4a545369 📝 doc: roadmap 2023-09-27 13:58:37 -05:00
_ bebc96916b allow Lua to call native Rust functions
And Value::BogusPrint is gone. So the only Bogus remaining is closures.
2023-09-27 01:46:53 -05:00
_ 5ab30ac5b4 ♻️ refactor: remove special types BogusArg and BogusEnv
This is a milestone towards actually implementing all of Lua.
2023-09-26 23:19:33 -05:00
_ 062c6e6a2d 🐛 bug: fix loader bug 2023-09-26 23:19:12 -05:00
_ 2f676adc19 FizzBuzz with some caveats 2023-09-26 22:45:41 -05:00
_ 9811d265f6 for loops with integer counters counting up only 2023-09-26 22:34:47 -05:00
_ fcfd9397ff 🐛 bug: switch to RefCell so some more of the tables tests will pass 2023-09-26 22:03:37 -05:00
_ 0d5e1098bc test: found a bug in my tables impl 2023-09-26 20:47:47 -05:00
_ 1d813b0f44 test: add tables test 2023-09-26 20:41:47 -05:00
_ 9d29aeb43b tables are working okay
Still missing a lot of stuff. And I'm pretty sure the Rc handling code
is totally wrong.

And they don't have the Lua 5.0 optimization of an internal array,
but that's probably easy to put in.
2023-09-26 20:31:00 -05:00
11 changed files with 464 additions and 237 deletions

14
README.md Normal file
View File

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

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

View File

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

View File

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

View File

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

View File

@ -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) => {

View File

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

View File

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

View File

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

View File

@ -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**!")