#![allow(non_snake_case, non_upper_case_globals, non_camel_case_types)]
use std::cell::RefCell;
use std::rc::Rc;
use crate::ported::charset::{vim_str2nr, STR2NR_ALL};
use crate::ported::eval::encode::{encode_tv2echo, encode_tv2string};
use crate::ported::eval::executor::eexe_mod_op;
use crate::ported::eval::typval_defs_h::{
blob_T, dict_T, float_T, list_T, listitem_T, typval_T, typval_vval_union::*, varnumber_T,
BoolVarValue, BoolVarValue::*, SpecialVarValue::*, VarLockStatus, VarType::*,
};
use crate::ported::eval_h::{FAIL, OK};
use crate::ported::message::{emsg, semsg};
static num_errors: [&str; 11] = [
"E685: Internal error", "", "", "E703: Using a Funcref as a Number", "E745: Using a List as a Number", "E728: Using a Dictionary as a Number", "E805: Using a Float as a Number", "", "", "E703: Using a Funcref as a Number", "E974: Using a Blob as a Number", ];
pub fn tv_get_number_chk(tv: &typval_T, ret_error: Option<&mut bool>) -> varnumber_T {
match tv.v_type {
VAR_FUNC | VAR_PARTIAL | VAR_LIST | VAR_DICT | VAR_BLOB | VAR_FLOAT => {
emsg(num_errors[tv.v_type as usize]);
}
VAR_NUMBER => {
if let v_number(n) = &tv.vval {
return *n;
}
}
VAR_STRING => {
let mut n: varnumber_T = 0;
if let v_string(s) = &tv.vval {
vim_str2nr(
s,
None,
None,
STR2NR_ALL,
Some(&mut n),
None,
0,
false,
None,
);
}
return n;
}
VAR_BOOL => {
if let v_bool(b) = &tv.vval {
return if *b == kBoolVarTrue { 1 } else { 0 };
}
}
VAR_SPECIAL => {
return 0;
}
VAR_UNKNOWN => {
semsg("E685: Internal error: tv_get_number(UNKNOWN)");
}
}
match ret_error {
Some(e) => {
*e = true;
0
}
None => -1,
}
}
pub fn tv_get_bool(tv: &typval_T) -> varnumber_T {
tv_get_number_chk(tv, None)
}
pub fn tv_get_float(tv: &typval_T) -> f64 {
match (tv.v_type, &tv.vval) {
(VAR_NUMBER, v_number(n)) => *n as f64, (VAR_FLOAT, v_float(f)) => *f, _ => {
emsg("E808: Number or Float required");
0.0
}
}
}
pub fn tv_get_string_buf_chk(tv: &typval_T) -> Option<String> {
match (tv.v_type, &tv.vval) {
(VAR_NUMBER, v_number(n)) => Some(n.to_string()),
(VAR_FLOAT, v_float(f)) => Some(if f.is_infinite() {
if *f < 0.0 { "-inf" } else { "inf" }.to_string()
} else if f.is_nan() {
"nan".to_string()
} else {
format!("{f}")
}),
(VAR_STRING, v_string(s)) => Some(s.clone()),
(VAR_FUNC, v_string(s)) => Some(s.clone()),
(VAR_BOOL, v_bool(b)) => Some(
if *b == kBoolVarTrue {
"v:true"
} else {
"v:false"
}
.to_string(),
),
(VAR_SPECIAL, _) => Some("v:null".to_string()),
_ => {
emsg("E730: Using a List/Dict/Funcref/Blob as a String");
None
}
}
}
pub fn tv_get_string(tv: &typval_T) -> String {
tv_get_string_buf_chk(tv).unwrap_or_default()
}
pub fn tv_equal(tv1: &typval_T, tv2: &typval_T, ic: bool) -> bool {
let is_func = |t| matches!(t, VAR_FUNC | VAR_PARTIAL);
if is_func(tv1.v_type) && is_func(tv2.v_type) {
if (tv1.v_type == VAR_PARTIAL && matches!(&tv1.vval, v_partial(None)))
|| (tv2.v_type == VAR_PARTIAL && matches!(&tv2.vval, v_partial(None)))
{
return false;
}
return crate::ported::eval::func_equal(tv1, tv2, ic);
}
match (&tv1.vval, &tv2.vval) {
(v_number(a), v_number(b)) => a == b,
(v_float(a), v_float(b)) => a == b,
(v_string(a), v_string(b)) => {
tv1.v_type == tv2.v_type
&& if ic {
a.to_lowercase() == b.to_lowercase()
} else {
a == b
}
}
(v_bool(a), v_bool(b)) => a == b,
(v_special(a), v_special(b)) => a == b,
(v_list(Some(a)), v_list(Some(b))) => tv_list_equal(a, b, ic),
(v_list(None), v_list(None)) => true,
(v_dict(Some(a)), v_dict(Some(b))) => tv_dict_equal(a, b, ic),
(v_dict(None), v_dict(None)) => true,
(v_blob(Some(a)), v_blob(Some(b))) => tv_blob_equal(a, b),
(v_blob(None), v_blob(None)) => true,
_ => false,
}
}
pub fn tv_list_alloc(_len: isize) -> Rc<RefCell<list_T>> {
Rc::new(RefCell::new(list_T::default()))
}
pub fn tv_list_alloc_ret(rettv: &mut typval_T, len: isize) -> Rc<RefCell<list_T>> {
let l = tv_list_alloc(len);
rettv.v_type = crate::ported::eval::typval_defs_h::VarType::VAR_LIST;
rettv.vval = v_list(Some(l.clone()));
l
}
pub fn tv_dict_alloc_ret(rettv: &mut typval_T) -> Rc<RefCell<dict_T>> {
let d = tv_dict_alloc();
rettv.v_type = crate::ported::eval::typval_defs_h::VarType::VAR_DICT;
rettv.vval = v_dict(Some(d.clone()));
d
}
pub fn tv_list_len(l: &list_T) -> i32 {
l.lv_len
}
pub fn tv_list_append_tv(l: &mut list_T, tv: typval_T) {
l.lv_items.push(listitem_T { li_tv: tv });
l.lv_len = l.lv_items.len() as i32;
}
pub fn tv_list_append_string(l: &mut list_T, s: &str) {
tv_list_append_tv(
l,
typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(s.to_string()),
},
);
}
pub fn tv_list_append_number(l: &mut list_T, n: varnumber_T) {
tv_list_append_tv(
l,
typval_T {
v_type: VAR_NUMBER,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(n),
},
);
}
pub fn tv_list_equal(l1: &Rc<RefCell<list_T>>, l2: &Rc<RefCell<list_T>>, ic: bool) -> bool {
if Rc::ptr_eq(l1, l2) {
return true;
}
let (l1, l2) = (l1.borrow(), l2.borrow());
if l1.lv_len != l2.lv_len {
return false;
}
l1.lv_items
.iter()
.zip(l2.lv_items.iter())
.all(|(a, b)| tv_equal(&a.li_tv, &b.li_tv, ic))
}
pub fn tv_list_uidx(l: &list_T, n: i32) -> i32 {
let mut n = n;
if n < 0 {
n += tv_list_len(l);
}
if n < 0 || n >= tv_list_len(l) {
return -1;
}
n
}
pub fn tv_list_find(l: &list_T, n: i32) -> Option<&listitem_T> {
let n = tv_list_uidx(l, n);
if n == -1 {
return None;
}
l.lv_items.get(n as usize)
}
fn tv_list_find_index(l: &list_T, idx: &mut i32) -> Option<usize> {
let n = tv_list_uidx(l, *idx);
if n != -1 {
return Some(n as usize);
}
if *idx < 0 {
*idx = 0;
let n0 = tv_list_uidx(l, 0);
if n0 != -1 {
return Some(n0 as usize);
}
}
None
}
pub(crate) fn tv_list_check_range_index_one(
l: &list_T,
n1: &mut i32,
quiet: bool,
) -> Option<usize> {
let r = tv_list_find_index(l, n1);
if r.is_none() && !quiet {
semsg(&format!("E684: List index out of range: {n1}"));
}
r
}
pub(crate) fn tv_list_check_range_index_two(
l: &list_T,
n1: &mut i32,
pos1: usize,
n2: &mut i32,
quiet: bool,
) -> i32 {
if *n2 < 0 {
let ni = tv_list_uidx(l, *n2);
if ni == -1 {
if !quiet {
semsg(&format!("E684: List index out of range: {n2}"));
}
return FAIL;
}
*n2 = ni;
}
if *n1 < 0 {
*n1 = pos1 as i32;
}
if *n2 < *n1 {
if !quiet {
semsg(&format!("E684: List index out of range: {n2}"));
}
return FAIL;
}
OK
}
pub fn tv_list_assign_range(
dest: &Rc<RefCell<list_T>>,
src: &list_T,
idx1_arg: i32,
idx2: i32,
empty_idx2: bool,
op: &str,
varname: &str,
) -> i32 {
let mut idx1 = idx1_arg;
let first_pos = match tv_list_find_index(&dest.borrow(), &mut idx1) {
Some(p) => p,
None => return FAIL,
};
let src_items: Vec<typval_T> = src.lv_items.iter().map(|it| it.li_tv.clone()).collect();
let opc = op.chars().next().filter(|&c| c != '=');
let mut idx = idx1;
let mut dpos = first_pos;
let mut si = 0usize;
while si < src_items.len() {
let v_lock = dest.borrow().lv_items[dpos].li_tv.v_lock;
if value_check_lock(v_lock, Some(varname), TV_CSTRING) {
return FAIL;
}
match opc {
Some(c) => {
let mut cur = dest.borrow().lv_items[dpos].li_tv.clone();
eexe_mod_op(&mut cur, &src_items[si], c);
dest.borrow_mut().lv_items[dpos].li_tv = cur;
}
None => dest.borrow_mut().lv_items[dpos].li_tv = src_items[si].clone(),
}
si += 1;
if si >= src_items.len() || (!empty_idx2 && idx2 == idx) {
break;
}
if dpos + 1 >= dest.borrow().lv_items.len() {
tv_list_append_number(&mut dest.borrow_mut(), 0);
dpos = dest.borrow().lv_items.len() - 1;
} else {
dpos += 1;
}
idx += 1;
}
if si < src_items.len() {
emsg("E710: List value has more items than target");
return FAIL;
}
let too_few = if empty_idx2 {
dpos + 1 < dest.borrow().lv_items.len()
} else {
idx != idx2
};
if too_few {
emsg("E711: List value has not enough items");
return FAIL;
}
OK
}
pub fn tv_list_find_nr(l: &list_T, n: i32, ret_error: Option<&mut bool>) -> varnumber_T {
match tv_list_find(l, n) {
None => {
if let Some(e) = ret_error {
*e = true;
}
-1
}
Some(li) => tv_get_number_chk(&li.li_tv, ret_error),
}
}
pub fn tv_list_find_str(l: &list_T, n: i32) -> Option<String> {
match tv_list_find(l, n) {
None => {
semsg(&format!("E684: list index out of range: {n}"));
None
}
Some(li) => Some(tv_get_string(&li.li_tv)),
}
}
pub fn tv_list_reverse(l: &mut list_T) {
if tv_list_len(l) <= 1 {
return;
}
l.lv_items.reverse();
}
pub fn tv_dict_alloc() -> Rc<RefCell<dict_T>> {
Rc::new(RefCell::new(dict_T::default()))
}
pub fn tv_dict_len(d: &dict_T) -> i32 {
d.dv_hashtab.len() as i32
}
pub fn tv_dict_find<'d>(d: &'d dict_T, key: &str) -> Option<&'d typval_T> {
d.dv_hashtab.get(key)
}
pub fn tv_dict_add_tv(d: &mut dict_T, key: &str, tv: typval_T) {
d.dv_hashtab.insert(key.to_string(), tv);
}
pub fn tv_dict_add(d: &mut dict_T, key: &str, tv: typval_T) -> i32 {
if d.dv_hashtab.contains_key(key) {
return FAIL;
}
d.dv_hashtab.insert(key.to_string(), tv);
OK
}
pub fn tv_dict_add_nr(d: &mut dict_T, key: &str, nr: varnumber_T) -> i32 {
tv_dict_add(
d,
key,
typval_T {
v_type: VAR_NUMBER,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(nr),
},
)
}
pub fn tv_dict_add_float(d: &mut dict_T, key: &str, nr: float_T) -> i32 {
tv_dict_add(
d,
key,
typval_T {
v_type: VAR_FLOAT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_float(nr),
},
)
}
pub fn tv_dict_add_bool(d: &mut dict_T, key: &str, val: BoolVarValue) -> i32 {
tv_dict_add(
d,
key,
typval_T {
v_type: VAR_BOOL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_bool(val),
},
)
}
pub fn tv_dict_add_str(d: &mut dict_T, key: &str, val: &str) -> i32 {
tv_dict_add_allocated_str(d, key, val.to_string())
}
pub fn tv_dict_add_allocated_str(d: &mut dict_T, key: &str, val: String) -> i32 {
tv_dict_add(
d,
key,
typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(val),
},
)
}
pub fn tv_dict_has_key(d: &dict_T, key: &str) -> bool {
tv_dict_find(d, key).is_some()
}
pub fn tv_dict_get_number(d: &dict_T, key: &str) -> varnumber_T {
tv_dict_get_number_def(d, key, 0)
}
pub fn tv_dict_get_number_def(d: &dict_T, key: &str, def: varnumber_T) -> varnumber_T {
match tv_dict_find(d, key) {
None => def,
Some(di) => tv_get_number(di),
}
}
pub fn tv_dict_get_bool(d: &dict_T, key: &str, def: varnumber_T) -> varnumber_T {
match tv_dict_find(d, key) {
None => def,
Some(di) => tv_get_bool(di),
}
}
pub fn tv_dict_get_string(d: &dict_T, key: &str) -> Option<String> {
tv_dict_find(d, key).map(tv_get_string)
}
pub fn tv_get_number(tv: &typval_T) -> varnumber_T {
let mut error = false;
tv_get_number_chk(tv, Some(&mut error))
}
pub fn tv2bool(tv: &typval_T) -> bool {
match (tv.v_type, &tv.vval) {
(VAR_NUMBER, v_number(n)) => *n != 0,
(VAR_FLOAT, v_float(f)) => *f != 0.0,
(VAR_FUNC, v_string(s)) | (VAR_STRING, v_string(s)) => !s.is_empty(),
(VAR_LIST, v_list(l)) => l.as_ref().is_some_and(|l| l.borrow().lv_len > 0),
(VAR_DICT, v_dict(d)) => d
.as_ref()
.is_some_and(|d| !d.borrow().dv_hashtab.is_empty()),
(VAR_BOOL, v_bool(b)) => *b == kBoolVarTrue,
(VAR_SPECIAL, v_special(s)) => *s != kSpecialVarNull,
(VAR_BLOB, v_blob(b)) => b.as_ref().is_some_and(|b| !b.borrow().bv_ga.is_empty()),
(VAR_PARTIAL, v_partial(p)) => p.is_some(),
_ => false,
}
}
pub fn tv_copy(from: &typval_T, to: &mut typval_T) {
*to = from.clone();
to.v_lock = VarLockStatus::VAR_UNLOCKED;
}
pub fn tv_check_str_or_nr(tv: &typval_T) -> bool {
match tv.v_type {
VAR_NUMBER | VAR_STRING => true,
VAR_FLOAT => {
emsg("E805: Expected a Number or a String, Float found");
false
}
VAR_PARTIAL | VAR_FUNC => {
emsg("E703: Expected a Number or a String, Funcref found");
false
}
VAR_LIST => {
emsg("E745: Expected a Number or a String, List found");
false
}
VAR_DICT => {
emsg("E728: Expected a Number or a String, Dictionary found");
false
}
VAR_BLOB => {
emsg("E974: Expected a Number or a String, Blob found");
false
}
VAR_BOOL => {
emsg("E5299: Expected a Number or a String, Boolean found");
false
}
VAR_SPECIAL => {
emsg("E5300: Expected a Number or a String");
false
}
VAR_UNKNOWN => {
semsg("E685: Internal error: tv_check_str_or_nr(UNKNOWN)");
false
}
}
}
pub fn tv_check_num(tv: &typval_T) -> bool {
match tv.v_type {
VAR_NUMBER | VAR_BOOL | VAR_SPECIAL | VAR_STRING => true,
VAR_FUNC | VAR_PARTIAL => {
emsg("E703: Using a Funcref as a Number");
false
}
VAR_LIST => {
emsg("E745: Using a List as a Number");
false
}
VAR_DICT => {
emsg("E728: Using a Dictionary as a Number");
false
}
VAR_FLOAT => {
emsg("E805: Using a Float as a Number");
false
}
VAR_BLOB => {
emsg("E974: Using a Blob as a Number");
false
}
VAR_UNKNOWN => {
emsg("E685: using an invalid value as a Number");
false
}
}
}
pub fn tv_check_str(tv: &typval_T) -> bool {
match tv.v_type {
VAR_NUMBER | VAR_BOOL | VAR_SPECIAL | VAR_STRING | VAR_FLOAT => true,
VAR_PARTIAL | VAR_FUNC => {
emsg("E729: Using a Funcref as a String");
false
}
VAR_LIST => {
emsg("E730: Using a List as a String");
false
}
VAR_DICT => {
emsg("E731: Using a Dictionary as a String");
false
}
VAR_BLOB => {
emsg("E976: Using a Blob as a String");
false
}
VAR_UNKNOWN => {
emsg("E908: Using an invalid value as a String");
false
}
}
}
pub fn tv_check_for_string_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map(|a| a.v_type) != Some(VAR_STRING) {
semsg(&format!("E1174: String required for argument {}", idx + 1));
return FAIL;
}
OK
}
pub fn tv_check_for_nonempty_string_arg(args: &[typval_T], idx: usize) -> i32 {
if tv_check_for_string_arg(args, idx) == FAIL {
return FAIL;
}
let empty = matches!(args.get(idx).map(|a| &a.vval), Some(v_string(s)) if s.is_empty());
if empty {
semsg(&format!(
"E1175: Non-empty string required for argument {}",
idx + 1
));
return FAIL;
}
OK
}
pub fn tv_check_for_opt_string_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map_or(VAR_UNKNOWN, |a| a.v_type) == VAR_UNKNOWN
|| tv_check_for_string_arg(args, idx) != FAIL
{
OK
} else {
FAIL
}
}
pub fn tv_check_for_number_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map(|a| a.v_type) != Some(VAR_NUMBER) {
semsg(&format!("E1210: Number required for argument {}", idx + 1));
return FAIL;
}
OK
}
pub fn tv_check_for_opt_number_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map_or(VAR_UNKNOWN, |a| a.v_type) == VAR_UNKNOWN
|| tv_check_for_number_arg(args, idx) != FAIL
{
OK
} else {
FAIL
}
}
pub fn tv_check_for_float_or_nr_arg(args: &[typval_T], idx: usize) -> i32 {
let t = args.get(idx).map(|a| a.v_type);
if t != Some(VAR_FLOAT) && t != Some(VAR_NUMBER) {
semsg(&format!(
"E1219: Float or Number required for argument {}",
idx + 1
));
return FAIL;
}
OK
}
pub fn tv_check_for_bool_arg(args: &[typval_T], idx: usize) -> i32 {
let ok = match args.get(idx) {
Some(a) if a.v_type == VAR_BOOL => true,
Some(a) if a.v_type == VAR_NUMBER => {
matches!(&a.vval, v_number(n) if *n == 0 || *n == 1)
}
_ => false,
};
if !ok {
semsg(&format!("E1212: Bool required for argument {}", idx + 1));
return FAIL;
}
OK
}
pub fn tv_check_for_opt_bool_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map_or(VAR_UNKNOWN, |a| a.v_type) == VAR_UNKNOWN {
return OK;
}
tv_check_for_bool_arg(args, idx)
}
pub fn tv_check_for_blob_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map(|a| a.v_type) != Some(VAR_BLOB) {
semsg(&format!("E1238: Blob required for argument {}", idx + 1));
return FAIL;
}
OK
}
pub fn tv_check_for_list_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map(|a| a.v_type) != Some(VAR_LIST) {
semsg(&format!("E1211: List required for argument {}", idx + 1));
return FAIL;
}
OK
}
pub fn tv_check_for_dict_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map(|a| a.v_type) != Some(VAR_DICT) {
semsg(&format!(
"E1206: Dictionary required for argument {}",
idx + 1
));
return FAIL;
}
OK
}
pub fn tv_check_for_nonnull_dict_arg(args: &[typval_T], idx: usize) -> i32 {
if tv_check_for_dict_arg(args, idx) == FAIL {
return FAIL;
}
if let Some(a) = args.get(idx) {
if let v_dict(None) = &a.vval {
semsg(&format!(
"E1297: Non-NULL Dictionary required for argument {}",
idx + 1
));
return FAIL;
}
}
OK
}
pub fn tv_get_lnum(tv: &typval_T) -> varnumber_T {
tv_get_number_chk(tv, None)
}
pub fn tv_get_lnum_buf(tv: &typval_T, _buf: Option<&()>) -> varnumber_T {
tv_get_number_chk(tv, None)
}
pub fn tv_check_for_opt_dict_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map_or(VAR_UNKNOWN, |a| a.v_type) == VAR_UNKNOWN
|| tv_check_for_dict_arg(args, idx) != FAIL
{
OK
} else {
FAIL
}
}
pub fn tv_check_for_string_or_number_arg(args: &[typval_T], idx: usize) -> i32 {
let t = args.get(idx).map(|a| a.v_type);
if t != Some(VAR_STRING) && t != Some(VAR_NUMBER) {
semsg(&format!(
"E1220: String or Number required for argument {}",
idx + 1
));
return FAIL;
}
OK
}
pub fn tv_check_for_buffer_arg(args: &[typval_T], idx: usize) -> i32 {
tv_check_for_string_or_number_arg(args, idx)
}
pub fn tv_check_for_lnum_arg(args: &[typval_T], idx: usize) -> i32 {
tv_check_for_string_or_number_arg(args, idx)
}
pub fn tv_check_for_string_or_list_arg(args: &[typval_T], idx: usize) -> i32 {
let t = args.get(idx).map(|a| a.v_type);
if t != Some(VAR_STRING) && t != Some(VAR_LIST) {
semsg(&format!(
"E1222: String or List required for argument {}",
idx + 1
));
return FAIL;
}
OK
}
pub fn tv_check_for_string_or_list_or_blob_arg(args: &[typval_T], idx: usize) -> i32 {
let t = args.get(idx).map(|a| a.v_type);
if t != Some(VAR_STRING) && t != Some(VAR_LIST) && t != Some(VAR_BLOB) {
semsg(&format!(
"E1252: String, List or Blob required for argument {}",
idx + 1
));
return FAIL;
}
OK
}
pub fn tv_check_for_opt_string_or_list_arg(args: &[typval_T], idx: usize) -> i32 {
if args.get(idx).map_or(VAR_UNKNOWN, |a| a.v_type) == VAR_UNKNOWN
|| tv_check_for_string_or_list_arg(args, idx) != FAIL
{
OK
} else {
FAIL
}
}
pub fn tv_check_for_string_or_func_arg(args: &[typval_T], idx: usize) -> i32 {
let t = args.get(idx).map(|a| a.v_type);
if t != Some(VAR_PARTIAL) && t != Some(VAR_FUNC) && t != Some(VAR_STRING) {
semsg(&format!(
"E1256: String or function required for argument {}",
idx + 1
));
return FAIL;
}
OK
}
pub fn tv_check_for_list_or_blob_arg(args: &[typval_T], idx: usize) -> i32 {
let t = args.get(idx).map(|a| a.v_type);
if t != Some(VAR_LIST) && t != Some(VAR_BLOB) {
semsg(&format!(
"E1226: List or Blob required for argument {}",
idx + 1
));
return FAIL;
}
OK
}
pub fn tv_dict_equal(d1: &Rc<RefCell<dict_T>>, d2: &Rc<RefCell<dict_T>>, ic: bool) -> bool {
if Rc::ptr_eq(d1, d2) {
return true;
}
let (d1, d2) = (d1.borrow(), d2.borrow());
if d1.dv_hashtab.len() != d2.dv_hashtab.len() {
return false;
}
d1.dv_hashtab
.iter()
.all(|(k, v)| d2.dv_hashtab.get(k).is_some_and(|w| tv_equal(v, w, ic)))
}
pub fn tv_blob_alloc() -> Rc<RefCell<blob_T>> {
Rc::new(RefCell::new(blob_T::default()))
}
pub fn tv_blob_len(b: &blob_T) -> i32 {
b.bv_ga.len() as i32
}
pub fn tv_blob_equal(b1: &Rc<RefCell<blob_T>>, b2: &Rc<RefCell<blob_T>>) -> bool {
if Rc::ptr_eq(b1, b2) {
return true;
}
b1.borrow().bv_ga == b2.borrow().bv_ga
}
pub fn tv_blob_get(b: &blob_T, idx: i32) -> u8 {
b.bv_ga[idx as usize]
}
pub fn tv_blob_set(blob: &mut blob_T, idx: i32, c: u8) {
blob.bv_ga[idx as usize] = c;
}
pub fn tv_blob_set_ret(tv: &mut typval_T, b: Rc<RefCell<blob_T>>) {
tv.v_type = VAR_BLOB;
tv.vval = v_blob(Some(b));
}
pub fn tv_blob_alloc_ret(ret_tv: &mut typval_T) -> Rc<RefCell<blob_T>> {
let b = tv_blob_alloc();
tv_blob_set_ret(ret_tv, b.clone());
b
}
pub fn tv_blob_copy(from: Option<&Rc<RefCell<blob_T>>>, to: &mut typval_T) {
to.v_type = VAR_BLOB;
to.v_lock = VarLockStatus::VAR_UNLOCKED;
match from {
None => to.vval = v_blob(None),
Some(from) => {
let b = tv_blob_alloc_ret(to);
b.borrow_mut().bv_ga = from.borrow().bv_ga.clone();
}
}
}
pub fn tv_blob_set_range(dest: &mut blob_T, n1: varnumber_T, n2: varnumber_T, src: &blob_T) -> i32 {
if n2 - n1 + 1 != tv_blob_len(src) as varnumber_T {
emsg("E972: Blob value does not have the right number of bytes");
return FAIL;
}
for (ir, il) in (n1..=n2).enumerate() {
tv_blob_set(dest, il as i32, tv_blob_get(src, ir as i32));
}
OK
}
pub fn tv_blob_set_append(blob: &mut blob_T, idx: i32, byte: u8) {
let ga_len = blob.bv_ga.len() as i32;
if idx <= ga_len {
if idx == ga_len {
blob.bv_ga.push(0);
}
tv_blob_set(blob, idx, byte);
}
}
pub fn f_blob2list(argvars: &[typval_T], rettv: &mut typval_T) {
let l = tv_list_alloc_ret(rettv, 0);
if tv_check_for_blob_arg(argvars, 0) == FAIL {
return;
}
if let v_blob(Some(blob)) = &argvars[0].vval {
let blob = blob.borrow();
let mut lb = l.borrow_mut();
for i in 0..tv_blob_len(&blob) {
tv_list_append_number(&mut lb, tv_blob_get(&blob, i) as varnumber_T);
}
}
}
pub fn f_list2blob(argvars: &[typval_T], rettv: &mut typval_T) {
let blob = tv_blob_alloc_ret(rettv);
if tv_check_for_list_arg(argvars, 0) == FAIL {
return;
}
if let v_list(Some(l)) = &argvars[0].vval {
let lb = l.borrow();
let mut bb = blob.borrow_mut();
for li in &lb.lv_items {
let mut error = false;
let n = tv_get_number_chk(&li.li_tv, Some(&mut error));
if error || !(0..=255).contains(&n) {
if !error {
emsg(&format!("E1239: Invalid value for blob: 0x{n:X}"));
}
bb.bv_ga.clear();
return;
}
bb.bv_ga.push(n as u8);
}
}
}
pub fn tv_blob_check_index(bloblen: i32, n1: varnumber_T, quiet: bool) -> i32 {
if n1 < 0 || n1 > bloblen as varnumber_T {
if !quiet {
emsg(&format!("E979: Blob index out of range: {n1}"));
}
return FAIL;
}
OK
}
pub fn tv_blob_check_range(bloblen: i32, n1: varnumber_T, n2: varnumber_T, quiet: bool) -> i32 {
if n2 < 0 || n2 >= bloblen as varnumber_T || n2 < n1 {
if !quiet {
emsg(&format!("E979: Blob index out of range: {n2}"));
}
return FAIL;
}
OK
}
fn tv_blob_index(blob: &blob_T, len: i32, mut idx: varnumber_T, rettv: &mut typval_T) -> i32 {
if idx < 0 {
idx += len as varnumber_T;
}
if idx < len as varnumber_T && idx >= 0 {
let v = tv_blob_get(blob, idx as i32) as varnumber_T;
tv_clear(rettv);
rettv.v_type = VAR_NUMBER;
rettv.vval = v_number(v);
} else {
emsg(&format!("E979: Blob index out of range: {idx}"));
return FAIL;
}
OK
}
fn tv_blob_slice(
blob: &blob_T,
len: i32,
mut n1: varnumber_T,
mut n2: varnumber_T,
exclusive: bool,
rettv: &mut typval_T,
) -> i32 {
let len = len as varnumber_T;
if n1 < 0 {
n1 += len;
if n1 < 0 {
n1 = 0;
}
}
if n2 < 0 {
n2 += len;
} else if n2 >= len {
n2 = len - if exclusive { 0 } else { 1 };
}
if exclusive {
n2 -= 1;
}
if n1 >= len || n2 < 0 || n1 > n2 {
tv_clear(rettv);
rettv.v_type = VAR_BLOB;
rettv.vval = v_blob(None);
} else {
let new_blob = tv_blob_alloc();
new_blob.borrow_mut().bv_ga = (n1..=n2).map(|i| tv_blob_get(blob, i as i32)).collect();
tv_clear(rettv);
tv_blob_set_ret(rettv, new_blob);
}
OK
}
pub fn tv_blob_slice_or_index(
blob: &blob_T,
is_range: bool,
n1: varnumber_T,
n2: varnumber_T,
exclusive: bool,
rettv: &mut typval_T,
) -> i32 {
let len = tv_blob_len(blob);
if is_range {
tv_blob_slice(blob, len, n1, n2, exclusive, rettv)
} else {
tv_blob_index(blob, len, n1, rettv)
}
}
pub fn tv_list_copy(orig: &Rc<RefCell<list_T>>, deep: bool) -> Rc<RefCell<list_T>> {
let items: Vec<typval_T> = orig
.borrow()
.lv_items
.iter()
.map(|it| {
if deep {
crate::ported::eval::funcs::var_item_copy(&it.li_tv)
} else {
{
let mut t = it.li_tv.clone();
t.v_lock = VarLockStatus::VAR_UNLOCKED;
t
}
}
})
.collect();
let copy = tv_list_alloc(items.len() as isize);
{
let mut c = copy.borrow_mut();
for tv in items {
tv_list_append_tv(&mut c, tv);
}
}
copy
}
pub fn tv_dict_copy(orig: &Rc<RefCell<dict_T>>, deep: bool) -> Rc<RefCell<dict_T>> {
let pairs: Vec<(String, typval_T)> = orig
.borrow()
.dv_hashtab
.iter()
.map(|(k, v)| {
let nv = if deep {
crate::ported::eval::funcs::var_item_copy(v)
} else {
{
let mut t = v.clone();
t.v_lock = VarLockStatus::VAR_UNLOCKED;
t
}
};
(k.clone(), nv)
})
.collect();
let copy = tv_dict_alloc();
{
let mut c = copy.borrow_mut();
for (k, v) in pairs {
tv_dict_add(&mut c, &k, v);
}
}
copy
}
pub fn tv_list_extend(l1: &mut list_T, l2: &list_T, bef: Option<usize>) {
let add: Vec<typval_T> = l2
.lv_items
.iter()
.map(|it| {
let mut t = it.li_tv.clone();
t.v_lock = VarLockStatus::VAR_UNLOCKED;
t
})
.collect();
match bef {
None => {
for tv in add {
tv_list_append_tv(l1, tv);
}
}
Some(mut i) => {
i = i.min(l1.lv_items.len());
for tv in add {
l1.lv_items.insert(i, listitem_T { li_tv: tv });
i += 1;
}
l1.lv_len = l1.lv_items.len() as i32;
}
}
}
pub fn tv_list_concat(
l1: Option<&Rc<RefCell<list_T>>>,
l2: Option<&Rc<RefCell<list_T>>>,
tv: &mut typval_T,
) -> i32 {
tv.v_type = VAR_BLOB; let l = match (l1, l2) {
(None, None) => None,
(None, Some(l2)) => Some(tv_list_copy(l2, false)),
(Some(l1), l2) => {
let copy = tv_list_copy(l1, false);
if let Some(l2) = l2 {
tv_list_extend(&mut copy.borrow_mut(), &l2.borrow(), None);
}
Some(copy)
}
};
tv.v_type = VAR_LIST;
tv.v_lock = VarLockStatus::VAR_UNLOCKED;
tv.vval = v_list(l);
OK
}
pub fn tv_list_slice(ol: &list_T, n1: varnumber_T, n2: varnumber_T) -> Rc<RefCell<list_T>> {
let l = tv_list_alloc((n2 - n1 + 1) as isize);
{
let mut lb = l.borrow_mut();
let mut i = n1;
while i <= n2 {
if let Some(it) = ol.lv_items.get(i as usize) {
tv_list_append_tv(&mut lb, {
let mut t = it.li_tv.clone();
t.v_lock = VarLockStatus::VAR_UNLOCKED;
t
});
}
i += 1;
}
}
l
}
pub fn tv_list_flatten(list: &mut list_T, maxitems: i64, maxdepth: i64) {
if maxdepth == 0 {
return;
}
let mut i = 0usize;
let mut done: i64 = 0;
while i < list.lv_items.len() && done < maxitems {
let inner = match (list.lv_items[i].li_tv.v_type, &list.lv_items[i].li_tv.vval) {
(VAR_LIST, v_list(Some(inner))) => Some(inner.clone()),
_ => None,
};
if let Some(inner) = inner {
let mut sub: Vec<typval_T> = inner
.borrow()
.lv_items
.iter()
.map(|it| {
let mut t = it.li_tv.clone();
t.v_lock = VarLockStatus::VAR_UNLOCKED;
t
})
.collect();
if maxdepth > 0 {
let tmp = tv_list_alloc(0);
let items: Vec<listitem_T> =
sub.into_iter().map(|tv| listitem_T { li_tv: tv }).collect();
let n = items.len() as i32;
{
let mut t = tmp.borrow_mut();
t.lv_items = items;
t.lv_len = n;
}
let inner_len = inner.borrow().lv_len as i64;
tv_list_flatten(&mut tmp.borrow_mut(), inner_len, maxdepth - 1);
sub = tmp
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect();
}
list.lv_items.remove(i);
for tv in sub {
list.lv_items.insert(i, listitem_T { li_tv: tv });
i += 1;
}
list.lv_len = list.lv_items.len() as i32;
} else {
i += 1;
}
done += 1;
}
}
pub fn tv_dict_alloc_lock(lock: VarLockStatus) -> Rc<RefCell<dict_T>> {
let d = tv_dict_alloc();
d.borrow_mut().dv_lock = lock;
d
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum DictListType {
kDict2ListKeys,
kDict2ListValues,
kDict2ListItems,
}
use DictListType::*;
pub fn tv_dict2list(argvars: &[typval_T], rettv: &mut typval_T, what: DictListType) {
if tv_check_for_dict_arg(argvars, 0) == FAIL {
tv_list_alloc_ret(rettv, 0);
return;
}
let (VAR_DICT, v_dict(d)) = (argvars[0].v_type, &argvars[0].vval) else {
tv_list_alloc_ret(rettv, 0);
return;
};
let Some(d) = d else {
tv_list_alloc_ret(rettv, 0);
return;
};
let pairs: Vec<(String, typval_T)> = d
.borrow()
.dv_hashtab
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let out = tv_list_alloc_ret(rettv, pairs.len() as isize);
let mut ob = out.borrow_mut();
for (k, v) in pairs {
match what {
kDict2ListKeys => tv_list_append_string(&mut ob, &k),
kDict2ListValues => tv_list_append_tv(&mut ob, {
let mut t = v.clone();
t.v_lock = VarLockStatus::VAR_UNLOCKED;
t
}),
kDict2ListItems => {
let sub = tv_list_alloc(2);
{
let mut sb = sub.borrow_mut();
tv_list_append_string(&mut sb, &k);
tv_list_append_tv(&mut sb, v);
}
tv_list_append_list(&mut ob, sub);
}
}
}
}
pub fn tv_blob2items(argvars: &[typval_T], rettv: &mut typval_T) {
let bytes: Vec<u8> = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_BLOB, v_blob(Some(b))) => b.borrow().bv_ga.clone(),
_ => Vec::new(),
};
let out = tv_list_alloc_ret(rettv, bytes.len() as isize);
let mut ob = out.borrow_mut();
for (i, byte) in bytes.iter().enumerate() {
let l2 = tv_list_alloc(2);
{
let mut lb = l2.borrow_mut();
tv_list_append_number(&mut lb, i as varnumber_T);
tv_list_append_number(&mut lb, *byte as varnumber_T);
}
tv_list_append_list(&mut ob, l2);
}
}
pub fn tv_list2items(argvars: &[typval_T], rettv: &mut typval_T) {
let items: Vec<typval_T> = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_LIST, v_list(Some(l))) => l
.borrow()
.lv_items
.iter()
.map(|it| it.li_tv.clone())
.collect(),
_ => Vec::new(),
};
let out = tv_list_alloc_ret(rettv, items.len() as isize);
let mut ob = out.borrow_mut();
for (idx, tv) in items.into_iter().enumerate() {
let l2 = tv_list_alloc(2);
{
let mut lb = l2.borrow_mut();
tv_list_append_number(&mut lb, idx as varnumber_T);
tv_list_append_tv(&mut lb, tv);
}
tv_list_append_list(&mut ob, l2);
}
}
pub fn tv_dict2items(argvars: &[typval_T], rettv: &mut typval_T) {
tv_dict2list(argvars, rettv, kDict2ListItems);
}
pub fn tv_string2items(argvars: &[typval_T], rettv: &mut typval_T) {
let s = match (argvars[0].v_type, &argvars[0].vval) {
(VAR_STRING, v_string(s)) => s.clone(),
_ => String::new(),
};
let out = tv_list_alloc_ret(rettv, 0);
let mut ob = out.borrow_mut();
for (idx, ch) in s.chars().enumerate() {
let l2 = tv_list_alloc(2);
{
let mut lb = l2.borrow_mut();
tv_list_append_number(&mut lb, idx as varnumber_T);
tv_list_append_string(&mut lb, &ch.to_string());
}
tv_list_append_list(&mut ob, l2);
}
}
pub fn tv_dict_set_keys_readonly(_dict: &mut dict_T) {}
pub fn tv_list_ref(l: &mut list_T) {
l.lv_refcount += 1;
}
pub fn tv_list_unref(l: &mut list_T) {
l.lv_refcount -= 1;
}
pub fn tv_list_free_contents(l: &mut list_T) {
l.lv_items.clear();
l.lv_len = 0;
}
pub fn tv_list_free_list(_l: &mut list_T) {}
pub fn tv_list_free(l: &mut list_T) {
tv_list_free_contents(l);
tv_list_free_list(l);
}
pub fn tv_dict_unref(d: &mut dict_T) {
d.dv_refcount -= 1;
}
pub fn tv_dict_free_contents(d: &mut dict_T) {
d.dv_hashtab.clear();
}
pub fn tv_dict_free(d: &mut dict_T) {
tv_dict_free_contents(d);
}
pub fn tv_blob_unref(b: &mut blob_T) {
b.bv_refcount -= 1;
}
pub fn tv_blob_free(b: &mut blob_T) {
b.bv_ga.clear();
}
pub fn tv_list_append_list(l: &mut list_T, itemlist: Rc<RefCell<list_T>>) {
tv_list_append_tv(
l,
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(itemlist)),
},
);
}
pub fn tv_list_append_dict(l: &mut list_T, dict: Rc<RefCell<dict_T>>) {
tv_list_append_tv(
l,
typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(dict)),
},
);
}
pub fn tv_list_append_allocated_string(l: &mut list_T, str: String) {
tv_list_append_tv(
l,
typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(str),
},
);
}
pub fn tv_dict_clear(d: &mut dict_T) {
d.dv_hashtab.clear();
}
pub fn tv_dict_extend(d1: &mut dict_T, d2: &dict_T, action: &str) {
let act = action.as_bytes().first().copied().unwrap_or(b'f');
let pairs: Vec<_> = d2
.dv_hashtab
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
for (k, v) in pairs {
if d1.dv_hashtab.contains_key(&k) {
match act {
b'e' => {
semsg(&format!("E737: Key already exists: {k}"));
break;
}
b'f' => {
d1.dv_hashtab.insert(k, v);
}
_ => {} }
} else {
d1.dv_hashtab.insert(k, v);
}
}
}
pub fn tv_dict_add_list(d: &mut dict_T, key: &str, list: Rc<RefCell<list_T>>) -> i32 {
tv_dict_add(
d,
key,
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(list)),
},
)
}
pub fn tv_dict_add_dict(d: &mut dict_T, key: &str, dict: Rc<RefCell<dict_T>>) -> i32 {
tv_dict_add(
d,
key,
typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(dict)),
},
)
}
pub fn tv_dict_add_str_len(d: &mut dict_T, key: &str, val: &str, len: i32) -> i32 {
let s = if len < 0 {
val.to_string()
} else {
val.chars().take(len as usize).collect()
};
tv_dict_add_allocated_str(d, key, s)
}
pub fn tv_dict_get_string_buf(d: &dict_T, key: &str) -> Option<String> {
tv_dict_find(d, key).map(tv_get_string)
}
pub fn tv_dict_get_string_buf_chk(d: &dict_T, key: &str, def: Option<String>) -> Option<String> {
match tv_dict_find(d, key) {
None => def,
Some(di) => tv_get_string_buf_chk(di),
}
}
pub fn tv_dict_get_tv(d: &dict_T, key: &str, rettv: &mut typval_T) -> i32 {
match tv_dict_find(d, key) {
None => FAIL,
Some(di) => {
let di = di.clone();
tv_copy(&di, rettv);
OK
}
}
}
pub fn tv_dict_to_env(denv: &dict_T) -> Vec<String> {
denv.dv_hashtab
.iter()
.map(|(k, v)| format!("{k}={}", tv_get_string(v)))
.collect()
}
pub fn tv_clear(tv: &mut typval_T) {
if tv.v_type == VAR_UNKNOWN {
return;
}
*tv = typval_T {
v_type: VAR_UNKNOWN,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(0),
};
}
pub fn tv_free(tv: &mut typval_T) {
tv_clear(tv);
}
pub fn tv_islocked(tv: &typval_T) -> bool {
if tv.v_lock == VarLockStatus::VAR_LOCKED {
return true;
}
match (tv.v_type, &tv.vval) {
(VAR_LIST, v_list(Some(l))) => l.borrow().lv_lock == VarLockStatus::VAR_LOCKED,
(VAR_DICT, v_dict(Some(d))) => d.borrow().dv_lock == VarLockStatus::VAR_LOCKED,
_ => false,
}
}
pub const TV_TRANSLATE: usize = usize::MAX;
pub const TV_CSTRING: usize = usize::MAX - 1;
const DICT_MAXNEST: i32 = 100;
pub fn value_check_lock(lock: VarLockStatus, name: Option<&str>, name_len: usize) -> bool {
let shown = |n: &str| -> String {
if name_len == TV_TRANSLATE || name_len == TV_CSTRING {
n.to_string()
} else {
n.get(..name_len).unwrap_or(n).to_string()
}
};
let msg = match lock {
VarLockStatus::VAR_UNLOCKED => return false,
VarLockStatus::VAR_LOCKED => match name {
None => "E741: Value is locked".to_string(),
Some(n) => format!("E741: Value is locked: {}", shown(n)),
},
VarLockStatus::VAR_FIXED => match name {
None => "E742: Cannot change value".to_string(),
Some(n) => format!("E742: Cannot change value of {}", shown(n)),
},
};
emsg(&msg);
true
}
pub fn tv_check_lock(tv: &typval_T, name: Option<&str>, name_len: usize) -> bool {
let lock = match (tv.v_type, &tv.vval) {
(VAR_BLOB, v_blob(Some(b))) => b.borrow().bv_lock,
(VAR_LIST, v_list(Some(l))) => l.borrow().lv_lock,
(VAR_DICT, v_dict(Some(d))) => d.borrow().dv_lock,
_ => VarLockStatus::VAR_UNLOCKED,
};
value_check_lock(tv.v_lock, name, name_len)
|| (lock != VarLockStatus::VAR_UNLOCKED && value_check_lock(lock, name, name_len))
}
thread_local! {
static TV_ITEM_LOCK_RECURSE: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
}
pub fn tv_item_lock(tv: &mut typval_T, deep: i32, lock: bool, check_refcount: bool) {
if TV_ITEM_LOCK_RECURSE.with(|r| r.get()) >= DICT_MAXNEST {
emsg("E743: Variable nested too deep for (un)lock");
return;
}
if deep == 0 {
return;
}
TV_ITEM_LOCK_RECURSE.with(|r| r.set(r.get() + 1));
let change_lock = |var: VarLockStatus| -> VarLockStatus {
match var {
VarLockStatus::VAR_FIXED => VarLockStatus::VAR_FIXED,
_ => {
if lock {
VarLockStatus::VAR_LOCKED
} else {
VarLockStatus::VAR_UNLOCKED
}
}
}
};
tv.v_lock = change_lock(tv.v_lock);
match (tv.v_type, &tv.vval) {
(VAR_BLOB, v_blob(Some(b))) => {
let mut bb = b.borrow_mut();
if !(check_refcount && bb.bv_refcount > 1) {
bb.bv_lock = change_lock(bb.bv_lock);
}
}
(VAR_LIST, v_list(Some(l))) => {
let recurse = {
let mut lb = l.borrow_mut();
if !(check_refcount && lb.lv_refcount > 1) {
lb.lv_lock = change_lock(lb.lv_lock);
!(0..=1).contains(&deep)
} else {
false
}
};
if recurse {
let mut lb = l.borrow_mut();
for li in lb.lv_items.iter_mut() {
tv_item_lock(&mut li.li_tv, deep - 1, lock, check_refcount);
}
}
}
(VAR_DICT, v_dict(Some(d))) => {
let recurse = {
let mut db = d.borrow_mut();
if !(check_refcount && db.dv_refcount > 1) {
db.dv_lock = change_lock(db.dv_lock);
!(0..=1).contains(&deep)
} else {
false
}
};
if recurse {
let mut db = d.borrow_mut();
for (_k, v) in db.dv_hashtab.iter_mut() {
tv_item_lock(v, deep - 1, lock, check_refcount);
}
}
}
_ => {}
}
TV_ITEM_LOCK_RECURSE.with(|r| r.set(r.get() - 1));
}
pub fn tv_get_bool_chk(tv: &typval_T, ret_error: Option<&mut bool>) -> varnumber_T {
tv_get_number_chk(tv, ret_error)
}
pub fn tv_get_string_chk(tv: &typval_T) -> Option<String> {
tv_get_string_buf_chk(tv)
}
pub fn tv_get_string_buf(tv: &typval_T) -> String {
tv_get_string_buf_chk(tv).unwrap_or_default()
}
pub fn tv_list_remove(argvars: &[typval_T], rettv: &mut typval_T, arg_errmsg: &str) {
let l = match &argvars[0].vval {
v_list(Some(l)) => l.clone(),
_ => return,
};
if value_check_lock(l.borrow().lv_lock, Some(arg_errmsg), TV_TRANSLATE) {
return;
}
let mut error = false;
let idx = tv_get_number_chk(&argvars[1], Some(&mut error));
if error {
return;
}
let len = tv_list_len(&l.borrow());
if tv_list_find(&l.borrow(), idx as i32).is_none() {
emsg(&format!("E684: List index out of range: {idx}"));
return;
}
let start = if idx < 0 {
len as varnumber_T + idx
} else {
idx
} as usize;
if argvars.len() < 3 {
let mut lb = l.borrow_mut();
let it = lb.lv_items.remove(start);
lb.lv_len = lb.lv_items.len() as i32;
*rettv = it.li_tv;
} else {
let mut error2 = false;
let end = tv_get_number_chk(&argvars[2], Some(&mut error2));
if error2 {
return;
}
if tv_list_find(&l.borrow(), end as i32).is_none() {
emsg(&format!("E684: List index out of range: {end}"));
return;
}
let endi = if end < 0 {
len as varnumber_T + end
} else {
end
} as usize;
if endi < start {
emsg("E16: Invalid range");
return;
}
let out = tv_list_alloc_ret(rettv, (endi - start + 1) as isize);
let drained: Vec<listitem_T> = {
let mut lb = l.borrow_mut();
let drained = lb.lv_items.drain(start..=endi).collect::<Vec<_>>();
lb.lv_len = lb.lv_items.len() as i32;
drained
};
let mut ob = out.borrow_mut();
ob.lv_len = drained.len() as i32;
ob.lv_items = drained;
}
}
pub fn tv_dict_remove(argvars: &[typval_T], rettv: &mut typval_T, arg_errmsg: &str) {
if argvars.len() > 2 {
emsg("E118: Too many arguments for function: remove()");
return;
}
let d = match &argvars[0].vval {
v_dict(Some(d)) => d.clone(),
_ => return,
};
if value_check_lock(d.borrow().dv_lock, Some(arg_errmsg), TV_TRANSLATE) {
return;
}
let key = match tv_get_string_chk(&argvars[1]) {
Some(k) => k,
None => return,
};
let removed = d.borrow_mut().dv_hashtab.shift_remove(&key);
match removed {
Some(v) => {
tv_dict_watcher_notify(&d, &key, None, Some(&v));
*rettv = v;
}
None => emsg(&format!("E716: Key not present in Dictionary: \"{key}\"")),
}
}
pub fn tv_blob_remove(argvars: &[typval_T], rettv: &mut typval_T, arg_errmsg: &str) {
let b = match &argvars[0].vval {
v_blob(Some(b)) => b.clone(),
_ => return,
};
if value_check_lock(b.borrow().bv_lock, Some(arg_errmsg), TV_TRANSLATE) {
return;
}
let mut error = false;
let mut idx = tv_get_number_chk(&argvars[1], Some(&mut error));
if error {
return;
}
let len = tv_blob_len(&b.borrow());
if idx < 0 {
idx += len as varnumber_T;
}
if idx < 0 || idx >= len as varnumber_T {
emsg(&format!("E979: Blob index out of range: {idx}"));
return;
}
let idx = idx as usize;
if argvars.len() < 3 {
let mut bb = b.borrow_mut();
let v = bb.bv_ga[idx] as varnumber_T;
bb.bv_ga.remove(idx);
rettv.vval = v_number(v);
} else {
let mut error2 = false;
let mut end = tv_get_number_chk(&argvars[2], Some(&mut error2));
if error2 {
return;
}
if end < 0 {
end += len as varnumber_T;
}
if end >= len as varnumber_T || idx as varnumber_T > end {
emsg(&format!("E979: Blob index out of range: {end}"));
return;
}
let end = end as usize;
let new_blob = tv_blob_alloc();
{
let mut bb = b.borrow_mut();
new_blob.borrow_mut().bv_ga = bb.bv_ga.drain(idx..=end).collect();
}
tv_blob_set_ret(rettv, new_blob);
}
}
fn list_join_inner(gap: &mut String, l: &list_T, sep: &str) -> i32 {
let mut first = true;
for item in &l.lv_items {
if first {
first = false;
} else {
gap.push_str(sep);
}
gap.push_str(&encode_tv2echo(&item.li_tv));
}
OK
}
pub fn tv_list_join(gap: &mut String, l: &list_T, sep: &str) -> i32 {
if tv_list_len(l) == 0 {
return OK;
}
list_join_inner(gap, l, sep)
}
pub fn f_join(argvars: &[typval_T], rettv: &mut typval_T) {
if argvars[0].v_type != VAR_LIST {
emsg("E714: List required");
return;
}
rettv.v_type = VAR_STRING;
let sep = if argvars.len() < 2 {
" ".to_string()
} else {
match tv_get_string_chk(&argvars[1]) {
Some(s) => s,
None => {
rettv.vval = v_string(String::new());
return;
}
}
};
let mut ga = String::new();
if let v_list(Some(l)) = &argvars[0].vval {
tv_list_join(&mut ga, &l.borrow(), &sep);
}
rettv.vval = v_string(ga);
}
pub fn tv_list_slice_or_index(
list: &Rc<RefCell<list_T>>,
range: bool,
n1_arg: varnumber_T,
n2_arg: varnumber_T,
exclusive: bool,
rettv: &mut typval_T,
verbose: bool,
) -> i32 {
let len = tv_list_len(&list.borrow());
let mut n1 = n1_arg;
let mut n2 = n2_arg;
if n1 < 0 {
n1 += len as varnumber_T;
}
if n1 < 0 || n1 >= len as varnumber_T {
if !range {
if verbose {
emsg(&format!("E684: List index out of range: {n1_arg}"));
}
return FAIL;
}
n1 = len as varnumber_T;
}
if range {
if n2 < 0 {
n2 += len as varnumber_T;
} else if n2 >= len as varnumber_T {
n2 = len as varnumber_T - if exclusive { 0 } else { 1 };
}
if exclusive {
n2 -= 1;
}
if n2 < 0 || n2 + 1 < n1 {
n2 = -1;
}
let l = tv_list_slice(&list.borrow(), n1, n2);
tv_clear(rettv);
rettv.v_type = VAR_LIST;
rettv.vval = v_list(Some(l));
} else {
let mut var1 = typval_T {
v_type: VAR_UNKNOWN,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(0),
};
if let Some(li) = tv_list_find(&list.borrow(), n1 as i32) {
tv_copy(&li.li_tv, &mut var1);
}
tv_clear(rettv);
*rettv = var1;
}
OK
}
#[derive(Default)]
pub struct sortinfo_T {
pub item_compare_ic: bool,
pub item_compare_lc: bool,
pub item_compare_numeric: bool,
pub item_compare_numbers: bool,
pub item_compare_float: bool,
pub item_compare_func: Option<String>,
pub item_compare_func_err: std::cell::Cell<bool>,
}
type SortFuncrefFn = fn(&str, &typval_T, &typval_T) -> Option<varnumber_T>;
type CallFuncFn = fn(&typval_T, &[typval_T]) -> Option<typval_T>;
type FuncExistsFn = fn(&str) -> bool;
type EvalStringFn = fn(&str) -> Option<typval_T>;
thread_local! {
pub static SORT_FUNCREF_HOOK: std::cell::RefCell<Option<SortFuncrefFn>> =
const { std::cell::RefCell::new(None) };
pub static CALL_FUNC_HOOK: std::cell::RefCell<Option<CallFuncFn>> =
const { std::cell::RefCell::new(None) };
pub static FUNC_EXISTS_HOOK: std::cell::RefCell<Option<FuncExistsFn>> =
const { std::cell::RefCell::new(None) };
pub static EVAL_STRING_HOOK: std::cell::RefCell<Option<EvalStringFn>> =
const { std::cell::RefCell::new(None) };
}
fn item_compare(tv1: &typval_T, tv2: &typval_T, info: &sortinfo_T) -> i32 {
if info.item_compare_numbers {
let v1 = tv_get_number(tv1);
let v2 = tv_get_number(tv2);
return if v1 == v2 {
0
} else if v1 > v2 {
1
} else {
-1
};
}
if info.item_compare_float {
let v1 = tv_get_float(tv1);
let v2 = tv_get_float(tv2);
return if v1 == v2 {
0
} else if v1 > v2 {
1
} else {
-1
};
}
let p1 = if tv1.v_type == VAR_STRING {
if tv2.v_type != VAR_STRING || info.item_compare_numeric {
"'".to_string()
} else {
tv_get_string(tv1)
}
} else {
encode_tv2string(tv1)
};
let p2 = if tv2.v_type == VAR_STRING {
if tv1.v_type != VAR_STRING || info.item_compare_numeric {
"'".to_string()
} else {
tv_get_string(tv2)
}
} else {
encode_tv2string(tv2)
};
if !info.item_compare_numeric {
let ord = if info.item_compare_lc {
p1.cmp(&p2)
} else if info.item_compare_ic {
p1.to_lowercase().cmp(&p2.to_lowercase())
} else {
p1.cmp(&p2)
};
match ord {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
} else {
let cs1 = std::ffi::CString::new(p1).unwrap_or_default();
let cs2 = std::ffi::CString::new(p2).unwrap_or_default();
let n1 = unsafe { nix::libc::strtod(cs1.as_ptr(), std::ptr::null_mut()) };
let n2 = unsafe { nix::libc::strtod(cs2.as_ptr(), std::ptr::null_mut()) };
if n1 == n2 {
0
} else if n1 > n2 {
1
} else {
-1
}
}
}
fn item_compare_keeping_zero(tv1: &typval_T, tv2: &typval_T, info: &sortinfo_T) -> i32 {
item_compare(tv1, tv2, info)
}
fn item_compare_not_keeping_zero(tv1: &typval_T, tv2: &typval_T, info: &sortinfo_T) -> i32 {
item_compare(tv1, tv2, info)
}
fn item_compare2(tv1: &typval_T, tv2: &typval_T, info: &sortinfo_T) -> i32 {
if info.item_compare_func_err.get() {
return 0;
}
let name = match &info.item_compare_func {
Some(n) => n,
None => return 0,
};
let hook = SORT_FUNCREF_HOOK.with(|h| *h.borrow());
let res = hook.and_then(|f| f(name, tv1, tv2));
match res {
Some(n) => {
if n > 0 {
1
} else if n < 0 {
-1
} else {
0
}
}
None => {
info.item_compare_func_err.set(true);
0
}
}
}
fn item_compare2_keeping_zero(tv1: &typval_T, tv2: &typval_T, info: &sortinfo_T) -> i32 {
item_compare2(tv1, tv2, info)
}
fn item_compare2_not_keeping_zero(tv1: &typval_T, tv2: &typval_T, info: &sortinfo_T) -> i32 {
item_compare2(tv1, tv2, info)
}
fn parse_sort_uniq_args(argvars: &[typval_T], info: &mut sortinfo_T) -> i32 {
if argvars.len() < 2 {
return OK;
}
let a1 = &argvars[1];
if a1.v_type == VAR_FUNC {
info.item_compare_func = Some(tv_get_string(a1));
} else {
let mut error = false;
let nr = tv_get_number_chk(a1, Some(&mut error)) as i32;
if error {
return FAIL;
}
if nr == 1 {
info.item_compare_ic = true;
} else if a1.v_type != VAR_NUMBER {
info.item_compare_func = Some(tv_get_string(a1));
} else if nr != 0 {
emsg("E474: Invalid argument");
return FAIL;
}
if let Some(f) = info.item_compare_func.clone() {
match f.as_str() {
"" => info.item_compare_func = None, "n" => {
info.item_compare_func = None;
info.item_compare_numeric = true;
}
"N" => {
info.item_compare_func = None;
info.item_compare_numbers = true;
}
"f" => {
info.item_compare_func = None;
info.item_compare_float = true;
}
"i" => {
info.item_compare_func = None;
info.item_compare_ic = true;
}
"l" => {
info.item_compare_func = None;
info.item_compare_lc = true;
}
_ => {}
}
}
}
if argvars.len() > 2 {
if tv_check_for_dict_arg(argvars, 2) == FAIL {
return FAIL;
}
}
OK
}
fn do_sort(l: &Rc<RefCell<list_T>>, info: &sortinfo_T) {
let has_func = info.item_compare_func.is_some();
info.item_compare_func_err.set(false);
let mut lb = l.borrow_mut();
let original = lb.lv_items.clone();
let mut items = std::mem::take(&mut lb.lv_items);
items.sort_by(|a, b| {
let r = if has_func {
item_compare2_not_keeping_zero(&a.li_tv, &b.li_tv, info)
} else {
item_compare_not_keeping_zero(&a.li_tv, &b.li_tv, info)
};
r.cmp(&0)
});
if info.item_compare_func_err.get() {
lb.lv_items = original;
emsg("E702: Sort compare function failed");
} else {
lb.lv_items = items;
}
lb.lv_len = lb.lv_items.len() as i32;
}
fn do_uniq(l: &Rc<RefCell<list_T>>, info: &sortinfo_T) {
let has_func = info.item_compare_func.is_some();
info.item_compare_func_err.set(false);
let mut lb = l.borrow_mut();
let items = std::mem::take(&mut lb.lv_items);
let mut out: Vec<listitem_T> = Vec::with_capacity(items.len());
let mut i = 0;
while i < items.len() {
let dup = if let Some(prev) = out.last() {
let r = if has_func {
item_compare2_keeping_zero(&prev.li_tv, &items[i].li_tv, info)
} else {
item_compare_keeping_zero(&prev.li_tv, &items[i].li_tv, info)
};
if info.item_compare_func_err.get() {
emsg("E882: Uniq compare function failed");
break;
}
r == 0
} else {
false
};
if !dup {
out.push(items[i].clone());
}
i += 1;
}
while i < items.len() {
out.push(items[i].clone());
i += 1;
}
lb.lv_items = out;
lb.lv_len = lb.lv_items.len() as i32;
}
fn do_sort_uniq(argvars: &[typval_T], rettv: &mut typval_T, sort: bool) {
if argvars[0].v_type != VAR_LIST {
emsg(&format!(
"E686: Argument of {} must be a List",
if sort { "sort()" } else { "uniq()" }
));
return;
}
let l = match &argvars[0].vval {
v_list(Some(l)) => l.clone(),
_ => {
rettv.v_type = VAR_LIST;
rettv.vval = v_list(None);
return;
}
};
let arg_errmsg = if sort {
"sort() argument"
} else {
"uniq() argument"
};
if value_check_lock(l.borrow().lv_lock, Some(arg_errmsg), TV_TRANSLATE) {
return;
}
rettv.v_type = VAR_LIST;
rettv.vval = v_list(Some(l.clone()));
if tv_list_len(&l.borrow()) <= 1 {
return; }
let mut info = sortinfo_T::default();
if parse_sort_uniq_args(argvars, &mut info) == FAIL {
return;
}
if sort {
do_sort(&l, &info);
} else {
do_uniq(&l, &info);
}
}
pub fn f_sort(argvars: &[typval_T], rettv: &mut typval_T) {
do_sort_uniq(argvars, rettv, true);
}
pub fn f_uniq(argvars: &[typval_T], rettv: &mut typval_T) {
do_sort_uniq(argvars, rettv, false);
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum Callback {
#[default]
None,
Funcref(String),
}
pub fn callback_copy(dest: &mut Callback, src: &Callback) {
*dest = src.clone();
}
pub fn callback_free(callback: &mut Callback) {
*callback = Callback::None;
}
pub fn callback_put(cb: &Callback, tv: &mut typval_T) {
match cb {
Callback::Funcref(name) => {
tv.v_type = VAR_FUNC;
tv.vval = v_string(name.clone());
}
Callback::None => {
tv.v_type = VAR_SPECIAL;
tv.vval =
v_special(crate::ported::eval::typval_defs_h::SpecialVarValue::kSpecialVarNull);
}
}
}
pub fn callback_to_string(cb: &Callback) -> String {
match cb {
Callback::Funcref(name) => format!("<vim function: {name}>"),
Callback::None => String::new(),
}
}
pub fn tv_callback_equal(cb1: &Callback, cb2: &Callback) -> bool {
cb1 == cb2
}
pub fn tv_dict_get_callback(d: &dict_T, key: &str, result: &mut Callback) -> bool {
*result = Callback::None;
let tv = match tv_dict_find(d, key) {
Some(tv) => tv,
None => return true,
};
match tv.v_type {
VAR_FUNC | VAR_STRING => {
*result = Callback::Funcref(tv_get_string(tv));
true
}
_ => {
emsg("E6000: Argument is not a function or function name");
false
}
}
}
pub fn callback_from_typval(callback: &mut Callback, tv: &typval_T) -> bool {
match tv.v_type {
VAR_FUNC => {
*callback = Callback::Funcref(tv_get_string(tv));
true
}
VAR_STRING => {
let s = tv_get_string(tv);
*callback = if s.is_empty() {
Callback::None
} else {
Callback::Funcref(s)
};
true
}
VAR_SPECIAL => {
*callback = Callback::None;
true
}
_ => false,
}
}
#[derive(Clone, Debug, Default)]
pub struct DictWatcher {
pub callback: Callback,
pub key_pattern: String,
}
pub fn tv_dict_watcher_add(dict: &Rc<RefCell<dict_T>>, key_pattern: &str, callback: Callback) {
dict.borrow_mut().dv_watchers.push(DictWatcher {
callback,
key_pattern: key_pattern.to_string(),
});
}
fn tv_dict_watcher_matches(watcher: &DictWatcher, key: &str) -> bool {
let p = &watcher.key_pattern;
if let Some(prefix) = p.strip_suffix('*') {
key.starts_with(prefix)
} else {
key == p
}
}
pub fn tv_dict_watcher_free(_watcher: DictWatcher) {}
pub fn tv_dict_watcher_remove(
dict: &Rc<RefCell<dict_T>>,
key_pattern: &str,
callback: &Callback,
) -> bool {
let mut d = dict.borrow_mut();
if let Some(i) = d
.dv_watchers
.iter()
.position(|w| tv_callback_equal(&w.callback, callback) && w.key_pattern == key_pattern)
{
let w = d.dv_watchers.remove(i);
tv_dict_watcher_free(w);
true
} else {
false
}
}
pub fn tv_dict_watcher_notify(
dict: &Rc<RefCell<dict_T>>,
key: &str,
newtv: Option<&typval_T>,
oldtv: Option<&typval_T>,
) {
let matching: Vec<Callback> = dict
.borrow()
.dv_watchers
.iter()
.filter(|w| tv_dict_watcher_matches(w, key))
.map(|w| w.callback.clone())
.collect();
if matching.is_empty() {
return;
}
let change = tv_dict_alloc();
if let Some(n) = newtv {
tv_dict_add_tv(&mut change.borrow_mut(), "new", n.clone());
}
if let Some(o) = oldtv {
if o.v_type != VAR_UNKNOWN {
tv_dict_add_tv(&mut change.borrow_mut(), "old", o.clone());
}
}
let mk = |t, v| typval_T {
v_type: t,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v,
};
let dict_tv = mk(VAR_DICT, v_dict(Some(dict.clone())));
let key_tv = mk(VAR_STRING, v_string(key.to_string()));
let change_tv = mk(VAR_DICT, v_dict(Some(change)));
let hook = CALL_FUNC_HOOK.with(|h| *h.borrow());
for cb in matching {
if let Callback::Funcref(name) = cb {
if let Some(f) = hook {
let func_tv = mk(VAR_FUNC, v_string(name.clone()));
let _ = f(
&func_tv,
&[dict_tv.clone(), key_tv.clone(), change_tv.clone()],
);
}
}
}
}
pub fn tv_list_item_alloc() -> listitem_T {
listitem_T {
li_tv: typval_T {
v_type: VAR_UNKNOWN,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_special(kSpecialVarNull),
},
}
}
pub fn tv_list_append(l: &mut list_T, item: listitem_T) {
l.lv_items.push(item);
l.lv_len = l.lv_items.len() as i32;
}
pub fn tv_list_append_owned_tv(l: &mut list_T, tv: typval_T) -> usize {
l.lv_items.push(listitem_T { li_tv: tv });
l.lv_len = l.lv_items.len() as i32;
l.lv_items.len() - 1
}
pub fn tv_list_insert(l: &mut list_T, item: listitem_T, before: usize) {
let at = before.min(l.lv_items.len());
l.lv_items.insert(at, item);
l.lv_len = l.lv_items.len() as i32;
}
pub fn tv_list_insert_tv(l: &mut list_T, tv: &typval_T, before: usize) {
let mut ni = tv_list_item_alloc();
tv_copy(tv, &mut ni.li_tv);
tv_list_insert(l, ni, before);
}
pub fn tv_list_idx_of_item(l: &list_T, tv: &typval_T) -> i32 {
l.lv_items
.iter()
.position(|it| tv_equal(&it.li_tv, tv, false))
.map_or(-1, |i| i as i32)
}
pub fn tv_list_drop_items(l: &mut list_T, i1: usize, i2: usize) {
let end = (i2 + 1).min(l.lv_items.len());
if i1 < end {
l.lv_items.drain(i1..end);
}
l.lv_len = l.lv_items.len() as i32;
}
pub fn tv_list_remove_items(l: &mut list_T, i1: usize, i2: usize) {
tv_list_drop_items(l, i1, i2);
}
pub fn tv_list_item_remove(l: &mut list_T, idx: usize) -> usize {
if idx < l.lv_items.len() {
l.lv_items.remove(idx);
l.lv_len = l.lv_items.len() as i32;
}
idx.min(l.lv_items.len())
}
pub fn tv_list_move_items(l: &mut list_T, i1: usize, i2: usize, tgt: &mut list_T) {
let end = (i2 + 1).min(l.lv_items.len());
if i1 < end {
let moved: Vec<listitem_T> = l.lv_items.drain(i1..end).collect();
l.lv_len = l.lv_items.len() as i32;
tgt.lv_items.extend(moved);
tgt.lv_len = tgt.lv_items.len() as i32;
}
}
pub fn tv_list_init_static(l: &mut list_T) {
*l = list_T::default();
}
pub fn tv_list_init_static10(l: &mut list_T) {
*l = list_T::default();
}
pub fn tv_dict_item_remove(dict: &mut dict_T, key: &str) {
dict.dv_hashtab.shift_remove(key);
}
pub fn tv_dict_item_copy(tv: &typval_T) -> typval_T {
let mut out = typval_T {
v_type: VAR_UNKNOWN,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_special(kSpecialVarNull),
};
tv_copy(tv, &mut out);
out
}
pub fn tv_dict_item_alloc_len(key: &str, key_len: usize) -> (String, typval_T) {
let di_key = String::from_utf8_lossy(&key.as_bytes()[..key_len]).into_owned();
let di_tv = typval_T::default();
(di_key, di_tv)
}
pub fn tv_dict_item_alloc(key: &str) -> (String, typval_T) {
tv_dict_item_alloc_len(key, key.len())
}
pub fn tv_dict_item_free() {}
pub fn tv_dict_free_dict(dict: &mut dict_T) {
dict.dv_hashtab.clear();
dict.dv_watchers.clear();
}
pub fn tv_dict_add_func(d: &mut dict_T, key: &str, fname: &str) -> i32 {
tv_dict_add(
d,
key,
typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(fname.to_string()),
},
)
}
pub fn tv_dict_wrong_func_name(d: &dict_T, tv: &typval_T, key: &str) -> bool {
d.dv_hashtab.contains_key(key)
&& tv.v_type != VAR_FUNC
&& key.chars().next().is_some_and(|c| c.is_ascii_uppercase())
}
pub fn _nothing_conv_empty_dict(dict: &mut dict_T) {
dict.dv_hashtab.clear();
}
pub fn _nothing_conv_dict_end() {}
pub fn _nothing_conv_func_start() {}
pub fn _nothing_conv_func_end() {}
pub fn _nothing_conv_real_list_after_start() {}
pub fn _nothing_conv_list_end() {}
pub fn _nothing_conv_real_dict_after_start() {}
pub fn tv_list_watch_add(_l: &mut list_T) {}
pub fn tv_list_watch_remove(_l: &mut list_T) {}
pub fn tv_list_watch_fix(_l: &mut list_T) {}
#[cfg(test)]
mod tests {
use super::*;
fn nr(n: varnumber_T) -> typval_T {
typval_T {
v_type: VAR_NUMBER,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(n),
}
}
#[test]
fn list_dict_structural_ops() {
let nums = |l: &list_T| -> Vec<varnumber_T> {
l.lv_items
.iter()
.map(|it| match it.li_tv.vval {
v_number(n) => n,
_ => -999,
})
.collect()
};
let mut l = list_T::default();
for n in [10, 20, 30, 40, 50] {
tv_list_append_owned_tv(&mut l, nr(n));
}
assert_eq!(l.lv_len, 5);
tv_list_insert_tv(&mut l, &nr(15), 1);
assert_eq!(nums(&l), vec![10, 15, 20, 30, 40, 50]);
assert_eq!(l.lv_len, 6);
assert_eq!(tv_list_idx_of_item(&l, &nr(30)), 3);
assert_eq!(tv_list_idx_of_item(&l, &nr(999)), -1);
tv_list_drop_items(&mut l, 1, 2);
assert_eq!(nums(&l), vec![10, 30, 40, 50]);
let next = tv_list_item_remove(&mut l, 0);
assert_eq!(next, 0);
assert_eq!(nums(&l), vec![30, 40, 50]);
let mut tgt = list_T::default();
tv_list_move_items(&mut l, 0, 1, &mut tgt);
assert_eq!(nums(&l), vec![50]);
assert_eq!(nums(&tgt), vec![30, 40]);
assert_eq!(tgt.lv_len, 2);
let mut d = dict_T::default();
assert_eq!(tv_dict_add_func(&mut d, "Cb", "MyFunc"), OK);
assert_eq!(tv_dict_add_func(&mut d, "Cb", "Again"), FAIL); assert!(d.dv_hashtab.contains_key("Cb"));
tv_dict_item_remove(&mut d, "Cb");
assert!(!d.dv_hashtab.contains_key("Cb"));
let src = nr(7);
let cp = tv_dict_item_copy(&src);
assert!(tv_equal(&src, &cp, false));
}
#[test]
fn dict_item_alloc_copies_key_and_defaults_tv() {
let (key, tv) = tv_dict_item_alloc("colors");
assert_eq!(key, "colors");
assert_eq!(tv.v_type, VAR_UNKNOWN);
assert_eq!(tv.v_lock, VarLockStatus::VAR_UNLOCKED);
assert!(matches!(tv.vval, v_unknown));
let (key, _) = tv_dict_item_alloc_len("colorscheme", 6);
assert_eq!(key, "colors");
let mut d = dict_T::default();
let (key, mut tv) = tv_dict_item_alloc("n");
tv.v_type = VAR_NUMBER;
tv.vval = v_number(42);
assert_eq!(tv_dict_add(&mut d, &key, tv), OK);
assert_eq!(tv_dict_get_number(&d, "n"), 42);
}
#[test]
fn dict_add_fails_on_existing_key_but_add_tv_overwrites() {
let d = tv_dict_alloc();
let mut db = d.borrow_mut();
assert_eq!(tv_dict_add_nr(&mut db, "a", 1), OK);
assert_eq!(tv_dict_add_nr(&mut db, "a", 2), FAIL);
assert_eq!(tv_dict_get_number(&db, "a"), 1);
tv_dict_add_tv(&mut db, "a", nr(9));
assert_eq!(tv_dict_get_number(&db, "a"), 9);
}
#[test]
fn dict_typed_getters_and_defaults() {
let d = tv_dict_alloc();
let mut db = d.borrow_mut();
tv_dict_add_str(&mut db, "s", "hi");
tv_dict_add_bool(&mut db, "b", kBoolVarTrue);
assert!(tv_dict_has_key(&db, "s"));
assert!(!tv_dict_has_key(&db, "missing"));
assert_eq!(tv_dict_get_string(&db, "s"), Some("hi".to_string()));
assert_eq!(tv_dict_get_string(&db, "missing"), None);
assert_eq!(tv_dict_get_number_def(&db, "missing", 7), 7);
assert_eq!(tv_dict_get_bool(&db, "b", 0), 1);
assert_eq!(tv_dict_get_bool(&db, "missing", 0), 0);
}
#[test]
fn list_find_uidx_reverse_and_copy() {
let l = tv_list_alloc(0);
{
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, 10);
tv_list_append_number(&mut lb, 20);
tv_list_append_number(&mut lb, 30);
}
let lb = l.borrow();
assert_eq!(tv_list_uidx(&lb, 0), 0);
assert_eq!(tv_list_uidx(&lb, -1), 2);
assert_eq!(tv_list_uidx(&lb, 3), -1);
assert_eq!(tv_list_uidx(&lb, -4), -1);
assert_eq!(tv_list_find_nr(&lb, 1, None), 20);
assert_eq!(tv_list_find_nr(&lb, -1, None), 30);
let mut err = false;
assert_eq!(tv_list_find_nr(&lb, 9, Some(&mut err)), -1);
assert!(err);
assert_eq!(tv_list_find_str(&lb, 0), Some("10".to_string()));
assert_eq!(tv_list_find_str(&lb, 9), None);
drop(lb);
tv_list_reverse(&mut l.borrow_mut());
assert_eq!(tv_list_find_nr(&l.borrow(), 0, None), 30);
assert_eq!(tv_list_find_nr(&l.borrow(), 2, None), 10);
let src = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_LOCKED,
vval: v_list(Some(l.clone())),
};
let mut dst = nr(0);
tv_copy(&src, &mut dst);
assert_eq!(dst.v_type, VAR_LIST);
assert_eq!(dst.v_lock, VarLockStatus::VAR_UNLOCKED);
if let v_list(Some(d)) = &dst.vval {
assert!(Rc::ptr_eq(d, &l));
} else {
panic!("expected shared list");
}
}
fn str_tv(s: &str) -> typval_T {
typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(s.to_string()),
}
}
fn blob_tv() -> typval_T {
typval_T {
v_type: VAR_BLOB,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_blob(Some(tv_blob_alloc())),
}
}
#[test]
fn callback_family() {
let func = |n: &str| typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(n.to_string()),
};
let mut c = Callback::None;
callback_copy(&mut c, &Callback::Funcref("Foo".to_string()));
assert_eq!(c, Callback::Funcref("Foo".to_string()));
assert_eq!(callback_to_string(&c), "<vim function: Foo>");
assert!(tv_callback_equal(&c, &Callback::Funcref("Foo".to_string())));
assert!(!tv_callback_equal(
&c,
&Callback::Funcref("Bar".to_string())
));
let mut tv = nr(0);
callback_put(&c, &mut tv);
assert!(matches!((tv.v_type, &tv.vval), (VAR_FUNC, v_string(s)) if s == "Foo"));
callback_free(&mut c);
assert_eq!(c, Callback::None);
let d = tv_dict_alloc();
tv_dict_add_tv(&mut d.borrow_mut(), "cb", func("Handler"));
let mut r = Callback::None;
assert!(tv_dict_get_callback(&d.borrow(), "cb", &mut r));
assert_eq!(r, Callback::Funcref("Handler".to_string()));
let mut r2 = Callback::Funcref("x".to_string());
assert!(tv_dict_get_callback(&d.borrow(), "nope", &mut r2));
assert_eq!(r2, Callback::None);
}
#[test]
fn lock_family() {
use VarLockStatus::*;
assert!(!value_check_lock(VAR_UNLOCKED, None, TV_TRANSLATE));
assert!(value_check_lock(VAR_LOCKED, None, TV_TRANSLATE));
assert!(value_check_lock(VAR_FIXED, Some("x"), TV_TRANSLATE));
let l = tv_list_alloc(0);
{
let mut lb = l.borrow_mut();
lb.lv_items = vec![listitem_T { li_tv: nr(1) }, listitem_T { li_tv: nr(2) }];
lb.lv_len = 2;
}
let mut tv = typval_T {
v_type: VAR_LIST,
v_lock: VAR_UNLOCKED,
vval: v_list(Some(l.clone())),
};
tv_item_lock(&mut tv, -1, true, false);
assert_eq!(tv.v_lock, VAR_LOCKED);
assert_eq!(l.borrow().lv_lock, VAR_LOCKED);
assert_eq!(l.borrow().lv_items[0].li_tv.v_lock, VAR_LOCKED); assert!(tv_check_lock(&tv, None, TV_TRANSLATE));
tv_item_lock(&mut tv, -1, false, false);
assert_eq!(l.borrow().lv_lock, VAR_UNLOCKED);
assert!(!tv_check_lock(&tv, None, TV_TRANSLATE));
let mut fixed = nr(1);
fixed.v_lock = VAR_FIXED;
tv_item_lock(&mut fixed, 1, false, false);
assert_eq!(fixed.v_lock, VAR_FIXED);
}
#[test]
fn blob_index_and_slice() {
let mk = || {
let b = tv_blob_alloc();
b.borrow_mut().bv_ga = vec![10, 20, 30, 40];
typval_T {
v_type: VAR_BLOB,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_blob(Some(b)),
}
};
let mut rt = mk();
let b = match &rt.vval {
v_blob(Some(b)) => b.clone(),
_ => unreachable!(),
};
assert_eq!(
tv_blob_slice_or_index(&b.borrow(), false, 2, 0, false, &mut rt),
OK
);
assert!(matches!((rt.v_type, &rt.vval), (VAR_NUMBER, v_number(30))));
let mut rt = mk();
let b = match &rt.vval {
v_blob(Some(b)) => b.clone(),
_ => unreachable!(),
};
assert_eq!(
tv_blob_slice_or_index(&b.borrow(), false, -1, 0, false, &mut rt),
OK
);
assert!(matches!((rt.v_type, &rt.vval), (VAR_NUMBER, v_number(40))));
let mut rt = mk();
let b = match &rt.vval {
v_blob(Some(b)) => b.clone(),
_ => unreachable!(),
};
assert_eq!(
tv_blob_slice_or_index(&b.borrow(), false, 10, 0, false, &mut rt),
FAIL
);
let mut rt = mk();
let b = match &rt.vval {
v_blob(Some(b)) => b.clone(),
_ => unreachable!(),
};
assert_eq!(
tv_blob_slice_or_index(&b.borrow(), true, 1, 2, false, &mut rt),
OK
);
match (&rt.v_type, &rt.vval) {
(VAR_BLOB, v_blob(Some(nb))) => assert_eq!(nb.borrow().bv_ga, vec![20, 30]),
_ => panic!("expected a blob slice"),
}
}
#[test]
fn arg_type_checks_required_and_optional() {
let args = [nr(5), str_tv("hi")];
assert_eq!(tv_check_for_number_arg(&args, 0), OK);
assert_eq!(tv_check_for_string_arg(&args, 1), OK);
assert_eq!(tv_check_for_string_arg(&args, 0), FAIL);
assert_eq!(tv_check_for_number_arg(&args, 1), FAIL);
assert_eq!(tv_check_for_string_arg(&args, 5), FAIL);
assert_eq!(tv_check_for_opt_string_arg(&args, 5), OK);
assert_eq!(tv_check_for_opt_string_arg(&args, 1), OK);
assert_eq!(tv_check_for_opt_number_arg(&args, 1), FAIL);
assert_eq!(tv_check_for_bool_arg(&[nr(1)], 0), OK);
assert_eq!(tv_check_for_bool_arg(&[nr(0)], 0), OK);
assert_eq!(tv_check_for_bool_arg(&[nr(2)], 0), FAIL);
assert_eq!(tv_check_for_nonempty_string_arg(&[str_tv("x")], 0), OK);
assert_eq!(tv_check_for_nonempty_string_arg(&[str_tv("")], 0), FAIL);
assert!(tv_check_str_or_nr(&nr(1)));
assert!(tv_check_str(&nr(1)));
assert!(tv_check_num(&str_tv("3")));
assert_eq!(tv_check_for_string_or_number_arg(&[nr(1)], 0), OK);
assert_eq!(tv_check_for_string_or_number_arg(&[str_tv("x")], 0), OK);
assert_eq!(tv_check_for_string_or_number_arg(&[blob_tv()], 0), FAIL);
assert_eq!(tv_check_for_buffer_arg(&[nr(2)], 0), OK);
assert_eq!(tv_check_for_lnum_arg(&[str_tv("$")], 0), OK);
assert_eq!(tv_check_for_list_or_blob_arg(&[blob_tv()], 0), OK);
assert_eq!(tv_check_for_list_or_blob_arg(&[nr(1)], 0), FAIL);
assert_eq!(tv_check_for_opt_string_or_list_arg(&[str_tv("x")], 5), OK);
assert_eq!(tv_check_for_opt_string_or_list_arg(&[str_tv("x")], 0), OK);
assert_eq!(tv_check_for_opt_string_or_list_arg(&[nr(1)], 0), FAIL);
}
#[test]
fn blob_get_set_copy_and_ranges() {
let b = tv_blob_alloc();
{
let mut bb = b.borrow_mut();
tv_blob_set_append(&mut bb, 0, 0xde);
tv_blob_set_append(&mut bb, 1, 0xad);
tv_blob_set_append(&mut bb, 2, 0xbe);
tv_blob_set_append(&mut bb, 9, 0xff); assert_eq!(tv_blob_len(&bb), 3);
assert_eq!(tv_blob_get(&bb, 0), 0xde);
tv_blob_set(&mut bb, 0, 0x00);
assert_eq!(tv_blob_get(&bb, 0), 0x00);
}
let mut dst = nr(0);
tv_blob_copy(Some(&b), &mut dst);
if let v_blob(Some(d)) = &dst.vval {
assert!(!Rc::ptr_eq(d, &b));
assert_eq!(d.borrow().bv_ga, b.borrow().bv_ga);
} else {
panic!("expected blob");
}
let mut dst2 = nr(0);
tv_blob_copy(None, &mut dst2);
assert!(matches!(dst2.vval, v_blob(None)));
let src = tv_blob_alloc();
src.borrow_mut().bv_ga = vec![1, 2];
assert_eq!(
tv_blob_set_range(&mut b.borrow_mut(), 0, 1, &src.borrow()),
OK
);
assert_eq!(b.borrow().bv_ga[..2], [1, 2]);
assert_eq!(
tv_blob_set_range(&mut b.borrow_mut(), 0, 2, &src.borrow()),
FAIL
);
}
#[test]
fn dict_extend_clear_env_and_tv_clear() {
let d1 = tv_dict_alloc();
let d2 = tv_dict_alloc();
{
let mut a = d1.borrow_mut();
tv_dict_add_nr(&mut a, "x", 1);
let mut b = d2.borrow_mut();
tv_dict_add_nr(&mut b, "x", 2);
tv_dict_add_str(&mut b, "y", "hi");
}
tv_dict_extend(&mut d1.borrow_mut(), &d2.borrow(), "keep");
assert_eq!(tv_dict_get_number(&d1.borrow(), "x"), 1);
assert!(tv_dict_has_key(&d1.borrow(), "y"));
tv_dict_extend(&mut d1.borrow_mut(), &d2.borrow(), "force");
assert_eq!(tv_dict_get_number(&d1.borrow(), "x"), 2);
let env = tv_dict_to_env(&d1.borrow());
assert!(env.contains(&"y=hi".to_string()));
tv_dict_clear(&mut d1.borrow_mut());
assert_eq!(tv_dict_len(&d1.borrow()), 0);
let mut t = str_tv("gone");
tv_clear(&mut t);
assert_eq!(t.v_type, VAR_UNKNOWN);
let l = tv_list_alloc(0);
l.borrow_mut().lv_lock = VarLockStatus::VAR_LOCKED;
let tv = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
};
assert!(tv_islocked(&tv));
}
#[test]
fn tv2bool_matches_vim_truthiness() {
assert!(!tv2bool(&nr(0)));
assert!(tv2bool(&nr(5)));
assert!(!tv2bool(&typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(String::new()),
}));
assert!(tv2bool(&typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string("x".to_string()),
}));
let l = tv_list_alloc(0);
assert!(!tv2bool(&typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l.clone())),
}));
tv_list_append_number(&mut l.borrow_mut(), 1);
assert!(tv2bool(&typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
}));
}
}