mod arrays;
mod conversion;
mod datetime;
mod environment;
mod file;
mod financial;
mod graphics;
mod interaction;
mod logic;
mod math;
mod objects;
mod resources;
mod string;
mod type_checking;
#[cfg(test)]
mod tests;
use std::collections::HashMap;
use std::sync::OnceLock;
use vb6core::error::{err_number, VBError, VBResult};
use vb6runtime::value::VBVariant;
#[macro_export]
macro_rules! typed_builtin {
($name:literal, $min:expr, $max:expr,
($($param:ident : $kind:ident),* $(,)?),
$body:expr) => {
Builtin {
name: $name,
min_args: $min,
max_args: $max,
call: |_args: &[::vb6runtime::VBVariant]| -> ::vb6core::error::VBResult<::vb6runtime::VBVariant> {
let mut __arg_index = 0usize;
let __result: ::vb6core::error::VBResult<::vb6runtime::VBVariant> = {
$(
let __param_name = stringify!($param);
let $param =
$crate::__convert_arg!(_args, __arg_index, $kind)
.map_err(|mut __e| {
__e.param_index = Some(__arg_index);
__e.param_name = Some(__param_name.to_string());
__e
})?;
__arg_index += 1;
)*
$body
};
__result
},
}
};
($name:literal, $min:expr, $max:expr,
($($param:ident : $kind:ident),* $(,)?),
rest $rest:ident : variants,
$body:expr) => {
Builtin {
name: $name,
min_args: $min,
max_args: $max,
call: |_args: &[::vb6runtime::VBVariant]| -> ::vb6core::error::VBResult<::vb6runtime::VBVariant> {
let mut __arg_index = 0usize;
let __result: ::vb6core::error::VBResult<::vb6runtime::VBVariant> = {
$(
let __param_name = stringify!($param);
let $param =
$crate::__convert_arg!(_args, __arg_index, $kind)
.map_err(|mut __e| {
__e.param_index = Some(__arg_index);
__e.param_name = Some(__param_name.to_string());
__e
})?;
__arg_index += 1;
)*
let $rest = &_args[__arg_index..];
$body
};
__result
},
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __convert_arg {
($args:ident, $index:expr, variant) => {
::vb6runtime::boundary::variant_arg($args, $index)
};
($args:ident, $index:expr, opt_variant) => {
Ok::<_, ::vb6core::error::VBError>(::vb6runtime::boundary::opt_variant_arg($args, $index))
};
($args:ident, $index:expr, string) => {
::vb6runtime::boundary::arg::<::vb6runtime::value::VBString>($args, $index)
};
($args:ident, $index:expr, propstring) => {{
if matches!($args.get($index), Some(::vb6runtime::VBVariant::Null)) {
return Ok(::vb6runtime::VBVariant::Null);
}
::vb6runtime::boundary::arg::<::vb6runtime::value::VBString>($args, $index)
}};
($args:ident, $index:expr, propdouble) => {{
if matches!($args.get($index), Some(::vb6runtime::VBVariant::Null)) {
return Ok(::vb6runtime::VBVariant::Null);
}
::vb6runtime::boundary::arg::<::vb6runtime::value::VBDouble>($args, $index)
}};
($args:ident, $index:expr, propdate) => {{
if matches!($args.get($index), Some(::vb6runtime::VBVariant::Null)) {
return Ok(::vb6runtime::VBVariant::Null);
}
::vb6runtime::boundary::arg::<::vb6runtime::value::VBDate>($args, $index)
}};
($args:ident, $index:expr, propboolean) => {{
if matches!($args.get($index), Some(::vb6runtime::VBVariant::Null)) {
return Ok(::vb6runtime::VBVariant::Null);
}
::vb6runtime::boundary::arg::<::vb6runtime::value::VBBoolean>($args, $index)
}};
($args:ident, $index:expr, proplong) => {{
if matches!($args.get($index), Some(::vb6runtime::VBVariant::Null)) {
return Ok(::vb6runtime::VBVariant::Null);
}
::vb6runtime::boundary::arg::<::vb6runtime::value::VBLong>($args, $index)
}};
($args:ident, $index:expr, long) => {
::vb6runtime::boundary::arg::<::vb6runtime::value::VBLong>($args, $index)
};
($args:ident, $index:expr, integer) => {
::vb6runtime::boundary::arg::<::vb6runtime::value::VBInteger>($args, $index)
};
($args:ident, $index:expr, byte) => {
::vb6runtime::boundary::arg::<::vb6runtime::value::VBByte>($args, $index)
};
($args:ident, $index:expr, boolean) => {
::vb6runtime::boundary::arg::<::vb6runtime::value::VBBoolean>($args, $index)
};
($args:ident, $index:expr, single) => {
::vb6runtime::boundary::arg::<::vb6runtime::value::VBSingle>($args, $index)
};
($args:ident, $index:expr, double) => {
::vb6runtime::boundary::arg::<::vb6runtime::value::VBDouble>($args, $index)
};
($args:ident, $index:expr, currency) => {
::vb6runtime::boundary::arg::<::vb6runtime::value::VBCurrency>($args, $index)
};
($args:ident, $index:expr, date) => {
::vb6runtime::boundary::arg::<::vb6runtime::value::VBDate>($args, $index)
};
($args:ident, $index:expr, opt_string) => {
::vb6runtime::boundary::opt_arg::<::vb6runtime::value::VBString>($args, $index)
};
($args:ident, $index:expr, opt_long) => {
::vb6runtime::boundary::opt_arg::<::vb6runtime::value::VBLong>($args, $index)
};
($args:ident, $index:expr, opt_integer) => {
::vb6runtime::boundary::opt_arg::<::vb6runtime::value::VBInteger>($args, $index)
};
($args:ident, $index:expr, opt_byte) => {
::vb6runtime::boundary::opt_arg::<::vb6runtime::value::VBByte>($args, $index)
};
($args:ident, $index:expr, opt_boolean) => {
::vb6runtime::boundary::opt_arg::<::vb6runtime::value::VBBoolean>($args, $index)
};
($args:ident, $index:expr, opt_single) => {
::vb6runtime::boundary::opt_arg::<::vb6runtime::value::VBSingle>($args, $index)
};
($args:ident, $index:expr, opt_double) => {
::vb6runtime::boundary::opt_arg::<::vb6runtime::value::VBDouble>($args, $index)
};
($args:ident, $index:expr, opt_currency) => {
::vb6runtime::boundary::opt_arg::<::vb6runtime::value::VBCurrency>($args, $index)
};
($args:ident, $index:expr, opt_date) => {
::vb6runtime::boundary::opt_arg::<::vb6runtime::value::VBDate>($args, $index)
};
}
type BuiltinFn = fn(&[VBVariant]) -> VBResult<VBVariant>;
struct Builtin {
name: &'static str,
min_args: usize,
max_args: usize,
call: BuiltinFn,
}
struct Registry {
by_name: HashMap<&'static str, Builtin>,
}
impl Registry {
fn new() -> Self {
Self {
by_name: HashMap::new(),
}
}
fn insert(&mut self, builtin: Builtin) {
self.by_name.insert(builtin.name, builtin);
}
fn dispatch(&self, name: &str, args: &[VBVariant]) -> Option<VBResult<VBVariant>> {
self.by_name.get(name).map(|builtin| {
if args.len() < builtin.min_args || args.len() > builtin.max_args {
return Err(VBError::new(err_number::WRONG_NUMBER_OF_ARGUMENTS));
}
(builtin.call)(args)
})
}
}
fn registry() -> &'static Registry {
static REGISTRY: OnceLock<Registry> = OnceLock::new();
REGISTRY.get_or_init(|| {
let mut registry = Registry::new();
arrays::register(&mut registry);
conversion::register(&mut registry);
datetime::register(&mut registry);
environment::register(&mut registry);
file::register(&mut registry);
financial::register(&mut registry);
graphics::register(&mut registry);
logic::register(&mut registry);
interaction::register(&mut registry);
string::register(&mut registry);
math::register(&mut registry);
objects::register(&mut registry);
resources::register(&mut registry);
type_checking::register(&mut registry);
registry
})
}
pub(crate) fn call_builtin(name: &str, args: &[VBVariant]) -> VBResult<VBVariant> {
let normalized_name = builtin_name(name);
registry()
.dispatch(&normalized_name, args)
.unwrap_or_else(|| {
Err(VBError::with_description(
35,
format!("Function '{name}' is not implemented yet"),
))
})
}
fn builtin_name(name: &str) -> String {
let trimmed = name.trim();
trimmed
.strip_suffix('%')
.or_else(|| trimmed.strip_suffix('&'))
.or_else(|| trimmed.strip_suffix('!'))
.or_else(|| trimmed.strip_suffix('#'))
.or_else(|| trimmed.strip_suffix('@'))
.unwrap_or(trimmed)
.to_lowercase()
}