Add do_call macro, implement Bool builtins, add tests

* I noticed that `fn call(...)` in all objects was identical, so I made
  a macro for it. This should make things a little easier to read, since
  do_call is about 30 lines a pop.
* Bool has a constructor now, and a to_int and to_float implementations
* Add tests for constructors and add new bool tests

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2024-09-30 12:41:13 -07:00
parent b9429d7c19
commit 3d0da0ec85
10 changed files with 240 additions and 78 deletions

View File

@@ -33,6 +33,39 @@ pub fn init_global_builtins() {
// //
// This would probably be doable in a procedural macro. // This would probably be doable in a procedural macro.
macro_rules! impl_do_call {
($name:ident) => {
pub(crate) fn do_call(vm: &mut Vm, state: FunctionState) -> FunctionResult {
match state {
FunctionState::Begin => {
// get the top item off the stack and call to_float on it
let arg = vm.peek();
let method = if let Some(method) =
arg.borrow().get_vtable_attr(arg.clone(), stringify!($name))
{
method
} else {
// TODO builtins::do_call - throw exception when target doesn't have a
// to_$name method
// BLOCKED-ON: exceptions
todo!(
concat!("{} does not have a ", stringify!($name), " method"),
arg.borrow().ty_name()
);
};
vm.push(method.clone());
method.borrow().call(vm, 0);
// resume execution
FunctionResult::Yield(0)
}
FunctionState::Resume(0) => FunctionResult::Return,
_ => unreachable!(),
}
}
};
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Global functions // Global functions
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -211,31 +244,7 @@ impl BaseObj {
// Str implementations // Str implementations
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
impl Str { impl Str {
pub(crate) fn do_call(vm: &mut Vm, state: FunctionState) -> FunctionResult { impl_do_call!(to_str);
match state {
FunctionState::Begin => {
// get the top item off the stack and call to_str on it
let arg = vm.peek();
let method =
if let Some(method) = arg.borrow().get_vtable_attr(arg.clone(), "to_str") {
method
} else {
// TODO Str::do_call - throw exception when target doesn't have a to_str()
// method
// BLOCKED-ON: exceptions
todo!("{} does not have a to_str() method", arg.borrow().ty_name());
// NOTE - every object should have to_str, right?
};
vm.push(method.clone());
method.borrow().call(vm, 0);
// resume execution
FunctionResult::Yield(0)
}
FunctionState::Resume(0) => FunctionResult::Return,
_ => unreachable!(),
}
}
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
// This is a no-op. We don't want the user-exposed `__init__` function to do anything, // This is a no-op. We don't want the user-exposed `__init__` function to do anything,
@@ -380,31 +389,7 @@ impl Int {
Float::create(int_value as f64).into() Float::create(int_value as f64).into()
} }
pub(crate) fn do_call(vm: &mut Vm, state: FunctionState) -> FunctionResult { impl_do_call!(to_int);
match state {
FunctionState::Begin => {
// Pop the argument and call `to_int` on it
let arg = vm.peek();
let method =
if let Some(method) = arg.borrow().get_vtable_attr(arg.clone(), "to_int") {
method
} else {
// TODO Int::do_call - throw exception when arg doesn't have to_int()
// method
// BLOCKED-ON: exceptions
todo!("{} does not have a to_int() method", arg.borrow().ty_name());
};
vm.push(method.clone());
method.borrow().call(vm, 0);
FunctionResult::Yield(0)
}
FunctionState::Resume(0) => FunctionResult::Return,
_ => unreachable!(),
}
}
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
// This is a no-op. We don't want the user-exposed `__init__` function to do anything, // This is a no-op. We don't want the user-exposed `__init__` function to do anything,
@@ -522,33 +507,7 @@ macro_rules! float_bin_op_logical {
} }
impl Float { impl Float {
pub(crate) fn do_call(vm: &mut Vm, state: FunctionState) -> FunctionResult { impl_do_call!(to_float);
match state {
FunctionState::Begin => {
// get the top item off the stack and call to_float on it
let arg = vm.peek();
let method =
if let Some(method) = arg.borrow().get_vtable_attr(arg.clone(), "to_float") {
method
} else {
// TODO Float::do_call - throw exception when target doesn't have a to_float()
// method
// BLOCKED-ON: exceptions
todo!(
"{} does not have a to_float() method",
arg.borrow().ty_name()
);
};
vm.push(method.clone());
method.borrow().call(vm, 0);
// resume execution
FunctionResult::Yield(0)
}
FunctionState::Resume(0) => FunctionResult::Return,
_ => unreachable!(),
}
}
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
// This is a no-op. We don't want the user-exposed `__init__` function to do anything, // This is a no-op. We don't want the user-exposed `__init__` function to do anything,
@@ -590,3 +549,23 @@ impl Float {
Float::create(-value).into() Float::create(-value).into()
} }
} }
impl Bool {
impl_do_call!(to_bool);
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
// This is a no-op. We don't want the user-exposed `__init__` function to do anything,
// instantiation is done in the `__call__` function.
FunctionResult::ReturnPush(Nil::create())
}
pub(crate) fn to_int(vm: &mut Vm, _state: FunctionState) -> FunctionResult {
let bool_value = with_obj_downcast(vm.frame_stack()[0].clone(), Bool::bool_value);
Int::create(bool_value as i64).into()
}
pub(crate) fn to_float(vm: &mut Vm, _state: FunctionState) -> FunctionResult {
let bool_value = with_obj_downcast(vm.frame_stack()[0].clone(), Bool::bool_value);
Float::create(bool_value as i64 as f64).into()
}
}

View File

@@ -210,7 +210,17 @@ pub fn init_types() {
__pos__ => BuiltinFunction::create("__pos__", Float::pos, 1), __pos__ => BuiltinFunction::create("__pos__", Float::pos, 1),
__neg__ => BuiltinFunction::create("__neg__", Float::neg, 1), __neg__ => BuiltinFunction::create("__neg__", Float::neg, 1),
}, },
Bool { }, Bool {
// Conversion methods
to_int => BuiltinFunction::create("to_int", Bool::to_int, 1),
to_float => BuiltinFunction::create("to_float", Bool::to_float, 1),
// Constructor
__call__ => BuiltinFunction::create("__call__", Bool::do_call, 2),
__init__ => BuiltinFunction::create("__init__", Bool::init, 2),
// Operators
},
Nil { }, Nil { },
BuiltinFunction { }, BuiltinFunction { },
UserFunction { }, UserFunction { },

