⭐ allow calling closures
parent
a2c91757cf
commit
81efebdda2
88
src/main.rs
88
src/main.rs
|
@ -4,29 +4,81 @@ mod state;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
use state::Instruction as Inst;
|
use state::{
|
||||||
use state::State;
|
Block,
|
||||||
|
Chunk,
|
||||||
|
Instruction as Inst,
|
||||||
|
State,
|
||||||
|
};
|
||||||
|
|
||||||
let chunk = state::Chunk {
|
/*
|
||||||
instructions: vec! [
|
local function bool_to_x (b)
|
||||||
Inst::VarArgPrep (0),
|
if b then
|
||||||
Inst::LoadK (0, 0),
|
return 99
|
||||||
Inst::LoadI (1, 3),
|
else
|
||||||
Inst::Add (2, 0, 1),
|
return 98
|
||||||
Inst::MmBin (0, 1, 6),
|
end
|
||||||
Inst::GetTabUp (3, 0, 1),
|
end
|
||||||
Inst::Move (4, 2),
|
|
||||||
Inst::Call (3, 2, 1),
|
local x = bool_to_x (not not arg [1])
|
||||||
Inst::Return (2, 2, 1),
|
print (x)
|
||||||
Inst::Return (3, 1, 1),
|
return x
|
||||||
],
|
*/
|
||||||
constants: vec! [
|
|
||||||
0.5.into (),
|
let chunk = Chunk {
|
||||||
"print".into (),
|
blocks: vec! [
|
||||||
|
Block {
|
||||||
|
instructions: vec! [
|
||||||
|
Inst::VarArgPrep (0),
|
||||||
|
Inst::Closure (0, 0),
|
||||||
|
Inst::GetTabUp (1, 0, 0),
|
||||||
|
Inst::Move (2, 0),
|
||||||
|
Inst::LoadFalse (3),
|
||||||
|
Inst::Call (2, 2, 0),
|
||||||
|
Inst::Call (1, 0, 1),
|
||||||
|
Inst::GetTabUp (1, 0, 0),
|
||||||
|
Inst::Move (2, 0),
|
||||||
|
Inst::LoadTrue (3),
|
||||||
|
Inst::Call (2, 2, 0),
|
||||||
|
Inst::Call (1, 0, 1),
|
||||||
|
Inst::Move (1, 0),
|
||||||
|
Inst::GetTabUp (2, 0, 1),
|
||||||
|
Inst::GetI (2, 2, 1),
|
||||||
|
Inst::Not (2, 2),
|
||||||
|
Inst::Not (2, 2),
|
||||||
|
Inst::Call (1, 2, 2),
|
||||||
|
Inst::GetTabUp (2, 0, 0),
|
||||||
|
Inst::Move (3, 1),
|
||||||
|
Inst::Call (2, 2, 1),
|
||||||
|
Inst::Return (1, 2, 1),
|
||||||
|
Inst::Return (2, 1, 1),
|
||||||
|
],
|
||||||
|
constants: vec! [
|
||||||
|
"print".into (),
|
||||||
|
"arg".into (),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Block {
|
||||||
|
instructions: vec! [
|
||||||
|
Inst::Test (0, 0),
|
||||||
|
Inst::Jmp (3),
|
||||||
|
Inst::LoadI (1, 99),
|
||||||
|
Inst::Return1 (1),
|
||||||
|
Inst::Jmp (2),
|
||||||
|
Inst::LoadI (1, 98),
|
||||||
|
Inst::Return1 (1),
|
||||||
|
Inst::Return0,
|
||||||
|
],
|
||||||
|
constants: vec! [],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut vm = State::default ();
|
let mut vm = State::default ();
|
||||||
|
if std::env::var("LUA_DEBUG").is_ok() {
|
||||||
|
vm.debug_print = true;
|
||||||
|
}
|
||||||
|
|
||||||
let upvalues = State::upvalues_from_args (std::env::args ());
|
let upvalues = State::upvalues_from_args (std::env::args ());
|
||||||
println! ("Returned: {:?}", vm.execute_chunk (&chunk, &upvalues));
|
println! ("Returned: {:?}", vm.execute_chunk (&chunk, &upvalues));
|
||||||
}
|
}
|
||||||
|
|
185
src/state.rs
185
src/state.rs
|
@ -19,28 +19,40 @@ pub enum Instruction {
|
||||||
// Jump
|
// Jump
|
||||||
Jmp (i32),
|
Jmp (i32),
|
||||||
|
|
||||||
|
LoadFalse (u8),
|
||||||
|
|
||||||
// Load Integer?
|
// Load Integer?
|
||||||
LoadI (u8, i32),
|
LoadI (u8, i32),
|
||||||
|
|
||||||
// Load Constant
|
// Load Constant
|
||||||
LoadK (u8, i32),
|
LoadK (u8, i32),
|
||||||
|
|
||||||
|
LoadTrue (u8),
|
||||||
|
|
||||||
// MetaMethod, Binary
|
// MetaMethod, Binary
|
||||||
MmBin (u8, u8, u8),
|
MmBin (u8, u8, u8),
|
||||||
|
|
||||||
Move (u8, u8),
|
Move (u8, u8),
|
||||||
|
|
||||||
|
Not (u8, u8),
|
||||||
|
|
||||||
// (A, B, _C) Return B - 1 registers starting with A
|
// (A, B, _C) Return B - 1 registers starting with A
|
||||||
Return (u8, u8, u8),
|
Return (u8, u8, u8),
|
||||||
|
|
||||||
|
Return0,
|
||||||
|
|
||||||
|
// Return just one register
|
||||||
|
Return1 (u8),
|
||||||
|
|
||||||
|
Test (u8, i32),
|
||||||
|
|
||||||
VarArgPrep (i32),
|
VarArgPrep (i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive (Clone, Debug, PartialEq)]
|
#[derive (Clone, Debug, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Nil,
|
Nil,
|
||||||
False,
|
Boolean (bool),
|
||||||
True,
|
|
||||||
Float (f64),
|
Float (f64),
|
||||||
String (String),
|
String (String),
|
||||||
|
|
||||||
|
@ -48,6 +60,7 @@ pub enum Value {
|
||||||
// tables and function pointers yet
|
// tables and function pointers yet
|
||||||
|
|
||||||
BogusArg (Vec <String>),
|
BogusArg (Vec <String>),
|
||||||
|
BogusClosure (usize),
|
||||||
BogusEnv (BTreeMap <String, Value>),
|
BogusEnv (BTreeMap <String, Value>),
|
||||||
BogusPrint,
|
BogusPrint,
|
||||||
}
|
}
|
||||||
|
@ -89,25 +102,50 @@ impl Value {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_truthy (&self) -> bool {
|
||||||
|
// And this is something Lua does better than JS and Python.
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Value::Nil => false,
|
||||||
|
Value::Boolean (false) => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Chunk {
|
pub struct Block {
|
||||||
pub instructions: Vec <Instruction>,
|
pub instructions: Vec <Instruction>,
|
||||||
pub constants: Vec <Value>,
|
pub constants: Vec <Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Chunk {
|
||||||
|
pub blocks: Vec <Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive (Debug)]
|
||||||
|
struct StackFrame {
|
||||||
|
// i32 makes it a little easier to implement jumps
|
||||||
|
// Starts at 0 right after OP_CALL
|
||||||
|
|
||||||
|
program_counter: i32,
|
||||||
|
|
||||||
|
// Starts from 0 for main and 1 for the first closure
|
||||||
|
block_idx: usize,
|
||||||
|
|
||||||
|
register_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
registers: Vec <Value>,
|
registers: Vec <Value>,
|
||||||
|
pub debug_print: bool,
|
||||||
// i32 makes it a little easier to implement jumps
|
|
||||||
program_counter: i32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for State {
|
impl Default for State {
|
||||||
fn default () -> Self {
|
fn default () -> Self {
|
||||||
Self {
|
Self {
|
||||||
registers: vec! [Value::Nil; 256],
|
registers: vec! [Value::Nil; 256],
|
||||||
program_counter: 0,
|
debug_print: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,11 +169,32 @@ impl State {
|
||||||
-> Vec <Value> {
|
-> Vec <Value> {
|
||||||
let max_iters = 2000;
|
let max_iters = 2000;
|
||||||
|
|
||||||
|
let mut stack = vec! [
|
||||||
|
StackFrame {
|
||||||
|
program_counter: 0,
|
||||||
|
block_idx: 0,
|
||||||
|
register_offset: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
for _ in 0..max_iters {
|
for _ in 0..max_iters {
|
||||||
let instruction = chunk.instructions.get (usize::try_from (self.program_counter).unwrap ()).unwrap ();
|
let stack_idx = stack.len () - 1;
|
||||||
|
let frame = stack.get_mut (stack_idx).unwrap ();
|
||||||
|
let block = chunk.blocks.get (frame.block_idx).unwrap ();
|
||||||
|
|
||||||
let r = &mut self.registers;
|
let mut next_pc = frame.program_counter;
|
||||||
let k = &chunk.constants;
|
|
||||||
|
let pc = usize::try_from (frame.program_counter).expect ("program_counter is not a valid usize");
|
||||||
|
let instruction = match block.instructions.get (pc) {
|
||||||
|
Some (x) => x,
|
||||||
|
None => {
|
||||||
|
dbg! (&stack);
|
||||||
|
panic! ("program_counter went out of bounds");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let r = &mut self.registers [frame.register_offset..];
|
||||||
|
let k = &block.constants;
|
||||||
|
|
||||||
match instruction {
|
match instruction {
|
||||||
Instruction::Add (a, b, c) => {
|
Instruction::Add (a, b, c) => {
|
||||||
|
@ -160,12 +219,47 @@ impl State {
|
||||||
// TODO: Only implement printing values for now
|
// TODO: Only implement printing values for now
|
||||||
|
|
||||||
let a = usize::try_from (*a).unwrap ();
|
let a = usize::try_from (*a).unwrap ();
|
||||||
|
let v_a = r.get (a).unwrap ();
|
||||||
|
|
||||||
assert_eq! (r.get (a).unwrap (), &Value::BogusPrint);
|
match v_a {
|
||||||
assert_eq! (*b, 2);
|
Value::BogusClosure (idx) => {
|
||||||
assert_eq! (*c, 1);
|
let block_idx = frame.block_idx;
|
||||||
|
let target_block = idx + 1;
|
||||||
|
|
||||||
|
let current_frame = &stack [stack.len () - 1];
|
||||||
|
|
||||||
|
stack.push (StackFrame {
|
||||||
|
program_counter: 0,
|
||||||
|
block_idx: target_block,
|
||||||
|
register_offset: current_frame.register_offset + a + 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.debug_print {
|
||||||
|
println! ("Inst {block_idx}:{pc} calls {target_block}:0");
|
||||||
|
let stack_depth = stack.len ();
|
||||||
|
println! ("stack_depth: {stack_depth}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// assert_eq! (*b, 2);
|
||||||
|
assert_eq! (*c, 1);
|
||||||
|
|
||||||
|
println! ("{:?}", r.get (a + 1).unwrap ());
|
||||||
|
},
|
||||||
|
_ => panic! ("Cannot call value {a:?}. backtrace: {stack:?}"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Closure (a, b) => {
|
||||||
|
let a = usize::try_from (*a).unwrap ();
|
||||||
|
let b = usize::try_from (*b).unwrap ();
|
||||||
|
|
||||||
println! ("{:?}", r.get (a + 1).unwrap ());
|
r [a] = Value::BogusClosure (b);
|
||||||
},
|
},
|
||||||
Instruction::EqK (a, b, c_k) => {
|
Instruction::EqK (a, b, c_k) => {
|
||||||
let a = usize::try_from (*a).unwrap ();
|
let a = usize::try_from (*a).unwrap ();
|
||||||
|
@ -174,8 +268,8 @@ impl State {
|
||||||
let equal = r [a] == k [b];
|
let equal = r [a] == k [b];
|
||||||
|
|
||||||
match (equal, c_k) {
|
match (equal, c_k) {
|
||||||
(true, 0) => self.program_counter += 1,
|
(true, 0) => next_pc += 1,
|
||||||
(false, 1) => self.program_counter += 1,
|
(false, 1) => next_pc += 1,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -212,7 +306,11 @@ impl State {
|
||||||
|
|
||||||
r [a] = value;
|
r [a] = value;
|
||||||
},
|
},
|
||||||
Instruction::Jmp (s_j) => self.program_counter += s_j,
|
Instruction::Jmp (s_j) => next_pc += s_j,
|
||||||
|
Instruction::LoadFalse (a) => {
|
||||||
|
let a = usize::try_from (*a).unwrap ();
|
||||||
|
r [a] = Value::Boolean (false);
|
||||||
|
},
|
||||||
Instruction::LoadI (a, sbx) => {
|
Instruction::LoadI (a, sbx) => {
|
||||||
let a = usize::try_from (*a).unwrap ();
|
let a = usize::try_from (*a).unwrap ();
|
||||||
|
|
||||||
|
@ -224,6 +322,10 @@ impl State {
|
||||||
|
|
||||||
r [a] = k [bx].clone ();
|
r [a] = k [bx].clone ();
|
||||||
},
|
},
|
||||||
|
Instruction::LoadTrue (a) => {
|
||||||
|
let a = usize::try_from (*a).unwrap ();
|
||||||
|
r [a] = Value::Boolean (true);
|
||||||
|
},
|
||||||
Instruction::MmBin (a, b, _c) => {
|
Instruction::MmBin (a, b, _c) => {
|
||||||
let a = usize::try_from (*a).unwrap ();
|
let a = usize::try_from (*a).unwrap ();
|
||||||
let b = usize::try_from (*b).unwrap ();
|
let b = usize::try_from (*b).unwrap ();
|
||||||
|
@ -244,19 +346,62 @@ impl State {
|
||||||
|
|
||||||
r [a] = r [b].clone ();
|
r [a] = r [b].clone ();
|
||||||
},
|
},
|
||||||
|
Instruction::Not (a, b) => {
|
||||||
|
let a = usize::try_from (*a).unwrap ();
|
||||||
|
let b = usize::try_from (*b).unwrap ();
|
||||||
|
|
||||||
|
r [a] = Value::Boolean (! r [b].is_truthy());
|
||||||
|
}
|
||||||
Instruction::Return (a, b, _c) => {
|
Instruction::Return (a, b, _c) => {
|
||||||
let a = usize::try_from (*a).unwrap ();
|
let a = usize::try_from (*a).unwrap ();
|
||||||
let b = usize::try_from (*b).unwrap ();
|
let b = usize::try_from (*b).unwrap ();
|
||||||
|
|
||||||
return self.registers [a..(a + b - 1)].to_vec();
|
return r [a..(a + b - 1)].to_vec();
|
||||||
|
},
|
||||||
|
Instruction::Return1 (a) => {
|
||||||
|
let a = usize::try_from (*a).unwrap ();
|
||||||
|
let popped_frame = stack.pop ().unwrap ();
|
||||||
|
|
||||||
|
self.registers [popped_frame.register_offset - 1] = r [a].clone ();
|
||||||
|
|
||||||
|
let stack_idx = stack.len () - 1;
|
||||||
|
let frame = stack.get (stack_idx).unwrap ();
|
||||||
|
let new_block = frame.block_idx;
|
||||||
|
next_pc = frame.program_counter;
|
||||||
|
|
||||||
|
if self.debug_print {
|
||||||
|
let old_block = popped_frame.block_idx;
|
||||||
|
let old_pc = popped_frame.program_counter;
|
||||||
|
println! ("Inst {old_block}:{old_pc} returns to inst {new_block}:{next_pc}");
|
||||||
|
let stack_depth = stack.len ();
|
||||||
|
println! ("stack_depth: {stack_depth}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Test (a, _k) => {
|
||||||
|
let a = usize::try_from (*a).unwrap ();
|
||||||
|
|
||||||
|
let a = r.get (a).unwrap ();
|
||||||
|
|
||||||
|
if self.debug_print {
|
||||||
|
println! ("Test {a:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.is_truthy() {
|
||||||
|
next_pc += 1;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Instruction::VarArgPrep (_) => (),
|
Instruction::VarArgPrep (_) => (),
|
||||||
x => panic! ("Unimplemented instruction {x:?}"),
|
x => panic! ("Unimplemented instruction {x:?}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.program_counter += 1;
|
next_pc += 1;
|
||||||
|
{
|
||||||
|
let stack_idx = stack.len () - 1;
|
||||||
|
let frame = stack.get_mut (stack_idx).unwrap ();
|
||||||
|
frame.program_counter = next_pc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
panic! ("Hit max iterations before chunk returned");
|
panic! ("Hit max iterations before block returned");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
87
src/tests.rs
87
src/tests.rs
|
@ -1,10 +1,87 @@
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
|
Block,
|
||||||
Chunk,
|
Chunk,
|
||||||
Instruction as Inst,
|
Instruction as Inst,
|
||||||
State,
|
State,
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bools () {
|
||||||
|
/*
|
||||||
|
local function bool_to_x (b)
|
||||||
|
if b then
|
||||||
|
return 99
|
||||||
|
else
|
||||||
|
return 98
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = bool_to_x (not not arg [1])
|
||||||
|
print (x)
|
||||||
|
return x
|
||||||
|
*/
|
||||||
|
|
||||||
|
let chunk = Chunk {
|
||||||
|
blocks: vec! [
|
||||||
|
Block {
|
||||||
|
instructions: vec! [
|
||||||
|
Inst::VarArgPrep (0),
|
||||||
|
Inst::Closure (0, 0),
|
||||||
|
Inst::Move (1, 0),
|
||||||
|
|
||||||
|
Inst::LoadFalse (2),
|
||||||
|
Inst::Call (1, 2, 1),
|
||||||
|
|
||||||
|
Inst::Move (1, 0),
|
||||||
|
Inst::LoadTrue (2),
|
||||||
|
Inst::Call (1, 2, 1),
|
||||||
|
|
||||||
|
Inst::Move (1, 0),
|
||||||
|
Inst::GetTabUp (2, 0, 0),
|
||||||
|
Inst::GetI (2, 2, 1),
|
||||||
|
Inst::Not (2, 2),
|
||||||
|
Inst::Not (2, 2),
|
||||||
|
Inst::Call (1, 2, 2),
|
||||||
|
Inst::GetTabUp (2, 0, 1),
|
||||||
|
Inst::Move (3, 1),
|
||||||
|
Inst::Call (2, 2, 1),
|
||||||
|
Inst::Return (1, 2, 1),
|
||||||
|
Inst::Return (2, 1, 1),
|
||||||
|
],
|
||||||
|
constants: vec! [
|
||||||
|
"arg".into (),
|
||||||
|
"print".into (),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Block {
|
||||||
|
instructions: vec! [
|
||||||
|
Inst::Test (0, 0),
|
||||||
|
Inst::Jmp (3),
|
||||||
|
Inst::LoadI (1, 99),
|
||||||
|
Inst::Return1 (1),
|
||||||
|
Inst::Jmp (2),
|
||||||
|
Inst::LoadI (1, 98),
|
||||||
|
Inst::Return1 (1),
|
||||||
|
Inst::Return0,
|
||||||
|
],
|
||||||
|
constants: vec! [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (arg, expected) in [
|
||||||
|
(vec! ["_exe_name"], vec! [98.into ()]),
|
||||||
|
(vec! ["_exe_name", "asdf"], vec! [99.into ()]),
|
||||||
|
] {
|
||||||
|
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);
|
||||||
|
assert_eq! (actual, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn floats () {
|
fn floats () {
|
||||||
/*
|
/*
|
||||||
|
@ -17,7 +94,7 @@ fn floats () {
|
||||||
return x
|
return x
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let chunk = Chunk {
|
let block = Block {
|
||||||
instructions: vec! [
|
instructions: vec! [
|
||||||
Inst::VarArgPrep (0),
|
Inst::VarArgPrep (0),
|
||||||
Inst::LoadK (0, 0),
|
Inst::LoadK (0, 0),
|
||||||
|
@ -35,6 +112,9 @@ fn floats () {
|
||||||
"print".into (),
|
"print".into (),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
let chunk = Chunk {
|
||||||
|
blocks: vec! [block],
|
||||||
|
};
|
||||||
|
|
||||||
for (arg, expected) in [
|
for (arg, expected) in [
|
||||||
(vec! ["_exe_name"], vec! [3.5.into ()]),
|
(vec! ["_exe_name"], vec! [3.5.into ()]),
|
||||||
|
@ -60,7 +140,7 @@ fn is_93 () {
|
||||||
end
|
end
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let chunk = Chunk {
|
let block = Block {
|
||||||
instructions: vec! [
|
instructions: vec! [
|
||||||
Inst::VarArgPrep (0),
|
Inst::VarArgPrep (0),
|
||||||
Inst::GetTabUp (1, 0, 0),
|
Inst::GetTabUp (1, 0, 0),
|
||||||
|
@ -88,6 +168,9 @@ fn is_93 () {
|
||||||
"it's not 93",
|
"it's not 93",
|
||||||
].into_iter ().map (Value::from).collect (),
|
].into_iter ().map (Value::from).collect (),
|
||||||
};
|
};
|
||||||
|
let chunk = Chunk {
|
||||||
|
blocks: vec! [block],
|
||||||
|
};
|
||||||
|
|
||||||
for (arg, expected) in [
|
for (arg, expected) in [
|
||||||
(vec! ["_exe_name"], vec! [1.into ()]),
|
(vec! ["_exe_name"], vec! [1.into ()]),
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
local function bool_to_x (b)
|
||||||
|
if b then
|
||||||
|
return 99
|
||||||
|
else
|
||||||
|
return 98
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print (bool_to_x (false))
|
||||||
|
print (bool_to_x (true))
|
||||||
|
|
||||||
|
local x = bool_to_x (not not arg [1])
|
||||||
|
print (x)
|
||||||
|
return x
|
Loading…
Reference in New Issue