#![allow(non_snake_case, non_upper_case_globals)]
use crate::ported::message::semsg;
pub fn eval_fname_sid(name: &str) -> bool {
let b = name.as_bytes();
b.first() == Some(&b's') || b.get(2).map(|c| c.to_ascii_uppercase()) == Some(b'I')
}
pub fn eval_fname_script(p: &str) -> i32 {
let lower = p.to_ascii_lowercase();
if p.starts_with('<') && (lower.starts_with("<sid>") || lower.starts_with("<snr>")) {
return 5;
}
if p.starts_with("s:") {
return 2;
}
0
}
pub fn func_name_refcount(name: &str) -> bool {
let b = name.as_bytes();
matches!(b.first(), Some(c) if c.is_ascii_digit())
|| (b.first() == Some(&b'<') && b.get(1) == Some(&b'l'))
}
pub fn builtin_function(name: &str, len: i32) -> bool {
let b = name.as_bytes();
if b.first().map(|c| !c.is_ascii_lowercase()).unwrap_or(true) || b.get(1) == Some(&b':') {
return false;
}
let slice = if len < 0 {
name
} else {
&name[..(len as usize).min(name.len())]
};
!slice.contains('#')
}
pub fn emsg_funcname(errmsg: &str, name: &str) {
let shown = name.strip_prefix("\u{1}").unwrap_or(name);
semsg(&errmsg.replace("%s", shown));
}
pub fn get_scriptlocal_funcname(funcname: Option<&str>) -> Option<String> {
let name = funcname?;
if !name.starts_with("s:") && !name.starts_with("<SID>") {
return None;
}
None
}
pub fn function_list_modified(_prev_ht_changed: i32) -> bool {
false
}
pub fn call_simple_luafunc() -> i32 {
crate::ported::eval_h::FAIL
}
pub fn func_init() {}
pub fn free_all_functions() {}
pub fn list_functions() {}
pub fn call_simple_func() -> i32 {
crate::ported::eval_h::FAIL
}
pub fn user_func_error(error: i32, name: &str, _found_var: bool) {
let msg = match error {
1 => format!("E119: Not enough arguments for function: {name}"),
2 => format!("E120: Using <SID> not in a script context: {name}"),
_ => format!("E117: Unknown function: {name}"),
};
semsg(&msg);
}
pub fn fname_trans_sid(name: &str) -> String {
name.to_string()
}
pub fn func_ref() {}
pub fn func_unref() {}
pub fn func_ptr_ref() {}
pub fn func_ptr_unref() {}
pub fn can_add_defer() -> bool {
false
}
pub fn add_defer() {}
pub fn ex_defer_inner() -> i32 {
crate::ported::eval_h::OK
}
pub fn handle_defer_one() {}
pub fn invoke_all_defer() {}
thread_local! {
static SAVED_FUNCCALS: std::cell::RefCell<Vec<Vec<crate::ported::eval::vars::FuncScope>>> =
const { std::cell::RefCell::new(Vec::new()) };
}
pub fn save_funccal() {
let cur =
crate::ported::eval::vars::funccal_stack.with(|s| std::mem::take(&mut *s.borrow_mut()));
SAVED_FUNCCALS.with(|s| s.borrow_mut().push(cur));
}
pub fn restore_funccal() {
match SAVED_FUNCCALS.with(|s| s.borrow_mut().pop()) {
Some(scope) => {
crate::ported::eval::vars::funccal_stack.with(|s| *s.borrow_mut() = scope);
}
None => crate::ported::message::emsg("INTERNAL: restore_funccal()"),
}
}
pub fn remove_funccal() {
crate::ported::eval::vars::funccal_stack.with(|s| {
s.borrow_mut().pop();
});
}
pub fn current_func_returned() -> bool {
false
}
pub fn get_user_func_name(_idx: i32) -> Option<String> {
None
}
pub fn list_func_head() -> i32 {
0
}
pub fn list_functions_matching_pat() -> Option<String> {
None
}
pub fn list_one_function() -> Option<()> {
None
}
pub fn list_func_vars(_first: &mut i32) {}
pub fn free_funccal() {}
pub fn free_funccal_contents() {}
pub fn funccal_unref() {}
pub fn func_clear_items() {}
pub fn func_clear() {}
pub fn func_free() {}
pub fn func_clear_free() {}
pub fn can_free_funccal() -> bool {
false
}
pub fn free_unref_funccal() -> bool {
false
}
pub fn set_ref_in_funccal() -> bool {
false
}
pub fn set_ref_in_call_stack() -> bool {
false
}
pub fn set_ref_in_func_args() -> bool {
false
}
pub fn set_ref_in_functions() -> bool {
false
}
pub fn set_ref_in_func() -> bool {
false
}
pub fn set_ref_in_previous_funccal() -> bool {
false
}
thread_local! {
static LAMBDA_NO: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
}
pub mod fcerr {
pub const FCERR_UNKNOWN: i32 = 0;
pub const FCERR_TOOMANY: i32 = 1;
pub const FCERR_TOOFEW: i32 = 2;
pub const FCERR_NONE: i32 = 5;
pub const FCERR_NOTMETHOD: i32 = 8;
}
pub mod fc {
pub const FC_ABORT: i32 = 0x01;
pub const FC_RANGE: i32 = 0x02;
pub const FC_DICT: i32 = 0x04;
pub const FC_CLOSURE: i32 = 0x08;
pub const FC_LUAREF: i32 = 0x800;
}
#[derive(Debug, Default, Clone)]
pub struct sctx_T {
pub sc_sid: i32,
pub sc_seq: i32,
pub sc_lnum: i64,
pub sc_chan: u64,
}
thread_local! {
pub static current_sctx: std::cell::RefCell<sctx_T> =
std::cell::RefCell::new(sctx_T::default());
}
#[derive(Debug, Default, Clone)]
pub struct ufunc_T {
pub uf_name: String,
pub uf_name_exp: Option<String>,
pub uf_args: Vec<String>,
pub uf_def_args: Vec<String>,
pub uf_varargs: bool,
pub uf_flags: i32,
pub uf_calls: i32,
pub uf_refcount: i32,
pub uf_luaref: i32,
pub uf_script_ctx: sctx_T,
}
pub type FindFuncFn = fn(&str) -> Option<ufunc_T>;
thread_local! {
pub static FIND_FUNC_HOOK: std::cell::RefCell<Option<FindFuncFn>> =
const { std::cell::RefCell::new(None) };
}
pub type RemoveFuncFn = fn(&str) -> bool;
thread_local! {
pub static REMOVE_FUNC_HOOK: std::cell::RefCell<Option<RemoveFuncFn>> =
const { std::cell::RefCell::new(None) };
}
pub fn find_var_in_scoped_ht() -> Option<crate::ported::eval::typval_defs_h::typval_T> {
None
}
pub fn get_func_line() -> Option<String> {
None
}
pub fn set_current_funccal() {}
pub fn cleanup_function_call() {}
pub fn register_closure() {}
pub fn func_remove(fp: &ufunc_T) -> bool {
REMOVE_FUNC_HOOK
.with(|h| *h.borrow())
.is_some_and(|f| f(&fp.uf_name))
}
pub fn find_func(name: &str) -> Option<ufunc_T> {
FIND_FUNC_HOOK.with(|h| *h.borrow()).and_then(|f| f(name))
}
pub fn alloc_ufunc(name: &str) -> ufunc_T {
ufunc_T {
uf_name: name.to_string(),
..Default::default()
}
}
pub fn call_user_func(
fp: &ufunc_T,
argvars: &[crate::ported::eval::typval_defs_h::typval_T],
rettv: &mut crate::ported::eval::typval_defs_h::typval_T,
) {
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::v_string, VarLockStatus, VarType::VAR_FUNC,
};
let callee = typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(fp.uf_name.clone()),
};
if let Some(result) = crate::ported::eval::typval::CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(&callee, argvars))
{
*rettv = result;
}
}
pub fn call_user_func_check(
fp: &ufunc_T,
argvars: &[crate::ported::eval::typval_defs_h::typval_T],
rettv: &mut crate::ported::eval::typval_defs_h::typval_T,
) -> i32 {
let err = check_user_func_argcount(fp, argvars.len() as i32);
if err != fcerr::FCERR_UNKNOWN {
return err;
}
call_user_func(fp, argvars, rettv);
fcerr::FCERR_NONE
}
pub fn check_user_func_argcount(fp: &ufunc_T, argcount: i32) -> i32 {
let regular_args = fp.uf_args.len() as i32;
if argcount < regular_args - fp.uf_def_args.len() as i32 {
fcerr::FCERR_TOOFEW
} else if !fp.uf_varargs && argcount > regular_args {
fcerr::FCERR_TOOMANY
} else {
fcerr::FCERR_UNKNOWN
}
}
pub fn func_is_global(ufunc: &ufunc_T) -> bool {
!ufunc.uf_name.starts_with("<SNR>")
}
pub fn cat_func_name(fp: &ufunc_T) -> String {
fp.uf_name.clone()
}
pub fn printable_func_name(fp: &ufunc_T) -> &str {
fp.uf_name_exp.as_deref().unwrap_or(&fp.uf_name)
}
pub fn get_func_arity(name: &str, fp: Option<&ufunc_T>) -> Option<(i32, i32, bool)> {
use crate::ported::eval::funcs_argc::BUILTIN_ARGC;
if let Ok(i) = BUILTIN_ARGC.binary_search_by(|e| e.0.cmp(name)) {
let (_, min_argc, max_argc) = BUILTIN_ARGC[i];
return Some((min_argc as i32, (max_argc - min_argc) as i32, false));
}
let f = fp?;
let argcount = f.uf_args.len() as i32;
let min_argcount = argcount - f.uf_def_args.len() as i32;
Some((min_argcount, argcount - min_argcount, f.uf_varargs))
}
pub fn get_lambda_name() -> String {
LAMBDA_NO.with(|n| {
let next = n.get() + 1;
n.set(next);
format!("<lambda>{next}")
})
}
thread_local! {
static func_hashtab: std::cell::RefCell<indexmap::IndexMap<String, ufunc_T>> =
std::cell::RefCell::new(indexmap::IndexMap::new());
}
pub fn register_luafunc(r#ref: i32) -> String {
let name = get_lambda_name(); let mut fp = alloc_ufunc(&name);
fp.uf_refcount = 1; fp.uf_varargs = true; fp.uf_flags = fc::FC_LUAREF; fp.uf_calls = 0; fp.uf_script_ctx = current_sctx.with(|c| c.borrow().clone()); fp.uf_luaref = r#ref;
let uf_name = fp.uf_name.clone();
func_hashtab.with(|h| h.borrow_mut().insert(uf_name.clone(), fp));
uf_name
}
pub fn get_func_tv(
name: &str,
args: &str,
rettv: &mut crate::ported::eval::typval_defs_h::typval_T,
) -> i32 {
match get_func_arguments(args) {
Some(argvars) => call_func(name, &argvars, rettv),
None => crate::ported::eval_h::FAIL,
}
}
pub fn get_func_arguments(args: &str) -> Option<Vec<crate::ported::eval::typval_defs_h::typval_T>> {
let parts = {
let b = args.as_bytes();
let mut out: Vec<&str> = Vec::new();
let (mut start, mut depth, mut i) = (0usize, 0i32, 0usize);
while i < b.len() {
match b[i] {
b'\'' => {
i += 1;
while i < b.len() && b[i] != b'\'' {
i += 1;
}
}
b'"' => {
i += 1;
while i < b.len() && b[i] != b'"' {
if b[i] == b'\\' && i + 1 < b.len() {
i += 1;
}
i += 1;
}
}
b'(' | b'[' | b'{' => depth += 1,
b')' | b']' | b'}' => depth -= 1,
b',' if depth == 0 => {
out.push(&args[start..i]);
start = i + 1;
}
_ => {}
}
i += 1;
}
out.push(&args[start..]);
out
};
let mut argvars = Vec::new();
for part in parts {
let part = part.trim();
if part.is_empty() {
continue;
}
let v = crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(part))?;
argvars.push(v);
}
Some(argvars)
}
pub fn get_function_args(params: &str) -> Option<(Vec<String>, Vec<String>, bool)> {
let parts = {
let b = params.as_bytes();
let mut out: Vec<&str> = Vec::new();
let (mut start, mut depth, mut i) = (0usize, 0i32, 0usize);
while i < b.len() {
match b[i] {
b'\'' => {
i += 1;
while i < b.len() && b[i] != b'\'' {
i += 1;
}
}
b'"' => {
i += 1;
while i < b.len() && b[i] != b'"' {
if b[i] == b'\\' && i + 1 < b.len() {
i += 1;
}
i += 1;
}
}
b'(' | b'[' | b'{' => depth += 1,
b')' | b']' | b'}' => depth -= 1,
b',' if depth == 0 => {
out.push(¶ms[start..i]);
start = i + 1;
}
_ => {}
}
i += 1;
}
out.push(¶ms[start..]);
out
};
let mut names: Vec<String> = Vec::new();
let mut defaults: Vec<String> = Vec::new();
let mut varargs = false;
for part in parts {
let part = part.trim();
if part.is_empty() {
continue;
}
if part == "..." {
varargs = true;
continue;
}
if let Some(eq) = part.find('=') {
let name = part[..eq].trim();
if one_function_arg(name, Some(&mut names), false) != name.len() {
return None;
}
defaults.push(part[eq + 1..].trim().to_string());
} else if one_function_arg(part, Some(&mut names), false) != part.len() {
return None;
}
}
Some((names, defaults, varargs))
}
pub fn one_function_arg(arg: &str, newargs: Option<&mut Vec<String>>, skip: bool) -> usize {
let b = arg.as_bytes();
let mut p = 0;
while p < b.len() && (b[p].is_ascii_alphanumeric() || b[p] == b'_') {
p += 1;
}
let name = &arg[..p];
if p == 0 || b[0].is_ascii_digit() || name == "firstline" || name == "lastline" {
if !skip {
semsg(&format!("E125: Illegal argument: {arg}"));
}
return 0;
}
if let Some(args) = newargs {
if args.iter().any(|a| a == name) {
semsg(&format!("E853: Duplicate argument name: {name}"));
return 0;
}
args.push(name.to_string());
}
p
}
pub fn argv_add_base(
basetv: Option<crate::ported::eval::typval_defs_h::typval_T>,
argvars: &[crate::ported::eval::typval_defs_h::typval_T],
) -> (Vec<crate::ported::eval::typval_defs_h::typval_T>, i32) {
match basetv {
Some(base) => {
let mut new_argvars = Vec::with_capacity(argvars.len() + 1);
new_argvars.push(base);
new_argvars.extend_from_slice(argvars);
(new_argvars, 1)
}
None => (argvars.to_vec(), 0),
}
}
pub fn call_func(
funcname: &str,
argvars: &[crate::ported::eval::typval_defs_h::typval_T],
rettv: &mut crate::ported::eval::typval_defs_h::typval_T,
) -> i32 {
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::v_string, VarLockStatus, VarType::VAR_FUNC,
};
use crate::ported::eval_h::{FAIL, OK};
let callee = typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(funcname.to_string()),
};
match crate::ported::eval::typval::CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(&callee, argvars))
{
Some(result) => {
*rettv = result;
OK
}
None => FAIL,
}
}
pub fn func_call(
name: &str,
args: &crate::ported::eval::typval_defs_h::typval_T,
partial: Option<&std::rc::Rc<crate::ported::eval::typval_defs_h::partial_T>>,
_selfdict: Option<&std::rc::Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::dict_T>>>,
rettv: &mut crate::ported::eval::typval_defs_h::typval_T,
) -> i32 {
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::*, VarLockStatus, VarType::*,
};
use crate::ported::eval_h::{FAIL, OK};
let argv: Vec<typval_T> = match &args.vval {
v_list(Some(l)) => l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect(),
_ => return FAIL,
};
let callee = match partial {
Some(p) => typval_T {
v_type: VAR_PARTIAL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_partial(Some(p.clone())),
},
None => typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(name.to_string()),
},
};
match crate::ported::eval::typval::CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(&callee, &argv))
{
Some(result) => {
*rettv = result;
OK
}
None => FAIL,
}
}
pub fn add_nr_var(
dp: &mut crate::ported::eval::typval_defs_h::dict_T,
name: &str,
nr: crate::ported::eval::typval_defs_h::varnumber_T,
) {
crate::ported::eval::typval::tv_dict_add_nr(dp, name, nr);
}
pub fn func_has_ended() -> bool {
false
}
pub fn func_has_abort() -> bool {
false
}
pub fn get_return_cmd(rettv: Option<&crate::ported::eval::typval_defs_h::typval_T>) -> String {
const IOSIZE: usize = 1024 + 1;
let s = rettv.map_or(String::new(), crate::ported::eval::encode::encode_tv2echo);
let mut buf = format!(":return {s}");
if buf.len() >= IOSIZE {
let mut cut = IOSIZE - 4;
while !buf.is_char_boundary(cut) {
cut -= 1;
}
buf.truncate(cut);
buf.push_str("...");
}
buf
}
pub fn get_funccal_local_var() -> Option<crate::ported::eval::typval_defs_h::typval_T> {
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::v_dict, VarLockStatus, VarType::VAR_DICT,
};
let nd = crate::ported::eval::typval::tv_dict_alloc();
nd.borrow_mut().dv_hashtab = get_funccal_local_dict()?.dv_hashtab;
Some(typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(nd)),
})
}
pub fn get_funccal_args_var() -> Option<crate::ported::eval::typval_defs_h::typval_T> {
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::v_dict, VarLockStatus, VarType::VAR_DICT,
};
let nd = crate::ported::eval::typval::tv_dict_alloc();
nd.borrow_mut().dv_hashtab = get_funccal_args_dict()?.dv_hashtab;
Some(typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(nd)),
})
}
pub fn get_funccal_local_dict() -> Option<crate::ported::eval::typval_defs_h::dict_T> {
crate::ported::eval::vars::funccal_stack
.with(|s| s.borrow().last().map(|f| f.fc_l_vars.clone()))
}
pub fn get_funccal_args_dict() -> Option<crate::ported::eval::typval_defs_h::dict_T> {
crate::ported::eval::vars::funccal_stack
.with(|s| s.borrow().last().map(|f| f.fc_l_avars.clone()))
}
pub fn get_funccal_local_ht(
) -> Option<indexmap::IndexMap<String, crate::ported::eval::typval_defs_h::typval_T>> {
get_funccal_local_dict().map(|d| d.dv_hashtab)
}
pub fn get_funccal_args_ht(
) -> Option<indexmap::IndexMap<String, crate::ported::eval::typval_defs_h::typval_T>> {
get_funccal_args_dict().map(|d| d.dv_hashtab)
}
pub fn func_name() -> String {
crate::ported::eval::vars::funccal_stack
.with(|s| s.borrow().last().map(|f| f.fc_name.clone()))
.unwrap_or_default()
}
pub fn func_breakpoint() -> i64 {
0
}
pub fn func_dbg_tick() -> i32 {
0
}
pub fn func_level() -> i32 {
crate::ported::eval::vars::funccal_stack.with(|s| s.borrow().len() as i32)
}
pub fn fc_referenced() -> bool {
false
}
pub fn callback_call_retnr(
callback: &crate::ported::eval::typval::Callback,
argvars: &[crate::ported::eval::typval_defs_h::typval_T],
) -> crate::ported::eval::typval_defs_h::varnumber_T {
let mut rettv = crate::ported::eval::typval_defs_h::typval_T::from(0);
if !crate::ported::eval::callback_call(callback, argvars, &mut rettv) {
return -2;
}
crate::ported::eval::typval::tv_get_number_chk(&rettv, None)
}
pub fn translated_function_exists(name: &str) -> bool {
if builtin_function(name, -1) {
crate::ported::eval::funcs_argc::BUILTIN_ARGC
.binary_search_by(|e| e.0.cmp(name))
.is_ok()
} else {
crate::ported::eval::typval::FUNC_EXISTS_HOOK
.with(|h| *h.borrow())
.is_some_and(|f| f(name))
}
}
pub fn function_exists(name: &str, no_deref: bool) -> bool {
let resolved = if no_deref {
name.to_string()
} else {
deref_func_name(name, true).name
};
translated_function_exists(&resolved)
}
pub struct DerefedFunc {
pub name: String,
pub partial: Option<std::rc::Rc<crate::ported::eval::typval_defs_h::partial_T>>,
pub found_var: bool,
}
pub fn deref_func_name(name: &str, _no_autoload: bool) -> DerefedFunc {
use crate::ported::eval::typval_defs_h::{typval_vval_union::*, VarType::*};
match crate::ported::eval::vars::eval_variable(name) {
None => DerefedFunc {
name: name.to_string(),
partial: None,
found_var: false,
},
Some(tv) => match (tv.v_type, tv.vval) {
(VAR_FUNC, v_string(s)) => DerefedFunc {
name: s,
partial: None,
found_var: true,
},
(VAR_PARTIAL, v_partial(Some(pt))) => DerefedFunc {
name: crate::ported::eval::partial_name(&pt).to_string(),
partial: Some(pt),
found_var: true,
},
_ => DerefedFunc {
name: name.to_string(),
partial: None,
found_var: true,
},
},
}
}
#[derive(Debug, Default, Clone)]
pub struct funccall_T {
pub fc_func: ufunc_T,
pub fc_returned: bool,
pub fc_l_vars: crate::ported::eval::typval_defs_h::dict_T,
pub fc_l_avars: crate::ported::eval::typval_defs_h::dict_T,
pub fc_rettv: Option<crate::ported::eval::typval_defs_h::typval_T>,
pub fc_caller: Option<std::rc::Rc<std::cell::RefCell<funccall_T>>>,
}
thread_local! {
static current_funccal: std::cell::RefCell<Option<std::rc::Rc<std::cell::RefCell<funccall_T>>>> =
const { std::cell::RefCell::new(None) };
}
pub fn create_funccal(
fp: &ufunc_T,
rettv: Option<crate::ported::eval::typval_defs_h::typval_T>,
) -> std::rc::Rc<std::cell::RefCell<funccall_T>> {
let fc = std::rc::Rc::new(std::cell::RefCell::new(funccall_T {
fc_caller: current_funccal.with(|c| c.borrow().clone()), fc_func: fp.clone(), fc_rettv: rettv, ..Default::default()
}));
current_funccal.with(|c| *c.borrow_mut() = Some(fc.clone())); func_ptr_ref(); fc
}
pub fn get_current_funccal() -> Option<std::rc::Rc<std::cell::RefCell<funccall_T>>> {
current_funccal.with(|c| c.borrow().clone())
}
pub fn get_funccal() -> Option<std::rc::Rc<std::cell::RefCell<funccall_T>>> {
let funccal = current_funccal.with(|c| c.borrow().clone()); funccal
}
pub fn get_current_funccal_dict(
_ht: &indexmap::IndexMap<String, crate::ported::eval::typval_defs_h::typval_T>,
) -> Option<crate::ported::eval::typval_defs_h::dict_T> {
current_funccal.with(|c| c.borrow().as_ref().map(|fc| fc.borrow().fc_l_vars.clone()))
}
pub fn do_return(
reanimate: bool,
_is_cmd: bool,
rettv: Option<crate::ported::eval::typval_defs_h::typval_T>,
) -> bool {
current_funccal.with(|c| {
if let Some(fc) = c.borrow().as_ref() {
let mut fc = fc.borrow_mut();
if reanimate {
fc.fc_returned = false; }
fc.fc_returned = true; if !reanimate {
if let Some(v) = rettv {
fc.fc_rettv = Some(v);
}
}
}
});
true
}
pub fn find_hi_in_scoped_ht(_name: &str) -> Option<crate::ported::eval::typval_defs_h::typval_T> {
None
}
const K_SPECIAL: u8 = 0x80;
const KS_EXTRA: u8 = 253;
const KE_SNR: u8 = 82;
pub mod tfn {
pub const TFN_INT: i32 = 1;
pub const TFN_QUIET: i32 = 2;
pub const TFN_NO_AUTOLOAD: i32 = 4;
pub const TFN_NO_DEREF: i32 = 8;
pub const TFN_READ_ONLY: i32 = 16;
}
#[derive(Debug, Default, Clone)]
pub struct funcdict_T {
pub fd_dict:
Option<std::rc::Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::dict_T>>>,
pub fd_newkey: Option<String>,
pub fd_di: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum cmdidx_T {
#[default]
CMD_call,
CMD_defer,
}
#[derive(Debug, Default, Clone)]
pub struct exarg_T {
pub arg: String,
pub cmd: String,
pub nextcmd: Option<String>,
pub cmdidx: cmdidx_T,
pub skip: bool,
pub forceit: bool,
pub addr_count: i32,
pub line1: i64,
pub line2: i64,
}
pub fn func_tbl_get() {}
pub fn trans_function_name(
pp: &mut &str,
skip: bool,
flags: i32,
mut fdp: Option<&mut funcdict_T>,
mut partial: Option<&mut Option<std::rc::Rc<crate::ported::eval::typval_defs_h::partial_T>>>,
) -> Option<String> {
use crate::ported::eval::typval_defs_h::{typval_vval_union::*, VarType::*};
use crate::ported::eval::{
check_luafunc_name, get_id_len, get_lval, is_luafunc, lval_T, partial_name, LlTv,
FNE_CHECK_START, GLV_READ_ONLY,
};
use crate::ported::message::{emsg, semsg};
use tfn::*;
let mut name: Option<String> = None;
let mut len: i32 = 0;
let mut lv = lval_T::default();
if let Some(fdp) = fdp.as_deref_mut() {
*fdp = funcdict_T::default();
}
let orig: &str = *pp;
{
let b = orig.as_bytes();
if b.first() == Some(&K_SPECIAL) && b.get(1) == Some(&KS_EXTRA) && b.get(2) == Some(&KE_SNR)
{
let after = &orig[3..];
len = get_id_len(after) + 3;
let out = orig[..(len as usize).min(orig.len())].to_string();
*pp = &orig[(len as usize).min(orig.len())..];
return Some(out); }
}
let mut lead = eval_fname_script(orig);
let mut start: &str = orig;
if lead > 2 {
start = &orig[lead as usize..]; }
let end = get_lval(
start,
None,
&mut lv,
false,
skip,
flags | GLV_READ_ONLY,
if lead > 2 { 0 } else { FNE_CHECK_START },
);
if end == Some(0) {
if !skip {
emsg("E129: Function name required"); }
return None; }
let ll_tv_present = !matches!(lv.ll_tv, LlTv::Null);
if end.is_none() || (ll_tv_present && (lead > 2 || lv.ll_range)) {
if end.is_some() {
semsg(&format!("E475: Invalid argument: {start}")); }
return None; }
let end = end.unwrap();
if ll_tv_present {
let tv = lv.ll_tv.get(); let ll_dict_none = lv.ll_dict.is_none();
if let Some(fdp) = fdp.as_deref_mut() {
fdp.fd_dict = lv.ll_dict.clone();
fdp.fd_newkey = lv.ll_newkey.take();
fdp.fd_di = lv.ll_di.clone();
}
match tv.map(|t| (t.v_type, t.vval)) {
Some((VAR_FUNC, v_string(s))) if !s.is_empty() => {
name = Some(s);
*pp = &start[end..];
}
Some((VAR_PARTIAL, v_partial(Some(pt)))) => {
let end_str = &start[end..];
if is_luafunc(&pt) && end_str.as_bytes().first() == Some(&b'.') {
let l = check_luafunc_name(&end_str[1..], true);
if l == 0 {
semsg("E15: Invalid expression: v:lua"); return None;
}
name = Some(end_str[1..1 + l as usize].to_string());
*pp = &start[end + 1 + l as usize..];
} else {
name = Some(partial_name(&pt).to_string()); *pp = &start[end..];
}
if let Some(partial) = partial.as_deref_mut() {
*partial = Some(pt); }
}
_ => {
let newkey_none = fdp
.as_deref()
.map(|f| f.fd_newkey.is_none())
.unwrap_or(true);
if !skip
&& (flags & TFN_QUIET) == 0
&& (fdp.is_none() || ll_dict_none || newkey_none)
{
emsg("E718: Funcref required"); } else {
*pp = &start[end..];
}
name = None;
}
}
return name; }
if lv.ll_name.is_none() {
*pp = &start[end..]; return None; }
if let Some(exp) = lv.ll_exp_name.clone() {
let d = deref_func_name(&exp, flags & TFN_NO_AUTOLOAD != 0); if d.name != exp {
name = Some(d.name);
if let Some(partial) = partial.as_deref_mut() {
*partial = d.partial;
}
}
} else if (flags & TFN_NO_DEREF) == 0 {
let src = &orig[..(end + if lead > 2 { lead as usize } else { 0 })];
let d = deref_func_name(src, flags & TFN_NO_AUTOLOAD != 0);
if d.name.as_str() != src {
name = Some(d.name);
if let Some(partial) = partial.as_deref_mut() {
*partial = d.partial;
}
}
}
if let Some(nm) = name {
*pp = &start[end..];
return Some(nm); }
let mut ll_name = lv.ll_name.clone().unwrap_or_default();
let mut ll_name_len = lv.ll_name_len;
len = 0;
if let Some(ref exp_s) = lv.ll_exp_name {
len = exp_s.len() as i32; if lead <= 2
&& lv.ll_name.as_deref() == Some(exp_s.as_str())
&& ll_name_len >= 2
&& ll_name.as_bytes().get(..2) == Some(b"s:".as_slice())
{
ll_name = ll_name[2..].to_string();
ll_name_len -= 2;
len -= 2;
lead = 2;
}
} else {
if lead == 2
|| (ll_name.as_bytes().first() == Some(&b'g')
&& ll_name.as_bytes().get(1) == Some(&b':'))
{
ll_name = ll_name[2..].to_string();
ll_name_len -= 2;
}
len = ll_name_len as i32;
}
let mut sid_buf = String::new();
if skip {
lead = 0; } else if lead > 0 {
lead = 3; if lv
.ll_exp_name
.as_deref()
.map(eval_fname_sid)
.unwrap_or(false)
|| eval_fname_sid(orig)
{
emsg("E81: Using <SID> not in a script context"); return None; }
let _ = &mut sid_buf;
} else if (flags & TFN_INT) == 0 && builtin_function(&ll_name, ll_name_len as i32) {
semsg(&format!(
"E128: Function name must start with a capital or \"s:\": {orig}"
));
return None; }
if !skip && (flags & TFN_QUIET) == 0 && (flags & TFN_NO_DEREF) == 0 {
if ll_name.as_bytes()[..ll_name_len]
.iter()
.rposition(|&c| c == b':')
.is_some()
{
semsg(&format!(
"E884: Function name cannot contain a colon: {orig}"
)); return None; }
}
let mut result = String::new();
if !skip && lead > 0 {
result.push_str("<SNR>");
if !sid_buf.is_empty() {
result.push_str(&sid_buf);
}
}
result.push_str(&ll_name[..(len as usize).min(ll_name.len())]); *pp = &start[end..]; Some(result)
}
pub fn save_function_name(
name: &mut &str,
skip: bool,
flags: i32,
fudi: Option<&mut funcdict_T>,
) -> Option<String> {
let orig: &str = *name; if orig.starts_with("<lambda>") {
let b = orig.as_bytes();
let mut p = 8;
while p < b.len() && b[p].is_ascii_digit() {
p += 1;
}
let saved = orig[..p].to_string(); if let Some(fudi) = fudi {
*fudi = funcdict_T::default(); }
*name = &orig[p..]; Some(saved)
} else {
let mut p: &str = orig;
let saved = trans_function_name(&mut p, skip, flags, fudi, None); *name = p;
saved
}
}
pub fn get_lambda_tv(
arg: &mut &str,
rettv: &mut crate::ported::eval::typval_defs_h::typval_T,
evalarg: Option<&crate::ported::eval::evalarg_T>,
) -> i32 {
use crate::ported::eval::skipwhite;
use crate::ported::eval::typval_defs_h::{
partial_T, typval_T, typval_vval_union::v_partial, VarLockStatus, VarType::VAR_PARTIAL,
};
use crate::ported::eval::{EVAL_EVALUATE, NOTDONE};
use crate::ported::eval_h::{FAIL, OK};
let evaluate = evalarg
.map(|e| e.eval_flags & EVAL_EVALUATE != 0)
.unwrap_or(false);
let orig: &str = *arg;
let after_brace = &orig[1..];
let arrow = {
let b = after_brace.as_bytes();
let (mut depth, mut i, mut found) = (0i32, 0usize, None);
while i < b.len() {
match b[i] {
b'\'' => {
i += 1;
while i < b.len() && b[i] != b'\'' {
i += 1;
}
}
b'"' => {
i += 1;
while i < b.len() && b[i] != b'"' {
if b[i] == b'\\' && i + 1 < b.len() {
i += 1;
}
i += 1;
}
}
b'(' | b'[' | b'{' => depth += 1,
b')' | b']' | b'}' => depth -= 1,
b'-' if depth == 0 && b.get(i + 1) == Some(&b'>') => {
found = Some(i);
break;
}
_ => {}
}
i += 1;
}
found
};
let arrow = match arrow {
Some(a) => a,
None => return NOTDONE,
};
let params = &after_brace[..arrow];
let (names, _defaults, _varargs) = match get_function_args(params) {
Some(v) => v,
None => return FAIL, };
let expr_tail = &after_brace[arrow + 2..];
let ws = expr_tail.len() - skipwhite(expr_tail).len();
let expr_region = skipwhite(expr_tail);
let close = {
let b = expr_region.as_bytes();
let (mut depth, mut i, mut found) = (0i32, 0usize, None);
while i < b.len() {
match b[i] {
b'\'' => {
i += 1;
while i < b.len() && b[i] != b'\'' {
i += 1;
}
}
b'"' => {
i += 1;
while i < b.len() && b[i] != b'"' {
if b[i] == b'\\' && i + 1 < b.len() {
i += 1;
}
i += 1;
}
}
b'(' | b'[' | b'{' => depth += 1,
b')' | b']' => depth -= 1,
b'}' if depth == 0 => {
found = Some(i);
break;
}
b'}' => depth -= 1,
_ => {}
}
i += 1;
}
found
};
let close = match close {
Some(c) => c,
None => {
crate::ported::message::semsg(&format!("E451: Expected }}: {expr_region}"));
return FAIL;
}
};
let close_abs = 1 + arrow + 2 + ws + close;
*arg = &orig[close_abs + 1..];
if evaluate {
let name = get_lambda_name();
let mut fp = alloc_ufunc(&name);
fp.uf_args = names;
fp.uf_varargs = true; let _ = fp;
let pt = partial_T {
pt_refcount: 1, pt_name: name,
pt_argv: Vec::new(),
pt_dict: None,
};
*rettv = typval_T {
v_type: VAR_PARTIAL, v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_partial(Some(std::rc::Rc::new(pt))), };
}
OK }
pub fn ex_return(eap: &mut exarg_T) {
use crate::ported::eval::typval::tv_clear;
use crate::ported::eval::{
eval0, evalarg_T, skipwhite, typval_defs_h::typval_T, EVAL_EVALUATE,
};
use crate::ported::eval_h::FAIL;
let arg = eap.arg.clone(); let mut returning = false;
if get_current_funccal().is_none() {
crate::ported::message::emsg("E133: :return not inside a function"); return;
}
let mut evalarg = evalarg_T {
eval_flags: if eap.skip { 0 } else { EVAL_EVALUATE },
};
eap.nextcmd = None; let first = arg.as_bytes().first().copied().unwrap_or(0);
let mut rettv = typval_T::default();
if first != 0
&& first != b'|'
&& first != b'\n'
&& eval0(&arg, &mut rettv, Some(&mut evalarg)) != FAIL
{
if !eap.skip {
returning = do_return(false, true, Some(rettv.clone())); } else {
tv_clear(&mut rettv); }
} else if !eap.skip {
returning = do_return(false, true, None); }
if returning {
eap.nextcmd = None; } else if eap.nextcmd.is_none() {
let p = skipwhite(&arg);
let c = p.as_bytes().first().copied().unwrap_or(0);
eap.nextcmd = if c == b'|' || c == b'\n' {
Some(p[1..].to_string())
} else {
None
};
}
}
pub fn ex_call_inner(
eap: &exarg_T,
name: &str,
args_text: &str,
rettv: &mut crate::ported::eval::typval_defs_h::typval_T,
) -> i32 {
use crate::ported::eval::handle_subscript;
use crate::ported::eval::typval::tv_clear;
use crate::ported::eval_h::FAIL;
let mut failed = false; let mut lnum = eap.line1; while lnum <= eap.line2 {
if get_func_tv(name, args_text, rettv) == FAIL {
failed = true; break;
}
if handle_subscript(rettv, &[], true) == FAIL {
failed = true; break;
}
tv_clear(rettv); lnum += 1;
}
failed as i32 }
pub fn ex_call(eap: &mut exarg_T) {
use crate::ported::eval::typval::tv_clear;
use crate::ported::eval::{
ends_excmd, eval0, evalarg_T, skipwhite, typval_defs_h::typval_T, EVAL_EVALUATE,
};
use crate::ported::message::semsg;
let orig_arg = eap.arg.clone(); let mut fudi = funcdict_T::default();
let mut partial: Option<std::rc::Rc<crate::ported::eval::typval_defs_h::partial_T>> = None;
let mut evalarg = evalarg_T {
eval_flags: if eap.skip { 0 } else { EVAL_EVALUATE },
};
if eap.skip {
let mut rettv = typval_T::default();
if eval0(&orig_arg, &mut rettv, Some(&mut evalarg)) != crate::ported::eval_h::FAIL {
tv_clear(&mut rettv); }
return; }
let mut arg: &str = &orig_arg;
let tofree = trans_function_name(
&mut arg,
false,
tfn::TFN_INT,
Some(&mut fudi),
Some(&mut partial),
);
if let Some(nk) = fudi.fd_newkey.take() {
semsg(&format!("E716: Key not present in Dictionary: {nk}")); }
let tofree = match tofree {
Some(t) => t, None => return,
};
let d = deref_func_name(&tofree, false);
let name = d.name;
if partial.is_none() {
partial = d.partial; }
let startarg = skipwhite(arg);
if startarg.as_bytes().first() != Some(&b'(') {
semsg(&format!("E107: Missing parentheses: {orig_arg}"));
return; }
let (args_text, remaining) = {
let b = startarg.as_bytes();
let (mut depth, mut i, mut close) = (0i32, 0usize, None);
while i < b.len() {
match b[i] {
b'\'' => {
i += 1;
while i < b.len() && b[i] != b'\'' {
i += 1;
}
}
b'"' => {
i += 1;
while i < b.len() && b[i] != b'"' {
if b[i] == b'\\' && i + 1 < b.len() {
i += 1;
}
i += 1;
}
}
b'(' | b'[' | b'{' => depth += 1,
b')' | b']' | b'}' => {
depth -= 1;
if depth == 0 {
close = Some(i);
break;
}
}
_ => {}
}
i += 1;
}
match close {
Some(c) => (&startarg[1..c], &startarg[c + 1..]),
None => (&startarg[1..], ""),
}
};
let failed;
let mut rettv = typval_T::default();
if eap.cmdidx == cmdidx_T::CMD_defer {
failed = ex_defer_inner() == crate::ported::eval_h::FAIL;
} else {
failed = ex_call_inner(eap, &name, args_text, &mut rettv) != 0;
}
if !failed {
let c = remaining.as_bytes().first().copied().unwrap_or(0);
if !ends_excmd(c) {
semsg(&format!("E488: Trailing characters: {remaining}"));
} else {
let p = skipwhite(remaining);
let c = p.as_bytes().first().copied().unwrap_or(0);
eap.nextcmd = if c == b'|' || c == b'\n' {
Some(p[1..].to_string())
} else {
None
};
}
}
}
pub fn ex_delfunction(eap: &mut exarg_T) {
use crate::ported::eval::{ends_excmd, skipwhite};
use crate::ported::message::{emsg, semsg};
let orig = eap.arg.clone(); let mut p: &str = &orig;
let mut fudi = funcdict_T::default();
let name = trans_function_name(&mut p, eap.skip, 0, Some(&mut fudi), None); fudi.fd_newkey = None; let name = match name {
None => {
if fudi.fd_dict.is_some() && !eap.skip {
emsg("E718: Funcref required"); }
return; }
Some(n) => n,
};
let sw = skipwhite(p);
if !ends_excmd(sw.as_bytes().first().copied().unwrap_or(0)) {
semsg(&format!("E488: Trailing characters: {p}")); return;
}
let c = sw.as_bytes().first().copied().unwrap_or(0);
eap.nextcmd = if c == b'|' || c == b'\n' {
Some(sw[1..].to_string())
} else {
None
};
if name
.as_bytes()
.first()
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
&& fudi.fd_dict.is_none()
{
if !eap.skip {
semsg(&format!("E475: Invalid argument: {orig}")); }
return;
}
let fp = if !eap.skip { find_func(&name) } else { None };
if !eap.skip {
match fp {
None => {
if !eap.forceit {
semsg(&format!("E130: Unknown function: {orig}")); }
}
Some(fp) => {
func_remove(&fp); }
}
}
}
pub fn get_function_body(
_eap: &exarg_T,
newlines: &mut Vec<String>,
line_arg_in: Option<&str>,
) -> i32 {
use crate::ported::eval_h::FAIL;
let _ = newlines;
let theline: Option<&str> = match line_arg_in {
Some(la) => {
match la.find('\n') {
Some(i) => Some(&la[..i]),
None => Some(la),
}
}
None => {
None
}
};
if theline.is_none() {
crate::ported::message::emsg("E126: Missing :endfunction"); return FAIL; }
FAIL
}
pub fn ex_function(eap: &mut exarg_T) {
use crate::ported::eval::{ends_excmd, skipwhite};
use crate::ported::eval_h::FAIL;
use crate::ported::message::semsg;
let orig = eap.arg.clone();
let mut fudi = funcdict_T::default();
if ends_excmd(orig.as_bytes().first().copied().unwrap_or(0)) {
if !eap.skip {
list_functions(); }
let c = orig.as_bytes().first().copied().unwrap_or(0);
eap.nextcmd = if c == b'|' || c == b'\n' {
Some(orig[1..].to_string())
} else {
None
};
return;
}
if orig.as_bytes().first() == Some(&b'/') {
let _ = list_functions_matching_pat(); eap.nextcmd = None;
return; }
let mut p: &str = &orig;
let name = save_function_name(&mut p, eap.skip, tfn::TFN_NO_AUTOLOAD, Some(&mut fudi)); let paren = p.contains('('); if name.is_none() && (fudi.fd_dict.is_none() || !paren) && !eap.skip {
if let Some(nk) = fudi.fd_newkey.take() {
semsg(&format!("E716: Key not present in Dictionary: {nk}")); }
return; }
if !paren {
let _ = list_one_function(); return; }
p = skipwhite(p);
if p.as_bytes().first() != Some(&b'(') {
if !eap.skip {
semsg(&format!("E124: Missing '(': {orig}")); return; }
}
p = if p.is_empty() { p } else { skipwhite(&p[1..]) };
let params = {
let b = p.as_bytes();
let (mut depth, mut i, mut close) = (0i32, 0usize, None);
while i < b.len() {
match b[i] {
b')' if depth == 0 => {
close = Some(i);
break;
}
b'(' | b'[' | b'{' => depth += 1,
b')' | b']' | b'}' => depth -= 1,
_ => {}
}
i += 1;
}
match close {
Some(c) => &p[..c],
None => p,
}
};
if get_function_args(params).is_none() {
return; }
let mut newlines: Vec<String> = Vec::new();
if get_function_body(eap, &mut newlines, None) == FAIL {
return; }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trans_function_name_plain() {
let mut p: &str = "MyFunc()";
let n = trans_function_name(&mut p, false, tfn::TFN_INT, None, None);
assert_eq!(n.as_deref(), Some("MyFunc"));
assert_eq!(p, "()");
let mut q: &str = "foo()";
assert!(trans_function_name(&mut q, false, 0, None, None).is_none());
}
#[test]
fn save_function_name_lambda_and_name() {
let mut n: &str = "<lambda>42()";
assert_eq!(
save_function_name(&mut n, false, 0, None).as_deref(),
Some("<lambda>42")
);
assert_eq!(n, "()");
let mut m: &str = "Helper(x)";
assert_eq!(
save_function_name(&mut m, false, tfn::TFN_INT, None).as_deref(),
Some("Helper")
);
assert_eq!(m, "(x)");
}
#[test]
fn get_lambda_tv_parses_and_declines() {
use crate::ported::eval::evalarg_T;
use crate::ported::eval::typval_defs_h::{typval_T, typval_vval_union::v_partial};
use crate::ported::eval::{EVAL_EVALUATE, NOTDONE};
use crate::ported::eval_h::OK;
let ea = evalarg_T {
eval_flags: EVAL_EVALUATE,
};
let mut arg: &str = "{a, b -> a + b} rest";
let mut rv = typval_T::default();
assert_eq!(get_lambda_tv(&mut arg, &mut rv, Some(&ea)), OK);
assert!(matches!(rv.vval, v_partial(Some(_))));
assert_eq!(arg, " rest");
let mut arg2: &str = "{'a': 1}";
let mut rv2 = typval_T::default();
assert_eq!(get_lambda_tv(&mut arg2, &mut rv2, Some(&ea)), NOTDONE);
}
#[test]
fn ex_return_carries_value() {
use crate::ported::eval::typval_defs_h::{typval_T, typval_vval_union::v_number};
current_funccal.with(|c| *c.borrow_mut() = None);
let mut eap = exarg_T {
arg: "1".into(),
..Default::default()
};
ex_return(&mut eap);
let fp = ufunc_T {
uf_name: "F".into(),
..Default::default()
};
let fc = create_funccal(&fp, None);
let mut eap2 = exarg_T {
arg: "42".into(),
..Default::default()
};
ex_return(&mut eap2);
assert!(fc.borrow().fc_returned);
assert!(matches!(
fc.borrow().fc_rettv.as_ref().unwrap().vval,
v_number(42)
));
let _ = typval_T::default();
current_funccal.with(|c| *c.borrow_mut() = None);
}
#[test]
fn ex_call_inner_dispatches_via_hooks() {
use crate::ported::eval::typval::{CALL_FUNC_HOOK, EVAL_STRING_HOOK};
use crate::ported::eval::typval_defs_h::{typval_T, typval_vval_union::v_number};
fn eval_hook(e: &str) -> Option<typval_T> {
e.trim().parse::<i64>().ok().map(typval_T::from)
}
fn call_hook(_c: &typval_T, args: &[typval_T]) -> Option<typval_T> {
Some(typval_T::from(
args.iter()
.map(|a| match a.vval {
v_number(n) => n,
_ => 0,
})
.sum::<i64>(),
))
}
let (se, sc) = (
EVAL_STRING_HOOK.with(|h| *h.borrow()),
CALL_FUNC_HOOK.with(|h| *h.borrow()),
);
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(eval_hook));
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(call_hook));
let eap = exarg_T {
line1: 1,
line2: 1,
..Default::default()
};
let mut rv = typval_T::default();
assert_eq!(ex_call_inner(&eap, "Sum", "2, 3, 4", &mut rv), 0);
let mut rv2 = typval_T::default();
assert_eq!(ex_call_inner(&eap, "Sum", "1, bad", &mut rv2), 1);
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = se);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = sc);
}
#[test]
fn ex_call_resolves_and_runs() {
use crate::ported::eval::typval::{CALL_FUNC_HOOK, EVAL_STRING_HOOK};
use crate::ported::eval::typval_defs_h::typval_T;
fn eval_hook(e: &str) -> Option<typval_T> {
e.trim().parse::<i64>().ok().map(typval_T::from)
}
fn call_hook(_c: &typval_T, args: &[typval_T]) -> Option<typval_T> {
Some(typval_T::from(args.len() as i64))
}
let (se, sc) = (
EVAL_STRING_HOOK.with(|h| *h.borrow()),
CALL_FUNC_HOOK.with(|h| *h.borrow()),
);
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(eval_hook));
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(call_hook));
let mut eap = exarg_T {
arg: "Sum(2, 3, 4)".into(),
line1: 1,
line2: 1,
..Default::default()
};
ex_call(&mut eap);
let mut eap2 = exarg_T {
arg: "Sum".into(),
..Default::default()
};
ex_call(&mut eap2);
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = se);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = sc);
}
#[test]
fn ex_delfunction_removes_via_hook() {
fn rm(name: &str) -> bool {
name == "MyFunc"
}
fn find(name: &str) -> Option<ufunc_T> {
(name == "MyFunc").then(|| ufunc_T {
uf_name: "MyFunc".into(),
..Default::default()
})
}
let savedr = REMOVE_FUNC_HOOK.with(|h| *h.borrow());
let savedf = FIND_FUNC_HOOK.with(|h| *h.borrow());
REMOVE_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(rm));
FIND_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(find));
let mut eap = exarg_T {
arg: "MyFunc".into(),
..Default::default()
};
ex_delfunction(&mut eap);
let mut eap2 = exarg_T {
arg: "Nope".into(),
forceit: true,
..Default::default()
};
ex_delfunction(&mut eap2);
REMOVE_FUNC_HOOK.with(|h| *h.borrow_mut() = savedr);
FIND_FUNC_HOOK.with(|h| *h.borrow_mut() = savedf);
}
#[test]
fn ex_function_body_needs_getline() {
assert_eq!(
get_function_body(&exarg_T::default(), &mut Vec::new(), None),
crate::ported::eval_h::FAIL
);
let mut eap = exarg_T {
arg: "Foo()".into(),
..Default::default()
};
ex_function(&mut eap);
}
#[test]
fn name_classification() {
assert!(builtin_function("strlen", -1));
assert!(!builtin_function("g:Foo", -1)); assert!(!builtin_function("Foo", -1)); assert!(!builtin_function("foo#bar", -1));
assert_eq!(eval_fname_script("s:Helper"), 2);
assert_eq!(eval_fname_script("<SID>Helper"), 5);
assert_eq!(eval_fname_script("<SNR>9_x"), 5);
assert_eq!(eval_fname_script("Helper"), 0);
assert!(eval_fname_sid("s:Helper"));
assert!(func_name_refcount("123")); assert!(func_name_refcount("<lambda>7"));
assert!(!func_name_refcount("MyFunc"));
assert_eq!(get_scriptlocal_funcname(Some("Plain")), None);
assert!(!function_list_modified(0));
}
#[test]
fn lambda_names_increment() {
let a = get_lambda_name();
let b = get_lambda_name();
assert!(a.starts_with("<lambda>"));
let na: i32 = a.trim_start_matches("<lambda>").parse().unwrap();
let nb: i32 = b.trim_start_matches("<lambda>").parse().unwrap();
assert_eq!(nb, na + 1);
}
#[test]
fn call_user_func_check_arity_and_dispatch() {
use crate::ported::eval::typval::CALL_FUNC_HOOK;
use crate::ported::eval::typval_defs_h::{typval_T, typval_vval_union::v_number};
use fcerr::*;
let fp = ufunc_T {
uf_name: "F".into(),
uf_args: vec!["a".into(), "b".into()],
..Default::default()
};
fn hook(_c: &typval_T, args: &[typval_T]) -> Option<typval_T> {
Some(typval_T::from(args.len() as i64))
}
let saved = CALL_FUNC_HOOK.with(|h| *h.borrow());
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let mut rv = typval_T::from(0);
assert_eq!(
call_user_func_check(&fp, &[typval_T::from(1)], &mut rv),
FCERR_TOOFEW
);
assert_eq!(
call_user_func_check(&fp, &[typval_T::from(1), typval_T::from(2)], &mut rv),
FCERR_NONE
);
assert!(matches!(rv.vval, v_number(2)));
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn find_func_via_hook() {
use super::{find_func, FIND_FUNC_HOOK};
fn hook(name: &str) -> Option<ufunc_T> {
(name == "MyFunc").then(|| ufunc_T {
uf_name: "MyFunc".into(),
uf_args: vec!["a".into(), "b".into()],
uf_def_args: vec!["b".into()],
uf_varargs: false,
..Default::default()
})
}
let saved = FIND_FUNC_HOOK.with(|h| *h.borrow());
FIND_FUNC_HOOK.with(|h| *h.borrow_mut() = None);
assert!(find_func("MyFunc").is_none()); FIND_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let f = find_func("MyFunc").unwrap();
assert_eq!(f.uf_args.len(), 2);
assert_eq!(get_func_arity("MyFunc", Some(&f)), Some((1, 1, false)));
assert!(find_func("nope").is_none());
FIND_FUNC_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn user_func_argcount_and_arity() {
use fcerr::*;
let f = ufunc_T {
uf_name: "F".into(),
uf_args: vec!["a".into(), "b".into()],
uf_def_args: vec!["b".into()],
uf_varargs: true,
..Default::default()
};
assert_eq!(check_user_func_argcount(&f, 0), FCERR_TOOFEW);
assert_eq!(check_user_func_argcount(&f, 1), FCERR_UNKNOWN); assert_eq!(check_user_func_argcount(&f, 9), FCERR_UNKNOWN); assert_eq!(get_func_arity("F", Some(&f)), Some((1, 1, true)));
let g = ufunc_T {
uf_name: "G".into(),
uf_args: vec!["x".into()],
..Default::default()
};
assert_eq!(check_user_func_argcount(&g, 2), FCERR_TOOMANY);
assert_eq!(get_func_arity("add", None), Some((2, 0, false)));
assert_eq!(get_func_arity("argc", None), Some((0, 1, false)));
assert_eq!(get_func_arity("no_such_func_xyz", None), None);
}
#[test]
fn get_return_cmd_formats() {
use crate::ported::eval::typval_defs_h::typval_T;
assert_eq!(get_return_cmd(Some(&typval_T::from(42))), ":return 42");
assert_eq!(
get_return_cmd(Some(&typval_T::from("hi".to_string()))),
":return hi"
);
assert_eq!(get_return_cmd(None), ":return ");
}
#[test]
fn add_nr_var_inserts() {
use crate::ported::eval::typval::tv_dict_find;
use crate::ported::eval::typval_defs_h::{dict_T, typval_vval_union::v_number};
let mut d = dict_T::default();
add_nr_var(&mut d, "lnum", 7);
add_nr_var(&mut d, "winid", 3);
assert!(matches!(
tv_dict_find(&d, "lnum").map(|tv| &tv.vval),
Some(v_number(7))
));
assert!(matches!(
tv_dict_find(&d, "winid").map(|tv| &tv.vval),
Some(v_number(3))
));
}
#[test]
fn func_call_via_hook() {
use crate::ported::eval::typval::tv_list_append_number;
use crate::ported::eval::typval::CALL_FUNC_HOOK;
use crate::ported::eval::typval_defs_h::{
list_T, typval_T, typval_vval_union::*, VarLockStatus, VarType::*,
};
use crate::ported::eval_h::{FAIL, OK};
fn hook(_c: &typval_T, args: &[typval_T]) -> Option<typval_T> {
Some(typval_T::from(args.len() as i64))
}
let saved = CALL_FUNC_HOOK.with(|h| *h.borrow());
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let mut l = list_T::default();
tv_list_append_number(&mut l, 1);
tv_list_append_number(&mut l, 2);
let args = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(std::rc::Rc::new(std::cell::RefCell::new(l)))),
};
let mut rv = typval_T::from(-1);
assert_eq!(func_call("F", &args, None, None, &mut rv), OK);
assert!(matches!(rv.vval, v_number(2)));
let mut rv2 = typval_T::from(-1);
assert_eq!(
func_call("F", &typval_T::from(0), None, None, &mut rv2),
FAIL
);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn function_exists_builtins() {
assert!(translated_function_exists("add"));
assert!(translated_function_exists("strlen"));
assert!(function_exists("add", true));
assert!(!translated_function_exists("notabuiltin_xyz"));
assert!(!translated_function_exists("MyFunc"));
assert!(!function_exists("MyFunc", true));
}
#[test]
fn get_func_tv_calls() {
use crate::ported::eval::typval::{CALL_FUNC_HOOK, EVAL_STRING_HOOK};
use crate::ported::eval::typval_defs_h::{typval_T, typval_vval_union::v_number};
use crate::ported::eval_h::{FAIL, OK};
fn eval_hook(e: &str) -> Option<typval_T> {
e.trim().parse::<i64>().ok().map(typval_T::from)
}
fn call_hook(_c: &typval_T, args: &[typval_T]) -> Option<typval_T> {
Some(typval_T::from(
args.iter()
.map(|a| match a.vval {
v_number(n) => n,
_ => 0,
})
.sum::<i64>(),
))
}
let (se, sc) = (
EVAL_STRING_HOOK.with(|h| *h.borrow()),
CALL_FUNC_HOOK.with(|h| *h.borrow()),
);
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(eval_hook));
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(call_hook));
let mut rv = typval_T::from(-1);
assert_eq!(get_func_tv("F", "2, 3, 4", &mut rv), OK);
assert!(matches!(rv.vval, v_number(9)));
assert_eq!(get_func_tv("F", "1, bad", &mut rv), FAIL);
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = se);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = sc);
}
#[test]
fn get_func_arguments_eval() {
use crate::ported::eval::typval::EVAL_STRING_HOOK;
use crate::ported::eval::typval_defs_h::{typval_T, typval_vval_union::v_number};
fn hook(e: &str) -> Option<typval_T> {
e.trim().parse::<i64>().ok().map(typval_T::from)
}
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let v = get_func_arguments("1, 2, 3").unwrap();
assert_eq!(v.len(), 3);
assert!(matches!(v[2].vval, v_number(3)));
assert_eq!(get_func_arguments("").unwrap().len(), 0);
assert!(get_func_arguments("1, bad").is_none());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn get_function_args_parsing() {
let (names, defaults, va) = get_function_args("a, b = 1 + 2, ...").unwrap();
assert_eq!(names, vec!["a".to_string(), "b".to_string()]);
assert_eq!(defaults, vec!["1 + 2".to_string()]);
assert!(va);
assert_eq!(get_function_args(""), Some((vec![], vec![], false)));
let (n2, d2, _) = get_function_args("x = f(a, b)").unwrap();
assert_eq!(n2, vec!["x".to_string()]);
assert_eq!(d2, vec!["f(a, b)".to_string()]);
assert_eq!(get_function_args("firstline"), None);
}
#[test]
fn one_function_arg_parsing() {
let mut args: Vec<String> = Vec::new();
assert_eq!(one_function_arg("foo, bar)", Some(&mut args), false), 3);
assert_eq!(args, vec!["foo".to_string()]);
assert_eq!(one_function_arg("foo)", Some(&mut args), true), 0);
assert_eq!(one_function_arg("firstline)", None, true), 0);
assert_eq!(one_function_arg("9bad", None, true), 0);
assert_eq!(one_function_arg(")", None, true), 0);
assert_eq!(one_function_arg("baz)", None, true), 3);
}
#[test]
fn argv_add_base_prepends() {
use crate::ported::eval::typval_defs_h::typval_T;
let a = typval_T::from(1);
let b = typval_T::from(2);
let base = typval_T::from(99);
let (with, off) = argv_add_base(Some(base), &[a.clone(), b.clone()]);
assert_eq!(off, 1);
assert_eq!(with.len(), 3);
let (without, off0) = argv_add_base(None, &[a.clone(), b.clone()]);
assert_eq!((without.len(), off0), (2, 0));
}
#[test]
fn funccal_scope_accessors() {
use crate::ported::eval::typval::tv_dict_add_nr;
use crate::ported::eval::vars::{funccal_stack, FuncScope};
funccal_stack.with(|s| s.borrow_mut().clear());
assert!(get_funccal_local_dict().is_none());
assert!(get_funccal_args_ht().is_none());
let mut frame = FuncScope::default();
tv_dict_add_nr(&mut frame.fc_l_vars, "x", 1);
tv_dict_add_nr(&mut frame.fc_l_avars, "1", 9);
funccal_stack.with(|s| s.borrow_mut().push(frame));
assert_eq!(get_funccal_local_ht().unwrap().len(), 1);
assert!(get_funccal_local_dict()
.unwrap()
.dv_hashtab
.contains_key("x"));
assert!(get_funccal_args_dict()
.unwrap()
.dv_hashtab
.contains_key("1"));
funccal_stack.with(|s| s.borrow_mut().clear());
}
#[test]
fn func_name_reads_active_scope() {
use crate::ported::eval::vars::{funccal_stack, FuncScope};
funccal_stack.with(|s| s.borrow_mut().clear());
assert_eq!(func_name(), "");
funccal_stack.with(|s| {
s.borrow_mut().push(FuncScope {
fc_name: "MyFunc".into(),
..Default::default()
})
});
assert_eq!(func_name(), "MyFunc");
funccal_stack.with(|s| s.borrow_mut().clear());
}
#[test]
fn save_restore_and_remove_funccal() {
use crate::ported::eval::vars::{funccal_stack, FuncScope};
funccal_stack.with(|s| {
s.borrow_mut().clear();
s.borrow_mut().push(FuncScope::default());
s.borrow_mut().push(FuncScope::default());
});
save_funccal();
assert_eq!(funccal_stack.with(|s| s.borrow().len()), 0);
restore_funccal();
assert_eq!(funccal_stack.with(|s| s.borrow().len()), 2);
remove_funccal();
assert_eq!(funccal_stack.with(|s| s.borrow().len()), 1);
funccal_stack.with(|s| s.borrow_mut().clear());
}
#[test]
fn func_is_global_and_cat_name() {
let g = ufunc_T {
uf_name: "MyFunc".into(),
..Default::default()
};
assert!(func_is_global(&g));
assert_eq!(cat_func_name(&g), "MyFunc");
let sl = ufunc_T {
uf_name: "<SNR>9_Helper".into(),
..Default::default()
};
assert!(!func_is_global(&sl));
assert_eq!(cat_func_name(&sl), "<SNR>9_Helper");
}
#[test]
fn printable_name_prefers_exp() {
let f = ufunc_T {
uf_name: "raw".into(),
uf_name_exp: Some("<SNR>9_raw".into()),
..Default::default()
};
assert_eq!(printable_func_name(&f), "<SNR>9_raw");
let g = ufunc_T {
uf_name: "plain".into(),
..Default::default()
};
assert_eq!(printable_func_name(&g), "plain");
}
#[test]
fn create_funccal_links_chain() {
use crate::ported::eval::typval_defs_h::typval_T;
current_funccal.with(|c| *c.borrow_mut() = None);
assert!(get_current_funccal().is_none());
let outer = ufunc_T {
uf_name: "Outer".into(),
..Default::default()
};
let inner = ufunc_T {
uf_name: "Inner".into(),
..Default::default()
};
let fc_outer = create_funccal(&outer, Some(typval_T::from(0)));
assert_eq!(fc_outer.borrow().fc_func.uf_name, "Outer");
assert!(fc_outer.borrow().fc_caller.is_none());
let fc_inner = create_funccal(&inner, Some(typval_T::from(0)));
assert_eq!(
get_current_funccal().unwrap().borrow().fc_func.uf_name,
"Inner"
);
assert_eq!(get_funccal().unwrap().borrow().fc_func.uf_name, "Inner");
assert_eq!(
fc_inner
.borrow()
.fc_caller
.as_ref()
.unwrap()
.borrow()
.fc_func
.uf_name,
"Outer"
);
current_funccal.with(|c| *c.borrow_mut() = None);
}
#[test]
fn do_return_sets_returned_and_rettv() {
use crate::ported::eval::typval_defs_h::{typval_T, typval_vval_union::v_number};
current_funccal.with(|c| *c.borrow_mut() = None);
assert!(do_return(false, true, Some(typval_T::from(1))));
let fp = ufunc_T {
uf_name: "F".into(),
..Default::default()
};
let fc = create_funccal(&fp, Some(typval_T::from(0)));
assert!(!fc.borrow().fc_returned);
assert!(do_return(false, true, Some(typval_T::from(42))));
assert!(fc.borrow().fc_returned);
assert!(matches!(
fc.borrow().fc_rettv.as_ref().unwrap().vval,
v_number(42)
));
assert!(do_return(true, false, Some(typval_T::from(7))));
assert!(matches!(
fc.borrow().fc_rettv.as_ref().unwrap().vval,
v_number(42)
));
current_funccal.with(|c| *c.borrow_mut() = None);
}
#[test]
fn get_current_funccal_dict_reads_l_vars() {
use crate::ported::eval::typval::tv_dict_add_nr;
use indexmap::IndexMap;
current_funccal.with(|c| *c.borrow_mut() = None);
let empty: IndexMap<String, crate::ported::eval::typval_defs_h::typval_T> = IndexMap::new();
assert!(get_current_funccal_dict(&empty).is_none());
let fp = ufunc_T {
uf_name: "F".into(),
..Default::default()
};
let fc = create_funccal(&fp, None);
tv_dict_add_nr(&mut fc.borrow_mut().fc_l_vars, "x", 5);
let d = get_current_funccal_dict(&empty).unwrap();
assert!(d.dv_hashtab.contains_key("x"));
current_funccal.with(|c| *c.borrow_mut() = None);
}
#[test]
fn find_hi_in_scoped_ht_is_none() {
assert!(find_hi_in_scoped_ht("anything").is_none());
}
#[test]
fn register_luafunc_registers_a_lambda() {
let name = register_luafunc(7);
assert!(name.starts_with("<lambda>"));
let fp = func_hashtab
.with(|h| h.borrow().get(&name).cloned())
.expect("registered lambda present in func_hashtab");
assert_eq!(fp.uf_name, name);
assert_eq!(fp.uf_refcount, 1);
assert!(fp.uf_varargs);
assert_eq!(fp.uf_flags, fc::FC_LUAREF);
assert_eq!(fp.uf_calls, 0);
assert_eq!(fp.uf_luaref, 7);
assert_eq!(fp.uf_script_ctx.sc_sid, 0);
let name2 = register_luafunc(9);
assert_ne!(name, name2);
}
}