#![allow(non_snake_case)]
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::*, BoolVarValue::*, SpecialVarValue::*, VarType::*,
};
pub(crate) fn vim_float_g(f: f64, precision: Option<i32>) -> String {
let abs_f = f.abs();
let use_exp = !((abs_f >= 0.001 && abs_f < 10000000.0) || abs_f == 0.0);
let prec_specified = precision.is_some();
let p = precision.unwrap_or(6).max(0) as usize;
let mut s: Vec<u8> = if use_exp {
format!("{f:.p$e}").into_bytes()
} else {
format!("{f:.p$}").into_bytes()
};
if !prec_specified {
let mut i = match s.iter().position(|&b| b == b'e') {
Some(epos) => epos - 1,
None => s.len() - 1,
};
while i > 2 && s[i] == b'0' && s[i - 1] != b'.' {
s.remove(i);
i -= 1;
}
}
String::from_utf8(s).unwrap()
}
pub fn encode_blob_write(blob: &mut crate::ported::eval::typval_defs_h::blob_T, buf: &[u8]) -> i32 {
blob.bv_ga.extend_from_slice(buf);
buf.len() as i32
}
pub fn conv_error(
msg: &str,
mpstack: &crate::ported::eval::typval_encode_h::MPConvStack,
objname: &str,
) -> i32 {
use crate::ported::eval::typval_defs_h::VarLockStatus::VAR_UNLOCKED;
use crate::ported::eval::typval_encode_h::{
kv_A, kv_size, MPConvPartialStage::*, MPConvStackValData, MPConvStackValType::*,
};
use crate::ported::eval_h::FAIL;
use crate::ported::message::semsg;
let mut msg_ga = String::new();
let key_msg = "key %s";
let key_pair_msg = "key %s at index %i from special map";
let idx_msg = "index %i";
let partial_arg_msg = "partial";
let partial_arg_i_msg = "argument %i";
let partial_self_msg = "partial self dictionary";
let snprintf_s = |fmt: &str, s: &str| -> String { fmt.replacen("%s", s, 1) };
let snprintf_i = |fmt: &str, i: i32| -> String { fmt.replacen("%i", &i.to_string(), 1) };
for i in 0..kv_size(mpstack) {
if i != 0 {
msg_ga.push_str(", ");
}
let v = kv_A(mpstack, i);
match v.r#type {
kMPConvDict => {
let (dict, hi) = match &v.data {
MPConvStackValData::d { dict, hi, .. } => (dict, *hi),
_ => continue,
};
let dict = dict.borrow();
let key_idx = match hi {
None => 0,
Some(h) => h - 1,
};
let hi_key = dict
.dv_hashtab
.get_index(key_idx)
.map(|(k, _)| k.clone())
.unwrap_or_default();
let key_tv = typval_T {
v_type: VAR_STRING,
v_lock: VAR_UNLOCKED,
vval: v_string(hi_key),
};
let key = encode_tv2string(&key_tv);
msg_ga.push_str(&snprintf_s(key_msg, &key));
}
kMPConvPairs | kMPConvList => {
let (list, li) = match &v.data {
MPConvStackValData::l { list, li } => (list, *li),
_ => continue,
};
let list = list.borrow();
let len = list.lv_items.len() as i32;
let idx = match li {
Some(0) => 0, None => len - 1, Some(l) => l as i32 - 1, };
let li_cur: Option<usize> = match li {
None => {
if len >= 1 {
Some((len - 1) as usize)
} else {
None
}
}
Some(0) => None, Some(l) => Some(l - 1),
};
let pair_first: Option<typval_T> = if v.r#type == kMPConvList {
None
} else {
match li_cur {
None => None,
Some(ci) => match &list.lv_items[ci].li_tv.vval {
v_list(Some(sub)) => {
sub.borrow().lv_items.first().map(|it| it.li_tv.clone())
}
_ => None,
},
}
};
match pair_first {
None => msg_ga.push_str(&snprintf_i(idx_msg, idx)),
Some(key_tv) => {
let key = encode_tv2echo(&key_tv);
msg_ga.push_str(&snprintf_i(&snprintf_s(key_pair_msg, &key), idx));
}
}
}
kMPConvPartial => {
let stage = match &v.data {
MPConvStackValData::p { stage, .. } => *stage,
_ => continue,
};
match stage {
kMPConvPartialArgs => panic!("conv_error: kMPConvPartialArgs"),
kMPConvPartialSelf => msg_ga.push_str(partial_arg_msg),
kMPConvPartialEnd => msg_ga.push_str(partial_self_msg),
}
}
kMPConvPartialList => {
let arg = match &v.data {
MPConvStackValData::a { arg, .. } => *arg,
_ => continue,
};
let idx = arg as i32 - 1;
msg_ga.push_str(&snprintf_i(partial_arg_i_msg, idx));
}
}
}
let path = if kv_size(mpstack) == 0 {
"itself"
} else {
msg_ga.as_str()
};
let out = {
let first = msg.replacen("%s", objname, 1);
first.replacen("%s", path, 1)
};
semsg(&out);
FAIL
}
pub fn encode_vim_list_to_buf(list: &crate::ported::eval::typval_defs_h::list_T) -> Option<String> {
let mut parts: Vec<String> = Vec::with_capacity(list.lv_items.len());
for it in &list.lv_items {
if it.li_tv.v_type != VAR_STRING {
return None;
}
match &it.li_tv.vval {
v_string(s) => parts.push(s.replace('\n', "\0")),
_ => parts.push(String::new()),
}
}
Some(parts.join("\n"))
}
#[derive(Debug, Clone, Copy)]
pub struct ListReaderState {
pub li: usize,
pub offset: usize,
pub li_length: usize,
}
pub fn encode_init_lrstate(list: &crate::ported::eval::typval_defs_h::list_T) -> ListReaderState {
let li_length = list.lv_items.first().map_or(0, |it| match &it.li_tv.vval {
v_string(s) => s.len(),
_ => 0,
});
ListReaderState {
li: 0,
offset: 0,
li_length,
}
}
pub fn encode_read_from_list(
state: &mut ListReaderState,
list: &crate::ported::eval::typval_defs_h::list_T,
buf: &mut [u8],
) -> (i32, usize) {
use crate::ported::eval_h::{FAIL, OK};
const NOTDONE: i32 = 2; let nbuf = buf.len();
let mut p = 0;
while p < nbuf {
if let Some(bytes) = list
.lv_items
.get(state.li)
.and_then(|it| match &it.li_tv.vval {
v_string(s) => Some(s.as_bytes()),
_ => None,
})
{
while state.offset < state.li_length && p < nbuf {
let ch = bytes[state.offset];
state.offset += 1;
buf[p] = if ch == b'\n' { 0 } else { ch };
p += 1;
}
}
if p < nbuf {
state.li += 1;
if state.li >= list.lv_items.len() {
return (OK, p);
}
buf[p] = b'\n';
p += 1;
match list.lv_items.get(state.li).map(|it| &it.li_tv) {
Some(tv) if tv.v_type == VAR_STRING => {
state.offset = 0;
state.li_length = match &tv.vval {
v_string(s) => s.len(),
_ => 0,
};
}
_ => return (FAIL, p),
}
}
}
let more = state.offset < state.li_length || state.li + 1 < list.lv_items.len();
(if more { NOTDONE } else { OK }, nbuf)
}
pub fn encode_list_write(list: &mut crate::ported::eval::typval_defs_h::list_T, buf: &str) {
use crate::ported::eval::typval::tv_list_append_string;
use crate::ported::eval::typval_defs_h::typval_vval_union::v_string;
if buf.is_empty() {
return;
}
let mut segments = buf.split('\n');
if !list.lv_items.is_empty() {
if let Some(first) = segments.next() {
let chunk = first.replace('\0', "\n");
if let v_string(s) = &mut list.lv_items.last_mut().unwrap().li_tv.vval {
s.push_str(&chunk);
}
}
}
for seg in segments {
tv_list_append_string(list, &seg.replace('\0', "\n"));
}
}
pub fn encode_tv2string(tv: &typval_T) -> String {
encode_vim_to_string(tv)
}
pub fn encode_tv2echo(tv: &typval_T) -> String {
match (tv.v_type, &tv.vval) {
(VAR_STRING | VAR_FUNC, v_string(s)) => s.clone(),
_ => encode_vim_to_echo(tv),
}
}
pub fn encode_vim_to_string(tv: &typval_T) -> String {
match (tv.v_type, &tv.vval) {
(VAR_NUMBER, v_number(n)) => n.to_string(),
(VAR_FLOAT, v_float(f)) => {
if f.is_nan() {
"str2float('nan')".to_string()
} else if f.is_infinite() {
if *f < 0.0 {
"-str2float('inf')"
} else {
"str2float('inf')"
}
.to_string()
} else {
let s = vim_float_g(*f, None);
if s.contains(['.', 'e', 'E']) {
s
} else {
format!("{s}.0")
}
}
}
(VAR_STRING, v_string(s)) => format!("'{}'", s.replace('\'', "''")),
(VAR_FUNC, v_string(s)) => format!("function('{}')", s.replace('\'', "''")),
(VAR_PARTIAL, v_partial(Some(p))) => {
let name = p.pt_name.replace('\'', "''");
if p.pt_argv.is_empty() {
format!("function('{name}')")
} else {
let args: Vec<String> = p.pt_argv.iter().map(encode_tv2string).collect();
format!("function('{name}', [{}])", args.join(", "))
}
}
(VAR_BOOL, v_bool(b)) => if *b == kBoolVarTrue {
"v:true"
} else {
"v:false"
}
.to_string(),
(VAR_SPECIAL, v_special(kSpecialVarNone)) => "v:none".to_string(),
(VAR_SPECIAL, _) => "v:null".to_string(),
(VAR_LIST, v_list(l)) => match l {
None => "[]".to_string(),
Some(l) => {
let l = l.borrow();
let mut out = String::from("[");
for (i, it) in l.lv_items.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(&encode_vim_to_string(&it.li_tv));
}
out.push(']');
out
}
},
(VAR_DICT, v_dict(d)) => match d {
None => "{}".to_string(),
Some(d) => {
let d = d.borrow();
let mut out = String::from("{");
for (i, (k, v)) in d.dv_hashtab.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(&format!("'{}'", k.replace('\'', "''")));
out.push_str(": ");
out.push_str(&encode_vim_to_string(v));
}
out.push('}');
out
}
},
(VAR_BLOB, v_blob(b)) => match b {
None => "0z".to_string(),
Some(b) => {
let b = b.borrow();
let mut out = String::from("0z");
for (i, byte) in b.bv_ga.iter().enumerate() {
if i > 0 && i % 4 == 0 {
out.push('.');
}
out.push_str(&format!("{byte:02X}"));
}
out
}
},
_ => String::new(),
}
}
pub fn encode_vim_to_echo(tv: &typval_T) -> String {
encode_vim_to_string(tv)
}
pub fn encode_tv2json(tv: &typval_T) -> String {
encode_vim_to_json(tv)
}
fn convert_to_json_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\t' => out.push_str("\\t"),
'\r' => out.push_str("\\r"),
'\x08' => out.push_str("\\b"),
'\x0c' => out.push_str("\\f"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c => out.push(c),
}
}
out.push('"');
out
}
pub fn encode_vim_to_json(tv: &typval_T) -> String {
match (tv.v_type, &tv.vval) {
(VAR_NUMBER, v_number(n)) => n.to_string(),
(VAR_FLOAT, v_float(f)) => {
if f.is_finite() {
let s = vim_float_g(*f, None);
if s.contains(['.', 'e', 'E']) {
s
} else {
format!("{s}.0")
}
} else {
"null".to_string() }
}
(VAR_STRING, v_string(s)) => convert_to_json_string(s),
(VAR_BOOL, v_bool(b)) => if *b == kBoolVarTrue { "true" } else { "false" }.to_string(),
(VAR_SPECIAL, _) => "null".to_string(),
(VAR_LIST, v_list(l)) => match l {
None => "[]".to_string(),
Some(l) => {
let l = l.borrow();
let mut out = String::from("[");
for (i, it) in l.lv_items.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push_str(&encode_vim_to_json(&it.li_tv));
}
out.push(']');
out
}
},
(VAR_DICT, v_dict(d)) => match d {
None => "{}".to_string(),
Some(d) => {
let d = d.borrow();
let mut out = String::from("{");
for (i, (k, v)) in d.dv_hashtab.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push_str(&convert_to_json_string(k));
out.push(':');
out.push_str(&encode_vim_to_json(v));
}
out.push('}');
out
}
},
_ => "null".to_string(),
}
}
pub fn encode_check_json_key(tv: &typval_T) -> bool {
use crate::ported::eval::decode::{eval_msgpack_type_lists, MessagePackType};
use crate::ported::eval::typval::tv_dict_find;
use std::rc::Rc;
if tv.v_type == VAR_STRING {
return true;
}
if tv.v_type != VAR_DICT {
return false;
}
let spdict = match &tv.vval {
v_dict(Some(d)) => d,
_ => return false,
};
let spdict = spdict.borrow();
if spdict.dv_hashtab.len() != 2 {
return false;
}
let type_tv = match tv_dict_find(&spdict, "_TYPE") {
None => return false,
Some(t) => t,
};
if type_tv.v_type != VAR_LIST {
return false;
}
let type_list = match &type_tv.vval {
v_list(Some(l)) => l,
_ => return false,
};
if !eval_msgpack_type_lists
.with(|arr| Rc::ptr_eq(type_list, &arr[MessagePackType::kMPString as usize]))
{
return false;
}
let val_tv = match tv_dict_find(&spdict, "_VAL") {
None => return false,
Some(v) => v,
};
if val_tv.v_type != VAR_LIST {
return false;
}
let val_list = match &val_tv.vval {
v_list(Some(l)) => l.clone(),
_ => return true,
};
for li in &val_list.borrow().lv_items {
if li.li_tv.v_type != VAR_STRING {
return false;
}
}
true
}
#[cfg(test)]
mod encode_check_json_key_tests {
use super::encode_check_json_key;
use crate::ported::eval::decode::{eval_msgpack_type_lists, MessagePackType};
use crate::ported::eval::typval::{
tv_dict_add, tv_dict_alloc, tv_list_alloc, tv_list_append_string,
};
use crate::ported::eval::typval_defs_h::{
typval_T, typval_vval_union::*, VarLockStatus::*, VarType::*,
};
fn list_tv(
rc: std::rc::Rc<std::cell::RefCell<crate::ported::eval::typval_defs_h::list_T>>,
) -> typval_T {
typval_T {
v_type: VAR_LIST,
v_lock: VAR_UNLOCKED,
vval: v_list(Some(rc)),
}
}
#[test]
fn plain_string_key_is_valid() {
assert!(encode_check_json_key(&typval_T::from("k".to_string())));
}
#[test]
fn number_key_is_invalid() {
assert!(!encode_check_json_key(&typval_T::from(
7 as crate::ported::eval::typval_defs_h::varnumber_T
)));
}
#[test]
fn plain_dict_is_invalid() {
let d = tv_dict_alloc();
tv_dict_add(&mut d.borrow_mut(), "a", typval_T::from("x".to_string()));
let tv = typval_T {
v_type: VAR_DICT,
v_lock: VAR_UNLOCKED,
vval: v_dict(Some(d)),
};
assert!(!encode_check_json_key(&tv));
}
#[test]
fn string_special_dict_of_strings_is_valid() {
let type_list =
eval_msgpack_type_lists.with(|a| a[MessagePackType::kMPString as usize].clone());
let val = tv_list_alloc(0);
tv_list_append_string(&mut val.borrow_mut(), "abc");
let d = tv_dict_alloc();
tv_dict_add(&mut d.borrow_mut(), "_TYPE", list_tv(type_list));
tv_dict_add(&mut d.borrow_mut(), "_VAL", list_tv(val));
let tv = typval_T {
v_type: VAR_DICT,
v_lock: VAR_UNLOCKED,
vval: v_dict(Some(d)),
};
assert!(encode_check_json_key(&tv));
}
#[test]
fn special_dict_with_nonstring_val_item_is_invalid() {
let type_list =
eval_msgpack_type_lists.with(|a| a[MessagePackType::kMPString as usize].clone());
let val = tv_list_alloc(0);
crate::ported::eval::typval::tv_list_append_number(&mut val.borrow_mut(), 3);
let d = tv_dict_alloc();
tv_dict_add(&mut d.borrow_mut(), "_TYPE", list_tv(type_list));
tv_dict_add(&mut d.borrow_mut(), "_VAL", list_tv(val));
let tv = typval_T {
v_type: VAR_DICT,
v_lock: VAR_UNLOCKED,
vval: v_dict(Some(d)),
};
assert!(!encode_check_json_key(&tv));
}
#[test]
fn special_dict_wrong_type_list_is_invalid() {
let type_list = tv_list_alloc(0);
let val = tv_list_alloc(0);
tv_list_append_string(&mut val.borrow_mut(), "abc");
let d = tv_dict_alloc();
tv_dict_add(&mut d.borrow_mut(), "_TYPE", list_tv(type_list));
tv_dict_add(&mut d.borrow_mut(), "_VAL", list_tv(val));
let tv = typval_T {
v_type: VAR_DICT,
v_lock: VAR_UNLOCKED,
vval: v_dict(Some(d)),
};
assert!(!encode_check_json_key(&tv));
}
}
#[cfg(test)]
mod conv_error_tests {
use super::conv_error;
use crate::ported::eval::typval::{
tv_dict_add, tv_dict_alloc, tv_list_alloc, tv_list_append_number, tv_list_append_string,
};
use crate::ported::eval::typval_defs_h::{
list_T, typval_T, typval_vval_union::*, VarLockStatus::*, VarType::*,
};
use crate::ported::eval::typval_encode_h::{
kvi_push, MPConvPartialStage::*, MPConvStack, MPConvStackVal, MPConvStackValData,
MPConvStackValType::*,
};
use crate::ported::message::{capture_errors_begin, capture_errors_take};
use std::cell::RefCell;
use std::rc::Rc;
fn list_tv(l: Rc<RefCell<list_T>>) -> typval_T {
typval_T {
v_type: VAR_LIST,
v_lock: VAR_UNLOCKED,
vval: v_list(Some(l)),
}
}
#[test]
fn empty_stack_uses_itself() {
let stack = MPConvStack::default();
capture_errors_begin();
assert_eq!(conv_error("E: %s, %s", &stack, "F"), 0);
let errs = capture_errors_take();
assert_eq!(errs, vec!["E: F, itself".to_string()]);
}
#[test]
fn walks_every_stack_variant() {
let mut stack = MPConvStack::default();
let d = tv_dict_alloc();
tv_dict_add(
&mut d.borrow_mut(),
"foo",
typval_T::from(1 as crate::ported::eval::typval_defs_h::varnumber_T),
);
kvi_push(
&mut stack,
MPConvStackVal {
r#type: kMPConvDict,
tv: None,
saved_copyID: 0,
data: MPConvStackValData::d {
dict: d.clone(),
dictp: d.clone(),
hi: None,
todo: 0,
},
},
);
let l = tv_list_alloc(0);
tv_list_append_string(&mut l.borrow_mut(), "a");
tv_list_append_string(&mut l.borrow_mut(), "b");
tv_list_append_string(&mut l.borrow_mut(), "c");
kvi_push(
&mut stack,
MPConvStackVal {
r#type: kMPConvList,
tv: None,
saved_copyID: 0,
data: MPConvStackValData::l {
list: l.clone(),
li: Some(2),
},
},
);
let pair = tv_list_alloc(0);
tv_list_append_string(&mut pair.borrow_mut(), "k");
tv_list_append_number(&mut pair.borrow_mut(), 9);
let pairs = tv_list_alloc(0);
pairs
.borrow_mut()
.lv_items
.push(crate::ported::eval::typval_defs_h::listitem_T {
li_tv: list_tv(pair),
});
kvi_push(
&mut stack,
MPConvStackVal {
r#type: kMPConvPairs,
tv: None,
saved_copyID: 0,
data: MPConvStackValData::l {
list: pairs,
li: None,
},
},
);
kvi_push(
&mut stack,
MPConvStackVal {
r#type: kMPConvPartial,
tv: None,
saved_copyID: 0,
data: MPConvStackValData::p {
stage: kMPConvPartialSelf,
pt: None,
},
},
);
kvi_push(
&mut stack,
MPConvStackVal {
r#type: kMPConvPartialList,
tv: None,
saved_copyID: 0,
data: MPConvStackValData::a {
arg: 2,
argv: Vec::new(),
todo: 0,
},
},
);
capture_errors_begin();
assert_eq!(conv_error("dump %s at %s", &stack, "object"), 0);
let errs = capture_errors_take();
assert_eq!(
errs,
vec!["dump object at key 'foo', index 1, key k at index 0 from special map, partial, argument 1".to_string()]
);
}
}
#[cfg(test)]
mod encode_io_tests {
use super::{encode_blob_write, encode_vim_list_to_buf};
use crate::ported::eval::typval::{tv_list_append_number, tv_list_append_string};
use crate::ported::eval::typval_defs_h::{blob_T, list_T};
#[test]
fn blob_write_appends_bytes() {
let mut b = blob_T::default();
assert_eq!(encode_blob_write(&mut b, &[1, 2, 3]), 3);
assert_eq!(encode_blob_write(&mut b, &[4]), 1);
assert_eq!(b.bv_ga, vec![1, 2, 3, 4]);
}
#[test]
fn read_from_list_matches_to_buf() {
use super::{encode_init_lrstate, encode_read_from_list, encode_vim_list_to_buf};
use crate::ported::eval_h::OK;
let mut l = list_T::default();
tv_list_append_string(&mut l, "a");
tv_list_append_string(&mut l, "x\ny"); tv_list_append_string(&mut l, "b");
let expected = encode_vim_list_to_buf(&l).unwrap();
let mut st = encode_init_lrstate(&l);
let mut buf = vec![0u8; 64];
let (status, n) = encode_read_from_list(&mut st, &l, &mut buf);
assert_eq!(status, OK);
assert_eq!(&buf[..n], expected.as_bytes());
let mut st2 = encode_init_lrstate(&l);
let mut small = vec![0u8; 2];
let (status2, n2) = encode_read_from_list(&mut st2, &l, &mut small);
assert_eq!(status2, 2);
assert_eq!(n2, 2);
}
#[test]
fn vim_list_to_buf_joins() {
let mut l = list_T::default();
tv_list_append_string(&mut l, "a");
tv_list_append_string(&mut l, "b");
assert_eq!(encode_vim_list_to_buf(&l).as_deref(), Some("a\nb"));
let mut l2 = list_T::default();
tv_list_append_string(&mut l2, "x\ny");
assert_eq!(encode_vim_list_to_buf(&l2), Some("x\0y".to_string()));
let mut l3 = list_T::default();
tv_list_append_string(&mut l3, "ok");
tv_list_append_number(&mut l3, 7);
assert_eq!(encode_vim_list_to_buf(&l3), None);
}
}