#![allow(non_snake_case)]
pub mod buffer;
pub mod decode;
pub mod encode;
pub mod executor;
pub mod fs;
pub mod funcs;
pub mod funcs_argc;
pub mod list;
pub mod typval;
pub mod typval_defs_h;
pub mod typval_encode_h;
pub mod userfunc;
pub mod vars;
pub mod window;
use std::cell::RefCell;
use std::rc::Rc;
use crate::ported::eval::typval::{
tv_blob_copy, tv_blob_equal, tv_clear, tv_dict_equal, tv_equal, tv_get_float,
tv_get_number_chk, tv_get_string, tv_get_string_chk, tv_list_equal, tv_list_find,
tv_list_find_nr, tv_list_len, tv_list_watch_add,
};
use crate::ported::eval::typval_defs_h::{
blob_T, dict_T, list_T, partial_T, typval_T, typval_vval_union::*, varnumber_T, VarLockStatus,
VarType::*, VARNUMBER_MAX, VARNUMBER_MIN,
};
use crate::ported::eval::vars::skip_var_list;
use crate::ported::eval_h::{exprtype_T, exprtype_T::*, FAIL, OK};
use crate::ported::message::emsg;
use crate::ported::buffer::{buf_T, buflist_findnr, curbuf, ml_get_buf, ml_get_buf_len};
use crate::ported::mbyte::utf_ptr2len;
use crate::ported::window::{colnr_T, curwin, linenr_T, pos_T, win_T};
pub fn num_divide(n1: varnumber_T, n2: varnumber_T) -> varnumber_T {
let result: varnumber_T;
if n2 == 0 {
if n1 == 0 {
result = VARNUMBER_MIN; } else if n1 < 0 {
result = -VARNUMBER_MAX;
} else {
result = VARNUMBER_MAX;
}
} else if n1 == VARNUMBER_MIN && n2 == -1 {
result = VARNUMBER_MAX;
} else {
result = n1 / n2;
}
result
}
pub fn string2float(text: &str) -> (f64, usize) {
let starts = |kw: &str| text.len() >= kw.len() && text[..kw.len()].eq_ignore_ascii_case(kw);
if starts("-inf") {
return (f64::NEG_INFINITY, 4);
}
if starts("inf") {
return (f64::INFINITY, 3);
}
if starts("nan") {
return (f64::NAN, 3);
}
{
let b = text.as_bytes();
let mut k = 0;
let neg = b.first() == Some(&b'-');
if matches!(b.first(), Some(b'+' | b'-')) {
k = 1;
}
if b.len() >= k + 2 && b[k] == b'0' && (b[k + 1] | 0x20) == b'x' {
let hexval = |c: u8| (c as char).to_digit(16).unwrap() as f64;
let mut j = k + 2;
let mut mant = 0.0f64;
let mut any = false;
while j < b.len() && b[j].is_ascii_hexdigit() {
mant = mant * 16.0 + hexval(b[j]);
j += 1;
any = true;
}
if j < b.len() && b[j] == b'.' {
j += 1;
let mut scale = 1.0 / 16.0;
while j < b.len() && b[j].is_ascii_hexdigit() {
mant += hexval(b[j]) * scale;
scale /= 16.0;
j += 1;
any = true;
}
}
if any {
let mut exp = 0i32;
if j < b.len() && (b[j] | 0x20) == b'p' {
let mut e = j + 1;
let mut es = 1i32;
if e < b.len() && matches!(b[e], b'+' | b'-') {
if b[e] == b'-' {
es = -1;
}
e += 1;
}
let mut ev = 0i32;
let mut ed = false;
while e < b.len() && b[e].is_ascii_digit() {
ev = ev * 10 + (b[e] - b'0') as i32;
e += 1;
ed = true;
}
if ed {
exp = es * ev;
j = e;
}
}
let mut val = mant * 2f64.powi(exp);
if neg {
val = -val;
}
return (val, j);
}
}
}
let b = text.as_bytes();
let mut i = 0;
if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
i += 1;
}
let mut saw_digit = false;
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
saw_digit = true;
}
if i < b.len() && b[i] == b'.' {
i += 1;
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
saw_digit = true;
}
}
if !saw_digit {
return (0.0, 0);
}
if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
let mut j = i + 1;
if j < b.len() && (b[j] == b'+' || b[j] == b'-') {
j += 1;
}
if j < b.len() && b[j].is_ascii_digit() {
while j < b.len() && b[j].is_ascii_digit() {
j += 1;
}
i = j;
}
}
(text[..i].parse::<f64>().unwrap_or(0.0), i)
}
pub fn num_modulus(n1: varnumber_T, n2: varnumber_T) -> varnumber_T {
if n2 == 0 {
0
} else {
n1 % n2
}
}
pub fn typval_compare(typ1: &mut typval_T, typ2: &typval_T, r#type: exprtype_T, ic: bool) -> i32 {
let mut n1: varnumber_T; let n2: varnumber_T;
let type_is = r#type == EXPR_IS || r#type == EXPR_ISNOT;
if type_is && typ1.v_type != typ2.v_type {
n1 = (r#type == EXPR_ISNOT) as varnumber_T;
} else if typ1.v_type == VAR_BLOB || typ2.v_type == VAR_BLOB {
if type_is {
n1 = (typ1.v_type == typ2.v_type
&& match (&typ1.vval, &typ2.vval) {
(v_blob(Some(x)), v_blob(Some(y))) => Rc::ptr_eq(x, y),
(v_blob(None), v_blob(None)) => true,
_ => false,
}) as varnumber_T;
if r#type == EXPR_ISNOT {
n1 = (n1 == 0) as varnumber_T;
}
} else if typ1.v_type != typ2.v_type || (r#type != EXPR_EQUAL && r#type != EXPR_NEQUAL) {
if typ1.v_type != typ2.v_type {
emsg("E977: Can only compare Blob with Blob");
} else {
emsg("E978: Invalid operation for Blob");
}
return FAIL;
} else {
n1 = match (&typ1.vval, &typ2.vval) {
(v_blob(Some(x)), v_blob(Some(y))) => tv_blob_equal(x, y),
(v_blob(None), v_blob(None)) => true,
_ => false,
} as varnumber_T;
if r#type == EXPR_NEQUAL {
n1 = (n1 == 0) as varnumber_T;
}
}
} else if typ1.v_type == VAR_LIST || typ2.v_type == VAR_LIST {
if type_is {
n1 = (typ1.v_type == typ2.v_type
&& match (&typ1.vval, &typ2.vval) {
(v_list(Some(x)), v_list(Some(y))) => Rc::ptr_eq(x, y),
(v_list(None), v_list(None)) => true,
_ => false,
}) as varnumber_T;
if r#type == EXPR_ISNOT {
n1 = (n1 == 0) as varnumber_T;
}
} else if typ1.v_type != typ2.v_type || (r#type != EXPR_EQUAL && r#type != EXPR_NEQUAL) {
if typ1.v_type != typ2.v_type {
emsg("E691: Can only compare List with List");
} else {
emsg("E692: Invalid operation for List");
}
return FAIL;
} else {
n1 = match (&typ1.vval, &typ2.vval) {
(v_list(Some(x)), v_list(Some(y))) => tv_list_equal(x, y, ic),
(v_list(None), v_list(None)) => true,
_ => false,
} as varnumber_T;
if r#type == EXPR_NEQUAL {
n1 = (n1 == 0) as varnumber_T;
}
}
} else if typ1.v_type == VAR_DICT || typ2.v_type == VAR_DICT {
if type_is {
n1 = (typ1.v_type == typ2.v_type
&& match (&typ1.vval, &typ2.vval) {
(v_dict(Some(x)), v_dict(Some(y))) => Rc::ptr_eq(x, y),
(v_dict(None), v_dict(None)) => true,
_ => false,
}) as varnumber_T;
if r#type == EXPR_ISNOT {
n1 = (n1 == 0) as varnumber_T;
}
} else if typ1.v_type != typ2.v_type || (r#type != EXPR_EQUAL && r#type != EXPR_NEQUAL) {
if typ1.v_type != typ2.v_type {
emsg("E735: Can only compare Dictionary with Dictionary");
} else {
emsg("E736: Invalid operation for Dictionary");
}
return FAIL;
} else {
n1 = match (&typ1.vval, &typ2.vval) {
(v_dict(Some(x)), v_dict(Some(y))) => tv_dict_equal(x, y, ic),
(v_dict(None), v_dict(None)) => true,
_ => false,
} as varnumber_T;
if r#type == EXPR_NEQUAL {
n1 = (n1 == 0) as varnumber_T;
}
}
} else if matches!(typ1.v_type, VAR_FUNC | VAR_PARTIAL)
|| matches!(typ2.v_type, VAR_FUNC | VAR_PARTIAL)
{
if r#type != EXPR_EQUAL
&& r#type != EXPR_NEQUAL
&& r#type != EXPR_IS
&& r#type != EXPR_ISNOT
{
emsg("E694: Invalid operation for Funcrefs");
return FAIL;
}
n1 = tv_equal(typ1, typ2, ic) as varnumber_T;
if r#type == EXPR_NEQUAL || r#type == EXPR_ISNOT {
n1 = (n1 == 0) as varnumber_T;
}
} else if (typ1.v_type == VAR_FLOAT || typ2.v_type == VAR_FLOAT)
&& r#type != EXPR_MATCH
&& r#type != EXPR_NOMATCH
{
let f1 = tv_get_float(typ1);
let f2 = tv_get_float(typ2);
n1 = match r#type {
EXPR_IS | EXPR_EQUAL => (f1 == f2) as varnumber_T,
EXPR_ISNOT | EXPR_NEQUAL => (f1 != f2) as varnumber_T,
EXPR_GREATER => (f1 > f2) as varnumber_T,
EXPR_GEQUAL => (f1 >= f2) as varnumber_T,
EXPR_SMALLER => (f1 < f2) as varnumber_T,
EXPR_SEQUAL => (f1 <= f2) as varnumber_T,
_ => 0,
};
} else if (typ1.v_type == VAR_NUMBER || typ2.v_type == VAR_NUMBER)
&& r#type != EXPR_MATCH
&& r#type != EXPR_NOMATCH
{
n1 = tv_get_number_chk(typ1, None);
n2 = tv_get_number_chk(typ2, None);
n1 = match r#type {
EXPR_IS | EXPR_EQUAL => (n1 == n2) as varnumber_T,
EXPR_ISNOT | EXPR_NEQUAL => (n1 != n2) as varnumber_T,
EXPR_GREATER => (n1 > n2) as varnumber_T,
EXPR_GEQUAL => (n1 >= n2) as varnumber_T,
EXPR_SMALLER => (n1 < n2) as varnumber_T,
EXPR_SEQUAL => (n1 <= n2) as varnumber_T,
_ => 0,
};
} else {
let s1 = tv_get_string(typ1);
let s2 = tv_get_string(typ2);
let i: i32 = if r#type != EXPR_MATCH && r#type != EXPR_NOMATCH {
mb_strcmp_ic(ic, &s1, &s2)
} else {
0
};
n1 = match r#type {
EXPR_IS | EXPR_EQUAL => (i == 0) as varnumber_T,
EXPR_ISNOT | EXPR_NEQUAL => (i != 0) as varnumber_T,
EXPR_GREATER => (i > 0) as varnumber_T,
EXPR_GEQUAL => (i >= 0) as varnumber_T,
EXPR_SMALLER => (i < 0) as varnumber_T,
EXPR_SEQUAL => (i <= 0) as varnumber_T,
EXPR_MATCH | EXPR_NOMATCH => {
let mut m = pattern_match(&s2, &s1, ic) as varnumber_T;
if r#type == EXPR_NOMATCH {
m = (m == 0) as varnumber_T;
}
m
}
EXPR_UNKNOWN => 0,
};
}
*typ1 = typval_T {
v_type: VAR_NUMBER,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(n1),
};
OK
}
fn mb_strcmp_ic(ic: bool, s1: &str, s2: &str) -> i32 {
let (a, b) = if ic {
(s1.to_lowercase(), s2.to_lowercase())
} else {
(s1.to_string(), s2.to_string())
};
match a.cmp(&b) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
}
fn pattern_match(pat: &str, subject: &str, ic: bool) -> bool {
let ic = ic
|| crate::ported::eval::typval::tv_get_bool(&crate::ported::option::get_option_value(
"ignorecase",
)) != 0;
crate::viml_regex::regex_match(pat, subject, ic)
}
pub const AUTOLOAD_CHAR: u8 = b'#';
pub fn eval_isnamec1(c: u8) -> bool {
c.is_ascii_alphabetic() || c == b'_'
}
pub fn eval_isnamec(c: u8) -> bool {
c.is_ascii_alphanumeric() || c == b'_' || c == b':' || c == AUTOLOAD_CHAR
}
pub fn eval_isdictc(c: u8) -> bool {
c.is_ascii_alphanumeric() || c == b'_'
}
pub fn partial_free(_pt: &partial_T) {}
pub fn partial_unref(_pt: Option<&Rc<partial_T>>) {}
pub fn typval_tostring(arg: Option<&typval_T>, quotes: bool) -> String {
match arg {
None => "(does not exist)".to_string(),
Some(tv) => {
if !quotes && tv.v_type == VAR_STRING {
match &tv.vval {
v_string(s) => s.clone(),
_ => String::new(),
}
} else {
crate::ported::eval::encode::encode_tv2string(tv)
}
}
}
}
pub fn set_selfdict(
rettv: &mut typval_T,
selfdict: Option<&Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::dict_T>>>,
) {
make_partial(selfdict, rettv);
}
pub fn make_partial(
_selfdict: Option<&Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::dict_T>>>,
_rettv: &mut typval_T,
) {
}
pub fn partial_name(pt: &partial_T) -> &str {
&pt.pt_name
}
pub fn func_equal(tv1: &typval_T, tv2: &typval_T, ic: bool) -> bool {
let name = |tv: &typval_T| -> String {
match (tv.v_type, &tv.vval) {
(VAR_FUNC, v_string(s)) => s.clone(),
(VAR_PARTIAL, v_partial(Some(p))) => partial_name(p).to_string(),
_ => String::new(),
}
};
let s1 = name(tv1);
let s2 = name(tv2);
if s1 != s2 {
return false;
}
let dict = |tv: &typval_T| -> Option<Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::dict_T>>> {
match (tv.v_type, &tv.vval) {
(VAR_PARTIAL, v_partial(Some(p))) => p.pt_dict.clone(),
_ => None,
}
};
match (dict(tv1), dict(tv2)) {
(None, None) => {}
(Some(d1), Some(d2)) => {
if !tv_dict_equal(&d1, &d2, ic) {
return false;
}
}
_ => return false,
}
let argv = |tv: &typval_T| -> Vec<typval_T> {
match (tv.v_type, &tv.vval) {
(VAR_PARTIAL, v_partial(Some(p))) => p.pt_argv.clone(),
_ => Vec::new(),
}
};
let a1 = argv(tv1);
let a2 = argv(tv2);
if a1.len() != a2.len() {
return false;
}
a1.iter().zip(a2.iter()).all(|(x, y)| tv_equal(x, y, ic))
}
pub fn eval_init() {}
pub fn eval_clear() {}
pub fn eval_expr_valid_arg(tv: &typval_T) -> bool {
tv.v_type != VAR_UNKNOWN
&& (tv.v_type != VAR_STRING || matches!(&tv.vval, v_string(s) if !s.is_empty()))
}
pub fn typval2string(tv: &typval_T, join_list: bool) -> String {
use crate::ported::eval::encode::encode_tv2string;
use crate::ported::eval::typval::tv_list_join;
if join_list && tv.v_type == VAR_LIST {
let mut out = String::new();
if let v_list(Some(l)) = &tv.vval {
let lb = l.borrow();
tv_list_join(&mut out, &lb, "\n");
if lb.lv_len > 0 {
out.push('\n');
}
}
out
} else if tv.v_type == VAR_LIST || tv.v_type == VAR_DICT {
encode_tv2string(tv)
} else {
tv_get_string(tv)
}
}
pub fn restore_v_event() {}
pub fn get_v_event() -> Option<Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::dict_T>>> {
crate::ported::eval::vars::get_vim_var_dict(crate::ported::eval::vars::vv::VV_EVENT)
}
pub fn get_copyID() -> i32 {
0
}
pub fn get_callback_depth() -> i32 {
0
}
fn vim_isIDc(c: u8) -> bool {
c == b'_' || c.is_ascii_alphanumeric()
}
pub fn get_env_len(s: &str) -> i32 {
s.bytes().take_while(|&c| vim_isIDc(c)).count() as i32
}
pub fn get_id_len(s: &str) -> i32 {
let b = s.as_bytes();
let mut p = 0;
while p < b.len() && eval_isnamec(b[p]) {
if b[p] == b':' {
let len = p;
let is_ns = len == 1 && b"abglstvw".contains(&b[0]);
if len > 1 || !is_ns {
break;
}
}
p += 1;
}
p as i32
}
pub fn skip_luafunc_name(s: &str) -> usize {
s.bytes()
.take_while(|&c| c.is_ascii_alphanumeric() || c == b'_' || c == b'.' || c == b'\'')
.count()
}
pub fn check_luafunc_name(str: &str, paren: bool) -> i32 {
let end = skip_luafunc_name(str);
let term_ok = if paren {
str.as_bytes().get(end) == Some(&b'(')
} else {
end == str.len()
};
if term_ok {
end as i32
} else {
0
}
}
thread_local! {
static echo_hl_id: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
}
pub fn get_echo_hl_id() -> i32 {
echo_hl_id.with(|h| h.get())
}
pub fn ex_echohl(arg: &str) {
let _ = arg;
echo_hl_id.with(|h| h.set(0));
}
pub fn ex_echo(arg: &str, skip: bool, echon: bool) -> String {
let mut arg = arg; let mut rettv = typval_T::default(); let mut atstart = true; let did_emsg_before = crate::ported::message::did_emsg.with(|d| d.get()); let mut out = String::new();
let mut evalarg = evalarg_T {
eval_flags: if skip { 0 } else { EVAL_EVALUATE },
};
while !matches!(arg.as_bytes().first(), None | Some(b'|') | Some(b'\n'))
&& !crate::ported::ex_eval::got_int.with(|g| g.get())
{
let p = arg;
if eval1(&mut arg, &mut rettv, Some(&mut evalarg)) == FAIL {
if !crate::ported::ex_eval::aborting()
&& crate::ported::message::did_emsg.with(|d| d.get()) == did_emsg_before
{
crate::ported::message::semsg(&format!("E15: Invalid expression: \"{p}\""));
}
break; }
if !skip {
if atstart {
atstart = false; } else if !echon {
out.push(' ');
}
out.push_str(&crate::ported::eval::encode::encode_tv2echo(&rettv));
}
crate::ported::eval::typval::tv_clear(&mut rettv); arg = skipwhite(arg); }
out
}
pub fn ex_execute(arg: &str, skip: bool, echomsg: bool, echoerr: bool) -> String {
let mut arg = arg; let mut rettv = typval_T::default(); let mut ret = OK; let mut ga = String::new(); let is_execute = !echomsg && !echoerr;
while !matches!(arg.as_bytes().first(), None | Some(b'|') | Some(b'\n')) {
{
let p = arg;
let mut evalarg = evalarg_T {
eval_flags: if skip { 0 } else { EVAL_EVALUATE },
};
ret = eval1(&mut arg, &mut rettv, Some(&mut evalarg)); if ret == FAIL && !crate::ported::ex_eval::aborting() {
crate::ported::message::semsg(&format!("E15: Invalid expression: \"{p}\""));
}
}
if ret == FAIL {
break; }
if !skip {
let argstr = if is_execute {
crate::ported::eval::typval::tv_get_string(&rettv)
} else if rettv.v_type == VAR_STRING {
crate::ported::eval::encode::encode_tv2echo(&rettv)
} else {
crate::ported::eval::encode::encode_tv2string(&rettv)
};
if !ga.is_empty() {
ga.push(' ');
}
ga.push_str(&argstr);
}
crate::ported::eval::typval::tv_clear(&mut rettv); arg = skipwhite(arg); }
if ret != FAIL && !ga.is_empty() {
if echomsg {
} else if echoerr {
crate::ported::message::emsg(&ga);
} else {
}
}
ga
}
pub fn may_call_simple_func() -> i32 {
NOTDONE
}
pub fn free_for_info() {}
pub fn timer_stop_all() {}
pub fn timer_teardown() {}
pub fn set_argv_var(argv: &[String]) {
use crate::ported::eval::typval::{tv_list_alloc, tv_list_append_string};
let l = tv_list_alloc(argv.len() as isize);
{
let mut lb = l.borrow_mut();
for a in argv {
tv_list_append_string(&mut lb, a);
}
}
crate::ported::eval::vars::set_vim_var_list(crate::ported::eval::vars::vv::VV_ARGV, Some(l));
}
pub fn get_name_len(s: &str) -> i32 {
get_id_len(s)
}
pub fn garbage_collect(_testing: bool) -> bool {
false
}
pub fn free_unref_items(_copy_id: i32) -> i32 {
0
}
pub fn set_ref_in_list_items() -> bool {
false
}
pub fn set_ref_in_item() -> bool {
false
}
pub fn set_ref_in_ht() -> bool {
false
}
pub fn set_ref_in_item_dict() -> bool {
false
}
pub fn set_ref_in_item_list() -> bool {
false
}
pub fn set_ref_in_item_partial() -> bool {
false
}
pub fn set_ref_in_callback() -> bool {
false
}
pub fn set_ref_in_callback_reader() -> bool {
false
}
pub fn eval_has_provider(_feat: &str, _throw_if_fast: bool) -> bool {
false
}
pub fn invoke_prompt_interrupt() -> bool {
false
}
pub fn eval_call_provider(
provider: &str,
_method: &str,
_arguments: Option<Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::list_T>>>,
_discard: bool,
) -> typval_T {
crate::ported::message::semsg(&format!(
"E319: No \"{provider}\" provider found. Run \":checkhealth vim.provider\""
));
typval_T::from(0)
}
pub fn fill_evalarg_from_eap() {}
pub fn clear_evalarg() {}
pub fn clear_lval(lp: &mut lval_T) {
lp.ll_exp_name = None;
lp.ll_newkey = None;
}
pub fn last_set_msg() {}
pub fn eval_foldexpr() -> i32 {
0
}
pub fn eval_foldtext() -> String {
String::new()
}
pub fn set_context_for_expression() {}
pub fn find_job() -> Option<u64> {
None
}
pub fn common_job_callbacks() -> bool {
false
}
pub fn timer_start(
_timeout: i64,
_repeat_count: i32,
_callback: &crate::ported::eval::typval::Callback,
) -> u64 {
0
}
pub fn timer_stop() {}
pub fn timer_decref() {}
pub fn timer_due_cb() {}
pub fn timer_close_cb() {}
pub fn find_timer_by_nr() -> Option<u64> {
None
}
pub fn add_timer_info() {}
pub fn add_timer_info_all() {}
pub fn prompt_get_input() -> Option<String> {
None
}
pub fn prompt_trim_scrollback() {}
pub fn prompt_invoke_callback() {}
pub fn script_host_eval() {}
pub fn next_for_item() -> bool {
false
}
pub const NAMESPACE_CHAR: &[u8] = b"abglstvw";
pub fn char_idx2byte(str: &str, idx: varnumber_T) -> Option<usize> {
if idx >= 0 {
match str.char_indices().nth(idx as usize) {
Some((b, _)) => Some(b),
None => Some(str.len()),
}
} else {
let nchar = (-idx) as usize;
let total = str.chars().count();
if nchar > total {
None
} else {
Some(
str.char_indices()
.nth(total - nchar)
.map_or(str.len(), |(b, _)| b),
)
}
}
}
pub fn char_from_string(str: &str, index: varnumber_T) -> Option<String> {
let chars: Vec<char> = str.chars().collect();
let nchar = if index < 0 {
let n = chars.len() as varnumber_T + index;
if n < 0 {
return None;
}
n
} else {
index
};
chars.get(nchar as usize).map(|c| c.to_string())
}
pub fn string_slice(
str: &str,
first: varnumber_T,
last: varnumber_T,
exclusive: bool,
) -> Option<String> {
let slen = str.len();
let start_byte = char_idx2byte(str, first).unwrap_or(0);
let end_byte = if (last == -1 && !exclusive) || last == VARNUMBER_MAX {
slen
} else {
match char_idx2byte(str, last) {
Some(b) if !exclusive && b < slen => {
b + str[b..].chars().next().map_or(0, char::len_utf8)
}
Some(b) => b,
None => return None,
}
};
if start_byte >= slen || end_byte <= start_byte {
return None;
}
Some(str[start_byte..end_byte].to_string())
}
pub fn eval7_leader(rettv: &mut typval_T, numeric_only: bool, leaders: &str) -> i32 {
let is_float = rettv.v_type == VAR_FLOAT;
let mut f = if is_float {
crate::ported::eval::typval::tv_get_float(rettv)
} else {
0.0
};
let mut error = false;
let mut val = if is_float {
0
} else {
tv_get_number_chk(rettv, Some(&mut error))
};
if error {
return FAIL;
}
let mut cur_float = is_float;
for c in leaders.chars().rev() {
match c {
'!' => {
if numeric_only {
break;
}
if cur_float {
val = i64::from(f == 0.0);
cur_float = false;
} else {
val = i64::from(val == 0);
}
}
'-' => {
if cur_float {
f = -f;
} else {
val = -val;
}
}
_ => {} }
}
*rettv = if cur_float {
typval_T {
v_type: VAR_FLOAT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_float(f),
}
} else {
typval_T::from(val)
};
OK
}
pub fn eval_addblob(tv1: &mut typval_T, tv2: &typval_T) {
let bytes = |tv: &typval_T| -> Vec<u8> {
match &tv.vval {
v_blob(Some(b)) => b.borrow().bv_ga.clone(),
_ => Vec::new(),
}
};
let mut joined = bytes(tv1);
joined.extend(bytes(tv2));
let nb = crate::ported::eval::typval::tv_blob_alloc();
nb.borrow_mut().bv_ga = joined;
crate::ported::eval::typval::tv_blob_set_ret(tv1, nb);
}
pub fn eval_addlist(tv1: &mut typval_T, tv2: &typval_T) -> i32 {
let l1 = match &tv1.vval {
v_list(l) => l.clone(),
_ => None,
};
let l2 = match &tv2.vval {
v_list(l) => l.clone(),
_ => None,
};
let mut var3 = typval_T::from(0);
if crate::ported::eval::typval::tv_list_concat(l1.as_ref(), l2.as_ref(), &mut var3) == FAIL {
return FAIL;
}
*tv1 = var3;
OK
}
pub fn eval_concat_str(tv1: &mut typval_T, tv2: &typval_T) -> i32 {
let s2 = match crate::ported::eval::typval::tv_get_string_chk(tv2) {
Some(s) => s,
None => return FAIL,
};
if grow_string_tv(tv1, &s2) == OK {
return OK;
}
let s1 = tv_get_string(tv1);
*tv1 = typval_T::from(format!("{s1}{s2}"));
OK
}
pub fn eval_addsub_number(tv1: &mut typval_T, tv2: &typval_T, op: u8) -> i32 {
let f = tv1.v_type == VAR_FLOAT || tv2.v_type == VAR_FLOAT;
let mut error = false;
let n1 = if tv1.v_type == VAR_FLOAT {
0
} else {
tv_get_number_chk(tv1, Some(&mut error))
};
if error {
return FAIL;
}
let n2 = if tv2.v_type == VAR_FLOAT {
0
} else {
tv_get_number_chk(tv2, Some(&mut error))
};
if error {
return FAIL;
}
if f {
let a = if tv1.v_type == VAR_FLOAT {
crate::ported::eval::typval::tv_get_float(tv1)
} else {
n1 as f64
};
let b = if tv2.v_type == VAR_FLOAT {
crate::ported::eval::typval::tv_get_float(tv2)
} else {
n2 as f64
};
*tv1 = typval_T {
v_type: VAR_FLOAT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_float(if op == b'+' { a + b } else { a - b }),
};
} else {
*tv1 = typval_T::from(if op == b'+' { n1 + n2 } else { n1 - n2 });
}
OK
}
pub fn eval_multdiv_number(tv1: &mut typval_T, tv2: &typval_T, op: u8) -> i32 {
let use_float = tv1.v_type == VAR_FLOAT || tv2.v_type == VAR_FLOAT;
let mut error = false;
let n1 = if tv1.v_type == VAR_FLOAT {
0
} else {
tv_get_number_chk(tv1, Some(&mut error))
};
if error {
return FAIL;
}
let n2 = if tv2.v_type == VAR_FLOAT {
0
} else {
tv_get_number_chk(tv2, Some(&mut error))
};
if error {
return FAIL;
}
if use_float {
let f1 = if tv1.v_type == VAR_FLOAT {
crate::ported::eval::typval::tv_get_float(tv1)
} else {
n1 as f64
};
let f2 = if tv2.v_type == VAR_FLOAT {
crate::ported::eval::typval::tv_get_float(tv2)
} else {
n2 as f64
};
let r = match op {
b'*' => f1 * f2,
b'/' => {
if f2 == 0.0 {
if f1 == 0.0 {
f64::NAN
} else if f1 > 0.0 {
f64::INFINITY
} else {
f64::NEG_INFINITY
}
} else {
f1 / f2
}
}
_ => {
emsg("E804: Cannot use '%' with Float");
return FAIL;
}
};
*tv1 = typval_T {
v_type: VAR_FLOAT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_float(r),
};
} else {
let r = match op {
b'*' => n1.wrapping_mul(n2),
b'/' => num_divide(n1, n2),
_ => num_modulus(n1, n2),
};
*tv1 = typval_T::from(r);
}
OK
}
pub fn do_string_sub(str: &str, pat: &str, sub: &str, flags: &str) -> String {
crate::viml_regex::regex_substitute(str, pat, sub, flags)
}
pub fn make_expanded_name(name: &str, expr_start: usize, expr_end: usize) -> Option<String> {
let val = eval_to_string(&name[expr_start + 1..expr_end])?;
let expanded = format!("{}{}{}", &name[..expr_start], val, &name[expr_end + 1..]);
match find_name_end(&expanded, 0) {
(_, Some(es), Some(ee)) => make_expanded_name(&expanded, es, ee),
_ => Some(expanded),
}
}
pub fn to_name_end(arg: &str, use_namespace: bool) -> usize {
let bytes = arg.as_bytes();
if bytes.is_empty() || !eval_isnamec1(bytes[0]) {
return 0;
}
let mut p = 1;
while p < bytes.len() && eval_isnamec(bytes[p]) {
if bytes[p] == b':' && (p != 1 || !use_namespace || !b"bgstvw".contains(&bytes[0])) {
break;
}
p += 1;
}
p
}
pub const FNE_INCL_BR: u32 = 1;
pub const FNE_CHECK_START: u32 = 2;
pub fn find_name_end(arg: &str, flags: u32) -> (usize, Option<usize>, Option<usize>) {
let b = arg.as_bytes();
let mut expr_start: Option<usize> = None;
let mut expr_end: Option<usize> = None;
if (flags & FNE_CHECK_START) != 0
&& !(b.first().is_some_and(|&c| eval_isnamec1(c)) || b.first() == Some(&b'{'))
{
return (0, None, None);
}
let mut mb_nest = 0i32;
let mut br_nest = 0i32;
let mut p = 0usize;
while p < b.len() {
let c = b[p];
let cont = eval_isnamec(c)
|| c == b'{'
|| ((flags & FNE_INCL_BR) != 0
&& (c == b'[' || (c == b'.' && b.get(p + 1).is_some_and(|&d| eval_isdictc(d)))))
|| mb_nest != 0
|| br_nest != 0;
if !cont {
break;
}
if c == b'\'' {
p += 1;
while p < b.len() && b[p] != b'\'' {
p += 1;
}
if p >= b.len() {
break;
}
} else if c == b'"' {
p += 1;
while p < b.len() && b[p] != b'"' {
if b[p] == b'\\' && p + 1 < b.len() {
p += 1;
}
p += 1;
}
if p >= b.len() {
break;
}
} else if br_nest == 0 && mb_nest == 0 && c == b':' {
let len = p;
if (len > 1 && b[p - 1] != b'}') || (len == 1 && !NAMESPACE_CHAR.contains(&b[0])) {
break;
}
}
if mb_nest == 0 {
if c == b'[' {
br_nest += 1;
} else if c == b']' {
br_nest -= 1;
}
}
if br_nest == 0 {
if c == b'{' {
mb_nest += 1;
if expr_start.is_none() {
expr_start = Some(p);
}
} else if c == b'}' {
mb_nest -= 1;
if mb_nest == 0 && expr_end.is_none() {
expr_end = Some(p);
}
}
}
p += 1;
}
(p, expr_start, expr_end)
}
pub fn get_literal_key(arg: &str) -> Option<(String, &str)> {
let b = arg.as_bytes();
let is_key = |c: u8| c.is_ascii_alphanumeric() || c == b'_' || c == b'-';
if b.is_empty() || !is_key(b[0]) {
return None;
}
let end = b.iter().position(|&c| !is_key(c)).unwrap_or(b.len());
let key = arg[..end].to_string();
let rest = arg[end..].trim_start_matches([' ', '\t']);
Some((key, rest))
}
pub fn string_to_list(
s: &str,
keepempty: bool,
) -> Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::list_T>> {
let s = if !keepempty && s.ends_with('\n') {
&s[..s.len() - 1]
} else {
s
};
let list = crate::ported::eval::typval::tv_list_alloc(-1);
crate::ported::eval::encode::encode_list_write(&mut list.borrow_mut(), s);
list
}
pub fn save_tv_as_string(tv: &typval_T, endnl: bool, crlf: bool) -> Option<String> {
match tv.v_type {
VAR_UNKNOWN | VAR_NUMBER => None,
VAR_LIST => match &tv.vval {
v_list(Some(l)) => {
let l = l.borrow();
let mut out = String::new();
let n = l.lv_items.len();
for (i, it) in l.lv_items.iter().enumerate() {
out.push_str(&tv_get_string(&it.li_tv).replace('\n', "\0"));
if endnl || i + 1 < n {
if crlf {
out.push('\r');
}
out.push('\n');
}
}
Some(out)
}
_ => Some(String::new()),
},
_ => Some(tv_get_string(tv)),
}
}
fn os_can_exe(name: &str) -> Option<String> {
use std::path::Path;
let is_exe = |p: &Path| -> bool {
match std::fs::metadata(p) {
Ok(m) if m.is_file() => {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
m.permissions().mode() & 0o111 != 0
}
#[cfg(not(unix))]
{
true
}
}
_ => false,
}
};
if name.contains('/') {
return is_exe(Path::new(name)).then(|| name.to_string());
}
let paths = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&paths) {
let cand = dir.join(name);
if is_exe(&cand) {
return Some(cand.to_string_lossy().into_owned());
}
}
None
}
fn shell_build_argv(cmd: &str) -> Vec<String> {
vec!["sh".to_string(), "-c".to_string(), cmd.to_string()]
}
fn os_system(argv: &[String], input: Option<&str>) -> (i32, Option<String>) {
use std::io::Write;
use std::process::{Command, Stdio};
if argv.is_empty() {
return (-1, None);
}
let mut command = Command::new(&argv[0]);
command.args(&argv[1..]).stdout(Stdio::piped());
command.stdin(if input.is_some() {
Stdio::piped()
} else {
Stdio::null()
});
let mut child = match command.spawn() {
Ok(c) => c,
Err(_) => return (-1, None),
};
if let Some(text) = input {
if let Some(mut stdin) = child.stdin.take() {
let _ = stdin.write_all(text.as_bytes());
}
}
match child.wait_with_output() {
Ok(out) => {
let status = out.status.code().unwrap_or(-1);
if out.stdout.is_empty() {
(status, None)
} else {
(
status,
Some(String::from_utf8_lossy(&out.stdout).into_owned()),
)
}
}
Err(_) => (-1, None),
}
}
pub fn tv_to_argv(
cmd_tv: &typval_T,
mut cmd: Option<&mut String>,
mut executable: Option<&mut bool>,
) -> Option<Vec<String>> {
use crate::ported::eval::typval::{tv_get_string_chk, tv_list_len};
if cmd_tv.v_type == VAR_STRING {
let cmd_str = tv_get_string(cmd_tv);
if let Some(c) = cmd.as_deref_mut() {
*c = cmd_str.clone(); }
return Some(shell_build_argv(&cmd_str)); }
if cmd_tv.v_type != VAR_LIST {
crate::ported::message::semsg(&format!(
"E475: Invalid argument: {}",
"expected String or List"
)); return None;
}
let argl = match &cmd_tv.vval {
v_list(Some(l)) => l.clone(),
_ => return None,
}; let argc = tv_list_len(&argl.borrow()); if argc == 0 {
crate::ported::message::emsg("E474: Invalid argument"); return None;
}
let arg0 = tv_get_string_chk(&argl.borrow().lv_items[0].li_tv);
let exe_resolved = arg0.as_deref().and_then(os_can_exe);
let exe_resolved = match (arg0.as_deref(), exe_resolved) {
(Some(_), Some(r)) => r,
(a0, _) => {
if let (Some(a0), Some(exe)) = (a0, executable.as_deref_mut()) {
crate::ported::message::semsg(&format!(
"E475: Invalid value for argument {}: {}",
"cmd",
format!("'{a0}' is not executable")
)); *exe = false; }
return None;
}
};
if let Some(c) = cmd.as_deref_mut() {
*c = exe_resolved.clone(); }
let mut argv: Vec<String> = Vec::with_capacity(argc as usize);
for item in argl.borrow().lv_items.iter() {
match tv_get_string_chk(&item.li_tv) {
Some(a) => argv.push(a), None => return None, }
}
if let Some(first) = argv.first_mut() {
*first = exe_resolved;
}
Some(argv)
}
pub fn get_system_output_as_rettv(argvars: &[typval_T], rettv: &mut typval_T, retlist: bool) {
use crate::ported::eval::typval::{tv_get_number, tv_list_ref};
use crate::ported::eval::vars::{set_vim_var_nr, vv::VV_SHELL_ERROR};
rettv.v_type = VAR_STRING; rettv.vval = v_string(String::new());
let has_input = argvars.len() > 1 && argvars[1].v_type != VAR_UNKNOWN;
let input = if has_input {
match save_tv_as_string(&argvars[1], false, false) {
Some(s) => Some(s),
None => return, }
} else {
None
};
let mut executable = true;
let argv = match tv_to_argv(&argvars[0], None, Some(&mut executable)) {
Some(a) => a,
None => {
if !executable {
set_vim_var_nr(VV_SHELL_ERROR, -1); }
return;
}
};
let (status, res) = os_system(&argv, input.as_deref());
set_vim_var_nr(VV_SHELL_ERROR, status as varnumber_T);
let res = match res {
Some(r) => r,
None => {
if retlist {
crate::ported::eval::typval::tv_list_alloc_ret(rettv, 0); } else {
rettv.vval = v_string(String::new()); }
return;
}
};
if retlist {
let mut keepempty = false; if argvars.len() > 2 && argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN
{
keepempty = tv_get_number(&argvars[2]) != 0; }
let list = string_to_list(&res, keepempty); tv_list_ref(&mut list.borrow_mut()); rettv.v_type = VAR_LIST; rettv.vval = v_list(Some(list));
} else {
rettv.vval = v_string(res.replace('\0', "\u{1}")); }
}
pub fn eval_index(rettv: &mut typval_T, subscript: &str, verbose: bool) -> i32 {
if check_can_index(rettv, true, verbose) != OK {
return FAIL;
}
let eval = |e: &str| -> Option<typval_T> {
crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(e))
};
if let Some(key) = subscript.strip_prefix('.') {
return eval_index_inner(rettv, false, None, None, false, Some(key), verbose);
}
let inner = match subscript
.strip_prefix('[')
.and_then(|s| s.strip_suffix(']'))
{
Some(i) => i,
None => return FAIL,
};
let colon = {
let b = inner.as_bytes();
let (mut depth, mut i, mut pos) = (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 => {
pos = Some(i);
break;
}
_ => {}
}
i += 1;
}
pos
};
let parse_side = |s: &str| -> Result<Option<typval_T>, ()> {
let s = s.trim();
if s.is_empty() {
Ok(None)
} else {
eval(s).map(Some).ok_or(())
}
};
match colon {
None => {
let n1 = match eval(inner.trim()) {
Some(v) => v,
None => return FAIL,
};
eval_index_inner(rettv, false, Some(&n1), None, false, None, verbose)
}
Some(c) => {
let (Ok(v1), Ok(v2)) = (parse_side(&inner[..c]), parse_side(&inner[c + 1..])) else {
return FAIL;
};
eval_index_inner(rettv, true, v1.as_ref(), v2.as_ref(), false, None, verbose)
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn eval_index_inner(
rettv: &mut typval_T,
is_range: bool,
var1: Option<&typval_T>,
var2: Option<&typval_T>,
exclusive: bool,
key: Option<&str>,
verbose: bool,
) -> i32 {
let n1 = match var1 {
Some(v) if rettv.v_type != VAR_DICT => tv_get_number_chk(v, None),
_ => 0,
};
let n2 = if is_range {
if rettv.v_type == VAR_DICT {
if verbose {
emsg("E719: Cannot slice a Dictionary");
}
return FAIL;
}
var2.map_or(VARNUMBER_MAX, |t| tv_get_number_chk(t, None))
} else {
0
};
match rettv.v_type {
VAR_BOOL | VAR_SPECIAL | VAR_FUNC | VAR_FLOAT | VAR_PARTIAL | VAR_UNKNOWN => OK,
VAR_NUMBER | VAR_STRING => {
let s = tv_get_string(rettv);
let v = if is_range {
string_slice(&s, n1, n2, exclusive)
} else {
char_from_string(&s, n1)
};
*rettv = typval_T::from(v.unwrap_or_default());
OK
}
VAR_BLOB => {
let b = match &rettv.vval {
v_blob(Some(b)) => b.clone(),
_ => return OK,
};
let bb = b.borrow();
crate::ported::eval::typval::tv_blob_slice_or_index(
&bb, is_range, n1, n2, exclusive, rettv,
)
}
VAR_LIST => {
let l = match &rettv.vval {
v_list(Some(l)) => l.clone(),
_ => return OK,
};
crate::ported::eval::typval::tv_list_slice_or_index(
&l, is_range, n1, n2, exclusive, rettv, verbose,
)
}
VAR_DICT => {
let d = match &rettv.vval {
v_dict(Some(d)) => d.clone(),
_ => return FAIL,
};
let k = match key
.map(String::from)
.or_else(|| var1.and_then(crate::ported::eval::typval::tv_get_string_chk))
{
Some(k) => k,
None => return FAIL,
};
let found = crate::ported::eval::typval::tv_dict_find(&d.borrow(), &k).cloned();
match found {
Some(v) if !tv_is_luafunc(&v) => {
*rettv = v;
OK
}
_ => {
if verbose {
emsg(&format!("E716: Key not present in Dictionary: \"{k}\""));
}
FAIL
}
}
}
}
}
pub fn check_can_index(rettv: &typval_T, evaluate: bool, verbose: bool) -> i32 {
match rettv.v_type {
VAR_FUNC | VAR_PARTIAL => {
if verbose {
emsg("E695: Cannot index a Funcref");
}
FAIL
}
VAR_FLOAT => {
if verbose {
emsg("E806: Using a Float as a String");
}
FAIL
}
VAR_BOOL | VAR_SPECIAL => {
if verbose {
emsg("E909: Cannot index a special variable");
}
FAIL
}
VAR_UNKNOWN => {
if evaluate {
emsg("E909: Cannot index a special variable");
FAIL
} else {
OK
}
}
VAR_STRING | VAR_NUMBER | VAR_LIST | VAR_DICT | VAR_BLOB => OK,
}
}
pub fn grow_string_tv(tv1: &mut typval_T, s2: &str) -> i32 {
match (tv1.v_type, &mut tv1.vval) {
(VAR_STRING, v_string(s)) => {
s.push_str(s2);
OK
}
_ => FAIL,
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum var_flavour_T {
VAR_FLAVOUR_DEFAULT,
VAR_FLAVOUR_SESSION,
VAR_FLAVOUR_SHADA,
}
pub fn var_flavour(varname: &str) -> var_flavour_T {
use var_flavour_T::*;
let mut chars = varname.chars();
match chars.next() {
Some(first) if first.is_ascii_uppercase() => {
if chars.any(|c| c.is_ascii_lowercase()) {
VAR_FLAVOUR_SESSION
} else {
VAR_FLAVOUR_SHADA
}
}
_ => VAR_FLAVOUR_DEFAULT,
}
}
pub fn eval_expr_string(expr: &typval_T, rettv: &mut typval_T) -> i32 {
let s = tv_get_string(expr);
match crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(s.trim_start()))
{
Some(result) => {
*rettv = result;
OK
}
None => FAIL,
}
}
pub fn eval_to_string_eap(arg: &str, join_list: bool) -> Option<String> {
crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(arg))
.map(|tv| typval2string(&tv, join_list))
}
pub fn eval_to_string(arg: &str) -> Option<String> {
eval_to_string_eap(arg, false)
}
pub fn eval_to_string_safe(arg: &str) -> Option<String> {
eval_to_string_eap(arg, false)
}
pub fn eval_to_string_skip(arg: &str, skip: bool) -> Option<String> {
if skip {
None
} else {
eval_to_string_eap(arg, false)
}
}
pub fn eval_expr_ext(arg: &str) -> Option<typval_T> {
eval_expr(arg)
}
pub fn eval_to_number(arg: &str) -> varnumber_T {
crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(arg))
.map_or(-1, |tv| tv_get_number_chk(&tv, None))
}
pub fn eval_expr(arg: &str) -> Option<typval_T> {
crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(arg))
}
pub fn eval_expr_typval(
expr: &typval_T,
want_func: bool,
argv: &[typval_T],
rettv: &mut typval_T,
) -> i32 {
match expr.v_type {
VAR_PARTIAL => eval_expr_partial(expr, argv, rettv),
VAR_FUNC => eval_expr_func(expr, argv, rettv),
_ if want_func => eval_expr_func(expr, argv, rettv),
_ => eval_expr_string(expr, rettv),
}
}
pub fn eval_expr_to_bool(expr: &typval_T) -> bool {
let mut rettv = typval_T::from(0);
if eval_expr_typval(expr, false, &[], &mut rettv) == FAIL {
return false;
}
tv_get_number_chk(&rettv, None) != 0
}
pub fn eval_to_bool(arg: &str) -> bool {
crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(arg))
.is_some_and(|tv| tv_get_number_chk(&tv, None) != 0)
}
pub fn eval1_emsg(arg: &str, rettv: &mut typval_T) -> i32 {
match crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(arg))
{
Some(result) => {
*rettv = result;
OK
}
None => FAIL,
}
}
pub fn handle_subscript(rettv: &mut typval_T, subscripts: &[&str], verbose: bool) -> i32 {
for sub in subscripts {
let r = if let Some(m) = sub.strip_prefix("->") {
match m.find('(') {
Some(open) if m.ends_with(')') => {
let base = rettv.clone();
eval_method(&m[..open], &m[open + 1..m.len() - 1], &base, rettv)
}
_ => FAIL,
}
} else if let Some(a) = sub.strip_prefix('(').and_then(|s| s.strip_suffix(')')) {
call_func_rettv(rettv, a)
} else {
eval_index(rettv, sub, verbose)
};
if r == FAIL {
return FAIL;
}
}
OK
}
pub fn call_func_rettv(rettv: &mut typval_T, args: &str) -> i32 {
let argvars = match crate::ported::eval::userfunc::get_func_arguments(args) {
Some(a) => a,
None => return FAIL,
};
let callee = rettv.clone();
eval_expr_func(&callee, &argvars, rettv)
}
pub fn eval_func(name: &str, args: &str, basetv: Option<&typval_T>, rettv: &mut typval_T) -> i32 {
match basetv {
Some(base) => eval_method(name, args, base, rettv),
None => crate::ported::eval::userfunc::get_func_tv(name, args, rettv),
}
}
pub fn eval_method(method: &str, args: &str, basetv: &typval_T, rettv: &mut typval_T) -> i32 {
use crate::ported::eval::userfunc::fcerr::*;
let argvars = match crate::ported::eval::userfunc::get_func_arguments(args) {
Some(a) => a,
None => return FAIL,
};
match crate::ported::eval::funcs::call_internal_method(method, &argvars, basetv, rettv) {
FCERR_NONE => OK,
FCERR_UNKNOWN => {
let mut full = Vec::with_capacity(argvars.len() + 1);
full.push(basetv.clone());
full.extend(argvars);
crate::ported::eval::userfunc::call_func(method, &full, rettv)
}
_ => FAIL,
}
}
pub fn eval_lambda(
arg: &mut &str,
rettv: &mut typval_T,
evalarg: Option<&mut evalarg_T>,
verbose: bool,
) -> i32 {
let evaluate = evalarg
.as_deref()
.map(|e| e.eval_flags & EVAL_EVALUATE != 0)
.unwrap_or(false); {
let s = *arg;
*arg = &s[2..];
}
let mut base = rettv.clone(); *rettv = typval_T::default();
let mut ret = crate::ported::eval::userfunc::get_lambda_tv(arg, rettv, evalarg.as_deref()); if ret != OK {
return FAIL; } else if (*arg).as_bytes().first() != Some(&b'(') {
if verbose {
if skipwhite(*arg).as_bytes().first() == Some(&b'(') {
emsg("E274: No white space allowed before parenthesis"); } else {
crate::ported::message::semsg(&format!("E107: Missing parentheses: {}", "lambda"));
}
}
crate::ported::eval::typval::tv_clear(rettv);
ret = FAIL; } else {
let src = *arg;
let b = src.as_bytes();
let (mut depth, mut i, mut endp) = (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 {
endp = Some(i);
break;
}
}
_ => {}
}
i += 1;
}
match endp {
Some(end) => {
let inner = &src[1..end];
*arg = &src[end + 1..];
if evaluate {
let callee = rettv.clone();
match crate::ported::eval::userfunc::get_func_arguments(inner) {
Some(argvars) => {
let mut full = Vec::with_capacity(argvars.len() + 1);
full.push(base.clone());
full.extend(argvars);
ret = eval_expr_partial(&callee, &full, rettv);
}
None => ret = FAIL,
}
} else {
ret = OK;
}
}
None => ret = FAIL,
}
}
if evaluate {
crate::ported::eval::typval::tv_clear(&mut base); }
ret }
pub fn eval_expr_partial(expr: &typval_T, argv: &[typval_T], rettv: &mut typval_T) -> i32 {
let name = match &expr.vval {
v_partial(Some(p)) => partial_name(p).to_string(),
_ => return FAIL,
};
if name.is_empty() {
return FAIL;
}
match crate::ported::eval::typval::CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(expr, argv))
{
Some(result) => {
*rettv = result;
OK
}
None => FAIL,
}
}
pub fn eval_expr_func(expr: &typval_T, argv: &[typval_T], rettv: &mut typval_T) -> i32 {
let name = match (expr.v_type, &expr.vval) {
(VAR_FUNC, v_string(s)) => s.clone(),
_ => tv_get_string(expr),
};
if name.is_empty() {
return FAIL;
}
match crate::ported::eval::typval::CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(expr, argv))
{
Some(result) => {
*rettv = result;
OK
}
None => FAIL,
}
}
pub fn call_vim_function(func: &str, argv: &[typval_T], rettv: &mut typval_T) -> i32 {
let callee = typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(func.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 call_func_retstr(func: &str, argv: &[typval_T]) -> Option<String> {
let mut rettv = typval_T::from(0);
if call_vim_function(func, argv, &mut rettv) == FAIL {
return None;
}
Some(tv_get_string(&rettv))
}
pub fn call_func_retlist(
func: &str,
argv: &[typval_T],
) -> Option<Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::list_T>>> {
let mut rettv = typval_T::from(0);
if call_vim_function(func, argv, &mut rettv) == FAIL {
return None;
}
match (rettv.v_type, rettv.vval) {
(VAR_LIST, v_list(Some(l))) => Some(l),
_ => None,
}
}
pub fn callback_call(
callback: &crate::ported::eval::typval::Callback,
argvars: &[typval_T],
rettv: &mut typval_T,
) -> bool {
use crate::ported::eval::typval::Callback;
match callback {
Callback::None => false,
Callback::Funcref(name) => {
let callee = typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(name.clone()),
};
match crate::ported::eval::typval::CALL_FUNC_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(&callee, argvars))
{
Some(result) => {
*rettv = result;
true
}
None => false,
}
}
}
}
pub fn is_luafunc(partial: &Rc<partial_T>) -> bool {
crate::ported::eval::vars::get_vim_var_partial(crate::ported::eval::vars::vv::VV_LUA)
.is_some_and(|lua| Rc::ptr_eq(&lua, partial))
}
pub fn tv_is_luafunc(tv: &typval_T) -> bool {
matches!((tv.v_type, &tv.vval), (VAR_PARTIAL, v_partial(Some(p))) if is_luafunc(p))
}
pub const EVAL_EVALUATE: i32 = 1;
pub const NOTDONE: i32 = 2;
#[derive(Debug, Default, Clone)]
pub struct evalarg_T {
pub eval_flags: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum glv_status_T {
GLV_FAIL,
GLV_OK,
GLV_STOP,
}
pub const GLV_QUIET: i32 = 2;
pub const GLV_NO_AUTOLOAD: i32 = 4;
pub const GLV_READ_ONLY: i32 = 16;
pub enum LlTv {
Null,
Var(typval_T),
DictItem(Rc<RefCell<dict_T>>, String),
ListItem(Rc<RefCell<list_T>>, usize),
}
impl LlTv {
fn get(&self) -> Option<typval_T> {
match self {
LlTv::Null => None,
LlTv::Var(tv) => Some(tv.clone()),
LlTv::DictItem(d, k) => d.borrow().dv_hashtab.get(k).cloned(),
LlTv::ListItem(l, i) => l.borrow().lv_items.get(*i).map(|it| it.li_tv.clone()),
}
}
fn write(&mut self, tv: typval_T) {
match self {
LlTv::Null => {}
LlTv::Var(slot) => *slot = tv,
LlTv::DictItem(d, k) => {
if let Some(v) = d.borrow_mut().dv_hashtab.get_mut(k) {
*v = tv;
}
}
LlTv::ListItem(l, i) => {
let idx = *i;
if let Some(it) = l.borrow_mut().lv_items.get_mut(idx) {
it.li_tv = tv;
}
}
}
}
}
pub struct lval_T {
pub ll_name: Option<String>,
pub ll_name_len: usize,
pub ll_exp_name: Option<String>,
pub ll_tv: LlTv,
pub ll_li: Option<usize>,
pub ll_list: Option<Rc<RefCell<list_T>>>,
pub ll_range: bool,
pub ll_empty2: bool,
pub ll_n1: i32,
pub ll_n2: i32,
pub ll_dict: Option<Rc<RefCell<dict_T>>>,
pub ll_di: Option<String>,
pub ll_newkey: Option<String>,
pub ll_blob: Option<Rc<RefCell<blob_T>>>,
}
impl Default for lval_T {
fn default() -> Self {
lval_T {
ll_name: None,
ll_name_len: 0,
ll_exp_name: None,
ll_tv: LlTv::Null,
ll_li: None,
ll_list: None,
ll_range: false,
ll_empty2: false,
ll_n1: 0,
ll_n2: 0,
ll_dict: None,
ll_di: None,
ll_newkey: None,
ll_blob: None,
}
}
}
#[allow(clippy::too_many_arguments)]
fn get_lval_dict_item(
lp: &mut lval_T,
name: &str,
key: Option<&str>,
len: i32,
p_off: usize,
var1: Option<&typval_T>,
flags: i32,
unlet: bool,
rettv: Option<&typval_T>,
) -> glv_status_T {
use glv_status_T::*;
let quiet = flags & GLV_QUIET != 0;
let key: String = if len == -1 {
tv_get_string(var1.unwrap())
} else {
key.unwrap()[..len as usize].to_string()
};
lp.ll_list = None;
let cur = lp.ll_tv.get().unwrap_or_default();
let dict_rc = match &cur.vval {
v_dict(Some(d)) => d.clone(),
_ => {
let nd = crate::ported::eval::typval::tv_dict_alloc();
let newcur = typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(nd.clone())),
};
lp.ll_tv.write(newcur);
nd
}
};
lp.ll_dict = Some(dict_rc.clone());
let found = dict_rc.borrow().dv_hashtab.contains_key(&key);
if found && len == -1 && rettv.is_none() {
let is_lua = dict_rc
.borrow()
.dv_hashtab
.get(&key)
.is_some_and(tv_is_luafunc);
if is_lua {
crate::ported::message::semsg("E461: Illegal variable name: v:['lua']");
return GLV_FAIL;
}
}
if !found {
let pc = name.as_bytes().get(p_off).copied();
if pc == Some(b'[') || pc == Some(b'.') || unlet {
if !quiet {
crate::ported::message::semsg(&format!(
"E716: Key not present in Dictionary: \"{key}\""
));
}
return GLV_FAIL;
}
lp.ll_newkey = Some(key);
return GLV_STOP;
}
lp.ll_di = Some(key.clone());
lp.ll_tv = LlTv::DictItem(dict_rc, key);
GLV_OK
}
fn get_lval_blob(
lp: &mut lval_T,
var1: Option<&typval_T>,
var2: Option<&typval_T>,
empty1: bool,
quiet: bool,
) -> i32 {
let cur = lp.ll_tv.get().unwrap_or_default();
let blob_rc = match &cur.vval {
v_blob(Some(b)) => b.clone(),
_ => return FAIL,
};
let bloblen = crate::ported::eval::typval::tv_blob_len(&blob_rc.borrow());
if empty1 {
lp.ll_n1 = 0;
} else {
lp.ll_n1 = crate::ported::eval::typval::tv_get_number(var1.unwrap()) as i32;
}
if crate::ported::eval::typval::tv_blob_check_index(bloblen, lp.ll_n1 as varnumber_T, quiet)
== FAIL
{
return FAIL;
}
if lp.ll_range && !lp.ll_empty2 {
lp.ll_n2 = crate::ported::eval::typval::tv_get_number(var2.unwrap()) as i32;
if crate::ported::eval::typval::tv_blob_check_range(
bloblen,
lp.ll_n1 as varnumber_T,
lp.ll_n2 as varnumber_T,
quiet,
) == FAIL
{
return FAIL;
}
}
lp.ll_blob = Some(blob_rc);
lp.ll_tv = LlTv::Null;
OK
}
fn get_lval_list(
lp: &mut lval_T,
var1: Option<&typval_T>,
var2: Option<&typval_T>,
empty1: bool,
_flags: i32,
quiet: bool,
) -> i32 {
if empty1 {
lp.ll_n1 = 0;
} else {
lp.ll_n1 = crate::ported::eval::typval::tv_get_number(var1.unwrap()) as i32;
}
lp.ll_dict = None;
let cur = lp.ll_tv.get().unwrap_or_default();
let list_rc = match &cur.vval {
v_list(Some(l)) => l.clone(),
_ => return FAIL,
};
lp.ll_list = Some(list_rc.clone());
let mut n1 = lp.ll_n1;
let li = crate::ported::eval::typval::tv_list_check_range_index_one(
&list_rc.borrow(),
&mut n1,
quiet,
);
lp.ll_n1 = n1;
let li = match li {
Some(i) => i,
None => return FAIL,
};
lp.ll_li = Some(li);
if lp.ll_range && !lp.ll_empty2 {
lp.ll_n2 = crate::ported::eval::typval::tv_get_number(var2.unwrap()) as i32;
let mut n1b = lp.ll_n1;
let mut n2 = lp.ll_n2;
if crate::ported::eval::typval::tv_list_check_range_index_two(
&list_rc.borrow(),
&mut n1b,
li,
&mut n2,
quiet,
) == FAIL
{
return FAIL;
}
lp.ll_n1 = n1b;
lp.ll_n2 = n2;
}
lp.ll_tv = LlTv::ListItem(list_rc, li);
OK
}
fn get_lval_subscript(
lp: &mut lval_T,
mut p: usize,
name: &str,
rettv: Option<&typval_T>,
unlet: bool,
flags: i32,
) -> Option<usize> {
let quiet = flags & GLV_QUIET != 0;
let b = name.as_bytes();
let eval = |e: &str| -> Option<typval_T> {
crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(e))
};
while p < b.len()
&& (b[p] == b'['
|| (b[p] == b'.' && b.get(p + 1) != Some(&b'=') && b.get(p + 1) != Some(&b'.')))
{
let cur = lp.ll_tv.get().unwrap_or_default();
if b[p] == b'.' && cur.v_type != VAR_DICT {
if !quiet {
crate::ported::message::semsg(&format!(
"E1203: Dot can only be used on a dictionary: {name}"
));
}
return None;
}
if !matches!(cur.v_type, VAR_LIST | VAR_DICT | VAR_BLOB) {
if !quiet {
emsg("E689: Can only index a List, Dictionary or Blob");
}
return None;
}
if cur.v_type == VAR_LIST && matches!(cur.vval, v_list(None)) {
let mut tmp = typval_T::default();
crate::ported::eval::typval::tv_list_alloc_ret(&mut tmp, -1);
lp.ll_tv.write(tmp);
} else if cur.v_type == VAR_BLOB && matches!(cur.vval, v_blob(None)) {
let mut tmp = typval_T::default();
crate::ported::eval::typval::tv_blob_alloc_ret(&mut tmp);
lp.ll_tv.write(tmp);
}
if lp.ll_range {
if !quiet {
emsg("E708: [:] must come last");
}
return None;
}
let mut len: i32 = -1;
let mut key: Option<String> = None;
let mut var1: Option<typval_T> = None;
let mut var2: Option<typval_T> = None;
let mut empty1 = false;
if b[p] == b'.' {
let ks = p + 1;
let mut ke = ks;
while ke < b.len() && (b[ke].is_ascii_alphanumeric() || b[ke] == b'_') {
ke += 1;
}
if ke == ks {
if !quiet {
emsg("E713: Cannot use empty key after .");
}
return None;
}
len = (ke - ks) as i32;
key = Some(name[ks..ke].to_string());
p = ke;
} else {
let mut depth = 0i32;
let mut i = p;
let mut close = 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 && b[i] == b']' {
close = Some(i);
break;
}
}
_ => {}
}
i += 1;
}
let close = match close {
Some(c) => c,
None => {
if !quiet {
emsg("E111: Missing ']'");
}
return None;
}
};
let inner = &name[p + 1..close];
let colon = {
let ib = inner.as_bytes();
let (mut d, mut i, mut pos) = (0i32, 0usize, None);
while i < ib.len() {
match ib[i] {
b'\'' => {
i += 1;
while i < ib.len() && ib[i] != b'\'' {
i += 1;
}
}
b'"' => {
i += 1;
while i < ib.len() && ib[i] != b'"' {
if ib[i] == b'\\' && i + 1 < ib.len() {
i += 1;
}
i += 1;
}
}
b'[' | b'(' | b'{' => d += 1,
b']' | b')' | b'}' => d -= 1,
b':' if d == 0 => {
pos = Some(i);
break;
}
_ => {}
}
i += 1;
}
pos
};
match colon {
None => {
empty1 = false;
let v = eval(inner.trim())?;
if !crate::ported::eval::typval::tv_check_str(&v) {
return None;
}
var1 = Some(v);
lp.ll_range = false;
}
Some(c) => {
let left = inner[..c].trim();
if left.is_empty() {
empty1 = true;
} else {
empty1 = false;
let v = eval(left)?;
if !crate::ported::eval::typval::tv_check_str(&v) {
return None;
}
var1 = Some(v);
}
if cur.v_type == VAR_DICT {
if !quiet {
emsg("E719: Cannot slice a Dictionary");
}
return None;
}
if let Some(rt) = rettv {
let ok = (rt.v_type == VAR_LIST && matches!(&rt.vval, v_list(Some(_))))
|| (rt.v_type == VAR_BLOB && matches!(&rt.vval, v_blob(Some(_))));
if !ok {
if !quiet {
emsg("E709: [:] requires a List or Blob value");
}
return None;
}
}
let right = inner[c + 1..].trim();
if right.is_empty() {
lp.ll_empty2 = true;
} else {
lp.ll_empty2 = false;
let v = eval(right)?;
if !crate::ported::eval::typval::tv_check_str(&v) {
return None;
}
var2 = Some(v);
}
lp.ll_range = true;
}
}
p = close + 1;
}
if cur.v_type == VAR_DICT {
let glv_status = get_lval_dict_item(
lp,
name,
key.as_deref(),
len,
p,
var1.as_ref(),
flags,
unlet,
rettv,
);
if glv_status == glv_status_T::GLV_FAIL {
return None;
}
if glv_status == glv_status_T::GLV_STOP {
break;
}
} else if cur.v_type == VAR_BLOB {
if get_lval_blob(lp, var1.as_ref(), var2.as_ref(), empty1, quiet) == FAIL {
return None;
}
break;
} else if get_lval_list(lp, var1.as_ref(), var2.as_ref(), empty1, flags, quiet) == FAIL {
return None;
}
}
Some(p)
}
#[allow(clippy::too_many_arguments)]
pub fn get_lval(
name: &str,
rettv: Option<&typval_T>,
lp: &mut lval_T,
unlet: bool,
skip: bool,
flags: i32,
fne_flags: u32,
) -> Option<usize> {
let quiet = flags & GLV_QUIET != 0;
*lp = lval_T::default();
if skip {
lp.ll_name = Some(name.to_string());
let (end, _, _) = find_name_end(name, FNE_INCL_BR | fne_flags);
return Some(end);
}
let (mut p, expr_start, expr_end) = find_name_end(name, fne_flags);
if let (Some(es), Some(ee)) = (expr_start, expr_end) {
let pc = name.as_bytes().get(p).copied();
if unlet
&& !matches!(pc, Some(b' ') | Some(b'\t'))
&& !ends_excmd(pc.unwrap_or(0))
&& pc != Some(b'[')
&& pc != Some(b'.')
{
crate::ported::message::semsg(&format!("E488: Trailing characters: {}", &name[p..]));
return None;
}
lp.ll_exp_name = make_expanded_name(name, es, ee);
lp.ll_name = lp.ll_exp_name.clone();
if lp.ll_exp_name.is_none() {
if !crate::ported::ex_eval::aborting() && !quiet {
crate::ported::message::semsg(&format!("E475: Invalid argument: {name}"));
return None;
}
lp.ll_name_len = 0;
} else {
lp.ll_name_len = lp.ll_name.as_ref().unwrap().len();
}
} else {
lp.ll_name = Some(name[..p].to_string());
lp.ll_name_len = p;
}
let pc = name.as_bytes().get(p).copied();
if (pc != Some(b'[') && pc != Some(b'.')) || lp.ll_name.is_none() {
return Some(p);
}
let ll_name = lp.ll_name.clone().unwrap();
let v = crate::ported::eval::vars::find_var(&ll_name, flags & GLV_NO_AUTOLOAD != 0);
match v {
None => {
if !quiet {
crate::ported::message::semsg(&format!("E121: Undefined variable: {ll_name}"));
}
None
}
Some(v) => {
let is_lua = tv_is_luafunc(&v);
lp.ll_tv = LlTv::Var(v);
if is_lua {
return Some(p);
}
p = get_lval_subscript(lp, p, name, rettv, unlet, flags)?;
lp.ll_name_len = p;
Some(p)
}
}
}
pub fn set_var_lval(
lp: &mut lval_T,
_endp: usize,
rettv: &mut typval_T,
copy: bool,
is_const: bool,
op: Option<&str>,
) {
let ll_name = lp.ll_name.clone().unwrap_or_default();
let is_compound = matches!(op, Some(o) if !o.starts_with('='));
let opc = op.and_then(|o| o.chars().next());
if matches!(lp.ll_tv, LlTv::Null) {
if let Some(blob_rc) = lp.ll_blob.clone() {
if is_compound {
crate::ported::message::semsg(&format!(
"E734: Wrong variable type for {}=",
op.unwrap()
));
return;
}
let lock = blob_rc.borrow().bv_lock;
if crate::ported::eval::typval::value_check_lock(
lock,
Some(&ll_name),
crate::ported::eval::typval::TV_CSTRING,
) {
return;
}
if lp.ll_range && rettv.v_type == VAR_BLOB {
if lp.ll_empty2 {
lp.ll_n2 = crate::ported::eval::typval::tv_blob_len(&blob_rc.borrow()) - 1;
}
let src = match &rettv.vval {
v_blob(Some(b)) => b.clone(),
_ => return,
};
if crate::ported::eval::typval::tv_blob_set_range(
&mut blob_rc.borrow_mut(),
lp.ll_n1 as varnumber_T,
lp.ll_n2 as varnumber_T,
&src.borrow(),
) == FAIL
{
return;
}
} else {
let mut error = false;
let val = tv_get_number_chk(rettv, Some(&mut error));
if !error {
if !(0..=255).contains(&val) {
crate::ported::message::semsg(&format!(
"E1239: Invalid value for blob: 0x{val:X}"
));
} else {
crate::ported::eval::typval::tv_blob_set_append(
&mut blob_rc.borrow_mut(),
lp.ll_n1,
val as u8,
);
}
}
}
} else if is_compound {
if is_const {
emsg("E995: Cannot modify existing variable");
return;
}
if let Some(mut tv) = crate::ported::eval::vars::eval_variable(&ll_name) {
if crate::ported::eval::executor::eexe_mod_op(&mut tv, rettv, opc.unwrap()) == OK {
crate::ported::eval::vars::set_var(&ll_name, lp.ll_name_len, tv, false);
}
}
} else {
crate::ported::eval::vars::set_var_const(
&ll_name,
lp.ll_name_len,
rettv.clone(),
copy,
is_const,
);
}
} else if crate::ported::eval::typval::value_check_lock(
if lp.ll_newkey.is_none() {
lp.ll_tv
.get()
.map_or(VarLockStatus::VAR_UNLOCKED, |t| t.v_lock)
} else {
lp.ll_dict
.as_ref()
.map_or(VarLockStatus::VAR_UNLOCKED, |d| d.borrow().dv_lock)
},
Some(&ll_name),
crate::ported::eval::typval::TV_CSTRING,
) {
} else if lp.ll_range {
if is_const {
emsg("E996: Cannot lock a range");
return;
}
let list_rc = match &lp.ll_list {
Some(l) => l.clone(),
None => return,
};
let src = match &rettv.vval {
v_list(Some(l)) => l.clone(),
_ => return,
};
crate::ported::eval::typval::tv_list_assign_range(
&list_rc,
&src.borrow(),
lp.ll_n1,
lp.ll_n2,
lp.ll_empty2,
op.unwrap_or("="),
&ll_name,
);
} else {
let dict = lp.ll_dict.clone();
let watched = dict
.as_ref()
.is_some_and(|d| !d.borrow().dv_watchers.is_empty());
if is_const {
emsg("E996: Cannot lock a list or dict");
return;
}
let mut oldtv = typval_T::default();
let mut do_assign = true;
if let Some(newkey) = lp.ll_newkey.clone() {
if is_compound {
crate::ported::message::semsg(&format!(
"E716: Key not present in Dictionary: \"{newkey}\""
));
return;
}
let d = match &lp.ll_dict {
Some(d) => d.clone(),
None => return,
};
if crate::ported::eval::typval::tv_dict_wrong_func_name(&d.borrow(), rettv, &newkey) {
return;
}
if crate::ported::eval::typval::tv_dict_add(
&mut d.borrow_mut(),
&newkey,
typval_T::default(),
) == FAIL
{
return;
}
lp.ll_tv = LlTv::DictItem(d, newkey);
} else {
if watched {
if let Some(cur) = lp.ll_tv.get() {
crate::ported::eval::typval::tv_copy(&cur, &mut oldtv);
}
}
if is_compound {
let mut cur = lp.ll_tv.get().unwrap_or_default();
crate::ported::eval::executor::eexe_mod_op(&mut cur, rettv, opc.unwrap());
lp.ll_tv.write(cur);
do_assign = false;
}
}
if do_assign {
if copy {
let mut dest = typval_T::default();
crate::ported::eval::typval::tv_copy(rettv, &mut dest);
lp.ll_tv.write(dest);
} else {
let mut moved = rettv.clone();
moved.v_lock = VarLockStatus::VAR_UNLOCKED;
lp.ll_tv.write(moved);
*rettv = typval_T::default();
}
}
if watched {
if let Some(d) = &dict {
let newtv = lp.ll_tv.get();
if oldtv.v_type == VAR_UNKNOWN {
crate::ported::eval::typval::tv_dict_watcher_notify(
d,
lp.ll_newkey.as_deref().unwrap_or_default(),
newtv.as_ref(),
None,
);
} else {
crate::ported::eval::typval::tv_dict_watcher_notify(
d,
lp.ll_di.as_deref().unwrap_or_default(),
newtv.as_ref(),
Some(&oldtv),
);
}
}
}
}
}
pub fn skipwhite(s: &str) -> &str {
s.trim_start_matches([' ', '\t'])
}
pub fn skipdigits(s: &str) -> &str {
s.trim_start_matches(|c: char| c.is_ascii_digit())
}
pub fn ends_excmd(c: u8) -> bool {
c == 0 || c == b'|' || c == b'"' || c == b'\n'
}
pub fn hex2nr(c: u8) -> u8 {
if c.is_ascii_digit() {
c - b'0'
} else {
(c | 0x20) - b'a' + 10
}
}
pub fn skip_expr(pp: &mut &str, mut evalarg: Option<&mut evalarg_T>) -> i32 {
let save_flags = evalarg.as_deref().map_or(0, |e| e.eval_flags);
if let Some(e) = evalarg.as_deref_mut() {
e.eval_flags &= !EVAL_EVALUATE; }
*pp = skipwhite(pp); let mut rettv = typval_T::default(); let res = eval1(pp, &mut rettv, None);
if let Some(e) = evalarg.as_deref_mut() {
e.eval_flags = save_flags; }
res }
pub fn eval0(arg: &str, rettv: &mut typval_T, mut evalarg: Option<&mut evalarg_T>) -> i32 {
let mut p = skipwhite(arg); let ret = eval1(&mut p, rettv, evalarg.as_deref_mut());
let mut end_error = false;
if ret != FAIL {
end_error = !ends_excmd(p.as_bytes().first().copied().unwrap_or(0)); }
if ret == FAIL || end_error {
if ret != FAIL {
crate::ported::eval::typval::tv_clear(rettv); }
if end_error {
crate::ported::message::semsg(&format!("E488: Trailing characters: {p}"));
} else {
crate::ported::message::semsg(&format!("E15: Invalid expression: \"{arg}\""));
}
return FAIL;
}
ret
}
pub fn eval0_simple_funccal(
arg: &str,
rettv: &mut typval_T,
evalarg: Option<&mut evalarg_T>,
) -> i32 {
let mut r = may_call_simple_func();
if r == NOTDONE {
r = eval0(arg, rettv, evalarg); }
r }
pub fn eval1(arg: &mut &str, rettv: &mut typval_T, mut evalarg: Option<&mut evalarg_T>) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
*rettv = typval_T::default();
if eval2(arg, rettv, evalarg.as_deref_mut()) == FAIL {
return FAIL;
}
if at(*arg, 0) == b'?' {
let op_falsy = at(*arg, 1) == b'?'; let mut local_evalarg = evalarg_T::default();
let ea: &mut evalarg_T = match evalarg.as_deref_mut() {
Some(e) => e,
None => &mut local_evalarg,
};
let orig_flags = ea.eval_flags; let evaluate = ea.eval_flags & EVAL_EVALUATE != 0;
let mut result = false;
if evaluate {
let mut error = false;
if op_falsy {
result = crate::ported::eval::typval::tv2bool(rettv); } else if tv_get_number_chk(rettv, Some(&mut error)) != 0 {
result = true; }
if error || !op_falsy || !result {
crate::ported::eval::typval::tv_clear(rettv); }
if error {
return FAIL;
}
}
if op_falsy {
let s = *arg;
*arg = &s[1..]; }
{
let s = *arg;
*arg = skipwhite(&s[1..]); }
ea.eval_flags = if (if op_falsy { !result } else { result }) {
orig_flags
} else {
orig_flags & !EVAL_EVALUATE
}; let mut var2 = typval_T::default();
if eval1(arg, &mut var2, Some(&mut *ea)) == FAIL {
ea.eval_flags = orig_flags;
return FAIL;
}
if !op_falsy || !result {
*rettv = var2; }
if !op_falsy {
if at(*arg, 0) != b':' {
emsg("E109: Missing ':' after '?'");
if evaluate && result {
crate::ported::eval::typval::tv_clear(rettv);
}
ea.eval_flags = orig_flags;
return FAIL;
}
{
let s = *arg;
*arg = skipwhite(&s[1..]);
}
ea.eval_flags = if !result {
orig_flags
} else {
orig_flags & !EVAL_EVALUATE
}; let mut var3 = typval_T::default();
if eval1(arg, &mut var3, Some(&mut *ea)) == FAIL {
if evaluate && result {
crate::ported::eval::typval::tv_clear(rettv);
}
ea.eval_flags = orig_flags;
return FAIL;
}
if evaluate && !result {
*rettv = var3; }
}
ea.eval_flags = orig_flags; }
OK
}
pub fn eval2(arg: &mut &str, rettv: &mut typval_T, mut evalarg: Option<&mut evalarg_T>) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
if eval3(arg, rettv, evalarg.as_deref_mut()) == FAIL {
return FAIL;
}
if at(*arg, 0) == b'|' && at(*arg, 1) == b'|' {
let mut local_evalarg = evalarg_T::default();
let ea: &mut evalarg_T = match evalarg.as_deref_mut() {
Some(e) => e,
None => &mut local_evalarg,
};
let orig_flags = ea.eval_flags;
let evaluate = ea.eval_flags & EVAL_EVALUATE != 0;
let mut result = false;
if evaluate {
let mut error = false;
if tv_get_number_chk(rettv, Some(&mut error)) != 0 {
result = true;
}
crate::ported::eval::typval::tv_clear(rettv);
if error {
return FAIL;
}
}
while at(*arg, 0) == b'|' && at(*arg, 1) == b'|' {
{
let s = *arg;
*arg = skipwhite(&s[2..]); }
ea.eval_flags = if !result {
orig_flags
} else {
orig_flags & !EVAL_EVALUATE
};
let mut var2 = typval_T::default();
if eval3(arg, &mut var2, Some(&mut *ea)) == FAIL {
return FAIL;
}
if evaluate && !result {
let mut error = false;
if tv_get_number_chk(&var2, Some(&mut error)) != 0 {
result = true;
}
crate::ported::eval::typval::tv_clear(&mut var2);
if error {
return FAIL;
}
}
if evaluate {
*rettv = typval_T::from(result as varnumber_T); }
}
ea.eval_flags = orig_flags;
}
OK
}
pub fn eval3(arg: &mut &str, rettv: &mut typval_T, mut evalarg: Option<&mut evalarg_T>) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
if eval4(arg, rettv, evalarg.as_deref_mut()) == FAIL {
return FAIL;
}
if at(*arg, 0) == b'&' && at(*arg, 1) == b'&' {
let mut local_evalarg = evalarg_T::default();
let ea: &mut evalarg_T = match evalarg.as_deref_mut() {
Some(e) => e,
None => &mut local_evalarg,
};
let orig_flags = ea.eval_flags;
let evaluate = ea.eval_flags & EVAL_EVALUATE != 0;
let mut result = true;
if evaluate {
let mut error = false;
if tv_get_number_chk(rettv, Some(&mut error)) == 0 {
result = false;
}
crate::ported::eval::typval::tv_clear(rettv);
if error {
return FAIL;
}
}
while at(*arg, 0) == b'&' && at(*arg, 1) == b'&' {
{
let s = *arg;
*arg = skipwhite(&s[2..]);
}
ea.eval_flags = if result {
orig_flags
} else {
orig_flags & !EVAL_EVALUATE
};
let mut var2 = typval_T::default();
if eval4(arg, &mut var2, Some(&mut *ea)) == FAIL {
return FAIL;
}
if evaluate && result {
let mut error = false;
if tv_get_number_chk(&var2, Some(&mut error)) == 0 {
result = false;
}
crate::ported::eval::typval::tv_clear(&mut var2);
if error {
return FAIL;
}
}
if evaluate {
*rettv = typval_T::from(result as varnumber_T);
}
}
ea.eval_flags = orig_flags;
}
OK
}
pub fn eval4(arg: &mut &str, rettv: &mut typval_T, mut evalarg: Option<&mut evalarg_T>) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
let mut r#type = EXPR_UNKNOWN; let mut len = 2usize;
if eval5(arg, rettv, evalarg.as_deref_mut()) == FAIL {
return FAIL;
}
let p = *arg; match at(p, 0) {
b'=' => {
if at(p, 1) == b'=' {
r#type = EXPR_EQUAL;
} else if at(p, 1) == b'~' {
r#type = EXPR_MATCH;
}
}
b'!' => {
if at(p, 1) == b'=' {
r#type = EXPR_NEQUAL;
} else if at(p, 1) == b'~' {
r#type = EXPR_NOMATCH;
}
}
b'>' => {
if at(p, 1) != b'=' {
r#type = EXPR_GREATER;
len = 1;
} else {
r#type = EXPR_GEQUAL;
}
}
b'<' => {
if at(p, 1) != b'=' {
r#type = EXPR_SMALLER;
len = 1;
} else {
r#type = EXPR_SEQUAL;
}
}
b'i' => {
if at(p, 1) == b's' {
if at(p, 2) == b'n' && at(p, 3) == b'o' && at(p, 4) == b't' {
len = 5; }
let c = at(p, len);
if !c.is_ascii_alphanumeric() && c != b'_' {
r#type = if len == 2 { EXPR_IS } else { EXPR_ISNOT }; }
}
}
_ => {}
}
if r#type != EXPR_UNKNOWN {
let ic;
if at(p, len) == b'?' {
ic = true;
len += 1;
} else if at(p, len) == b'#' {
ic = false;
len += 1;
} else {
ic = crate::ported::eval::typval::tv_get_bool(
&crate::ported::option::get_option_value("ignorecase"),
) != 0; }
*arg = skipwhite(&p[len..]);
let mut var2 = typval_T::default();
if eval5(arg, &mut var2, evalarg.as_deref_mut()) == FAIL {
crate::ported::eval::typval::tv_clear(rettv);
return FAIL;
}
let evaluate = evalarg
.as_deref()
.map_or(false, |e| e.eval_flags & EVAL_EVALUATE != 0);
if evaluate {
let ret = typval_compare(rettv, &var2, r#type, ic); return ret;
}
}
OK
}
pub fn eval5(arg: &mut &str, rettv: &mut typval_T, mut evalarg: Option<&mut evalarg_T>) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
if eval6(arg, rettv, evalarg.as_deref_mut(), false) == FAIL {
return FAIL;
}
loop {
let op = at(*arg, 0);
let concat = op == b'.';
if op != b'+' && op != b'-' && !concat {
break;
}
let evaluate = evalarg
.as_deref()
.map_or(false, |e| e.eval_flags & EVAL_EVALUATE != 0); if (op != b'+' || (rettv.v_type != VAR_LIST && rettv.v_type != VAR_BLOB))
&& (op == b'.' || rettv.v_type != VAR_FLOAT)
&& evaluate
{
if (op == b'.' && !crate::ported::eval::typval::tv_check_str(rettv))
|| (op != b'.' && !crate::ported::eval::typval::tv_check_num(rettv))
{
crate::ported::eval::typval::tv_clear(rettv);
return FAIL;
}
}
{
let s = *arg;
if op == b'.' && at(s, 1) == b'.' {
*arg = &s[1..]; }
}
{
let s = *arg;
*arg = skipwhite(&s[1..]); }
let mut var2 = typval_T::default();
if eval6(arg, &mut var2, evalarg.as_deref_mut(), op == b'.') == FAIL {
crate::ported::eval::typval::tv_clear(rettv);
return FAIL;
}
if evaluate {
if op == b'.' {
if eval_concat_str(rettv, &var2) == FAIL {
return FAIL;
}
} else if op == b'+' && rettv.v_type == VAR_BLOB && var2.v_type == VAR_BLOB {
eval_addblob(rettv, &var2);
} else if op == b'+' && rettv.v_type == VAR_LIST && var2.v_type == VAR_LIST {
if eval_addlist(rettv, &var2) == FAIL {
return FAIL;
}
} else if eval_addsub_number(rettv, &var2, op) == FAIL {
return FAIL;
}
}
}
OK
}
pub fn eval6(
arg: &mut &str,
rettv: &mut typval_T,
mut evalarg: Option<&mut evalarg_T>,
want_string: bool,
) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
if eval7(arg, rettv, evalarg.as_deref_mut(), want_string) == FAIL {
return FAIL;
}
loop {
let op = at(*arg, 0);
if op != b'*' && op != b'/' && op != b'%' {
break;
}
let evaluate = evalarg
.as_deref()
.map_or(false, |e| e.eval_flags & EVAL_EVALUATE != 0);
{
let s = *arg;
*arg = skipwhite(&s[1..]);
}
let mut var2 = typval_T::default();
if eval7(arg, &mut var2, evalarg.as_deref_mut(), false) == FAIL {
return FAIL;
}
if evaluate {
if eval_multdiv_number(rettv, &var2, op) == FAIL {
return FAIL;
}
}
}
OK
}
pub fn eval7(
arg: &mut &str,
rettv: &mut typval_T,
mut evalarg: Option<&mut evalarg_T>,
want_string: bool,
) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
let evaluate = evalarg
.as_deref()
.map_or(false, |e| e.eval_flags & EVAL_EVALUATE != 0); let verbose = true;
rettv.v_type = VAR_UNKNOWN; rettv.vval = crate::ported::eval::typval_defs_h::typval_vval_union::v_unknown;
let start_leader = *arg;
let mut p: &str = *arg;
while matches!(at(p, 0), b'!' | b'-' | b'+') {
let s = p;
p = skipwhite(&s[1..]);
}
let leaders = &start_leader[..start_leader.len() - p.len()];
let find_close = |s: &str, open: u8, close: u8| -> Option<usize> {
let b = s.as_bytes();
let mut depth = 0i32;
let mut i = 0usize;
while i < b.len() {
let c = b[i];
if c == b'\'' {
i += 1;
while i < b.len() && b[i] != b'\'' {
i += 1;
}
} else if c == b'"' {
i += 1;
while i < b.len() && b[i] != b'"' {
if b[i] == b'\\' && i + 1 < b.len() {
i += 1;
}
i += 1;
}
} else if c == open {
depth += 1;
} else if c == close {
depth -= 1;
if depth == 0 {
return Some(i);
}
}
i += 1;
}
None
};
let mut ret;
let mut did_numeric = false;
match at(p, 0) {
b'0'..=b'9' => {
let mut pp = p;
ret = eval_number(&mut pp, rettv, evaluate, want_string);
p = pp;
did_numeric = true;
if ret == OK && evaluate && !leaders.is_empty() {
eval7_leader(rettv, true, leaders);
}
}
b'"' => {
let mut pp = p;
ret = eval_string(&mut pp, rettv, evaluate, false);
p = pp;
}
b'\'' => {
let mut pp = p;
ret = eval_lit_string(&mut pp, rettv, evaluate, false);
p = pp;
}
b'[' => {
let mut pp = p;
ret = eval_list(&mut pp, rettv, evalarg.as_deref_mut());
p = pp;
}
b'#' => {
let mut pp = p;
ret = eval_lit_dict(&mut pp, rettv, evalarg.as_deref_mut());
p = pp;
}
b'{' => {
let mut pp = p;
ret = eval_dict(&mut pp, rettv, evalarg.as_deref_mut(), false);
p = pp;
}
b'&' => {
let mut pp = p;
ret = eval_option(&mut pp, rettv, evaluate);
p = pp;
}
b'$' => {
let mut pp = p;
if at(pp, 1) == b'"' || at(pp, 1) == b'\'' {
ret = eval_interp_string(&mut pp, rettv, evaluate, evalarg.as_deref_mut());
} else {
ret = eval_env_var(&mut pp, rettv, evaluate);
}
p = pp;
}
b'@' => {
{
let s = p;
p = &s[1..]; }
if evaluate {
let name = at(p, 0) as char;
let s = crate::ported::ops::get_reg_contents(name)
.map(|v| v.join("\n"))
.unwrap_or_default();
*rettv = typval_T::from(s);
}
if at(p, 0) != 0 {
let s = p;
p = &s[1..];
}
ret = OK;
}
b'(' => {
{
let s = p;
p = skipwhite(&s[1..]);
}
let mut pp = p;
ret = eval1(&mut pp, rettv, evalarg.as_deref_mut()); p = pp;
if at(p, 0) == b')' {
let s = p;
p = &s[1..];
} else if ret == OK {
emsg("E110: Missing ')'");
crate::ported::eval::typval::tv_clear(rettv);
ret = FAIL;
}
}
_ => {
ret = NOTDONE; }
}
if ret == NOTDONE {
let name_at = p;
let len = get_name_len(name_at); if len <= 0 {
ret = FAIL; } else {
let name = &name_at[..len as usize];
p = &name_at[len as usize..];
let after = skipwhite(p);
if at(after, 0) == b'(' {
if let Some(cl) = find_close(after, b'(', b')') {
let inner = &after[1..cl];
ret = eval_func(name, inner, None, rettv);
let s = after;
p = &s[cl + 1..];
} else {
ret = FAIL;
}
} else if evaluate {
match crate::ported::eval::vars::eval_variable(name) {
Some(v) => {
*rettv = v;
ret = OK;
}
None => {
crate::ported::message::semsg(&format!("E121: Undefined variable: {name}"));
ret = FAIL;
}
}
} else {
ret = OK; }
}
}
{
let s = p;
p = skipwhite(s); }
if ret == OK {
let mut subs: Vec<String> = Vec::new();
loop {
let c0 = at(p, 0);
if c0 == b'[' {
if let Some(cl) = find_close(p, b'[', b']') {
subs.push(p[..=cl].to_string());
let s = p;
p = &s[cl + 1..];
} else {
break;
}
} else if c0 == b'.' && eval_isdictc(at(p, 1)) {
let mut e = 1usize;
while eval_isdictc(at(p, e)) {
e += 1;
}
subs.push(p[..e].to_string());
let s = p;
p = &s[e..];
} else if c0 == b'(' {
if let Some(cl) = find_close(p, b'(', b')') {
subs.push(p[..=cl].to_string());
let s = p;
p = &s[cl + 1..];
} else {
break;
}
} else if c0 == b'-' && at(p, 1) == b'>' {
let mut e = 2usize;
while eval_isnamec(at(p, e)) {
e += 1;
}
if at(p, e) == b'(' {
if let Some(cl) = find_close(&p[e..], b'(', b')') {
let total = e + cl + 1;
subs.push(p[..total].to_string());
let s = p;
p = &s[total..];
} else {
break;
}
} else {
break;
}
} else {
break;
}
}
if !subs.is_empty() {
let refs: Vec<&str> = subs.iter().map(|x| x.as_str()).collect();
ret = handle_subscript(rettv, &refs, verbose);
}
}
if ret == OK && evaluate && !leaders.is_empty() {
let rem: &str = if did_numeric {
match leaders.rfind('!') {
Some(i) => &leaders[..=i],
None => "",
}
} else {
leaders
};
if !rem.is_empty() {
eval7_leader(rettv, false, rem);
}
}
*arg = p;
ret
}
pub fn eval_number(arg: &mut &str, rettv: &mut typval_T, evaluate: bool, want_string: bool) -> i32 {
let src = *arg;
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
let mut p = skipdigits(&src[1..]); let mut get_float = false;
if !want_string && at(p, 0) == b'.' && at(p, 1).is_ascii_digit() {
get_float = true;
p = skipdigits(&p[2..]);
if at(p, 0) == b'e' || at(p, 0) == b'E' {
let mut q = &p[1..];
if at(q, 0) == b'-' || at(q, 0) == b'+' {
q = &q[1..];
}
if !at(q, 0).is_ascii_digit() {
get_float = false;
} else {
p = skipdigits(&q[1..]);
}
}
if at(p, 0).is_ascii_alphabetic() || at(p, 0) == b'.' {
get_float = false; }
}
if get_float {
let (f, consumed) = string2float(src); *arg = &src[consumed..];
if evaluate {
*rettv = typval_T {
v_type: VAR_FLOAT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_float(f),
};
}
} else if at(src, 0) == b'0' && (at(src, 1) == b'z' || at(src, 1) == b'Z') {
let blob = if evaluate {
Some(crate::ported::eval::typval::tv_blob_alloc())
} else {
None
};
let b = src.as_bytes();
let mut bp = 2usize; while bp < b.len() && b[bp].is_ascii_hexdigit() {
if !(bp + 1 < b.len() && b[bp + 1].is_ascii_hexdigit()) {
if blob.is_some() {
emsg("E973: Blob literal should have an even number of hex characters");
}
return FAIL;
}
if let Some(ref bl) = blob {
bl.borrow_mut()
.bv_ga
.push((hex2nr(b[bp]) << 4) + hex2nr(b[bp + 1]));
}
if bp + 3 < b.len() && b[bp + 2] == b'.' && b[bp + 3].is_ascii_hexdigit() {
bp += 1; }
bp += 2;
}
if let Some(bl) = blob {
crate::ported::eval::typval::tv_blob_set_ret(rettv, bl);
}
*arg = &src[bp..];
} else {
let mut len = 0i32;
let mut n: varnumber_T = 0;
crate::ported::charset::vim_str2nr(
src,
None,
Some(&mut len),
crate::ported::charset::STR2NR_ALL,
Some(&mut n),
None,
0,
true,
None,
);
if len == 0 {
if evaluate {
crate::ported::message::semsg(&format!("E15: Invalid expression: \"{src}\""));
}
return FAIL;
}
*arg = &src[len as usize..];
if evaluate {
*rettv = typval_T::from(n);
}
}
OK
}
pub fn eval_string(arg: &mut &str, rettv: &mut typval_T, evaluate: bool, interpolate: bool) -> i32 {
let src = *arg;
let b = src.as_bytes();
let off = if interpolate { 0usize } else { 1 };
let mut out = String::new();
let mut i = off;
let mut term: u8 = 0;
while i < b.len() {
let c = b[i];
if c == b'"' {
term = b'"'; break;
}
if c == b'\\' && i + 1 < b.len() {
i += 1;
let nc = b[i];
match nc {
b'b' => {
out.push('\u{08}');
i += 1;
} b'e' => {
out.push('\u{1b}');
i += 1;
} b'f' => {
out.push('\u{0c}');
i += 1;
} b'n' => {
out.push('\n');
i += 1;
} b'r' => {
out.push('\r');
i += 1;
} b't' => {
out.push('\t');
i += 1;
} b'X' | b'x' | b'u' | b'U' => {
if i + 1 < b.len() && b[i + 1].is_ascii_hexdigit() {
let up = nc & !0x20; let mut n = if up == b'X' {
2
} else if nc == b'u' {
4
} else {
8
};
let mut nr: u32 = 0;
while n > 0 && i + 1 < b.len() && b[i + 1].is_ascii_hexdigit() {
i += 1;
nr = (nr << 4) + hex2nr(b[i]) as u32;
n -= 1;
}
i += 1;
if up != b'X' {
if let Some(ch) = char::from_u32(nr) {
out.push(ch); }
} else {
out.push(char::from(nr as u8)); }
}
}
b'0'..=b'7' => {
let mut val = (b[i] - b'0') as u32;
i += 1;
if i < b.len() && (b'0'..=b'7').contains(&b[i]) {
val = (val << 3) + (b[i] - b'0') as u32;
i += 1;
if i < b.len() && (b'0'..=b'7').contains(&b[i]) {
val = (val << 3) + (b[i] - b'0') as u32;
i += 1;
}
}
out.push(char::from(val as u8));
}
b'<' => {
out.push('<');
i += 1;
}
_ => {
let ch = src[i..].chars().next().unwrap();
out.push(ch);
i += ch.len_utf8();
}
}
continue;
} else if interpolate && (c == b'{' || c == b'}') {
if c == b'{' && b.get(i + 1) != Some(&b'{') {
term = b'{'; break;
}
i += 1; if i < b.len() {
let ch = src[i..].chars().next().unwrap();
out.push(ch);
i += ch.len_utf8();
}
continue;
} else {
let ch = src[i..].chars().next().unwrap();
out.push(ch);
i += ch.len_utf8();
}
}
if term != b'"' && !(interpolate && term == b'{') {
crate::ported::message::semsg(&format!("E114: Missing quote: {src}")); return FAIL;
}
if !evaluate {
*arg = &src[i + off..]; return OK;
}
*rettv = typval_T::from(out); let mut end = i;
if term == b'"' && !interpolate {
end += 1; }
*arg = &src[end..];
OK
}
pub fn eval_lit_string(
arg: &mut &str,
rettv: &mut typval_T,
evaluate: bool,
interpolate: bool,
) -> i32 {
let src = *arg;
let b = src.as_bytes();
let off = if interpolate { 0usize } else { 1 };
let mut out = String::new();
let mut i = off;
let mut term: u8 = 0;
while i < b.len() {
let c = b[i];
if c == b'\'' {
if b.get(i + 1) != Some(&b'\'') {
term = b'\''; break;
}
out.push('\''); i += 2;
continue;
} else if interpolate && c == b'{' {
if b.get(i + 1) != Some(&b'{') {
term = b'{'; break;
}
out.push('{'); i += 2;
continue;
} else if interpolate && c == b'}' {
i += 1; if b.get(i) != Some(&b'}') {
crate::ported::message::semsg(&format!(
"E1278: Stray '}}' without a matching '{{': {src}"
));
return FAIL;
}
out.push('}');
i += 1;
continue;
} else {
let ch = src[i..].chars().next().unwrap();
out.push(ch);
i += ch.len_utf8();
}
}
if term != b'\'' && !(interpolate && term == b'{') {
crate::ported::message::semsg(&format!("E115: Missing quote: {src}")); return FAIL;
}
if !evaluate {
*arg = &src[i + off..]; return OK;
}
*rettv = typval_T::from(out); *arg = &src[i + off..]; OK
}
pub fn eval_interp_string(
arg: &mut &str,
rettv: &mut typval_T,
evaluate: bool,
mut evalarg: Option<&mut evalarg_T>,
) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
let mut ga = String::new();
{
let s = *arg;
*arg = &s[1..];
}
let quote = at(*arg, 0);
{
let s = *arg;
*arg = &s[1..];
}
let mut ret;
loop {
let mut tv = typval_T::default();
if quote == b'"' {
ret = eval_string(arg, &mut tv, evaluate, true);
} else {
ret = eval_lit_string(arg, &mut tv, evaluate, true);
}
if ret == FAIL {
break;
}
if evaluate {
ga.push_str(&crate::ported::eval::typval::tv_get_string(&tv)); }
if at(*arg, 0) != b'{' {
let s = *arg;
*arg = &s[1..];
break;
}
{
let s = *arg;
*arg = &s[1..];
}
let mut etv = typval_T::default();
if eval1(arg, &mut etv, evalarg.as_deref_mut()) == FAIL {
ret = FAIL;
break;
}
{
let s = *arg;
*arg = skipwhite(s);
}
if at(*arg, 0) != b'}' {
crate::ported::message::semsg(&format!("E1279: Missing '}}': {}", *arg));
ret = FAIL;
break;
}
{
let s = *arg;
*arg = &s[1..];
}
if evaluate {
ga.push_str(&crate::ported::eval::typval::tv_get_string(&etv));
}
}
let _ = ret;
*rettv = typval_T::from(ga);
OK
}
pub fn eval_list(arg: &mut &str, rettv: &mut typval_T, mut evalarg: Option<&mut evalarg_T>) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
let evaluate = evalarg
.as_deref()
.map_or(false, |e| e.eval_flags & EVAL_EVALUATE != 0); let l = if evaluate {
Some(crate::ported::eval::typval::tv_list_alloc(-1))
} else {
None
};
{
let s = *arg;
*arg = skipwhite(&s[1..]); }
while at(*arg, 0) != b']' && at(*arg, 0) != 0 {
let mut tv = typval_T::default();
if eval1(arg, &mut tv, evalarg.as_deref_mut()) == FAIL {
return FAIL;
}
if evaluate {
tv.v_lock = VarLockStatus::VAR_UNLOCKED;
if let Some(ref l) = l {
crate::ported::eval::typval::tv_list_append_owned_tv(&mut l.borrow_mut(), tv);
}
}
let had_comma = at(*arg, 0) == b',';
if had_comma {
let s = *arg;
*arg = skipwhite(&s[1..]);
}
if at(*arg, 0) == b']' {
break;
}
if !had_comma {
crate::ported::message::semsg(&format!("E696: Missing comma in List: {}", *arg));
return FAIL;
}
}
if at(*arg, 0) != b']' {
crate::ported::message::semsg(&format!("E697: Missing end of List ']': {}", *arg)); return FAIL;
}
{
let s = *arg;
*arg = skipwhite(&s[1..]); }
if evaluate {
if let Some(l) = l {
*rettv = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
};
}
}
OK
}
pub fn eval_dict(
arg: &mut &str,
rettv: &mut typval_T,
mut evalarg: Option<&mut evalarg_T>,
literal: bool,
) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
let evaluate = evalarg
.as_deref()
.map_or(false, |e| e.eval_flags & EVAL_EVALUATE != 0); let src = *arg;
let curly = skipwhite(&src[1..]);
if at(curly, 0) != b'}' && !literal {
let mut cp = curly;
let mut tv = typval_T::default();
if eval1(&mut cp, &mut tv, None) == OK && at(skipwhite(cp), 0) == b'}' {
return NOTDONE; }
}
let d = if evaluate {
Some(crate::ported::eval::typval::tv_dict_alloc())
} else {
None
};
{
let s = *arg;
*arg = skipwhite(&s[1..]); }
while at(*arg, 0) != b'}' && at(*arg, 0) != 0 {
let mut tvkey = typval_T::default();
let keyok = if literal {
match get_literal_key(*arg) {
Some((k, rest)) => {
tvkey = typval_T::from(k);
*arg = rest;
true
}
None => false,
}
} else {
eval1(arg, &mut tvkey, evalarg.as_deref_mut()) == OK
};
if !keyok {
return FAIL;
}
if at(*arg, 0) != b':' {
crate::ported::message::semsg(&format!("E720: Missing colon in Dictionary: {}", *arg));
return FAIL;
}
let key = if evaluate {
match crate::ported::eval::typval::tv_get_string_buf_chk(&tvkey) {
Some(k) => k,
None => return FAIL, }
} else {
String::new()
};
{
let s = *arg;
*arg = skipwhite(&s[1..]); }
let mut tv = typval_T::default();
if eval1(arg, &mut tv, evalarg.as_deref_mut()) == FAIL {
return FAIL;
}
if evaluate {
if let Some(ref d) = d {
if crate::ported::eval::typval::tv_dict_find(&d.borrow(), &key).is_some() {
crate::ported::message::semsg(&format!(
"E721: Duplicate key in Dictionary: \"{key}\""
)); return FAIL;
}
tv.v_lock = VarLockStatus::VAR_UNLOCKED;
crate::ported::eval::typval::tv_dict_add(&mut d.borrow_mut(), &key, tv);
}
}
let had_comma = at(*arg, 0) == b',';
if had_comma {
let s = *arg;
*arg = skipwhite(&s[1..]);
}
if at(*arg, 0) == b'}' {
break;
}
if !had_comma {
crate::ported::message::semsg(&format!("E722: Missing comma in Dictionary: {}", *arg));
return FAIL;
}
}
if at(*arg, 0) != b'}' {
crate::ported::message::semsg(&format!("E723: Missing end of Dictionary '}}': {}", *arg)); return FAIL;
}
{
let s = *arg;
*arg = skipwhite(&s[1..]); }
if evaluate {
if let Some(d) = d {
*rettv = typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(d)),
};
}
}
OK
}
pub fn eval_lit_dict(arg: &mut &str, rettv: &mut typval_T, evalarg: Option<&mut evalarg_T>) -> i32 {
let at = |s: &str, i: usize| s.as_bytes().get(i).copied().unwrap_or(0);
if at(*arg, 1) == b'{' {
{
let s = *arg;
*arg = &s[1..]; }
eval_dict(arg, rettv, evalarg, true)
} else {
NOTDONE }
}
pub fn eval_env_var(arg: &mut &str, rettv: &mut typval_T, evaluate: bool) -> i32 {
{
let s = *arg;
*arg = &s[1..]; }
let name_full = *arg;
let len = get_env_len(name_full) as usize;
if evaluate && len == 0 {
return FAIL; }
if evaluate {
let name = &name_full[..len];
let string = std::env::var(name).unwrap_or_default(); *rettv = typval_T::from(string);
}
*arg = &name_full[len..];
OK
}
pub fn eval_option(arg: &mut &str, rettv: &mut typval_T, evaluate: bool) -> i32 {
let src = *arg;
let b = src.as_bytes();
let mut i = 1usize; if b.get(i) == Some(&b'+') || b.get(i) == Some(&b'<') {
i += 1; }
if (b.get(i) == Some(&b'g') || b.get(i) == Some(&b'l')) && b.get(i + 1) == Some(&b':') {
i += 2; }
let name_start = i;
while i < b.len() && b[i].is_ascii_alphabetic() {
i += 1;
}
if i == name_start {
if evaluate {
crate::ported::message::semsg(&format!("E112: Option name missing: {src}"));
}
return FAIL;
}
let name = &src[name_start..i];
*arg = &src[i..];
if evaluate {
*rettv = crate::ported::option::get_option_value(name); }
OK
}
pub const OPT_GLOBAL: i32 = 0x01;
pub const OPT_LOCAL: i32 = 0x02;
pub fn find_option_var_end(arg: &mut &str, opt_flags: &mut i32) -> Option<usize> {
let src = *arg;
let b = src.as_bytes();
let mut p = 1usize; if b.get(p) == Some(&b'g') && b.get(p + 1) == Some(&b':') {
*opt_flags = OPT_GLOBAL; p += 2;
} else if b.get(p) == Some(&b'l') && b.get(p + 1) == Some(&b':') {
*opt_flags = OPT_LOCAL; p += 2;
} else {
*opt_flags = 0; }
let name_start = p;
while p < b.len() && b[p].is_ascii_alphabetic() {
p += 1;
}
if p == name_start {
return None; }
*arg = &src[name_start..]; Some(p - name_start)
}
pub fn eval_fmt_source_name_line() -> String {
"?".to_string() }
#[derive(Debug, Default)]
pub struct forinfo_T {
pub fi_semicolon: i32,
pub fi_varcount: i32,
pub fi_list: Option<Rc<RefCell<list_T>>>,
pub fi_bi: i32,
pub fi_blob: Option<Rc<RefCell<blob_T>>>,
pub fi_string: Option<String>,
pub fi_byte_idx: i32,
}
pub fn eval_for_line(arg: &str, errp: &mut bool, evalarg: &mut evalarg_T) -> forinfo_T {
let mut fi = forinfo_T::default(); let mut tv = typval_T::default(); let skip = evalarg.eval_flags & EVAL_EVALUATE == 0;
*errp = true;
let (consumed, varcount, semicolon) = match skip_var_list(arg, false) {
Some(t) => t,
None => return fi, };
fi.fi_varcount = varcount;
fi.fi_semicolon = i32::from(semicolon);
let expr = &arg[consumed..];
let expr = skipwhite(expr); let eb = expr.as_bytes();
let c2 = eb.get(2).copied().unwrap_or(0);
if eb.first().copied().unwrap_or(0) != b'i'
|| eb.get(1).copied().unwrap_or(0) != b'n'
|| !(c2 == 0 || c2 == b' ' || c2 == b'\t')
{
emsg("E690: Missing \"in\" after :for"); return fi;
}
let expr = skipwhite(&expr[2..]); if eval0(expr, &mut tv, Some(evalarg)) == OK {
*errp = false; if !skip {
match tv.v_type {
VAR_LIST => {
let l = if let v_list(l) = &tv.vval {
l.clone()
} else {
None
};
match l {
None => tv_clear(&mut tv), Some(lst) => {
tv_list_watch_add(&mut lst.borrow_mut()); fi.fi_list = Some(lst); }
}
}
VAR_BLOB => {
fi.fi_bi = 0; let b = if let v_blob(b) = &tv.vval {
b.clone()
} else {
None
};
if let Some(bb) = b {
let mut btv = typval_T::default(); tv_blob_copy(Some(&bb), &mut btv); if let v_blob(nb) = btv.vval {
fi.fi_blob = nb; }
}
tv_clear(&mut tv); }
VAR_STRING => {
fi.fi_byte_idx = 0; let s = if let v_string(s) = &tv.vval {
Some(s.clone())
} else {
None
};
fi.fi_string = s; tv.vval = v_unknown;
tv.v_type = VAR_UNKNOWN;
if fi.fi_string.is_none() {
fi.fi_string = Some(String::new()); }
}
_ => {
emsg("E1098: String, List or Blob required"); tv_clear(&mut tv); }
}
}
}
fi }
pub fn buf_byteidx_to_charidx(
buf: Option<&Rc<RefCell<buf_T>>>,
mut lnum: linenr_T,
byteidx: i32,
) -> i32 {
let buf = match buf {
Some(b) if b.borrow().b_ml.ml_mfp => b,
_ => return -1,
};
let mut b = buf.borrow_mut();
if lnum > b.b_ml.ml_line_count {
lnum = b.b_ml.ml_line_count;
}
let str = ml_get_buf(&mut b, lnum); let sb = str.as_bytes();
if sb.is_empty() {
return 0;
}
let mut t = 0usize; let mut count = 0i32;
while t < sb.len() && (t as i32) <= byteidx {
count += 1;
t += utf_ptr2len(&sb[t..]) as usize;
}
if t >= sb.len() && byteidx != 0 && t as i32 == byteidx {
count += 1;
}
count - 1 }
pub fn buf_charidx_to_byteidx(
buf: Option<&Rc<RefCell<buf_T>>>,
mut lnum: linenr_T,
mut charidx: i32,
) -> i32 {
let buf = match buf {
Some(b) if b.borrow().b_ml.ml_mfp => b,
_ => return -1,
};
let mut b = buf.borrow_mut();
if lnum > b.b_ml.ml_line_count {
lnum = b.b_ml.ml_line_count;
}
let str = ml_get_buf(&mut b, lnum); let sb = str.as_bytes();
let mut t = 0usize; while t < sb.len() && {
charidx -= 1;
charidx > 0
} {
t += utf_ptr2len(&sb[t..]) as usize;
}
t as i32 }
pub fn var2fpos(
tv: &typval_T,
dollar_lnum: bool,
ret_fnum: &mut i32,
charcol: bool,
wp: &Rc<RefCell<win_T>>,
) -> Option<pos_T> {
let mut pos = pos_T::default();
let bp = wp.borrow().w_buffer.clone()?;
if tv.v_type == VAR_LIST {
let mut error = false;
let l = match &tv.vval {
v_list(Some(l)) => l.clone(),
_ => return None, };
let lb = l.borrow();
pos.lnum = tv_list_find_nr(&lb, 0, Some(&mut error)) as linenr_T;
if error || pos.lnum <= 0 || pos.lnum > bp.borrow().b_ml.ml_line_count {
return None;
}
pos.col = tv_list_find_nr(&lb, 1, Some(&mut error)) as colnr_T;
if error {
return None; }
let len: i32;
if charcol {
let line = ml_get_buf(&mut bp.borrow_mut(), pos.lnum);
let lbytes = line.as_bytes();
let mut i = 0usize;
let mut cnt = 0i32;
while i < lbytes.len() {
cnt += 1;
i += utf_ptr2len(&lbytes[i..]) as usize;
}
len = cnt;
} else {
len = ml_get_buf_len(&mut bp.borrow_mut(), pos.lnum);
}
let is_dollar = match tv_list_find(&lb, 1) {
Some(li) => matches!(&li.li_tv.vval, v_string(s) if s == "$"),
None => false,
};
if is_dollar {
pos.col = len + 1; }
if pos.col == 0 || pos.col > len + 1 {
return None;
}
pos.col -= 1;
pos.coladd = tv_list_find_nr(&lb, 2, Some(&mut error)) as colnr_T;
if error {
pos.coladd = 0; }
return Some(pos); }
let name = tv_get_string_chk(tv)?;
let nb = name.as_bytes();
pos.lnum = 0; if nb.first() == Some(&b'.') {
pos = wp.borrow().w_cursor; } else if nb.first() == Some(&b'v') && nb.get(1).copied().unwrap_or(0) == 0 {
pos = wp.borrow().w_cursor;
} else if nb.first() == Some(&b'\'') {
}
if pos.lnum != 0 {
if charcol {
pos.col = buf_byteidx_to_charidx(Some(&bp), pos.lnum, pos.col);
}
return Some(pos); }
pos.coladd = 0;
if nb.first() == Some(&b'w') && dollar_lnum {
} else if nb.first() == Some(&b'$') {
if dollar_lnum {
pos.lnum = bp.borrow().b_ml.ml_line_count; pos.col = 0; } else {
let cursor_lnum = wp.borrow().w_cursor.lnum;
pos.lnum = cursor_lnum; if charcol {
let line = ml_get_buf(&mut bp.borrow_mut(), cursor_lnum);
let lbytes = line.as_bytes();
let mut i = 0usize;
let mut cnt = 0i32;
while i < lbytes.len() {
cnt += 1;
i += utf_ptr2len(&lbytes[i..]) as usize;
}
pos.col = cnt;
} else {
pos.col = ml_get_buf_len(&mut bp.borrow_mut(), cursor_lnum);
}
}
return Some(pos); }
None }
pub fn list2fpos(
arg: &typval_T,
posp: &mut pos_T,
mut fnump: Option<&mut i32>,
curswantp: Option<&mut colnr_T>,
charcol: bool,
) -> i32 {
let l = match &arg.vval {
v_list(Some(l)) if arg.v_type == VAR_LIST => l.clone(),
_ => return FAIL, };
{
let lb = l.borrow();
let n = tv_list_len(&lb);
let (lo, hi) = if fnump.is_none() { (2, 4) } else { (3, 5) };
if n < lo || n > hi {
return FAIL;
}
}
let lb = l.borrow();
let mut i = 0; if let Some(fp) = fnump.as_deref_mut() {
let mut n = tv_list_find_nr(&lb, i, None) as i32; i += 1;
if n < 0 {
return FAIL; }
if n == 0 {
n = curbuf
.with(|c| c.borrow().clone())
.map(|b| b.borrow().handle)
.unwrap_or(0); }
*fp = n; }
let mut n = tv_list_find_nr(&lb, i, None) as i32; i += 1;
if n < 0 {
return FAIL; }
posp.lnum = n;
n = tv_list_find_nr(&lb, i, None) as i32; i += 1;
if n < 0 {
return FAIL; }
if charcol {
let fnum = match fnump.as_deref() {
None => curbuf
.with(|c| c.borrow().clone())
.map(|b| b.borrow().handle)
.unwrap_or(0),
Some(fp) => *fp,
};
let buf = buflist_findnr(fnum);
match &buf {
Some(b) if b.borrow().b_ml.ml_mfp => {}
_ => return FAIL,
}
let lnum = if posp.lnum == 0 {
curwin
.with(|c| c.borrow().clone())
.map(|w| w.borrow().w_cursor.lnum)
.unwrap_or(0)
} else {
posp.lnum
};
n = buf_charidx_to_byteidx(buf.as_ref(), lnum, n) + 1; }
posp.col = n;
n = tv_list_find_nr(&lb, i, None) as i32; if n < 0 {
posp.coladd = 0; } else {
posp.coladd = n; }
if let Some(cw) = curswantp {
*cw = tv_list_find_nr(&lb, i + 1, None) as colnr_T;
}
OK }
#[cfg(test)]
mod tests {
use super::string2float;
#[test]
fn handle_subscript_chain() {
use super::handle_subscript;
use crate::ported::eval::typval::EVAL_STRING_HOOK;
use crate::ported::eval_h::OK;
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 mut s = typval_T::from("abcdef".to_string());
assert_eq!(handle_subscript(&mut s, &["[1:4]", "[1]"], false), OK);
assert!(matches!(&s.vval, v_string(t) if t == "c"));
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn eval_index_subscripts() {
use super::eval_index;
use crate::ported::eval::typval::EVAL_STRING_HOOK;
use crate::ported::eval_h::OK;
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 mut s = typval_T::from("héllo".to_string());
assert_eq!(eval_index(&mut s, "[1]", false), OK);
assert!(matches!(&s.vval, v_string(t) if t == "é"));
let mut s2 = typval_T::from("abcdef".to_string());
eval_index(&mut s2, "[1:3]", false);
assert!(matches!(&s2.vval, v_string(t) if t == "bcd"));
let mut s3 = typval_T::from("abcdef".to_string());
eval_index(&mut s3, "[2:]", false);
assert!(matches!(&s3.vval, v_string(t) if t == "cdef"));
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn eval_index_inner_types() {
use super::eval_index_inner;
use crate::ported::eval::typval::{tv_dict_add_nr, tv_list_alloc, tv_list_append_number};
use crate::ported::eval::typval_defs_h::{
dict_T, typval_vval_union::v_dict, VarLockStatus, VarType::VAR_DICT,
};
use crate::ported::eval_h::{FAIL, OK};
use std::{cell::RefCell, rc::Rc};
let mut s = typval_T::from("héllo".to_string());
assert_eq!(
eval_index_inner(
&mut s,
false,
Some(&typval_T::from(1)),
None,
false,
None,
false
),
OK
);
assert!(matches!(&s.vval, v_string(t) if t == "é"));
let mut s2 = typval_T::from("abcdef".to_string());
eval_index_inner(
&mut s2,
true,
Some(&typval_T::from(1)),
Some(&typval_T::from(3)),
false,
None,
false,
);
assert!(matches!(&s2.vval, v_string(t) if t == "bcd"));
let l = tv_list_alloc(-1);
for n in [10, 20, 30] {
tv_list_append_number(&mut l.borrow_mut(), n);
}
let mut lv = typval_T {
v_type: crate::ported::eval::typval_defs_h::VarType::VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: crate::ported::eval::typval_defs_h::typval_vval_union::v_list(Some(l)),
};
eval_index_inner(
&mut lv,
false,
Some(&typval_T::from(2)),
None,
false,
None,
false,
);
assert!(matches!(lv.vval, v_number(30)));
let mut d = dict_T::default();
tv_dict_add_nr(&mut d, "k", 7);
let mut dv = typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(Rc::new(RefCell::new(d)))),
};
assert_eq!(
eval_index_inner(&mut dv, false, None, None, false, Some("k"), false),
OK
);
assert!(matches!(dv.vval, v_number(7)));
let mut d2 = dict_T::default();
tv_dict_add_nr(&mut d2, "k", 7);
let mut dv2 = typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(Rc::new(RefCell::new(d2)))),
};
assert_eq!(
eval_index_inner(&mut dv2, false, None, None, false, Some("nope"), false),
FAIL
);
}
use super::{
char_from_string, char_idx2byte, find_name_end, get_literal_key, is_luafunc, string_slice,
to_name_end, tv_is_luafunc, var_flavour, var_flavour_T::*,
};
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::*, VarLockStatus, VarType::*,
};
use std::rc::Rc;
#[test]
fn char_idx2byte_ascii_and_neg() {
assert_eq!(char_idx2byte("hello", 0), Some(0));
assert_eq!(char_idx2byte("hello", 4), Some(4));
assert_eq!(char_idx2byte("hello", 5), Some(5)); assert_eq!(char_idx2byte("hello", -1), Some(4)); assert_eq!(char_idx2byte("hello", -5), Some(0));
assert_eq!(char_idx2byte("hello", -6), None); }
#[test]
fn char_from_string_unicode() {
assert_eq!(char_from_string("héllo", 1).as_deref(), Some("é"));
assert_eq!(char_from_string("héllo", -1).as_deref(), Some("o"));
assert_eq!(char_from_string("ab", 5), None);
assert_eq!(char_from_string("ab", -3), None);
}
#[test]
fn string_slice_inclusive_and_exclusive() {
assert_eq!(string_slice("abcdef", 1, 3, false).as_deref(), Some("bcd"));
assert_eq!(string_slice("abcdef", 1, 3, true).as_deref(), Some("bc"));
assert_eq!(
string_slice("abcdef", 1, -1, false).as_deref(),
Some("bcdef")
);
assert_eq!(string_slice("abc", 2, 1, false), None);
}
#[test]
fn eval7_leader_unary() {
use super::eval7_leader;
let not = |n: i64, l: &str| {
let mut tv = typval_T::from(n);
eval7_leader(&mut tv, false, l);
match tv.vval {
v_number(x) => x,
_ => -999,
}
};
assert_eq!(not(5, "!"), 0); assert_eq!(not(0, "!"), 1); assert_eq!(not(5, "-"), -5); assert_eq!(not(5, "--"), 5); assert_eq!(not(5, "!-"), 0); let mut tv = typval_T::from(5);
eval7_leader(&mut tv, true, "!");
assert!(matches!(tv.vval, v_number(5)));
}
#[test]
fn binary_op_helpers() {
use super::{eval_addsub_number, eval_concat_str};
use crate::ported::eval_h::OK;
let mut a = typval_T::from(3);
assert_eq!(eval_addsub_number(&mut a, &typval_T::from(4), b'+'), OK);
assert!(matches!(a.vval, v_number(7)));
let mut b = typval_T::from(10);
eval_addsub_number(&mut b, &typval_T::from(4), b'-');
assert!(matches!(b.vval, v_number(6)));
let mut c = typval_T::from(3);
let f = typval_T {
v_type: VAR_FLOAT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_float(1.5),
};
eval_addsub_number(&mut c, &f, b'+');
assert!(matches!(c.vval, v_float(x) if (x - 4.5).abs() < 1e-9));
let mut s = typval_T::from("foo".to_string());
assert_eq!(
eval_concat_str(&mut s, &typval_T::from("bar".to_string())),
OK
);
assert!(matches!(&s.vval, v_string(t) if t == "foobar"));
use super::eval_multdiv_number;
let mut m = typval_T::from(6);
eval_multdiv_number(&mut m, &typval_T::from(7), b'*');
assert!(matches!(m.vval, v_number(42)));
let mut d = typval_T::from(17);
eval_multdiv_number(&mut d, &typval_T::from(5), b'/');
assert!(matches!(d.vval, v_number(3)));
let mut md = typval_T::from(17);
eval_multdiv_number(&mut md, &typval_T::from(5), b'%');
assert!(matches!(md.vval, v_number(2)));
}
#[test]
fn do_string_sub_basic() {
use super::do_string_sub;
assert_eq!(do_string_sub("hello", "l", "L", "g"), "heLLo");
assert_eq!(do_string_sub("hello", "l", "L", ""), "heLlo");
assert_eq!(do_string_sub("abc", "x", "y", "g"), "abc");
}
#[test]
fn make_expanded_name_curly() {
use super::make_expanded_name;
use crate::ported::eval::typval::EVAL_STRING_HOOK;
fn hook(e: &str) -> Option<typval_T> {
match e {
"x" => Some(typval_T::from("BAR".to_string())),
"1" => Some(typval_T::from("ONE".to_string())),
_ => None,
}
}
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(hook));
assert_eq!(
make_expanded_name("foo{x}baz", 3, 5).as_deref(),
Some("fooBARbaz")
);
assert_eq!(
make_expanded_name("a{x}b{1}c", 1, 3).as_deref(),
Some("aBARbONEc")
);
assert_eq!(make_expanded_name("p{bad}q", 1, 5), None);
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn to_name_end_namespace() {
assert_eq!(to_name_end("foo + 1", true), 3);
assert_eq!(&"s:var = 1"[..to_name_end("s:var = 1", true)], "s:var");
assert_eq!(&"n:]"[..to_name_end("n:]", true)], "n");
assert_eq!(to_name_end("123", true), 0); }
#[test]
fn find_name_end_curly_and_bracket() {
let (end, s, e) = find_name_end("foo{bar}baz rest", 0);
assert_eq!(&"foo{bar}baz rest"[..end], "foo{bar}baz");
assert_eq!((s, e), (Some(3), Some(7)));
let (end2, _, _) = find_name_end("foo[0]", 0);
assert_eq!(&"foo[0]"[..end2], "foo");
let (end2b, _, _) = find_name_end("foo[0]", 1);
assert_eq!(&"foo[0]"[..end2b], "foo[0]");
let (end3, s3, _) = find_name_end("plain", 0);
assert_eq!((end3, s3), (5, None));
}
#[test]
fn get_literal_key_basic() {
assert_eq!(get_literal_key("key: 1").unwrap().0, "key");
assert_eq!(
get_literal_key("a-b : x").unwrap(),
("a-b".to_string(), ": x")
);
assert!(get_literal_key(":bad").is_none());
}
#[test]
fn var_flavour_classes() {
assert_eq!(var_flavour("FOO"), VAR_FLAVOUR_SHADA);
assert_eq!(var_flavour("Foo"), VAR_FLAVOUR_SESSION);
assert_eq!(var_flavour("foo"), VAR_FLAVOUR_DEFAULT);
}
#[test]
fn string_to_list_splits() {
use super::string_to_list;
use crate::ported::eval::typval_defs_h::typval_vval_union::v_string;
let lines = |s: &str, keep: bool| -> Vec<String> {
string_to_list(s, keep)
.borrow()
.lv_items
.iter()
.map(|it| match &it.li_tv.vval {
v_string(s) => s.clone(),
_ => String::new(),
})
.collect()
};
assert_eq!(
lines("a\nb\n", false),
vec!["a".to_string(), "b".to_string()]
);
assert_eq!(
lines("a\nb\n", true),
vec!["a".to_string(), "b".to_string(), String::new()]
);
assert_eq!(lines("a\nb", false), vec!["a".to_string(), "b".to_string()]);
assert_eq!(lines("x\0y", false), vec!["x\ny".to_string()]);
}
#[test]
fn save_tv_as_string_modes() {
use super::save_tv_as_string;
use crate::ported::eval::typval::{tv_list_alloc, tv_list_append_string};
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::v_list, VarLockStatus, VarType::VAR_LIST,
};
assert_eq!(
save_tv_as_string(&typval_T::from("hi".to_string()), false, false).as_deref(),
Some("hi")
);
assert_eq!(save_tv_as_string(&typval_T::from(5), false, false), None);
let l = tv_list_alloc(-1);
tv_list_append_string(&mut l.borrow_mut(), "a");
tv_list_append_string(&mut l.borrow_mut(), "b");
let lv = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
};
assert_eq!(
save_tv_as_string(&lv, false, false).as_deref(),
Some("a\nb")
);
assert_eq!(
save_tv_as_string(&lv, true, false).as_deref(),
Some("a\nb\n")
);
}
#[test]
fn check_can_index_by_type() {
use super::check_can_index;
use crate::ported::eval_h::{FAIL, OK};
let tv = |t, v| typval_T {
v_type: t,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v,
};
assert_eq!(
check_can_index(&tv(VAR_STRING, v_string("x".into())), true, false),
OK
);
assert_eq!(
check_can_index(&tv(VAR_NUMBER, v_number(1)), true, false),
OK
);
assert_eq!(
check_can_index(&tv(VAR_FLOAT, v_float(1.0)), true, false),
FAIL
);
assert_eq!(
check_can_index(
&tv(
VAR_BOOL,
v_bool(crate::ported::eval::typval_defs_h::BoolVarValue::kBoolVarTrue)
),
true,
false
),
FAIL
);
assert_eq!(
check_can_index(&tv(VAR_FUNC, v_string("F".into())), true, false),
FAIL
);
assert_eq!(
check_can_index(&tv(VAR_UNKNOWN, v_number(0)), true, false),
FAIL
);
assert_eq!(
check_can_index(&tv(VAR_UNKNOWN, v_number(0)), false, false),
OK
);
}
#[test]
fn grow_string_tv_appends() {
use super::grow_string_tv;
use crate::ported::eval_h::{FAIL, OK};
let mut s = typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string("foo".to_string()),
};
assert_eq!(grow_string_tv(&mut s, "bar"), OK);
assert!(matches!(&s.vval, v_string(t) if t == "foobar"));
let mut n = typval_T {
v_type: VAR_NUMBER,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(1),
};
assert_eq!(grow_string_tv(&mut n, "x"), FAIL);
}
#[test]
fn typval_tostring_modes() {
use super::typval_tostring;
let s = typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string("hi".to_string()),
};
assert_eq!(typval_tostring(Some(&s), false), "hi");
assert_eq!(typval_tostring(Some(&s), true), "'hi'");
assert_eq!(typval_tostring(None, false), "(does not exist)");
let n = typval_T {
v_type: VAR_NUMBER,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_number(42),
};
assert_eq!(typval_tostring(Some(&n), false), "42");
}
#[test]
fn eval_string_wrappers_via_hook() {
use super::{eval_expr, eval_to_number, eval_to_string};
use crate::ported::eval::typval::EVAL_STRING_HOOK;
fn hook(expr: &str) -> Option<typval_T> {
match expr {
"42" => Some(typval_T::from(42)),
"str" => Some(typval_T::from("hi".to_string())),
_ => None,
}
}
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(hook));
assert_eq!(eval_to_number("42"), 42);
assert_eq!(eval_to_string("str").as_deref(), Some("hi"));
assert!(eval_expr("42").is_some());
assert_eq!(eval_to_number("bad"), -1);
assert_eq!(eval_to_string("bad"), None);
assert!(eval_expr("bad").is_none());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn eval_expr_func_and_partial() {
use super::{eval_expr_func, eval_expr_partial};
use crate::ported::eval::typval::CALL_FUNC_HOOK;
use crate::ported::eval::typval_defs_h::partial_T;
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 f = typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string("F".into()),
};
let mut rv = typval_T::from(-1);
assert_eq!(eval_expr_func(&f, &[typval_T::from(1)], &mut rv), OK);
assert!(matches!(rv.vval, v_number(1)));
let empty = typval_T {
v_type: VAR_FUNC,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string(String::new()),
};
assert_eq!(eval_expr_func(&empty, &[], &mut rv), FAIL);
let p = typval_T {
v_type: VAR_PARTIAL,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_partial(Some(Rc::new(partial_T {
pt_refcount: 1,
pt_name: "P".into(),
pt_argv: vec![],
pt_dict: None,
}))),
};
let mut rv2 = typval_T::from(-1);
assert_eq!(eval_expr_partial(&p, &[], &mut rv2), OK);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn call_func_helpers_via_hook() {
use super::{call_func_retlist, call_func_retstr, call_vim_function};
use crate::ported::eval::typval::CALL_FUNC_HOOK;
use crate::ported::eval_h::FAIL;
fn hook_str(_c: &typval_T, _args: &[typval_T]) -> Option<typval_T> {
Some(typval_T::from("hi".to_string()))
}
let saved = CALL_FUNC_HOOK.with(|h| *h.borrow());
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = None);
let mut rv = typval_T::from(0);
assert_eq!(call_vim_function("F", &[], &mut rv), FAIL);
assert_eq!(call_func_retstr("F", &[]), None);
assert!(call_func_retlist("F", &[]).is_none());
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(hook_str));
assert_eq!(call_func_retstr("F", &[]).as_deref(), Some("hi"));
assert!(call_func_retlist("F", &[]).is_none());
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn callback_call_via_hook() {
use super::callback_call;
use crate::ported::eval::typval::{Callback, CALL_FUNC_HOOK};
use crate::ported::eval::userfunc::callback_call_retnr;
fn hook(_callee: &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() = None);
let mut rv = typval_T::from(0);
assert!(!callback_call(&Callback::None, &[], &mut rv));
assert!(!callback_call(&Callback::Funcref("F".into()), &[], &mut rv));
assert_eq!(callback_call_retnr(&Callback::Funcref("F".into()), &[]), -2);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let mut rv2 = typval_T::from(0);
assert!(callback_call(
&Callback::Funcref("F".into()),
&[typval_T::from(1), typval_T::from(2)],
&mut rv2
));
assert!(matches!(rv2.vval, v_number(2)));
assert_eq!(
callback_call_retnr(&Callback::Funcref("F".into()), &[typval_T::from(9)]),
1
);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn luafunc_predicates() {
let s = typval_T {
v_type: VAR_STRING,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_string("x".to_string()),
};
assert!(!tv_is_luafunc(&s));
let other = Rc::new(crate::ported::eval::typval_defs_h::partial_T {
pt_refcount: 1,
pt_name: String::new(),
pt_argv: Vec::new(),
pt_dict: None,
});
assert!(!is_luafunc(&other));
}
#[test]
#[allow(clippy::approx_constant)]
fn string2float_leading_prefix() {
assert_eq!(string2float("3.14"), (3.14, 4));
assert_eq!(string2float("3.14abc"), (3.14, 4));
assert_eq!(string2float("2.5e3xyz"), (2500.0, 5));
assert_eq!(string2float(".5"), (0.5, 2));
assert_eq!(string2float("42"), (42.0, 2));
assert_eq!(string2float("abc"), (0.0, 0));
assert_eq!(string2float("inf"), (f64::INFINITY, 3));
assert_eq!(string2float("-inf"), (f64::NEG_INFINITY, 4));
assert!(string2float("nan").0.is_nan());
assert_eq!(string2float("5e"), (5.0, 1));
}
fn ev() -> super::evalarg_T {
super::evalarg_T {
eval_flags: super::EVAL_EVALUATE,
}
}
#[test]
fn eval0_arithmetic_precedence() {
use super::eval0;
use crate::ported::eval_h::OK;
let run = |src: &str| -> typval_T {
let mut tv = typval_T::default();
let mut e = ev();
assert_eq!(eval0(src, &mut tv, Some(&mut e)), OK, "eval0 {src}");
tv
};
assert!(matches!(run("1 + 2 * 3").vval, v_number(7)));
assert!(matches!(run("2 * (3 + 4)").vval, v_number(14)));
assert!(matches!(run("17 - 5").vval, v_number(12)));
assert!(matches!(run("17 / 5").vval, v_number(3)));
assert!(matches!(run("17 % 5").vval, v_number(2)));
}
#[test]
fn eval0_comparisons_and_logic() {
use super::eval0;
let n = |src: &str| -> i64 {
let mut tv = typval_T::default();
let mut e = ev();
super::eval0(src, &mut tv, Some(&mut e));
match tv.vval {
v_number(x) => x,
_ => -999,
}
};
let _ = eval0;
assert_eq!(n("1 < 2"), 1);
assert_eq!(n("2 <= 2"), 1);
assert_eq!(n("3 > 5"), 0);
assert_eq!(n("1 == 1"), 1);
assert_eq!(n("1 != 1"), 0);
assert_eq!(n("1 && 0"), 0);
assert_eq!(n("0 || 5"), 1);
assert_eq!(n("1 ? 10 : 20"), 10);
assert_eq!(n("0 ? 10 : 20"), 20);
assert_eq!(n("0 ?? 7"), 7);
assert_eq!(n("!0"), 1);
assert_eq!(n("!5"), 0);
assert_eq!(n("--3"), 3);
}
#[test]
fn eval0_string_concat() {
use super::eval0;
let mut tv = typval_T::default();
let mut e = ev();
eval0(r#"'ab' . 'cd'"#, &mut tv, Some(&mut e));
assert!(matches!(&tv.vval, v_string(s) if s == "abcd"));
let mut tv2 = typval_T::default();
let mut e2 = ev();
eval0(r#"'x' .. 'y'"#, &mut tv2, Some(&mut e2));
assert!(matches!(&tv2.vval, v_string(s) if s == "xy"));
}
#[test]
fn eval_number_forms() {
use super::eval_number;
use crate::ported::eval_h::OK;
let mut s = "42 rest";
let mut tv = typval_T::default();
assert_eq!(eval_number(&mut s, &mut tv, true, false), OK);
assert!(matches!(tv.vval, v_number(42)));
assert_eq!(s, " rest");
let mut s = "0x1f";
let mut tv = typval_T::default();
eval_number(&mut s, &mut tv, true, false);
assert!(matches!(tv.vval, v_number(31)));
let mut s = "3.5";
let mut tv = typval_T::default();
eval_number(&mut s, &mut tv, true, false);
assert!(matches!(tv.vval, v_float(x) if (x - 3.5).abs() < 1e-9));
let mut s = "0zDEADBEEF";
let mut tv = typval_T::default();
eval_number(&mut s, &mut tv, true, false);
match &tv.vval {
v_blob(Some(b)) => assert_eq!(b.borrow().bv_ga, vec![0xDE, 0xAD, 0xBE, 0xEF]),
_ => panic!("expected blob"),
}
}
#[test]
fn eval_string_escapes() {
use super::eval_string;
let s = |src: &str| -> String {
let mut a = src;
let mut tv = typval_T::default();
eval_string(&mut a, &mut tv, true, false);
match tv.vval {
v_string(x) => x,
_ => String::new(),
}
};
assert_eq!(s(r#""a\tb""#), "a\tb");
assert_eq!(s(r#""x\ny""#), "x\ny");
assert_eq!(s(r#""\x41""#), "A"); assert_eq!(s(r#""\101""#), "A"); assert_eq!(s(r#""plain""#), "plain");
}
#[test]
fn eval_lit_string_reduces_quotes() {
use super::eval_lit_string;
let mut a = r#"'it''s'"#;
let mut tv = typval_T::default();
eval_lit_string(&mut a, &mut tv, true, false);
assert!(matches!(&tv.vval, v_string(x) if x == "it's"));
}
#[test]
fn eval_list_and_dict_literals() {
use super::{eval_dict, eval_list};
use crate::ported::eval::typval::tv_dict_find;
let mut a = "[1, 2, 3]";
let mut tv = typval_T::default();
let mut e = ev();
eval_list(&mut a, &mut tv, Some(&mut e));
match &tv.vval {
v_list(Some(l)) => assert_eq!(l.borrow().lv_items.len(), 3),
_ => panic!("expected list"),
}
let mut a = r#"{'a': 1, 'b': 2}"#;
let mut tv = typval_T::default();
let mut e = ev();
eval_dict(&mut a, &mut tv, Some(&mut e), false);
match &tv.vval {
v_dict(Some(d)) => {
let d = d.borrow();
assert!(matches!(tv_dict_find(&d, "a"), Some(v) if matches!(v.vval, v_number(1))));
assert!(matches!(tv_dict_find(&d, "b"), Some(v) if matches!(v.vval, v_number(2))));
}
_ => panic!("expected dict"),
}
}
#[test]
fn eval7_full_stack_number_with_subscript() {
use super::eval0;
use crate::ported::eval::typval::EVAL_STRING_HOOK;
fn hook(expr: &str) -> Option<typval_T> {
match expr.trim() {
"1" => Some(typval_T::from(1)),
_ => None,
}
}
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(hook));
let mut tv = typval_T::default();
let mut e = ev();
eval0("[10, 20, 30][1]", &mut tv, Some(&mut e));
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
assert!(matches!(tv.vval, v_number(20)));
}
#[test]
fn get_lval_and_set_dict_item() {
use super::{get_lval, lval_T, set_var_lval, LlTv};
use crate::ported::eval::typval::tv_dict_alloc;
use crate::ported::eval::typval_defs_h::typval_vval_union::{v_dict, v_number};
use crate::ported::eval::typval_defs_h::{typval_T, VarLockStatus, VarType::VAR_DICT};
use crate::ported::eval::vars::set_var;
let d = tv_dict_alloc();
d.borrow_mut()
.dv_hashtab
.insert("a".to_string(), typval_T::from(1));
set_var(
"g:gld",
0,
typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(d.clone())),
},
false,
);
let mut lp = lval_T::default();
let end = get_lval("g:gld.a", None, &mut lp, false, false, 0, 0);
assert!(end.is_some());
assert!(matches!(lp.ll_tv, LlTv::DictItem(_, ref k) if k == "a"));
let mut rettv = typval_T::from(99);
set_var_lval(&mut lp, 0, &mut rettv, true, false, Some("="));
assert!(
matches!(d.borrow().dv_hashtab.get("a"), Some(t) if matches!(t.vval, v_number(99)))
);
}
#[test]
fn get_lval_adds_new_dict_key() {
use super::{get_lval, lval_T, set_var_lval};
use crate::ported::eval::typval::tv_dict_alloc;
use crate::ported::eval::typval_defs_h::typval_vval_union::{v_dict, v_number};
use crate::ported::eval::typval_defs_h::{typval_T, VarLockStatus, VarType::VAR_DICT};
use crate::ported::eval::vars::set_var;
let d = tv_dict_alloc();
set_var(
"g:gldnew",
0,
typval_T {
v_type: VAR_DICT,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_dict(Some(d.clone())),
},
false,
);
let mut lp = lval_T::default();
let end = get_lval("g:gldnew.fresh", None, &mut lp, false, false, 0, 0);
assert!(end.is_some());
assert_eq!(lp.ll_newkey.as_deref(), Some("fresh"));
let mut rettv = typval_T::from(7);
set_var_lval(&mut lp, 0, &mut rettv, true, false, Some("="));
assert!(
matches!(d.borrow().dv_hashtab.get("fresh"), Some(t) if matches!(t.vval, v_number(7)))
);
}
#[test]
fn get_lval_and_set_list_item() {
use super::{get_lval, lval_T, set_var_lval};
use crate::ported::eval::typval::{tv_list_alloc, tv_list_append_number, EVAL_STRING_HOOK};
use crate::ported::eval::typval_defs_h::typval_vval_union::{v_list, v_number};
use crate::ported::eval::typval_defs_h::{typval_T, VarLockStatus, VarType::VAR_LIST};
use crate::ported::eval::vars::set_var;
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 l = tv_list_alloc(-1);
for n in [10i64, 20, 30] {
tv_list_append_number(&mut l.borrow_mut(), n);
}
set_var(
"g:gllst",
0,
typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l.clone())),
},
false,
);
let mut lp = lval_T::default();
let end = get_lval("g:gllst[1]", None, &mut lp, false, false, 0, 0);
assert!(end.is_some());
assert_eq!(lp.ll_n1, 1);
let mut rettv = typval_T::from(99);
set_var_lval(&mut lp, 0, &mut rettv, true, false, Some("="));
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
assert!(matches!(l.borrow().lv_items[1].li_tv.vval, v_number(99)));
}
#[test]
fn get_lval_and_set_blob_byte() {
use super::{get_lval, lval_T, set_var_lval};
use crate::ported::eval::typval::{tv_blob_alloc, EVAL_STRING_HOOK};
use crate::ported::eval::typval_defs_h::typval_vval_union::v_blob;
use crate::ported::eval::typval_defs_h::{typval_T, VarLockStatus, VarType::VAR_BLOB};
use crate::ported::eval::vars::set_var;
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 b = tv_blob_alloc();
b.borrow_mut().bv_ga = vec![1u8, 2, 3];
set_var(
"g:glblb",
0,
typval_T {
v_type: VAR_BLOB,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_blob(Some(b.clone())),
},
false,
);
let mut lp = lval_T::default();
let end = get_lval("g:glblb[0]", None, &mut lp, false, false, 0, 0);
assert!(end.is_some());
assert!(lp.ll_blob.is_some());
let mut rettv = typval_T::from(255);
set_var_lval(&mut lp, 0, &mut rettv, true, false, Some("="));
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
assert_eq!(b.borrow().bv_ga[0], 255);
}
#[test]
fn clear_lval_frees_names() {
use super::{clear_lval, lval_T};
let mut lp = lval_T::default();
lp.ll_exp_name = Some("expanded".to_string());
lp.ll_newkey = Some("k".to_string());
clear_lval(&mut lp);
assert!(lp.ll_exp_name.is_none());
assert!(lp.ll_newkey.is_none());
}
#[test]
fn skip_expr_advances_and_restores_flags() {
use super::{evalarg_T, skip_expr, EVAL_EVALUATE};
use crate::ported::eval_h::OK;
let mut p: &str = "1 + 2 | rest";
assert_eq!(skip_expr(&mut p, None), OK);
assert!(p.starts_with('|'), "pp not advanced past expr: {p:?}");
let mut ea = evalarg_T {
eval_flags: EVAL_EVALUATE,
};
let mut q: &str = "3 * 4";
assert_eq!(skip_expr(&mut q, Some(&mut ea)), OK);
assert_eq!(ea.eval_flags, EVAL_EVALUATE);
}
#[test]
fn eval0_simple_funccal_falls_through_to_eval0() {
use super::{eval0_simple_funccal, evalarg_T, EVAL_EVALUATE};
use crate::ported::eval_h::OK;
let mut ea = evalarg_T {
eval_flags: EVAL_EVALUATE,
};
let mut rv = typval_T::default();
assert_eq!(eval0_simple_funccal("1 + 2", &mut rv, Some(&mut ea)), OK);
assert!(matches!(rv.vval, v_number(3)));
}
#[test]
fn find_option_var_end_scopes() {
use super::{find_option_var_end, OPT_GLOBAL, OPT_LOCAL};
let mut flags = -1;
let mut a: &str = "&g:number";
assert_eq!(find_option_var_end(&mut a, &mut flags), Some(6));
assert_eq!(a, "number");
assert_eq!(flags, OPT_GLOBAL);
let mut b: &str = "&l:ai=1";
assert_eq!(find_option_var_end(&mut b, &mut flags), Some(2));
assert_eq!(b, "ai=1"); assert_eq!(flags, OPT_LOCAL);
let mut c: &str = "&wrap";
assert_eq!(find_option_var_end(&mut c, &mut flags), Some(4));
assert_eq!(c, "wrap");
assert_eq!(flags, 0);
let mut d: &str = "&123";
assert_eq!(find_option_var_end(&mut d, &mut flags), None);
assert_eq!(d, "&123");
}
#[test]
fn eval_fmt_source_name_line_no_stack() {
use super::eval_fmt_source_name_line;
assert_eq!(eval_fmt_source_name_line(), "?");
}
#[test]
fn eval_lambda_prepends_base_and_calls() {
use super::{eval_lambda, evalarg_T, EVAL_EVALUATE};
use crate::ported::eval::typval::{CALL_FUNC_HOOK, EVAL_STRING_HOOK};
use crate::ported::eval_h::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> {
let sum: i64 = args
.iter()
.map(|a| match a.vval {
v_number(n) => n,
_ => 0,
})
.sum();
Some(typval_T::from(sum))
}
let saved_e = EVAL_STRING_HOOK.with(|h| *h.borrow());
let saved_c = 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 ea = evalarg_T {
eval_flags: EVAL_EVALUATE,
};
let mut rv = typval_T::from(7);
let mut arg: &str = "->{x, y -> x}(2, 3) tail";
assert_eq!(eval_lambda(&mut arg, &mut rv, Some(&mut ea), true), OK);
assert!(matches!(rv.vval, v_number(12)), "got {:?}", rv.vval);
assert_eq!(arg, " tail");
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved_e);
CALL_FUNC_HOOK.with(|h| *h.borrow_mut() = saved_c);
}
#[test]
fn tv_to_argv_string_uses_shell() {
use super::tv_to_argv;
let cmd = typval_T::from("echo hi".to_string());
let mut name = String::new();
let argv = tv_to_argv(&cmd, Some(&mut name), None).expect("argv");
assert_eq!(
argv,
vec!["sh".to_string(), "-c".to_string(), "echo hi".to_string()]
);
assert_eq!(name, "echo hi");
}
#[cfg(unix)]
#[test]
fn tv_to_argv_list_resolves_argv0() {
use super::tv_to_argv;
use crate::ported::eval::typval::{tv_list_alloc, tv_list_append_string};
let l = tv_list_alloc(-1);
{
let mut lb = l.borrow_mut();
tv_list_append_string(&mut lb, "/bin/echo");
tv_list_append_string(&mut lb, "hi");
}
let cmd = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
};
let mut exe = true;
let argv = tv_to_argv(&cmd, None, Some(&mut exe)).expect("argv");
assert!(exe);
assert_eq!(argv[0], "/bin/echo"); assert_eq!(argv[1], "hi");
}
#[cfg(unix)]
#[test]
fn get_system_output_as_rettv_captures_stdout() {
use super::get_system_output_as_rettv;
let argvars = vec![typval_T::from("printf hi".to_string())];
let mut rv = typval_T::default();
get_system_output_as_rettv(&argvars, &mut rv, false);
assert!(
matches!(&rv.vval, v_string(s) if s == "hi"),
"got {:?}",
rv.vval
);
}
use crate::ported::buffer::{buflist_new, curbuf, firstbuf, lastbuf, top_file_num, BLN_LISTED};
use crate::ported::eval_h::{FAIL, OK};
use crate::ported::window::{curwin, win_T};
use std::cell::RefCell;
fn reset_editor() {
firstbuf.with(|f| *f.borrow_mut() = None);
lastbuf.with(|l| *l.borrow_mut() = None);
curbuf.with(|c| *c.borrow_mut() = None);
top_file_num.with(|t| t.set(1));
curwin.with(|c| *c.borrow_mut() = None);
}
fn load_lines(buf: &Rc<RefCell<crate::ported::buffer::buf_T>>, lines: &[&str]) {
let mut b = buf.borrow_mut();
b.b_ml.ml_mfp = true;
b.b_ml.ml_lines = lines.iter().map(|s| s.to_string()).collect();
b.b_ml.ml_line_count = lines.len() as i32;
}
fn make_curwin(
buf: &Rc<RefCell<crate::ported::buffer::buf_T>>,
lnum: i32,
col: i32,
) -> Rc<RefCell<win_T>> {
let w = Rc::new(RefCell::new(win_T {
w_buffer: Some(buf.clone()),
..Default::default()
}));
w.borrow_mut().w_cursor = super::pos_T {
lnum,
col,
coladd: 0,
};
curwin.with(|c| *c.borrow_mut() = Some(w.clone()));
w
}
#[test]
fn byteidx_charidx_conversions_utf8() {
use super::{buf_byteidx_to_charidx, buf_charidx_to_byteidx};
reset_editor();
let buf = buflist_new(Some("/tmp/mb".into()), None, 0, BLN_LISTED).unwrap();
load_lines(&buf, &["héllo"]);
assert_eq!(buf_byteidx_to_charidx(Some(&buf), 1, 3), 2);
assert_eq!(buf_charidx_to_byteidx(Some(&buf), 1, 3), 3);
assert_eq!(buf_byteidx_to_charidx(None, 1, 0), -1);
assert_eq!(buf_charidx_to_byteidx(None, 1, 0), -1);
let buf2 = buflist_new(Some("/tmp/empty".into()), None, 0, BLN_LISTED).unwrap();
load_lines(&buf2, &[""]);
assert_eq!(buf_byteidx_to_charidx(Some(&buf2), 1, 5), 0);
}
#[test]
fn var2fpos_list_and_names() {
use super::var2fpos;
use crate::ported::eval::typval::{tv_list_alloc, tv_list_append_number};
reset_editor();
let buf = buflist_new(Some("/tmp/vf".into()), None, 0, BLN_LISTED).unwrap();
load_lines(&buf, &["hello", "world"]);
let wp = make_curwin(&buf, 2, 3);
let mut fnum = 0;
let l = tv_list_alloc(-1);
{
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, 1);
tv_list_append_number(&mut lb, 2);
}
let tv = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
};
let p = var2fpos(&tv, false, &mut fnum, false, &wp).expect("list pos");
assert_eq!(p.lnum, 1);
assert_eq!(p.col, 1);
let dot = typval_T::from(".".to_string());
let p = var2fpos(&dot, false, &mut fnum, false, &wp).expect("cursor");
assert_eq!(p.lnum, 2);
assert_eq!(p.col, 3);
let dollar = typval_T::from("$".to_string());
let p = var2fpos(&dollar, true, &mut fnum, false, &wp).expect("last line");
assert_eq!(p.lnum, 2);
assert_eq!(p.col, 0);
let p = var2fpos(&dollar, false, &mut fnum, false, &wp).expect("last col");
assert_eq!(p.lnum, 2);
assert_eq!(p.col, 5);
let mark = typval_T::from("'a".to_string());
assert!(var2fpos(&mark, false, &mut fnum, false, &wp).is_none());
}
#[test]
fn list2fpos_with_and_without_fnum() {
use super::list2fpos;
use crate::ported::eval::typval::{tv_list_alloc, tv_list_append_number};
reset_editor();
let buf = buflist_new(Some("/tmp/l2".into()), None, 0, BLN_LISTED).unwrap();
load_lines(&buf, &["a", "b"]);
curbuf.with(|c| *c.borrow_mut() = Some(buf.clone()));
let l = tv_list_alloc(-1);
{
let mut lb = l.borrow_mut();
tv_list_append_number(&mut lb, 1);
tv_list_append_number(&mut lb, 2);
tv_list_append_number(&mut lb, 3);
}
let arg = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l)),
};
let mut posp = super::pos_T::default();
assert_eq!(list2fpos(&arg, &mut posp, None, None, false), OK);
assert_eq!(posp.lnum, 1);
assert_eq!(posp.col, 2);
assert_eq!(posp.coladd, 3);
let l2 = tv_list_alloc(-1);
{
let mut lb = l2.borrow_mut();
tv_list_append_number(&mut lb, 0);
tv_list_append_number(&mut lb, 5);
tv_list_append_number(&mut lb, 2);
}
let arg2 = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(l2)),
};
let mut posp2 = super::pos_T::default();
let mut fnum = -1;
assert_eq!(
list2fpos(&arg2, &mut posp2, Some(&mut fnum), None, false),
OK
);
let curbuf_handle = curbuf
.with(|c| c.borrow().clone())
.map(|b| b.borrow().handle)
.unwrap();
assert_eq!(fnum, curbuf_handle);
assert_eq!(posp2.lnum, 5);
assert_eq!(posp2.col, 2);
let short = tv_list_alloc(-1);
tv_list_append_number(&mut short.borrow_mut(), 1);
let args = typval_T {
v_type: VAR_LIST,
v_lock: VarLockStatus::VAR_UNLOCKED,
vval: v_list(Some(short)),
};
let mut p3 = super::pos_T::default();
assert_eq!(list2fpos(&args, &mut p3, None, None, false), FAIL);
}
#[test]
fn eval_for_line_list_and_errors() {
use super::{eval_for_line, evalarg_T};
use crate::ported::eval::typval::EVAL_STRING_HOOK;
fn hook(expr: &str) -> Option<typval_T> {
expr.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 mut errp = true;
let mut ea = evalarg_T {
eval_flags: super::EVAL_EVALUATE,
};
let fi = eval_for_line("x in [1, 2, 3]", &mut errp, &mut ea);
assert!(!errp);
assert_eq!(fi.fi_varcount, 1);
assert_eq!(fi.fi_semicolon, 0);
let l = fi.fi_list.expect("fi_list");
assert_eq!(crate::ported::eval::typval::tv_list_len(&l.borrow()), 3);
let mut errp2 = true;
let mut ea2 = evalarg_T {
eval_flags: super::EVAL_EVALUATE,
};
let fi2 = eval_for_line("x [1]", &mut errp2, &mut ea2);
assert!(errp2);
assert!(fi2.fi_list.is_none());
let mut errp3 = true;
let mut ea3 = evalarg_T {
eval_flags: super::EVAL_EVALUATE,
};
let fi3 = eval_for_line("c in \"abc\"", &mut errp3, &mut ea3);
assert!(!errp3);
assert_eq!(fi3.fi_string.as_deref(), Some("abc"));
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
fn eval0_hook(e: &str) -> Option<typval_T> {
let mut tv = typval_T::default();
let mut ea = super::evalarg_T {
eval_flags: super::EVAL_EVALUATE,
};
if super::eval0(e, &mut tv, Some(&mut ea)) == crate::ported::eval_h::OK {
Some(tv)
} else {
None
}
}
#[test]
fn ex_echo_joins_and_echon_concats() {
use crate::ported::eval::typval::EVAL_STRING_HOOK;
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(eval0_hook));
assert_eq!(super::ex_echo("1 2 3", false, false), "1 2 3");
assert_eq!(super::ex_echo("1 2 3", false, true), "123");
assert_eq!(super::ex_echo("'ab' 'cd'", false, false), "ab cd");
assert_eq!(super::ex_echo("1 2", true, false), "");
assert_eq!(super::ex_echo("40 + 2", false, false), "42");
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn ex_echo_invalid_expr_stops() {
use crate::ported::eval::typval::EVAL_STRING_HOOK;
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(eval0_hook));
crate::ported::message::capture_errors_begin();
let out = super::ex_echo("1 )", false, false);
assert_eq!(out, "1");
let errs = crate::ported::message::capture_errors_take();
assert!(errs.iter().any(|e| e.contains("E15")), "errs={errs:?}");
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn ex_execute_concatenates_args() {
use crate::ported::eval::typval::EVAL_STRING_HOOK;
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(eval0_hook));
assert_eq!(
super::ex_execute("'echo' 'hi'", false, false, false),
"echo hi"
);
assert_eq!(super::ex_execute("1 2", false, true, false), "1 2");
assert_eq!(super::ex_execute("'a' 'b'", true, false, false), "");
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn ex_execute_echoerr_routes_to_emsg() {
use crate::ported::eval::typval::EVAL_STRING_HOOK;
let saved = EVAL_STRING_HOOK.with(|h| *h.borrow());
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = Some(eval0_hook));
crate::ported::message::capture_errors_begin();
let out = super::ex_execute("'boom'", false, false, true);
assert_eq!(out, "boom");
let errs = crate::ported::message::capture_errors_take();
assert!(errs.iter().any(|e| e == "boom"), "errs={errs:?}");
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
#[test]
fn ex_echohl_resolves_to_zero_standalone() {
super::ex_echohl("ErrorMsg");
assert_eq!(super::get_echo_hl_id(), 0);
}
}