#![allow(
non_upper_case_globals,
non_camel_case_types,
dead_code,
non_snake_case
)]
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use crate::ported::eval::typval_defs_h::typval_T;
use crate::ported::eval_h::{FAIL, OK};
use crate::ported::message::{did_emsg, emsg, semsg};
type linenr_T = i32;
const e_argreq: &str = "E471: Argument required"; const e_endif: &str = "E171: Missing :endif"; const e_endtry: &str = "E600: Missing :endtry"; const e_endwhile: &str = "E170: Missing :endwhile"; const e_endfor: &str = "E170: Missing :endfor"; const e_while: &str = "E588: :endwhile without :while"; const e_for: &str = "E588: :endfor without :for"; const e_invarg2: &str = "E475: Invalid argument: %s"; const e_invexpr2: &str = "E15: Invalid expression: \"%s\""; const e_trailing_arg: &str = "E488: Trailing characters: %s"; const e_str_not_inside_function: &str = "E193: %s not inside a function"; const e_multiple_else: &str = "E583: Multiple :else"; const e_multiple_finally: &str = "E607: Multiple :finally";
pub const CSTACK_LEN: usize = 50;
pub const CSF_TRUE: i32 = 0x0001; pub const CSF_ACTIVE: i32 = 0x0002; pub const CSF_ELSE: i32 = 0x0004; pub const CSF_WHILE: i32 = 0x0008; pub const CSF_FOR: i32 = 0x0010; pub const CSF_TRY: i32 = 0x0100; pub const CSF_FINALLY: i32 = 0x0200; pub const CSF_THROWN: i32 = 0x0800; pub const CSF_CAUGHT: i32 = 0x1000; pub const CSF_FINISHED: i32 = 0x2000; pub const CSF_SILENT: i32 = 0x4000;
pub const CSTP_NONE: i32 = 0;
pub const CSTP_ERROR: i32 = 1;
pub const CSTP_INTERRUPT: i32 = 2;
pub const CSTP_THROW: i32 = 4;
pub const CSTP_BREAK: i32 = 8;
pub const CSTP_CONTINUE: i32 = 16;
pub const CSTP_RETURN: i32 = 24;
pub const CSTP_FINISH: i32 = 32;
pub const CSL_HAD_LOOP: i32 = 1;
pub const CSL_HAD_ENDLOOP: i32 = 2;
pub const CSL_HAD_CONT: i32 = 4;
pub const CSL_HAD_FINA: i32 = 8;
const RP_MAKE: i32 = 0;
const RP_RESUME: i32 = 1;
const RP_DISCARD: i32 = 2;
const THROW_ON_ERROR: bool = true;
const THROW_ON_INTERRUPT: bool = true;
const RE_MAGIC: i32 = 1;
const RE_STRING: i32 = 2;
thread_local! {
pub static force_abort: Cell<bool> = const { Cell::new(false) };
pub static got_int: Cell<bool> = const { Cell::new(false) };
pub static did_throw: Cell<bool> = const { Cell::new(false) };
pub static trylevel: Cell<i32> = const { Cell::new(0) };
pub static emsg_silent: Cell<i32> = const { Cell::new(0) };
pub static did_endif: Cell<bool> = const { Cell::new(false) };
static cause_abort: Cell<bool> = const { Cell::new(false) };
pub static need_rethrow: Cell<bool> = const { Cell::new(false) };
pub static suppress_errthrow: Cell<bool> = const { Cell::new(false) };
static p_verbose: Cell<i64> = const { Cell::new(0) };
static debug_break_level: Cell<i32> = const { Cell::new(0) };
static emsg_off: Cell<i32> = const { Cell::new(0) };
pub static current_exception: RefCell<Option<Rc<RefCell<except_T>>>> =
const { RefCell::new(None) };
static caught_stack: RefCell<Option<Rc<RefCell<except_T>>>> = const { RefCell::new(None) };
}
const SOURCING_LNUM: linenr_T = 0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum cmdidx_T {
CMD_if,
CMD_elseif,
CMD_else,
CMD_endif,
CMD_while,
CMD_for,
CMD_continue,
CMD_break,
CMD_endwhile,
CMD_endfor,
CMD_try,
CMD_catch,
CMD_finally,
CMD_endtry,
CMD_throw,
CMD_eval,
CMD_endfunction,
}
pub struct eslist_T {
pub saved_emsg_silent: i32,
pub next: Option<Box<eslist_T>>,
}
pub struct msglist_T {
pub next: Option<Box<msglist_T>>,
pub msg: String,
pub throw_msg: String,
pub sfile: String,
pub slnum: linenr_T,
pub multiline: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum except_type_T {
ET_USER,
ET_ERROR,
ET_INTERRUPT,
}
pub struct except_T {
pub type_: except_type_T,
pub value: String,
pub messages: Option<Box<msglist_T>>,
pub throw_name: String,
pub throw_lnum: linenr_T,
pub caught: Option<Rc<RefCell<except_T>>>,
}
pub struct cleanup_T {
pub pending: i32,
pub exception: Option<Rc<RefCell<except_T>>>,
}
pub struct cs_pend_T {
pub csp_rv: [Option<typval_T>; CSTACK_LEN],
pub csp_ex: [Option<Rc<RefCell<except_T>>>; CSTACK_LEN],
}
pub struct cstack_T {
pub cs_flags: [i32; CSTACK_LEN],
pub cs_pending: [i8; CSTACK_LEN],
pub cs_pend: cs_pend_T,
pub cs_forinfo: [Option<()>; CSTACK_LEN],
pub cs_line: [i32; CSTACK_LEN],
pub cs_idx: i32,
pub cs_looplevel: i32,
pub cs_trylevel: i32,
pub cs_emsg_silent_list: Option<Box<eslist_T>>,
pub cs_lflags: i32,
}
impl Default for cstack_T {
fn default() -> Self {
cstack_T {
cs_flags: [0; CSTACK_LEN],
cs_pending: [0; CSTACK_LEN],
cs_pend: cs_pend_T {
csp_rv: [const { None }; CSTACK_LEN],
csp_ex: [const { None }; CSTACK_LEN],
},
cs_forinfo: [const { None }; CSTACK_LEN],
cs_line: [0; CSTACK_LEN],
cs_idx: -1,
cs_looplevel: 0,
cs_trylevel: 0,
cs_emsg_silent_list: None,
cs_lflags: 0,
}
}
}
pub struct exarg_T {
pub arg: String,
pub cmdidx: cmdidx_T,
pub forceit: bool,
pub skip: bool,
pub errmsg: Option<&'static str>,
pub nextcmd: Option<String>,
pub cstack: cstack_T,
}
#[derive(Clone, Default)]
pub struct exception_state_T {
pub estate_current_exception: Option<Rc<RefCell<except_T>>>,
pub estate_did_throw: bool,
pub estate_need_rethrow: bool,
pub estate_trylevel: i32,
pub estate_did_emsg: u64,
}
pub struct regprog_T;
pub struct regmatch_T {
pub regprog: Option<Box<regprog_T>>,
pub rm_ic: bool,
}
fn dbg_check_skipped(_eap: &exarg_T) -> bool {
false
}
fn do_finish(_eap: &mut exarg_T, _reanimate: bool) {}
fn find_nextcmd(_p: &str) -> Option<String> {
None
}
fn skip_regexp_err(_pat: &str, _delim: u8, _magic: bool) -> Option<String> {
None
}
fn vim_regcomp(_expr: &str, _re_flags: i32) -> Option<Box<regprog_T>> {
unimplemented!("deferred: vim_regcomp — vendor regexp.c not vendored")
}
fn vim_regexec_nl(_rmp: &mut regmatch_T, _line: &str, _col: usize) -> bool {
unimplemented!("deferred: vim_regexec_nl — vendor regexp.c not vendored")
}
fn vim_regfree(_prog: Option<Box<regprog_T>>) {}
fn internal_error(_where: &str) {}
fn estack_sfile() -> Option<String> {
None
}
fn set_vim_var_string(_idx: i32, _val: Option<&str>, _len: isize) {}
fn set_vim_var_list() {}
fn discard_pending_return(_p: Option<typval_T>) {}
pub fn aborting() -> bool {
(did_emsg.with(|d| d.get()) != 0 && force_abort.with(|f| f.get())) || got_int.with(|g| g.get())
|| did_throw.with(|t| t.get())
}
pub fn update_force_abort() {
if cause_abort.with(|c| c.get()) {
force_abort.with(|f| f.set(true)); }
}
pub fn should_abort(retcode: i32) -> bool {
(retcode == FAIL && trylevel.with(|t| t.get()) != 0 && emsg_silent.with(|e| e.get()) == 0)
|| aborting() }
pub fn aborted_in_try() -> bool {
force_abort.with(|f| f.get()) }
pub fn cause_errthrow(_mesg: &str, _multiline: bool, _concat: bool, _severe: bool) -> bool {
if suppress_errthrow.with(|s| s.get()) {
return false; }
if did_emsg.with(|d| d.get()) == 0 {
cause_abort.with(|c| c.set(force_abort.with(|f| f.get()))); force_abort.with(|f| f.set(false)); }
if ((trylevel.with(|t| t.get()) == 0 && !cause_abort.with(|c| c.get()))
|| emsg_silent.with(|e| e.get()) != 0)
&& !did_throw.with(|t| t.get())
{
return false; }
cause_abort.with(|c| c.set(true));
if did_throw.with(|t| t.get()) {
discard_current_exception();
}
true
}
fn free_msglist(_l: Option<Box<msglist_T>>) {}
pub fn free_global_msglist() {}
pub fn do_errthrow(_cstack: Option<&mut cstack_T>, _cmdname: Option<&str>) {
if cause_abort.with(|c| c.get()) {
cause_abort.with(|c| c.set(false)); force_abort.with(|f| f.set(true)); }
}
pub fn do_intthrow(cstack: &mut cstack_T) -> bool {
if !got_int.with(|g| g.get())
|| (trylevel.with(|t| t.get()) == 0 && !did_throw.with(|t| t.get()))
{
return false;
}
if did_throw.with(|t| t.get()) {
if current_exception.with(|c| c.borrow().as_ref().map(|e| e.borrow().type_))
== Some(except_type_T::ET_INTERRUPT)
{
return false;
}
discard_current_exception();
}
if throw_exception(
"Vim:Interrupt".to_string(),
except_type_T::ET_INTERRUPT,
None,
) != FAIL
{
do_throw(cstack); }
true
}
pub fn get_exception_string(value: &str, type_: except_type_T, cmdname: Option<&str>) -> String {
match type_ {
except_type_T::ET_ERROR => match cmdname.filter(|c| !c.is_empty()) {
Some(cmd) => format!("Vim({cmd}):{value}"), None => format!("Vim:{value}"), },
except_type_T::ET_USER | except_type_T::ET_INTERRUPT => value.to_string(), }
}
fn throw_exception(value: String, type_: except_type_T, cmdname: Option<&str>) -> i32 {
if type_ == except_type_T::ET_USER {
if value.starts_with("Vim")
&& matches!(value.as_bytes().get(3), None | Some(b':') | Some(b'('))
{
emsg("E608: Cannot :throw exceptions with 'Vim' prefix"); current_exception.with(|c| *c.borrow_mut() = None); return FAIL;
}
}
let excp = except_T {
type_,
value: get_exception_string(&value, type_, cmdname),
messages: None,
throw_name: estack_sfile().unwrap_or_default(),
throw_lnum: SOURCING_LNUM,
caught: None,
};
current_exception.with(|c| *c.borrow_mut() = Some(Rc::new(RefCell::new(excp)))); OK
}
fn discard_exception(excp: &Rc<RefCell<except_T>>, _was_finished: bool) {
let is_current =
current_exception.with(|c| c.borrow().as_ref().is_some_and(|ce| Rc::ptr_eq(ce, excp)));
if is_current {
current_exception.with(|c| *c.borrow_mut() = None);
}
}
pub fn discard_current_exception() {
let cur = current_exception.with(|c| c.borrow().clone());
if let Some(excp) = cur.as_ref() {
discard_exception(excp, false); }
did_throw.with(|t| t.set(false)); need_rethrow.with(|n| n.set(false)); }
fn catch_exception(excp: &Rc<RefCell<except_T>>) {
excp.borrow_mut().caught = caught_stack.with(|c| c.borrow().clone()); caught_stack.with(|c| *c.borrow_mut() = Some(excp.clone())); set_vim_var_string(0, Some(excp.borrow().value.as_str()), -1); set_vim_var_list(); }
fn finish_exception(excp: &Rc<RefCell<except_T>>) {
let is_top = caught_stack.with(|c| c.borrow().as_ref().is_some_and(|t| Rc::ptr_eq(t, excp)));
if !is_top {
internal_error("finish_exception()"); }
let next = caught_stack.with(|c| c.borrow().as_ref().and_then(|t| t.borrow().caught.clone()));
caught_stack.with(|c| *c.borrow_mut() = next.clone()); if let Some(top) = next.as_ref() {
set_vim_var_string(0, Some(top.borrow().value.as_str()), -1); set_vim_var_list(); } else {
set_vim_var_string(0, None, -1); set_vim_var_string(0, None, -1); set_vim_var_list(); }
discard_exception(excp, true);
}
pub fn exception_state_save() -> exception_state_T {
exception_state_T {
estate_current_exception: current_exception.with(|c| c.borrow().clone()),
estate_did_throw: did_throw.with(|t| t.get()),
estate_need_rethrow: need_rethrow.with(|n| n.get()),
estate_trylevel: trylevel.with(|t| t.get()),
estate_did_emsg: did_emsg.with(|d| d.get()),
}
}
pub fn exception_state_restore(estate: exception_state_T) {
if did_throw.with(|t| t.get()) {
handle_did_throw();
}
current_exception.with(|c| *c.borrow_mut() = estate.estate_current_exception);
did_throw.with(|t| t.set(estate.estate_did_throw));
need_rethrow.with(|n| n.set(estate.estate_need_rethrow));
trylevel.with(|t| t.set(estate.estate_trylevel));
did_emsg.with(|d| d.set(estate.estate_did_emsg));
}
pub fn exception_state_clear() {
current_exception.with(|c| *c.borrow_mut() = None);
did_throw.with(|t| t.set(false));
need_rethrow.with(|n| n.set(false));
trylevel.with(|t| t.set(0));
did_emsg.with(|d| d.set(0));
force_abort.with(|f| f.set(false));
got_int.with(|g| g.set(false));
}
fn handle_did_throw() {
discard_current_exception();
}
fn report_pending(_action: i32, _pending: i32, _value: Option<Rc<RefCell<except_T>>>) {}
fn report_make_pending(pending: i32, value: Option<Rc<RefCell<except_T>>>) {
if p_verbose.with(|p| p.get()) >= 14 || debug_break_level.with(|d| d.get()) > 0 {
report_pending(RP_MAKE, pending, value);
}
}
fn report_resume_pending(pending: i32, value: Option<Rc<RefCell<except_T>>>) {
if p_verbose.with(|p| p.get()) >= 14 || debug_break_level.with(|d| d.get()) > 0 {
report_pending(RP_RESUME, pending, value);
}
}
fn report_discard_pending(pending: i32, value: Option<Rc<RefCell<except_T>>>) {
if p_verbose.with(|p| p.get()) >= 14 || debug_break_level.with(|d| d.get()) > 0 {
report_pending(RP_DISCARD, pending, value);
}
}
pub fn ex_eval(eap: &mut exarg_T) {
let mut tv = typval_T::default();
crate::ported::eval::fill_evalarg_from_eap();
if crate::ported::eval::eval0(&eap.arg, &mut tv, None) == OK {
crate::ported::eval::typval::tv_clear(&mut tv); }
crate::ported::eval::clear_evalarg(); }
pub fn ex_if(eap: &mut exarg_T) {
if eap.cstack.cs_idx == CSTACK_LEN as i32 - 1 {
eap.errmsg = Some("E579: :if nesting too deep"); } else {
eap.cstack.cs_idx += 1; let idx = eap.cstack.cs_idx as usize;
eap.cstack.cs_flags[idx] = 0;
let skip = did_emsg.with(|d| d.get()) != 0
|| got_int.with(|g| g.get())
|| did_throw.with(|t| t.get())
|| (eap.cstack.cs_idx > 0
&& (eap.cstack.cs_flags[(eap.cstack.cs_idx - 1) as usize] & CSF_ACTIVE) == 0);
let (result, error) = eval_to_bool(&eap.arg, skip);
if !skip && !error {
if result {
eap.cstack.cs_flags[idx] = CSF_ACTIVE | CSF_TRUE; }
} else {
eap.cstack.cs_flags[idx] = CSF_TRUE;
}
}
}
pub fn ex_endif(eap: &mut exarg_T) {
did_endif.with(|d| d.set(true)); let idx = eap.cstack.cs_idx;
if idx < 0 || (eap.cstack.cs_flags[idx as usize] & (CSF_WHILE | CSF_FOR | CSF_TRY)) != 0 {
eap.errmsg = Some("E580: :endif without :if"); } else {
if (eap.cstack.cs_flags[idx as usize] & CSF_TRUE) == 0 && dbg_check_skipped(eap) {
do_intthrow(&mut eap.cstack); }
eap.cstack.cs_idx -= 1; }
}
pub fn ex_else(eap: &mut exarg_T) {
let mut skip = did_emsg.with(|d| d.get()) != 0
|| got_int.with(|g| g.get())
|| did_throw.with(|t| t.get())
|| (eap.cstack.cs_idx > 0
&& (eap.cstack.cs_flags[(eap.cstack.cs_idx - 1) as usize] & CSF_ACTIVE) == 0);
let idx = eap.cstack.cs_idx;
if idx < 0 || (eap.cstack.cs_flags[idx as usize] & (CSF_WHILE | CSF_FOR | CSF_TRY)) != 0 {
if eap.cmdidx == cmdidx_T::CMD_else {
eap.errmsg = Some("E581: :else without :if"); return;
}
eap.errmsg = Some("E582: :elseif without :if"); skip = true;
} else if eap.cstack.cs_flags[idx as usize] & CSF_ELSE != 0 {
if eap.cmdidx == cmdidx_T::CMD_else {
eap.errmsg = Some(e_multiple_else); return;
}
eap.errmsg = Some("E584: :elseif after :else"); skip = true;
}
let i = eap.cstack.cs_idx as usize;
if skip || eap.cstack.cs_flags[i] & CSF_TRUE != 0 {
if eap.errmsg.is_none() {
eap.cstack.cs_flags[i] = CSF_TRUE; }
skip = true; } else {
eap.cstack.cs_flags[i] = CSF_ACTIVE; }
if !skip && dbg_check_skipped(eap) && got_int.with(|g| g.get()) {
do_intthrow(&mut eap.cstack);
skip = true;
}
if eap.cmdidx == cmdidx_T::CMD_elseif {
let mut result = false;
let mut error = false;
if skip
&& eap.arg.as_bytes().first() != Some(&b'"')
&& crate::ported::eval::ends_excmd(eap.arg.as_bytes().first().copied().unwrap_or(0))
{
semsg(e_invexpr2); } else {
let r = eval_to_bool(&eap.arg, skip);
result = r.0;
error = r.1;
}
if !skip && !error {
if result {
eap.cstack.cs_flags[i] = CSF_ACTIVE | CSF_TRUE; } else {
eap.cstack.cs_flags[i] = 0; }
} else if eap.errmsg.is_none() {
eap.cstack.cs_flags[i] = CSF_TRUE;
}
} else {
eap.cstack.cs_flags[i] |= CSF_ELSE; }
}
pub fn ex_while(eap: &mut exarg_T) {
let mut error = false;
if eap.cstack.cs_idx == CSTACK_LEN as i32 - 1 {
eap.errmsg = Some("E585: :while/:for nesting too deep"); } else {
let result;
if eap.cstack.cs_lflags & CSL_HAD_LOOP == 0 {
eap.cstack.cs_idx += 1;
eap.cstack.cs_looplevel += 1;
eap.cstack.cs_line[eap.cstack.cs_idx as usize] = -1;
}
let idx = eap.cstack.cs_idx as usize;
eap.cstack.cs_flags[idx] = if eap.cmdidx == cmdidx_T::CMD_while {
CSF_WHILE
} else {
CSF_FOR
};
let skip = did_emsg.with(|d| d.get()) != 0
|| got_int.with(|g| g.get())
|| did_throw.with(|t| t.get())
|| (eap.cstack.cs_idx > 0
&& (eap.cstack.cs_flags[(eap.cstack.cs_idx - 1) as usize] & CSF_ACTIVE) == 0);
if eap.cmdidx == cmdidx_T::CMD_while {
let r = eval_to_bool(&eap.arg, skip);
result = r.0;
error = r.1;
} else {
crate::ported::eval::fill_evalarg_from_eap();
let fi: Option<()>;
if eap.cstack.cs_lflags & CSL_HAD_LOOP != 0 {
fi = eap.cstack.cs_forinfo[idx];
error = false;
} else {
fi = None;
eap.cstack.cs_forinfo[idx] = None;
}
if !error && fi.is_some() && !skip {
result = crate::ported::eval::next_for_item();
} else {
result = false;
}
if !result {
crate::ported::eval::free_for_info(); eap.cstack.cs_forinfo[idx] = None;
}
crate::ported::eval::clear_evalarg();
}
if !skip && !error && result {
eap.cstack.cs_flags[idx] |= CSF_ACTIVE | CSF_TRUE;
eap.cstack.cs_lflags ^= CSL_HAD_LOOP;
} else {
eap.cstack.cs_lflags &= !CSL_HAD_LOOP;
if !skip && !error {
eap.cstack.cs_flags[idx] |= CSF_TRUE; }
}
}
}
pub fn ex_continue(eap: &mut exarg_T) {
if eap.cstack.cs_looplevel <= 0 || eap.cstack.cs_idx < 0 {
eap.errmsg = Some("E586: :continue without :while or :for"); } else {
let idx = cleanup_conditionals(&mut eap.cstack, CSF_WHILE | CSF_FOR, false);
debug_assert!(idx >= 0);
if eap.cstack.cs_flags[idx as usize] & (CSF_WHILE | CSF_FOR) != 0 {
let mut trylvl = eap.cstack.cs_trylevel;
rewind_conditionals(&mut eap.cstack, idx, CSF_TRY, &mut trylvl); eap.cstack.cs_trylevel = trylvl;
eap.cstack.cs_lflags |= CSL_HAD_CONT;
} else {
eap.cstack.cs_pending[idx as usize] = CSTP_CONTINUE as i8;
report_make_pending(CSTP_CONTINUE, None);
}
}
}
pub fn ex_break(eap: &mut exarg_T) {
if eap.cstack.cs_looplevel <= 0 || eap.cstack.cs_idx < 0 {
eap.errmsg = Some("E587: :break without :while or :for"); } else {
let idx = cleanup_conditionals(&mut eap.cstack, CSF_WHILE | CSF_FOR, true);
if idx >= 0 && eap.cstack.cs_flags[idx as usize] & (CSF_WHILE | CSF_FOR) == 0 {
eap.cstack.cs_pending[idx as usize] = CSTP_BREAK as i8; report_make_pending(CSTP_BREAK, None);
}
}
}
pub fn ex_endwhile(eap: &mut exarg_T) {
let err: &'static str;
let csf: i32;
if eap.cmdidx == cmdidx_T::CMD_endwhile {
err = e_while; csf = CSF_WHILE;
} else {
err = e_for; csf = CSF_FOR;
}
if eap.cstack.cs_looplevel <= 0 || eap.cstack.cs_idx < 0 {
eap.errmsg = Some(err); } else {
let cur = eap.cstack.cs_idx as usize;
let mut fl = eap.cstack.cs_flags[cur];
if fl & csf == 0 {
if fl & CSF_WHILE != 0 {
eap.errmsg = Some("E732: Using :endfor with :while"); } else if fl & CSF_FOR != 0 {
eap.errmsg = Some("E733: Using :endwhile with :for"); }
}
if fl & (CSF_WHILE | CSF_FOR) == 0 {
if fl & CSF_TRY == 0 {
eap.errmsg = Some(e_endif); } else if fl & CSF_FINALLY != 0 {
eap.errmsg = Some(e_endtry); }
let mut idx = eap.cstack.cs_idx;
while idx > 0 {
fl = eap.cstack.cs_flags[idx as usize];
if (fl & CSF_TRY) != 0 && (fl & CSF_FINALLY) == 0 {
eap.errmsg = Some(err);
return;
}
if fl & csf != 0 {
break; }
idx -= 1;
}
cleanup_conditionals(&mut eap.cstack, CSF_WHILE | CSF_FOR, false);
let mut trylvl = eap.cstack.cs_trylevel;
rewind_conditionals(&mut eap.cstack, idx, CSF_TRY, &mut trylvl);
eap.cstack.cs_trylevel = trylvl;
} else if eap.cstack.cs_flags[cur] & CSF_TRUE != 0
&& eap.cstack.cs_flags[cur] & CSF_ACTIVE == 0
&& dbg_check_skipped(eap)
{
do_intthrow(&mut eap.cstack);
}
eap.cstack.cs_lflags |= CSL_HAD_ENDLOOP;
}
}
pub fn ex_throw(eap: &mut exarg_T) {
let value: Option<String>;
let first = eap.arg.as_bytes().first().copied();
if !eap.arg.is_empty() && first != Some(b'|') && first != Some(b'\n') {
value = crate::ported::eval::eval_to_string_skip(&eap.arg, eap.skip);
} else {
emsg(e_argreq); value = None;
}
if !eap.skip {
if let Some(v) = value {
if throw_exception(v, except_type_T::ET_USER, None) == FAIL {
} else {
do_throw(&mut eap.cstack); }
}
}
}
pub fn do_throw(cstack: &mut cstack_T) {
let inactivate_try = false;
let idx = cleanup_conditionals(cstack, 0, inactivate_try); if idx >= 0 {
let i = idx as usize;
if cstack.cs_flags[i] & CSF_CAUGHT == 0 {
if cstack.cs_flags[i] & CSF_ACTIVE != 0 {
cstack.cs_flags[i] |= CSF_THROWN; } else {
cstack.cs_flags[i] &= !CSF_THROWN;
}
}
cstack.cs_flags[i] &= !CSF_ACTIVE; cstack.cs_pend.csp_ex[i] = current_exception.with(|c| c.borrow().clone());
}
did_throw.with(|t| t.set(true)); }
pub fn ex_try(eap: &mut exarg_T) {
if eap.cstack.cs_idx == CSTACK_LEN as i32 - 1 {
eap.errmsg = Some("E601: :try nesting too deep"); } else {
eap.cstack.cs_idx += 1; eap.cstack.cs_trylevel += 1;
let idx = eap.cstack.cs_idx as usize;
eap.cstack.cs_flags[idx] = CSF_TRY;
eap.cstack.cs_pending[idx] = CSTP_NONE as i8;
let skip = did_emsg.with(|d| d.get()) != 0
|| got_int.with(|g| g.get())
|| did_throw.with(|t| t.get())
|| (eap.cstack.cs_idx > 0
&& (eap.cstack.cs_flags[(eap.cstack.cs_idx - 1) as usize] & CSF_ACTIVE) == 0);
if !skip {
eap.cstack.cs_flags[idx] |= CSF_ACTIVE | CSF_TRUE;
if emsg_silent.with(|e| e.get()) != 0 {
let elem = Box::new(eslist_T {
saved_emsg_silent: emsg_silent.with(|e| e.get()),
next: eap.cstack.cs_emsg_silent_list.take(),
});
eap.cstack.cs_emsg_silent_list = Some(elem);
eap.cstack.cs_flags[idx] |= CSF_SILENT;
emsg_silent.with(|e| e.set(0));
}
}
}
}
pub fn ex_catch(eap: &mut exarg_T) {
let mut idx: i32 = 0;
let mut give_up = false;
let mut skip = false;
let end: Option<String>;
let pat: String;
if eap.cstack.cs_trylevel <= 0 || eap.cstack.cs_idx < 0 {
eap.errmsg = Some("E603: :catch without :try"); give_up = true;
} else {
if eap.cstack.cs_flags[eap.cstack.cs_idx as usize] & CSF_TRY == 0 {
eap.errmsg = Some(get_end_emsg(&eap.cstack));
skip = true;
}
idx = eap.cstack.cs_idx;
while idx > 0 {
if eap.cstack.cs_flags[idx as usize] & CSF_TRY != 0 {
break; }
idx -= 1;
}
if eap.cstack.cs_flags[idx as usize] & CSF_FINALLY != 0 {
eap.errmsg = Some("E604: :catch after :finally");
give_up = true;
} else {
let mut looplvl = eap.cstack.cs_looplevel;
rewind_conditionals(&mut eap.cstack, idx, CSF_WHILE | CSF_FOR, &mut looplvl); eap.cstack.cs_looplevel = looplvl;
}
}
if crate::ported::eval::ends_excmd(eap.arg.as_bytes().first().copied().unwrap_or(0)) {
pat = ".*".to_string();
end = None;
eap.nextcmd = find_nextcmd(&eap.arg);
} else {
pat = eap.arg.get(1..).unwrap_or("").to_string();
end = skip_regexp_err(&pat, eap.arg.as_bytes()[0], true);
if end.is_none() {
give_up = true; }
}
if !give_up {
let mut caught = false;
if !did_throw.with(|t| t.get()) || eap.cstack.cs_flags[idx as usize] & CSF_TRUE == 0 {
skip = true;
}
if !skip
&& eap.cstack.cs_flags[idx as usize] & CSF_THROWN != 0
&& eap.cstack.cs_flags[idx as usize] & CSF_CAUGHT == 0
{
if let Some(e) = end.as_deref() {
let after = crate::ported::eval::skipwhite(e.get(1..).unwrap_or(""));
if !e.is_empty()
&& !crate::ported::eval::ends_excmd(
after.as_bytes().first().copied().unwrap_or(0),
)
{
semsg(e_trailing_arg); return;
}
}
if !dbg_check_skipped(eap) || !do_intthrow(&mut eap.cstack) {
emsg_off.with(|e| e.set(e.get() + 1)); let mut regmatch = regmatch_T {
regprog: vim_regcomp(&pat, RE_MAGIC + RE_STRING), rm_ic: false,
};
emsg_off.with(|e| e.set(e.get() - 1)); if regmatch.regprog.is_none() {
semsg(e_invarg2); } else {
let prev_got_int = got_int.with(|g| g.get());
got_int.with(|g| g.set(false));
let cur_val = current_exception
.with(|c| c.borrow().as_ref().map(|e| e.borrow().value.clone()));
caught = vim_regexec_nl(&mut regmatch, cur_val.as_deref().unwrap_or(""), 0);
got_int.with(|g| g.set(g.get() | prev_got_int));
vim_regfree(regmatch.regprog.take());
}
}
}
if caught {
eap.cstack.cs_flags[idx as usize] |= CSF_ACTIVE | CSF_CAUGHT;
did_emsg.with(|d| d.set(0));
got_int.with(|g| g.set(false));
did_throw.with(|t| t.set(false));
if let Some(excp) = eap.cstack.cs_pend.csp_ex[idx as usize].clone() {
catch_exception(&excp); }
let same = eap.cstack.cs_pend.csp_ex[eap.cstack.cs_idx as usize]
.as_ref()
.zip(current_exception.with(|c| c.borrow().clone()))
.map(|(a, b)| Rc::ptr_eq(a, &b))
.unwrap_or(false);
if !same {
internal_error("ex_catch()"); }
} else {
cleanup_conditionals(&mut eap.cstack, CSF_TRY, true);
}
}
if let Some(e) = end {
eap.nextcmd = find_nextcmd(&e); }
}
pub fn ex_finally(eap: &mut exarg_T) {
let mut idx: i32 = eap.cstack.cs_idx;
let mut pending = CSTP_NONE;
while idx >= 0 {
if eap.cstack.cs_flags[idx as usize] & CSF_TRY != 0 {
break; }
idx -= 1;
}
if eap.cstack.cs_trylevel <= 0 || idx < 0 {
eap.errmsg = Some("E606: :finally without :try"); return;
}
if eap.cstack.cs_flags[eap.cstack.cs_idx as usize] & CSF_TRY == 0 {
eap.errmsg = Some(get_end_emsg(&eap.cstack)); pending = CSTP_ERROR;
}
if eap.cstack.cs_flags[idx as usize] & CSF_FINALLY != 0 {
eap.errmsg = Some(e_multiple_finally); return;
}
let mut looplvl = eap.cstack.cs_looplevel;
rewind_conditionals(&mut eap.cstack, idx, CSF_WHILE | CSF_FOR, &mut looplvl); eap.cstack.cs_looplevel = looplvl;
let skip = (eap.cstack.cs_flags[eap.cstack.cs_idx as usize] & CSF_TRUE) == 0;
if !skip {
if dbg_check_skipped(eap) {
do_intthrow(&mut eap.cstack);
}
cleanup_conditionals(&mut eap.cstack, CSF_TRY, false);
if pending == CSTP_ERROR
|| did_emsg.with(|d| d.get()) != 0
|| got_int.with(|g| g.get())
|| did_throw.with(|t| t.get())
{
let ci = eap.cstack.cs_idx as usize;
if eap.cstack.cs_pending[ci] as i32 == CSTP_RETURN {
report_discard_pending(CSTP_RETURN, None); discard_pending_return(eap.cstack.cs_pend.csp_rv[ci].take()); }
if pending == CSTP_ERROR && did_emsg.with(|d| d.get()) == 0 {
pending |= if THROW_ON_ERROR { CSTP_THROW } else { 0 }; } else {
pending |= if did_throw.with(|t| t.get()) {
CSTP_THROW
} else {
0
}; }
pending |= if did_emsg.with(|d| d.get()) != 0 {
CSTP_ERROR
} else {
0
}; pending |= if got_int.with(|g| g.get()) {
CSTP_INTERRUPT
} else {
0
}; eap.cstack.cs_pending[ci] = pending as i8;
if did_throw.with(|t| t.get()) {
let same = eap.cstack.cs_pend.csp_ex[ci]
.as_ref()
.zip(current_exception.with(|c| c.borrow().clone()))
.map(|(a, b)| Rc::ptr_eq(a, &b))
.unwrap_or(false);
if !same {
internal_error("ex_finally()");
}
}
}
eap.cstack.cs_lflags |= CSL_HAD_FINA;
}
}
pub fn ex_endtry(eap: &mut exarg_T) {
let mut idx: i32 = eap.cstack.cs_idx;
let mut rethrow = false;
let mut pending = CSTP_NONE;
let mut rettv: Option<typval_T> = None;
while idx >= 0 {
if eap.cstack.cs_flags[idx as usize] & CSF_TRY != 0 {
break; }
idx -= 1;
}
if eap.cstack.cs_trylevel <= 0 || idx < 0 {
eap.errmsg = Some("E602: :endtry without :try"); return;
}
let mut skip = did_emsg.with(|d| d.get()) != 0
|| got_int.with(|g| g.get())
|| did_throw.with(|t| t.get())
|| (eap.cstack.cs_flags[eap.cstack.cs_idx as usize] & CSF_TRUE) == 0;
if eap.cstack.cs_flags[eap.cstack.cs_idx as usize] & CSF_TRY == 0 {
eap.errmsg = Some(get_end_emsg(&eap.cstack));
let mut looplvl = eap.cstack.cs_looplevel;
rewind_conditionals(&mut eap.cstack, idx, CSF_WHILE | CSF_FOR, &mut looplvl);
eap.cstack.cs_looplevel = looplvl;
skip = true;
if did_throw.with(|t| t.get()) {
discard_current_exception();
}
did_emsg.with(|d| d.set(0)); } else {
idx = eap.cstack.cs_idx;
if did_throw.with(|t| t.get())
&& eap.cstack.cs_flags[idx as usize] & CSF_TRUE != 0
&& eap.cstack.cs_flags[idx as usize] & CSF_FINALLY == 0
{
rethrow = true;
}
}
if (rethrow
|| (!skip
&& eap.cstack.cs_flags[idx as usize] & CSF_FINALLY == 0
&& eap.cstack.cs_pending[idx as usize] == 0))
&& dbg_check_skipped(eap)
{
if got_int.with(|g| g.get()) {
skip = true;
do_intthrow(&mut eap.cstack);
rethrow = false;
if did_throw.with(|t| t.get()) && eap.cstack.cs_flags[idx as usize] & CSF_FINALLY == 0 {
rethrow = true;
}
}
}
if !skip {
pending = eap.cstack.cs_pending[idx as usize] as i32;
eap.cstack.cs_pending[idx as usize] = CSTP_NONE as i8;
if pending == CSTP_RETURN {
rettv = eap.cstack.cs_pend.csp_rv[idx as usize].take(); } else if pending & CSTP_THROW != 0 {
current_exception
.with(|c| *c.borrow_mut() = eap.cstack.cs_pend.csp_ex[idx as usize].clone());
}
}
cleanup_conditionals(&mut eap.cstack, CSF_TRY | CSF_SILENT, true);
if eap.cstack.cs_idx >= 0 && eap.cstack.cs_flags[eap.cstack.cs_idx as usize] & CSF_TRY != 0 {
eap.cstack.cs_idx -= 1; }
eap.cstack.cs_trylevel -= 1;
if !skip {
report_resume_pending(
pending,
if pending & CSTP_THROW != 0 {
current_exception.with(|c| c.borrow().clone())
} else {
None
},
); match pending {
CSTP_NONE => {} CSTP_CONTINUE => ex_continue(eap),
CSTP_BREAK => ex_break(eap),
CSTP_RETURN => {
crate::ported::eval::userfunc::do_return(false, false, rettv); }
CSTP_FINISH => do_finish(eap, false), _ => {
if pending & CSTP_ERROR != 0 {
did_emsg.with(|d| d.set(1)); }
if pending & CSTP_INTERRUPT != 0 {
got_int.with(|g| g.set(true)); }
if pending & CSTP_THROW != 0 {
rethrow = true; }
}
}
}
if rethrow {
do_throw(&mut eap.cstack); }
}
pub fn enter_cleanup(csp: &mut cleanup_T) {
let pending = CSTP_NONE;
if did_emsg.with(|d| d.get()) != 0
|| got_int.with(|g| g.get())
|| did_throw.with(|t| t.get())
|| need_rethrow.with(|n| n.get())
{
csp.pending = (if did_emsg.with(|d| d.get()) != 0 {
CSTP_ERROR
} else {
0
}) | (if got_int.with(|g| g.get()) {
CSTP_INTERRUPT
} else {
0
}) | (if did_throw.with(|t| t.get()) {
CSTP_THROW
} else {
0
}) | (if need_rethrow.with(|n| n.get()) {
CSTP_THROW
} else {
0
});
if did_throw.with(|t| t.get()) || need_rethrow.with(|n| n.get()) {
csp.exception = current_exception.with(|c| c.borrow().clone()); current_exception.with(|c| *c.borrow_mut() = None);
} else {
csp.exception = None; if did_emsg.with(|d| d.get()) != 0 {
force_abort.with(|f| f.set(f.get() | cause_abort.with(|c| c.get()))); cause_abort.with(|c| c.set(false));
}
}
did_emsg.with(|d| d.set(0));
got_int.with(|g| g.set(false));
did_throw.with(|t| t.set(false));
need_rethrow.with(|n| n.set(false));
report_make_pending(pending, csp.exception.clone()); } else {
csp.pending = CSTP_NONE; csp.exception = None;
}
}
pub fn leave_cleanup(csp: &mut cleanup_T) {
let pending = csp.pending;
if pending == CSTP_NONE {
return; }
if aborting() || need_rethrow.with(|n| n.get()) {
if pending & CSTP_THROW != 0 {
if let Some(excp) = csp.exception.as_ref() {
discard_exception(excp, false); }
} else {
report_discard_pending(pending, None); }
} else {
if pending & CSTP_THROW != 0 {
current_exception.with(|c| *c.borrow_mut() = csp.exception.take());
} else if pending & CSTP_ERROR != 0 {
cause_abort.with(|c| c.set(force_abort.with(|f| f.get()))); force_abort.with(|f| f.set(false));
}
if pending & CSTP_ERROR != 0 {
did_emsg.with(|d| d.set(1)); }
if pending & CSTP_INTERRUPT != 0 {
got_int.with(|g| g.set(true)); }
if pending & CSTP_THROW != 0 {
need_rethrow.with(|n| n.set(true)); }
report_resume_pending(
pending,
if pending & CSTP_THROW != 0 {
current_exception.with(|c| c.borrow().clone())
} else {
None
},
); }
}
pub fn cleanup_conditionals(cstack: &mut cstack_T, searched_cond: i32, inclusive: bool) -> i32 {
let mut idx: i32 = cstack.cs_idx;
let mut stop = false;
while idx >= 0 {
let i = idx as usize;
if cstack.cs_flags[i] & CSF_TRY != 0 {
if did_emsg.with(|d| d.get()) != 0
|| got_int.with(|g| g.get())
|| cstack.cs_flags[i] & CSF_FINALLY != 0
{
match cstack.cs_pending[i] as i32 {
CSTP_NONE => {} CSTP_CONTINUE | CSTP_BREAK | CSTP_FINISH => {
report_discard_pending(cstack.cs_pending[i] as i32, None); cstack.cs_pending[i] = CSTP_NONE as i8;
}
CSTP_RETURN => {
report_discard_pending(CSTP_RETURN, None); discard_pending_return(cstack.cs_pend.csp_rv[i].take());
cstack.cs_pending[i] = CSTP_NONE as i8;
}
p => {
if cstack.cs_flags[i] & CSF_FINALLY != 0 {
if (p & CSTP_THROW) != 0 && cstack.cs_pend.csp_ex[i].is_some() {
if let Some(excp) = cstack.cs_pend.csp_ex[i].clone() {
discard_exception(&excp, false);
}
} else {
report_discard_pending(p, None); }
cstack.cs_pending[i] = CSTP_NONE as i8;
}
}
}
}
if cstack.cs_flags[i] & CSF_FINALLY == 0 {
if cstack.cs_flags[i] & CSF_ACTIVE != 0
&& cstack.cs_flags[i] & CSF_CAUGHT != 0
&& cstack.cs_flags[i] & CSF_FINISHED == 0
{
if let Some(excp) = cstack.cs_pend.csp_ex[i].clone() {
finish_exception(&excp); }
cstack.cs_flags[i] |= CSF_FINISHED;
}
if cstack.cs_flags[i] & CSF_TRUE != 0 {
if searched_cond == 0 && !inclusive {
break; }
stop = true;
}
}
}
if cstack.cs_flags[i] & searched_cond != 0 {
if !inclusive {
break; }
stop = true;
}
cstack.cs_flags[i] &= !CSF_ACTIVE; if stop && searched_cond != (CSF_TRY | CSF_SILENT) {
break; }
if cstack.cs_flags[i] & CSF_TRY != 0 && cstack.cs_flags[i] & CSF_SILENT != 0 {
if let Some(mut elem) = cstack.cs_emsg_silent_list.take() {
cstack.cs_emsg_silent_list = elem.next.take();
emsg_silent.with(|e| e.set(elem.saved_emsg_silent));
}
cstack.cs_flags[i] &= !CSF_SILENT;
}
if stop {
break; }
idx -= 1;
}
idx
}
fn get_end_emsg(cstack: &cstack_T) -> &'static str {
if cstack.cs_flags[cstack.cs_idx as usize] & CSF_WHILE != 0 {
return e_endwhile; }
if cstack.cs_flags[cstack.cs_idx as usize] & CSF_FOR != 0 {
return e_endfor; }
e_endif }
pub fn rewind_conditionals(cstack: &mut cstack_T, idx: i32, cond_type: i32, cond_level: &mut i32) {
while cstack.cs_idx > idx {
let i = cstack.cs_idx as usize;
if cstack.cs_flags[i] & cond_type != 0 {
*cond_level -= 1; }
if cstack.cs_flags[i] & CSF_FOR != 0 {
crate::ported::eval::free_for_info(); cstack.cs_forinfo[i] = None;
}
cstack.cs_idx -= 1; }
}
pub fn ex_endfunction(_eap: &mut exarg_T) {
semsg(e_str_not_inside_function); }
pub fn has_loop_cmd(p: &str) -> bool {
let t = p.trim_start_matches([' ', '\t', ':']);
let b = t.as_bytes();
(b.first() == Some(&b'w') && b.get(1) == Some(&b'h'))
|| (b.first() == Some(&b'f') && b.get(1) == Some(&b'o') && b.get(2) == Some(&b'r'))
}
fn eval_to_bool(arg: &str, skip: bool) -> (bool, bool) {
if skip {
return (false, false);
}
match crate::ported::eval::typval::EVAL_STRING_HOOK
.with(|h| *h.borrow())
.and_then(|f| f(arg))
{
Some(tv) => (
crate::ported::eval::typval::tv_get_number_chk(&tv, None) != 0,
false,
),
None => (false, true),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::eval::typval::EVAL_STRING_HOOK;
fn with_num_hook<F: FnOnce()>(f: F) {
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));
f();
EVAL_STRING_HOOK.with(|h| *h.borrow_mut() = saved);
}
fn new_eap(cmdidx: cmdidx_T, arg: &str) -> exarg_T {
exarg_T {
arg: arg.to_string(),
cmdidx,
forceit: false,
skip: false,
errmsg: None,
nextcmd: None,
cstack: cstack_T::default(),
}
}
fn reset_globals() {
exception_state_clear();
emsg_silent.with(|e| e.set(0));
need_rethrow.with(|n| n.set(false));
caught_stack.with(|c| *c.borrow_mut() = None);
}
#[test]
fn exception_string_formatting() {
use super::except_type_T::*;
assert_eq!(get_exception_string("boom", ET_USER, None), "boom");
assert_eq!(get_exception_string("boom", ET_USER, Some("catch")), "boom");
assert_eq!(
get_exception_string("E492: msg", ET_ERROR, None),
"Vim:E492: msg"
);
assert_eq!(
get_exception_string("E492: msg", ET_ERROR, Some("echo")),
"Vim(echo):E492: msg"
);
}
#[test]
fn if_true_activates_then_endif_pops() {
reset_globals();
with_num_hook(|| {
let mut eap = new_eap(cmdidx_T::CMD_if, "1");
ex_if(&mut eap);
assert_eq!(eap.cstack.cs_idx, 0);
assert_eq!(
eap.cstack.cs_flags[0] & (CSF_ACTIVE | CSF_TRUE),
CSF_ACTIVE | CSF_TRUE
);
assert!(eap.errmsg.is_none());
eap.cmdidx = cmdidx_T::CMD_endif;
ex_endif(&mut eap);
assert_eq!(eap.cstack.cs_idx, -1);
assert!(eap.errmsg.is_none());
assert!(did_endif.with(|d| d.get()));
});
}
#[test]
fn if_false_is_inactive_but_true_flag_set() {
reset_globals();
with_num_hook(|| {
let mut eap = new_eap(cmdidx_T::CMD_if, "0");
ex_if(&mut eap);
assert_eq!(eap.cstack.cs_flags[0] & CSF_ACTIVE, 0);
assert_eq!(eap.cstack.cs_flags[0] & CSF_TRUE, 0);
});
}
#[test]
fn else_activates_after_false_if() {
reset_globals();
with_num_hook(|| {
let mut eap = new_eap(cmdidx_T::CMD_if, "0");
ex_if(&mut eap);
eap.cmdidx = cmdidx_T::CMD_else;
eap.arg.clear();
ex_else(&mut eap);
assert_ne!(eap.cstack.cs_flags[0] & CSF_ACTIVE, 0);
assert_ne!(eap.cstack.cs_flags[0] & CSF_ELSE, 0);
});
}
#[test]
fn else_without_if_errors() {
reset_globals();
let mut eap = new_eap(cmdidx_T::CMD_else, "");
ex_else(&mut eap);
assert_eq!(eap.errmsg, Some("E581: :else without :if"));
}
#[test]
fn endif_without_if_errors() {
reset_globals();
let mut eap = new_eap(cmdidx_T::CMD_endif, "");
ex_endif(&mut eap);
assert_eq!(eap.errmsg, Some("E580: :endif without :if"));
}
#[test]
fn while_true_activates_and_sets_loop_flag() {
reset_globals();
with_num_hook(|| {
let mut eap = new_eap(cmdidx_T::CMD_while, "1");
ex_while(&mut eap);
assert_eq!(eap.cstack.cs_idx, 0);
assert_eq!(eap.cstack.cs_looplevel, 1);
assert_ne!(eap.cstack.cs_flags[0] & CSF_WHILE, 0);
assert_eq!(
eap.cstack.cs_flags[0] & (CSF_ACTIVE | CSF_TRUE),
CSF_ACTIVE | CSF_TRUE
);
assert_ne!(eap.cstack.cs_lflags & CSL_HAD_LOOP, 0);
});
}
#[test]
fn continue_break_without_loop_error() {
reset_globals();
let mut eap = new_eap(cmdidx_T::CMD_continue, "");
ex_continue(&mut eap);
assert_eq!(eap.errmsg, Some("E586: :continue without :while or :for"));
let mut eap2 = new_eap(cmdidx_T::CMD_break, "");
ex_break(&mut eap2);
assert_eq!(eap2.errmsg, Some("E587: :break without :while or :for"));
}
#[test]
fn try_pushes_and_endtry_pops() {
reset_globals();
let mut eap = new_eap(cmdidx_T::CMD_try, "");
ex_try(&mut eap);
assert_eq!(eap.cstack.cs_idx, 0);
assert_eq!(eap.cstack.cs_trylevel, 1);
assert_ne!(eap.cstack.cs_flags[0] & CSF_TRY, 0);
assert_eq!(
eap.cstack.cs_flags[0] & (CSF_ACTIVE | CSF_TRUE),
CSF_ACTIVE | CSF_TRUE
);
eap.cmdidx = cmdidx_T::CMD_endtry;
ex_endtry(&mut eap);
assert_eq!(eap.cstack.cs_idx, -1);
assert_eq!(eap.cstack.cs_trylevel, 0);
assert!(eap.errmsg.is_none());
}
#[test]
fn catch_finally_without_try_error() {
reset_globals();
let mut eap = new_eap(cmdidx_T::CMD_catch, "");
ex_catch(&mut eap);
assert_eq!(eap.errmsg, Some("E603: :catch without :try"));
let mut eap2 = new_eap(cmdidx_T::CMD_finally, "");
ex_finally(&mut eap2);
assert_eq!(eap2.errmsg, Some("E606: :finally without :try"));
let mut eap3 = new_eap(cmdidx_T::CMD_endtry, "");
ex_endtry(&mut eap3);
assert_eq!(eap3.errmsg, Some("E602: :endtry without :try"));
}
#[test]
fn throw_sets_current_exception_and_did_throw() {
reset_globals();
with_num_hook(|| {
let mut eap = new_eap(cmdidx_T::CMD_try, "");
ex_try(&mut eap);
eap.cmdidx = cmdidx_T::CMD_throw;
eap.arg = "42".to_string();
ex_throw(&mut eap);
assert!(did_throw.with(|t| t.get()));
let v =
current_exception.with(|c| c.borrow().as_ref().map(|e| e.borrow().value.clone()));
assert_eq!(v.as_deref(), Some("42"));
assert_ne!(eap.cstack.cs_flags[0] & CSF_THROWN, 0);
});
reset_globals();
}
#[test]
fn throw_vim_prefix_rejected() {
reset_globals();
assert_eq!(
throw_exception("Vim:boom".to_string(), except_type_T::ET_USER, None),
FAIL
);
assert!(current_exception.with(|c| c.borrow().is_none()));
}
#[test]
fn abort_state_defaults_and_toggles() {
reset_globals();
assert!(!aborting());
assert!(!aborted_in_try());
assert!(!should_abort(FAIL));
trylevel.with(|t| t.set(1));
assert!(should_abort(FAIL));
trylevel.with(|t| t.set(0));
did_throw.with(|t| t.set(true));
assert!(aborting());
discard_current_exception();
assert!(!aborting());
assert!(has_loop_cmd(" :while x"));
assert!(has_loop_cmd("for i in x"));
assert!(!has_loop_cmd("echo 1"));
let snap = exception_state_save();
did_throw.with(|t| t.set(true));
exception_state_restore(snap);
assert!(!did_throw.with(|t| t.get()));
}
#[test]
fn nesting_too_deep_errors() {
reset_globals();
with_num_hook(|| {
let mut eap = new_eap(cmdidx_T::CMD_if, "1");
eap.cstack.cs_idx = CSTACK_LEN as i32 - 1;
ex_if(&mut eap);
assert_eq!(eap.errmsg, Some("E579: :if nesting too deep"));
});
}
}