72
tests/bool.npp Normal file
View File

@@ -0,0 +1,72 @@
# Bool type operator and function tests
a = true
b = false
# __not__
println("__not__")
println(!a)
println(!!a)
println(!!!a)
println(!!!!a)
println(!b)
println(!!b)
println(!!!b)
println(!!!!b)
# __and__
println("__and__")
println(a && b)
println(b && a)
println(a && a)
println(b && b)
# __or__
println("__or__")
println(a || b)
println(b || a)
println(a || a)
println(b || b)
# __eq__
println("__eq__")
println(a == a)
println(a == b)
println(b == b)
println(b == a)
println(a == true)
println(true == a)
println(a == false)
println(false == a)
println(b == true)
println(true == b)
println(b == false)
println(false == b)
# __ne__
println("__ne__")
println(a != a)
println(a != b)
println(b != b)
println(b != a)
println(a != true)
println(true != a)
println(a != false)
println(false != a)
println(b != true)
println(true != b)
println(b != false)
println(false != b)
# constructor
println("constructor")
println(Bool(""))
println(Bool(0))
println(Bool(0.0))
println(Bool(false))
# This is a fun one. Bool constructor does *not* parse bools, it just checks if they are empty.
println(Bool("false"))
println(Bool("true"))
println(Bool(1))
println(Bool(1.0))
println(Bool(true))

55
tests/bool.npp.expect Normal file
View File

@@ -0,0 +1,55 @@
__not__
false
true
false
true
true
false
true
false
__and__
false
false
true
false
__or__
true
true
true
false
__eq__
true
false
true
false
true
true
false
false
false
false
true
true
__ne__
false
true
false
true
false
false
true
true
true
true
false
false
constructor
false
false
false
false
true
true
true
true
true

View File

@@ -128,3 +128,12 @@ println(-0.0)
println(--0.0) println(--0.0)
println(---0.0) println(---0.0)
# constructor
println("constructor")
println(Float("10"))
println(Float("10e3"))
println(Float("12e-3"))
println(Float(1234))
println(Float(1.5))
println(Float(true))
println(Float(false))

View File

@@ -96,3 +96,11 @@ __neg__
-0.0 -0.0
0.0 0.0
-0.0 -0.0
constructor
10.0
10000.0
0.012
1234.0
1.5
1.0
0.0

View File

@@ -123,3 +123,11 @@ println(10 - -20)
println(-10 - 20) println(-10 - 20)
println(-10 - -20) println(-10 - -20)
println(-0xff) println(-0xff)
# constructor
println("constructor")
println(Int("10"))
println(Int(1234))
println(Int(1.5))
println(Int(true))
println(Int(false))

View File

@@ -93,3 +93,9 @@ __neg__
-30 -30
10 10
-255 -255
constructor
10
1234
1
1
0

View File

@@ -33,8 +33,17 @@ println(a + a)
println(a + a + a) println(a + a + a)
println(a + b) println(a + b)
println(b + a) println(b + a)
# __mul__ # __mul__
println("__mul__") println("__mul__")
println(a * 4) println(a * 4)
println(b * 5) println(b * 5)
println((a * 6).to_repr()) println((a * 6).to_repr())
# constructor
println("constructor")
println(Str("asdf"))
println(Str(1234))
println(Str(1.0))
println(Str(true))
println(Str(false))

View File

@@ -24,3 +24,9 @@ __mul__
asdfasdfasdfasdf asdfasdfasdfasdf
This is a longer sentenceThis is a longer sentenceThis is a longer sentenceThis is a longer sentenceThis is a longer sentence This is a longer sentenceThis is a longer sentenceThis is a longer sentenceThis is a longer sentenceThis is a longer sentence
'asdfasdfasdfasdfasdfasdf' 'asdfasdfasdfasdfasdfasdf'
constructor
asdf
1234
1.0
true
false