⭐ can create an empty table
parent
e12d749c7c
commit
72b2d6e656
|
@ -45,6 +45,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option <Inst>
|
|||
|
||||
let a = (buf [0] >> 7) | ((buf [1] & 0x7f) << 1);
|
||||
let b = buf [2];
|
||||
let ax = a as u32 + ((b as u32) << 8);
|
||||
let c = buf [3];
|
||||
let bx =
|
||||
(((buf [1] >> 7) as u32) << 0) |
|
||||
|
@ -67,6 +68,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option <Inst>
|
|||
0x0b => Inst::GetTabUp (a, b, c),
|
||||
0x0d => Inst::GetI (a, b, c),
|
||||
0x0f => Inst::SetTabUp (a, b, c),
|
||||
0x13 => Inst::NewTable (a),
|
||||
0x22 => Inst::Add (a, b, c),
|
||||
0x24 => Inst::Mul (a, b, c),
|
||||
0x2e => Inst::MmBin (a, b, c),
|
||||
|
@ -81,6 +83,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option <Inst>
|
|||
0x48 => Inst::Return1 (a),
|
||||
0x4f => Inst::Closure (a, bx),
|
||||
0x51 => Inst::VarArgPrep (a.into ()),
|
||||
0x52 => Inst::ExtraArg (ax),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
@ -264,6 +267,7 @@ mod tests {
|
|||
([0xbc, 0x00, 0x01, 0x00], Inst::EqK (1, 1, 0)),
|
||||
([0xb8, 0x02, 0x00, 0x80], Inst::Jmp (6)),
|
||||
([0x38, 0x02, 0x00, 0x80], Inst::Jmp (5)),
|
||||
([0x52, 0x00, 0x00, 0x00], Inst::ExtraArg (0)),
|
||||
] {
|
||||
let actual = super::parse_inst (input).unwrap ();
|
||||
assert_eq!(actual, expected);
|
||||
|
|
25
src/state.rs
25
src/state.rs
|
@ -1,7 +1,8 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::value::{
|
||||
BogusClosure,
|
||||
Table,
|
||||
Value,
|
||||
};
|
||||
|
||||
|
@ -15,6 +16,8 @@ pub enum Instruction {
|
|||
// Equals Constant?
|
||||
EqK (u8, u8, u8),
|
||||
|
||||
ExtraArg (u32),
|
||||
|
||||
// Get Immediate?
|
||||
GetI (u8, u8, u8),
|
||||
|
||||
|
@ -48,6 +51,8 @@ pub enum Instruction {
|
|||
|
||||
Mul (u8, u8, u8),
|
||||
|
||||
NewTable (u8),
|
||||
|
||||
Not (u8, u8),
|
||||
|
||||
// (A, B, _C, k) Return B - 1 registers starting with A
|
||||
|
@ -129,7 +134,7 @@ impl State {
|
|||
{
|
||||
let arg: Vec <_> = args.map (|s| s.to_string ()).collect ();
|
||||
|
||||
let env = BTreeMap::from_iter ([
|
||||
let env = HashMap::from_iter ([
|
||||
("arg", Value::BogusArg (arg.into ())),
|
||||
("print", Value::BogusPrint),
|
||||
].map (|(k, v)| (k.to_string (), v)));
|
||||
|
@ -247,6 +252,7 @@ impl State {
|
|||
Value::Float (x) => println! ("{:?}", x),
|
||||
Value::Integer (x) => println! ("{}", x),
|
||||
Value::String (s) => println! ("{}", s),
|
||||
Value::Table (t) => println! ("table: {:?}", std::rc::Rc::as_ptr (t)),
|
||||
_ => unimplemented! (),
|
||||
};
|
||||
|
||||
|
@ -285,6 +291,12 @@ impl State {
|
|||
_ => (),
|
||||
}
|
||||
},
|
||||
Instruction::ExtraArg (ax) => {
|
||||
// This is used for NewTable. Maybe it's for reserving
|
||||
// capacity in the array or something?
|
||||
|
||||
assert_eq! (*ax, 0, "implemented only for ax == 0");
|
||||
},
|
||||
Instruction::GetTabUp (a, b, c) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
let b = usize::try_from (*b).unwrap ();
|
||||
|
@ -383,6 +395,11 @@ impl State {
|
|||
let r = self.register_window_mut();
|
||||
r [a] = r [b].clone ();
|
||||
},
|
||||
Instruction::Mul (_a, _b, _c) => unimplemented!(),
|
||||
Instruction::NewTable (a) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
self.register_window_mut ()[a] = Value::Table (Default::default ());
|
||||
},
|
||||
Instruction::Not (a, b) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
let b = usize::try_from (*b).unwrap ();
|
||||
|
@ -444,6 +461,7 @@ impl State {
|
|||
return self.registers [a..(a + b - 1)].to_vec();
|
||||
}
|
||||
},
|
||||
Instruction::Return0 => unimplemented! (),
|
||||
Instruction::Return1 (a) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
let popped_frame = self.stack.pop ().unwrap ();
|
||||
|
@ -467,6 +485,8 @@ impl State {
|
|||
let offset = popped_frame.register_offset;
|
||||
self.registers [offset - 1] = self.registers [offset + a].take ();
|
||||
},
|
||||
Instruction::SetTabUp (_a, _b, _c) => unimplemented! (),
|
||||
Instruction::TailCall (_a, _b, _c, _k) => unimplemented! (),
|
||||
Instruction::Test (a, _k) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
|
||||
|
@ -481,7 +501,6 @@ impl State {
|
|||
}
|
||||
},
|
||||
Instruction::VarArgPrep (_) => (),
|
||||
x => panic! ("Unimplemented instruction {x:?}"),
|
||||
}
|
||||
|
||||
next_pc += 1;
|
||||
|
|
35
src/tests.rs
35
src/tests.rs
|
@ -1,8 +1,11 @@
|
|||
use crate::state::{
|
||||
Block,
|
||||
Chunk,
|
||||
Instruction as Inst,
|
||||
State,
|
||||
use crate::{
|
||||
state::{
|
||||
Block,
|
||||
Chunk,
|
||||
Instruction as Inst,
|
||||
State,
|
||||
},
|
||||
value::Value,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
@ -75,6 +78,8 @@ fn bools () {
|
|||
(vec! ["_exe_name"], vec! [98.into ()]),
|
||||
(vec! ["_exe_name", "asdf"], vec! [99.into ()]),
|
||||
] {
|
||||
let expected: Vec <Value> = expected;
|
||||
|
||||
let mut vm = State::default ();
|
||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||
|
||||
|
@ -94,6 +99,7 @@ fn closure () {
|
|||
(vec! ["_exe_name"], vec! [23.0.into ()]),
|
||||
(vec! ["_exe_name"], vec! [23.0.into ()]),
|
||||
] {
|
||||
let expected: Vec <Value> = expected;
|
||||
let mut vm = State::default ();
|
||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||
let actual = vm.execute_chunk (&file, &upvalues);
|
||||
|
@ -141,6 +147,7 @@ fn floats () {
|
|||
(vec! ["_exe_name"], vec! [3.5.into ()]),
|
||||
(vec! ["_exe_name", " "], vec! [3.5.into ()]),
|
||||
] {
|
||||
let expected: Vec <Value> = expected;
|
||||
let mut vm = State::default ();
|
||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||
let actual = vm.execute_chunk (&chunk, &upvalues);
|
||||
|
@ -160,6 +167,7 @@ fn fma () {
|
|||
(vec! ["_exe_name"], vec! [122.into ()]),
|
||||
(vec! ["_exe_name"], vec! [122.into ()]),
|
||||
] {
|
||||
let expected: Vec <Value> = expected;
|
||||
let mut vm = State::default ();
|
||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||
let actual = vm.execute_chunk (&file, &upvalues);
|
||||
|
@ -184,6 +192,7 @@ fn is_93 () {
|
|||
(vec! ["_exe_name", "93"], vec! [0.into ()]),
|
||||
(vec! ["_exe_name", "94"], vec! [1.into ()]),
|
||||
] {
|
||||
let expected: Vec <Value> = expected;
|
||||
let mut vm = State::default ();
|
||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||
let actual = vm.execute_chunk (&file, &upvalues);
|
||||
|
@ -200,28 +209,16 @@ fn value_size () {
|
|||
// Lua's tagged union values are 12-16 bytes on a 32-bit system
|
||||
// with 64-bit floats
|
||||
//
|
||||
// It is very nice if LunarWaveVM is the same or better.
|
||||
// There is some exploratory things in this test, too
|
||||
// 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,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
assert! (size_of::<Box <()>> () <= 8);
|
||||
assert! (size_of::<std::rc::Rc <()>> () <= 8);
|
||||
|
||||
enum Value {
|
||||
Nil,
|
||||
Boolean (bool),
|
||||
Float (f64),
|
||||
String (Rc <String>),
|
||||
}
|
||||
|
||||
let sz = size_of::<Value> ();
|
||||
let expected = 16;
|
||||
assert! (sz <= expected, "{sz} > {expected}");
|
||||
|
||||
let sz = size_of::<crate::value::Value> ();
|
||||
let expected = 16;
|
||||
assert! (sz <= expected, "{sz} > {expected}");
|
||||
|
|
114
src/value.rs
114
src/value.rs
|
@ -1,9 +1,13 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
cmp::{
|
||||
Eq,
|
||||
PartialEq,
|
||||
},
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
#[derive (Clone, Debug, Hash, PartialEq)]
|
||||
#[derive (Debug, Eq, PartialEq)]
|
||||
pub struct BogusClosure {
|
||||
pub idx: usize,
|
||||
pub upvalues: Vec <Value>,
|
||||
|
@ -13,16 +17,21 @@ pub struct BogusClosure {
|
|||
pub enum Value {
|
||||
Nil,
|
||||
Boolean (bool),
|
||||
|
||||
// Rust is very strict about float equality, so some of my code
|
||||
// here is probably wrong in subtle ways.
|
||||
Float (f64),
|
||||
|
||||
Integer (i64),
|
||||
String (Rc <String>),
|
||||
Table (Rc <Table>),
|
||||
|
||||
// These are all bogus, I haven't figured out how to implement
|
||||
// tables and function pointers yet
|
||||
|
||||
BogusArg (Rc <Vec <String>>),
|
||||
BogusClosure (Rc <BogusClosure>),
|
||||
BogusEnv (Rc <BTreeMap <String, Value>>),
|
||||
BogusEnv (Rc <HashMap <String, Value>>),
|
||||
BogusPrint,
|
||||
}
|
||||
|
||||
|
@ -46,7 +55,13 @@ impl From <&str> for Value {
|
|||
|
||||
impl From <i32> for Value {
|
||||
fn from (x: i32) -> Self {
|
||||
Self::Integer (i64::try_from (x).unwrap ())
|
||||
Self::Integer (i64::from (x))
|
||||
}
|
||||
}
|
||||
|
||||
impl From <i64> for Value {
|
||||
fn from (x: i64) -> Self {
|
||||
Self::Integer (x)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,6 +71,14 @@ impl From <f64> for Value {
|
|||
}
|
||||
}
|
||||
|
||||
impl From <Table> for Value {
|
||||
fn from (x: Table) -> Self {
|
||||
Self::Table (x.into ())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Value {}
|
||||
|
||||
impl std::hash::Hash for Value {
|
||||
fn hash <H: std::hash::Hasher> (&self, state: &mut H) {
|
||||
// Per https://doc.rust-lang.org/std/hash/trait.Hash.html#prefix-collisions
|
||||
|
@ -67,7 +90,11 @@ impl std::hash::Hash for Value {
|
|||
Self::Boolean (x) => x.hash (state),
|
||||
Self::Float (x) => x.to_ne_bytes ().hash (state),
|
||||
Self::Integer (x) => x.hash (state),
|
||||
Self::String (x) => x.as_ptr ().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"),
|
||||
|
@ -76,6 +103,12 @@ impl std::hash::Hash for Value {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq <i64> for Value {
|
||||
fn eq (&self, rhs: &i64) -> bool {
|
||||
*self == Value::from (*rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn as_float (&self) -> Option <f64> {
|
||||
match self {
|
||||
|
@ -102,3 +135,74 @@ impl Value {
|
|||
x
|
||||
}
|
||||
}
|
||||
|
||||
#[derive (Debug, Default, Eq, PartialEq)]
|
||||
pub struct Table {
|
||||
array: Vec <Value>,
|
||||
hash: HashMap <Value, Value>,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
fn get_inner (&self, key: &Value) -> Value {
|
||||
self.hash.get (key).cloned ().unwrap_or_default ()
|
||||
}
|
||||
|
||||
pub fn get <A: Into <Value>> (&self, key: A) -> Value {
|
||||
self.get_inner (&(key.into ()))
|
||||
}
|
||||
|
||||
fn insert_inner (&mut self, a: Value, b: Value) {
|
||||
self.hash.insert (a, b);
|
||||
}
|
||||
|
||||
pub fn insert <A: Into <Value>, B: Into <Value>> (
|
||||
&mut self,
|
||||
a: A,
|
||||
b: B,
|
||||
) {
|
||||
self.insert_inner (a.into (), b.into ())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg (test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use super::{
|
||||
Table,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn smoke () {
|
||||
let v_a = Value::from (5.0);
|
||||
let v_b = Value::from (5);
|
||||
|
||||
assert_eq! (v_a.as_float (), Some (5.0));
|
||||
assert_eq! (v_b.as_float (), Some (5.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tables () {
|
||||
let nil = Value::Nil;
|
||||
|
||||
assert_ne! (Value::from (18.0), Value::from (19.0));
|
||||
|
||||
let mut t = HashMap::new ();
|
||||
t.insert (Value::from ("x"), Value::from (19.0));
|
||||
assert_eq! (t.get (&Value::from ("x")), Some (&Value::from (19.0)));
|
||||
|
||||
let mut t = Table::default ();
|
||||
|
||||
assert_eq! (t.get ("a"), nil);
|
||||
assert_eq! (t.get ("b"), nil);
|
||||
|
||||
t.insert ("a", 1993);
|
||||
t.insert ("b", 2007);
|
||||
|
||||
assert_eq! (t.get ("a"), 1993);
|
||||
assert_eq! (t.get ("b"), 2007);
|
||||
|
||||
t.insert (19, 93);
|
||||
assert_eq! (t.get (19), 93);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,3 +4,6 @@ print (true)
|
|||
print (1993)
|
||||
print (1993.00)
|
||||
print "Hello."
|
||||
|
||||
local t = {}
|
||||
print (t)
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
main <test_vectors/hello.lua:0,0> (25 instructions at 0x55938922ecd0)
|
||||
0+ params, 3 slots, 1 upvalue, 1 local, 2 constants, 0 functions
|
||||
1 [1] VARARGPREP 0
|
||||
2 [1] GETTABUP 0 0 0 ; _ENV "print"
|
||||
3 [1] LOADNIL 1 0 ; 1 out
|
||||
4 [1] CALL 0 2 1 ; 1 in 0 out
|
||||
5 [2] GETTABUP 0 0 0 ; _ENV "print"
|
||||
6 [2] LOADFALSE 1
|
||||
7 [2] CALL 0 2 1 ; 1 in 0 out
|
||||
8 [3] GETTABUP 0 0 0 ; _ENV "print"
|
||||
9 [3] LOADTRUE 1
|
||||
10 [3] CALL 0 2 1 ; 1 in 0 out
|
||||
11 [4] GETTABUP 0 0 0 ; _ENV "print"
|
||||
12 [4] LOADI 1 1993
|
||||
13 [4] CALL 0 2 1 ; 1 in 0 out
|
||||
14 [5] GETTABUP 0 0 0 ; _ENV "print"
|
||||
15 [5] LOADF 1 1993
|
||||
16 [5] CALL 0 2 1 ; 1 in 0 out
|
||||
17 [6] GETTABUP 0 0 0 ; _ENV "print"
|
||||
18 [6] LOADK 1 1 ; "Hello."
|
||||
19 [6] CALL 0 2 1 ; 1 in 0 out
|
||||
20 [8] NEWTABLE 0 0 0 ; 0
|
||||
21 [8] EXTRAARG 0
|
||||
22 [9] GETTABUP 1 0 0 ; _ENV "print"
|
||||
23 [9] MOVE 2 0
|
||||
24 [9] CALL 1 2 1 ; 1 in 0 out
|
||||
25 [9] RETURN 1 1 1 ; 0 out
|
||||
constants (2) for 0x55938922ecd0:
|
||||
0 S "print"
|
||||
1 S "Hello."
|
||||
locals (1) for 0x55938922ecd0:
|
||||
0 t 22 26
|
||||
upvalues (1) for 0x55938922ecd0:
|
||||
0 _ENV 1 0
|
Loading…
Reference in New Issue