use super::*;
pub(super) fn exec_call_builtin(ctx: &mut VmCtx<'_>, name: &str, argc: u16) -> Result<()> {
let argc = argc as usize;
if ctx.cp.functions.contains_key(name) {
return exec_call_user(ctx, name, argc as u16);
}
let start = ctx.stack.len() - argc;
let args: Vec<Value> = ctx.stack.drain(start..).collect();
let result = exec_builtin_dispatch(ctx, name, args)?;
ctx.push(result);
Ok(())
}
pub(crate) fn exec_builtin_dispatch(
ctx: &mut VmCtx<'_>,
name: &str,
args: Vec<Value>,
) -> Result<Value> {
if ctx.rt.posix || ctx.rt.traditional {
const GAWK_ONLY_BUILTINS: &[&str] = &[
"and",
"or",
"xor",
"compl",
"lshift",
"rshift",
"gensub",
"patsplit",
"mkbool",
"mktime",
"strftime",
"systime",
"isarray",
"typeof",
"strtonum",
"dcgettext",
"dcngettext",
"bindtextdomain",
"chdir",
"stat",
"statvfs",
"fts",
"chr",
"ord",
"gettimeofday",
"getlocaltime",
"sleep",
"readfile",
"readdir",
"reada",
"writea",
"inplace_tmpfile",
"inplace_commit",
"rename",
"revoutput",
"revtwoway",
"intdiv",
"intdiv0",
];
if GAWK_ONLY_BUILTINS.contains(&name) {
return Err(Error::Runtime(format!(
"`{name}` is a gawk extension not available in POSIX/traditional mode"
)));
}
}
let argc = args.len();
let result = match name {
"length" => {
if argc > 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for length"
)));
}
if args.is_empty() {
let n = if ctx.rt.characters_as_bytes {
ctx.rt.record.len()
} else {
ctx.rt.record.chars().count()
};
Value::Num(n as f64)
} else {
match &args[0] {
Value::Array(a) => Value::Num(a.len() as f64),
v => {
let s = v.as_str();
let n = if ctx.rt.characters_as_bytes {
s.len()
} else {
s.chars().count()
};
Value::Num(n as f64)
}
}
}
}
"index" => {
if argc != 2 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for index"
)));
}
let hay = args[0].as_str();
let needle = args[1].as_str();
if needle.is_empty() {
Value::Num(1.0)
} else {
let pos = if ctx.rt.ignore_case_flag() {
let hay_lc = hay.to_lowercase();
let needle_lc = needle.to_lowercase();
hay_lc.find(needle_lc.as_str()).map(|b| {
if ctx.rt.characters_as_bytes {
b + 1
} else {
hay.char_indices().take_while(|&(off, _)| off < b).count() + 1
}
})
} else {
hay.find(needle.as_str()).map(|b| {
if ctx.rt.characters_as_bytes {
b + 1
} else {
hay[..b].chars().count() + 1
}
})
};
match pos {
Some(p) => Value::Num(p as f64),
None => Value::Num(0.0),
}
}
}
"substr" => {
if !(2..=3).contains(&argc) {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for substr"
)));
}
let s = args[0].as_str();
let start_raw = args[1].as_number();
let mut m = start_raw as i64;
let len_opt = if let Some(v) = args.get(2) {
let l = v.as_number() as i64;
if l <= 0 {
return Ok(Value::Str(String::new()));
}
Some(l)
} else {
None
};
if m < 1 {
ctx.rt.lint_warn(&format!(
"substr: start index {start_raw} is less than 1, treated as 1"
));
m = 1;
}
let len = len_opt.map(|l| l as usize).unwrap_or(usize::MAX);
let start0 = (m as usize).saturating_sub(1);
if ctx.rt.characters_as_bytes {
let b = s.as_bytes();
let slice = b
.get(start0..)
.map(|rest| {
let take = len.min(rest.len());
String::from_utf8_lossy(&rest[..take]).into_owned()
})
.unwrap_or_default();
Value::Str(slice)
} else {
let slice: String = s.chars().skip(start0).take(len).collect();
Value::Str(slice)
}
}
"tolower" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for tolower"
)));
}
Value::Str(args[0].as_str().to_lowercase())
}
"toupper" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for toupper"
)));
}
Value::Str(args[0].as_str().to_uppercase())
}
"int" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for int"
)));
}
bignum::awk_int_value(&args[0], ctx.rt)
}
"intdiv" => {
if argc != 2 {
return Err(Error::Runtime("`intdiv` expects two arguments".into()));
}
bignum::awk_intdiv_values(&args[0], &args[1], ctx.rt)?
}
"mkbool" => {
if argc != 1 {
return Err(Error::Runtime("`mkbool` expects one argument".into()));
}
Value::Num(if truthy(&args[0])? { 1.0 } else { 0.0 })
}
"sqrt" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for sqrt"
)));
}
if ctx.rt.bignum {
let prec = ctx.rt.mpfr_prec_bits();
let round = ctx.rt.mpfr_round();
let f = value_to_float(&args[0], prec, round);
if matches!(f.cmp0(), Some(Ordering::Less)) {
ctx.rt.warn_builtin_negative_arg("sqrt", f.to_f64());
}
Value::Mpfr(Float::with_val_round(prec, f.sqrt(), round).0)
} else {
let x = args[0].as_number();
if x < 0.0 {
ctx.rt.warn_builtin_negative_arg("sqrt", x);
}
let r = x.sqrt();
Value::Num(if r.is_nan() { f64::NAN } else { r })
}
}
"sin" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for sin"
)));
}
if ctx.rt.bignum {
let prec = ctx.rt.mpfr_prec_bits();
let round = ctx.rt.mpfr_round();
let f = value_to_float(&args[0], prec, round);
Value::Mpfr(Float::with_val_round(prec, f.sin(), round).0)
} else {
let r = args[0].as_number().sin();
Value::Num(if r.is_nan() { f64::NAN } else { r })
}
}
"cos" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for cos"
)));
}
if ctx.rt.bignum {
let prec = ctx.rt.mpfr_prec_bits();
let round = ctx.rt.mpfr_round();
let f = value_to_float(&args[0], prec, round);
Value::Mpfr(Float::with_val_round(prec, f.cos(), round).0)
} else {
let r = args[0].as_number().cos();
Value::Num(if r.is_nan() { f64::NAN } else { r })
}
}
"atan2" => {
if argc != 2 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for atan2"
)));
}
if ctx.rt.bignum {
let prec = ctx.rt.mpfr_prec_bits();
let round = ctx.rt.mpfr_round();
let y = value_to_float(&args[0], prec, round);
let x = value_to_float(&args[1], prec, round);
Value::Mpfr(Float::with_val_round(prec, y.atan2(&x), round).0)
} else {
let r = args[0].as_number().atan2(args[1].as_number());
Value::Num(if r.is_nan() { f64::NAN } else { r })
}
}
"exp" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for exp"
)));
}
if ctx.rt.bignum {
let prec = ctx.rt.mpfr_prec_bits();
let round = ctx.rt.mpfr_round();
let f = value_to_float(&args[0], prec, round);
Value::Mpfr(Float::with_val_round(prec, f.exp(), round).0)
} else {
let r = args[0].as_number().exp();
Value::Num(if r.is_nan() { f64::NAN } else { r })
}
}
"log" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for log"
)));
}
if ctx.rt.bignum {
let prec = ctx.rt.mpfr_prec_bits();
let round = ctx.rt.mpfr_round();
let f = value_to_float(&args[0], prec, round);
match f.cmp0() {
Some(Ordering::Less) => {
ctx.rt.warn_builtin_negative_arg("log", f.to_f64());
}
Some(Ordering::Equal) => {
ctx.rt.lint_warn("log: zero argument yields -infinity");
}
Some(Ordering::Greater) | None => {}
}
Value::Mpfr(Float::with_val_round(prec, f.ln(), round).0)
} else {
let x = args[0].as_number();
if x < 0.0 {
ctx.rt.warn_builtin_negative_arg("log", x);
} else if x == 0.0 {
ctx.rt.lint_warn("log: zero argument yields -infinity");
}
let r = x.ln();
Value::Num(if r.is_nan() { f64::NAN } else { r })
}
}
"systime" => {
if argc != 0 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for systime"
)));
}
Value::Num(builtins::awk_systime())
}
"strftime" => builtins::awk_strftime(&args).map_err(Error::Runtime)?,
"mktime" => {
if !(1..=2).contains(&argc) {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for mktime"
)));
}
let utc = argc == 2 && args[1].as_number() != 0.0;
Value::Num(builtins::awk_mktime_with_utc(&args[0].as_str(), utc))
}
"rand" => {
if argc != 0 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for rand"
)));
}
Value::Num(ctx.rt.rand())
}
"srand" => {
if argc > 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for srand"
)));
}
let n = match args.first() {
None => None,
Some(v) => {
if ctx.rt.bignum {
let prec = ctx.rt.mpfr_prec_bits();
let round = ctx.rt.mpfr_round();
let f = value_to_float(v, prec, round);
Some(bignum::float_trunc_integer(&f).to_u64_wrapping())
} else {
Some(v.as_number() as u32 as u64)
}
}
};
Value::Num(ctx.rt.srand(n))
}
"system" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for system"
)));
}
if ctx.rt.sandbox {
return Err(Error::Runtime(
"sandbox: system() is disabled (-S/--sandbox)".into(),
));
}
use std::process::Command;
flush_print_buf(&mut ctx.rt.print_buf)?;
let _ = std::io::stdout().flush();
ctx.rt.flush_all_output_handles();
let cmd = args[0].as_str();
let st = Command::new("sh")
.arg("-c")
.arg(&cmd)
.status()
.map_err(Error::Io)?;
Value::Num(st.code().unwrap_or(-1) as f64)
}
"close" => {
if argc != 1 && argc != 2 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for close"
)));
}
let path = args[0].as_str();
Value::Num(ctx.rt.close_handle(&path))
}
"fflush" => {
if args.is_empty() {
ctx.emit_flush()?;
} else {
let path = args[0].as_str();
if path.is_empty() {
ctx.emit_flush()?;
} else {
ctx.rt.flush_redirect_target(&path)?;
}
}
Value::Num(0.0)
}
"sprintf" => {
if args.is_empty() {
return Err(Error::Runtime("sprintf: need format".into()));
}
let fmt = args[0].as_str();
sprintf_simple(
&fmt,
&args[1..],
ctx.rt.numeric_decimal,
ctx.rt.numeric_thousands_sep,
ctx.rt,
)?
}
"printf" => {
if args.is_empty() {
return Err(Error::Runtime("printf: need format".into()));
}
let fmt = args[0].as_str();
let s = sprintf_simple(
&fmt,
&args[1..],
ctx.rt.numeric_decimal,
ctx.rt.numeric_thousands_sep,
ctx.rt,
)?
.as_str();
ctx.emit_print(&s);
Value::Num(0.0)
}
"and" => {
if argc < 2 {
return Err(Error::Runtime(
"and: called with less than two arguments".into(),
));
}
let mut acc = bignum::awk_and_values(&args[0], &args[1], ctx.rt);
for a in &args[2..] {
acc = bignum::awk_and_values(&acc, a, ctx.rt);
}
acc
}
"or" => {
if argc < 2 {
return Err(Error::Runtime(
"or: called with less than two arguments".into(),
));
}
let mut acc = bignum::awk_or_values(&args[0], &args[1], ctx.rt);
for a in &args[2..] {
acc = bignum::awk_or_values(&acc, a, ctx.rt);
}
acc
}
"xor" => {
if argc < 2 {
return Err(Error::Runtime(
"xor: called with less than two arguments".into(),
));
}
let mut acc = bignum::awk_xor_values(&args[0], &args[1], ctx.rt);
for a in &args[2..] {
acc = bignum::awk_xor_values(&acc, a, ctx.rt);
}
acc
}
"lshift" => {
if argc != 2 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for lshift"
)));
}
let av = args[0].as_number();
let bv = args[1].as_number();
if av < 0.0 {
return Err(Error::Runtime(format!(
"lshift({av:.6}, {bv:.6}): negative values are not allowed"
)));
}
if bv < 0.0 {
return Err(Error::Runtime(format!(
"lshift({av:.6}, {bv:.6}): negative values are not allowed"
)));
}
bignum::awk_lshift_values(&args[0], &args[1], ctx.rt)
}
"rshift" => {
if argc != 2 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for rshift"
)));
}
let av = args[0].as_number();
let bv = args[1].as_number();
if av < 0.0 {
return Err(Error::Runtime(format!(
"rshift({av:.6}, {bv:.6}): negative values are not allowed"
)));
}
if bv < 0.0 {
return Err(Error::Runtime(format!(
"rshift({av:.6}, {bv:.6}): negative values are not allowed"
)));
}
bignum::awk_rshift_values(&args[0], &args[1], ctx.rt)
}
"compl" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for compl"
)));
}
let av = args[0].as_number();
if av < 0.0 {
return Err(Error::Runtime(format!(
"compl({av:.6}): negative value is not allowed"
)));
}
bignum::awk_compl_values(&args[0], ctx.rt)
}
"strtonum" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for strtonum"
)));
}
bignum::awk_strtonum_value(&args[0].as_str(), ctx.rt)
}
"typeof" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for typeof"
)));
}
Value::Str(builtins::awk_typeof_value(&args[0]).into())
}
"gensub" => {
if !(3..=4).contains(&argc) {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for gensub"
)));
}
let target = if argc == 4 {
Some(args[3].as_str())
} else {
None
};
let out = builtins::awk_gensub(
ctx.rt,
&args[0].as_str(),
&args[1].as_str(),
&args[2],
target,
)?;
Value::Str(out)
}
"isarray" => {
if argc != 1 {
return Err(Error::Runtime(format!(
"{argc} is invalid as number of arguments for isarray"
)));
}
Value::Num(match &args[0] {
Value::Array(_) => 1.0,
_ => 0.0,
})
}
"bindtextdomain" => {
if argc != 2 {
return Err(Error::Runtime(
"`bindtextdomain` expects two arguments (domain, dirname)".into(),
));
}
let domain = args[0].as_str();
let dirname = args[1].as_str();
ctx.rt.gettext_dir = dirname.clone();
ctx.rt
.vars
.insert("TEXTDOMAIN".into(), Value::Str(domain.clone()));
if let Some(cat) = crate::gettext_util::try_load_gettext_catalog(&domain, &dirname) {
ctx.rt.gettext_catalogs.insert(domain, cat);
}
Value::Str(dirname)
}
"dcgettext" => {
if argc != 3 {
return Err(Error::Runtime(
"`dcgettext` expects three arguments (string, domain, category)".into(),
));
}
let msgid = args[0].as_str();
let domain = args[1].as_str();
let _cat = args[2].as_number() as i32;
if let Some(cat) = ctx.rt.gettext_catalogs.get(&domain) {
Value::Str(cat.gettext(msgid.as_str()).to_string())
} else {
Value::Str(msgid)
}
}
"dcngettext" => {
if argc != 5 {
return Err(Error::Runtime(
"`dcngettext` expects five arguments (s1, s2, n, domain, category)".into(),
));
}
let s1 = args[0].as_str();
let s2 = args[1].as_str();
let n = args[2].as_number();
let domain = args[3].as_str();
let _ = args[4].as_number() as i32;
if let Some(cat) = ctx.rt.gettext_catalogs.get(&domain) {
Value::Str(cat.ngettext(s1.as_str(), s2.as_str(), n as u64).to_string())
} else {
Value::Str((if n == 1.0 { s1 } else { s2 }).to_string())
}
}
"chdir" => {
if argc != 1 {
return Err(Error::Runtime("`chdir` expects one argument".into()));
}
crate::gawk_extensions::chdir(ctx.rt, &args[0].as_str())?
}
"stat" => {
if argc != 2 {
return Err(Error::Runtime("`stat` expects two arguments".into()));
}
crate::gawk_extensions::stat(ctx.rt, &args[0].as_str(), &args[1].as_str())?
}
"statvfs" => {
if argc != 2 {
return Err(Error::Runtime("`statvfs` expects two arguments".into()));
}
crate::gawk_extensions::statvfs(ctx.rt, &args[0].as_str(), &args[1].as_str())?
}
"fts" => {
if argc != 2 {
return Err(Error::Runtime("`fts` expects two arguments".into()));
}
crate::gawk_extensions::fts(ctx.rt, &args[0].as_str(), &args[1].as_str())?
}
"gettimeofday" => {
if argc != 1 {
return Err(Error::Runtime("`gettimeofday` expects one argument".into()));
}
crate::gawk_extensions::gettimeofday(ctx.rt, &args[0].as_str())?
}
"sleep" => {
if argc != 1 {
return Err(Error::Runtime("`sleep` expects one argument".into()));
}
crate::gawk_extensions::sleep_secs(ctx.rt, args[0].as_number())?
}
"ord" => {
if argc != 1 {
return Err(Error::Runtime("`ord` expects one argument".into()));
}
crate::gawk_extensions::ord(ctx.rt, &args[0].as_str())?
}
"chr" => {
if argc != 1 {
return Err(Error::Runtime("`chr` expects one argument".into()));
}
crate::gawk_extensions::chr(ctx.rt, args[0].as_number())?
}
"readfile" => {
if argc != 1 {
return Err(Error::Runtime("`readfile` expects one argument".into()));
}
crate::gawk_extensions::readfile(ctx.rt, &args[0].as_str())?
}
"revoutput" => {
if argc != 1 {
return Err(Error::Runtime("`revoutput` expects one argument".into()));
}
crate::gawk_extensions::revoutput(ctx.rt, &args[0].as_str())?
}
"revtwoway" => {
if argc != 1 {
return Err(Error::Runtime("`revtwoway` expects one argument".into()));
}
crate::gawk_extensions::revtwoway(ctx.rt, &args[0].as_str())?
}
"rename" => {
if argc != 2 {
return Err(Error::Runtime("`rename` expects two arguments".into()));
}
crate::gawk_extensions::rename(ctx.rt, &args[0].as_str(), &args[1].as_str())?
}
"inplace_tmpfile" => {
if argc != 1 {
return Err(Error::Runtime(
"`inplace_tmpfile` expects one argument".into(),
));
}
crate::gawk_extensions::inplace_tmpfile(ctx.rt, &args[0].as_str())?
}
"inplace_commit" => {
if argc != 2 {
return Err(Error::Runtime(
"`inplace_commit` expects two arguments".into(),
));
}
crate::gawk_extensions::inplace_commit(ctx.rt, &args[0].as_str(), &args[1].as_str())?
}
"writea" => {
if argc != 2 {
return Err(Error::Runtime("`writea` expects two arguments".into()));
}
crate::gawk_extensions::writea(ctx.rt, &args[0].as_str(), &args[1].as_str())?
}
"reada" => {
if argc != 2 {
return Err(Error::Runtime("`reada` expects two arguments".into()));
}
crate::gawk_extensions::reada(ctx.rt, &args[0].as_str(), &args[1].as_str())?
}
"intdiv0" => {
if argc != 2 {
return Err(Error::Runtime("`intdiv0` expects two arguments".into()));
}
crate::gawk_extensions::intdiv0(ctx.rt, &args[0], &args[1])?
}
"readdir" => {
if argc != 2 {
return Err(Error::Runtime(
"`readdir` expects two arguments (path, array)".into(),
));
}
let path = args[0].as_str().to_string();
let arr_name = args[1].as_str().to_string();
crate::gawk_extensions::readdir(ctx.rt, &path, &arr_name)?
}
"getlocaltime" => {
if !(1..=2).contains(&argc) {
return Err(Error::Runtime(
"`getlocaltime` expects 1 or 2 arguments (array [, timestamp])".into(),
));
}
let arr_name = args[0].as_str().to_string();
let ts = if argc == 2 {
Some(args[1].as_number())
} else {
None
};
crate::gawk_extensions::getlocaltime(ctx.rt, &arr_name, ts)?
}
_ => return Err(Error::Runtime(format!("unknown function `{name}`"))),
};
Ok(result)
}
pub(super) fn sort_keys_with_custom_cmp(
ctx: &mut VmCtx<'_>,
keys: &mut [String],
fname: &str,
arr_name: &str,
) -> Result<()> {
let Some(func) = ctx.cp.functions.get(fname) else {
return Err(Error::Runtime(format!(
"sorted_in: unknown function `{fname}`"
)));
};
let argc = func.params.len();
if !(argc == 2 || argc == 4) {
return Err(Error::Runtime(format!(
"sorted_in: comparison function `{fname}` must have 2 or 4 parameters (has {argc})"
)));
}
let err: RefCell<Option<Error>> = RefCell::new(None);
keys.sort_by(|a, b| {
if err.borrow().is_some() {
return Ordering::Equal;
}
let vals = if argc == 2 {
vec![Value::Str(a.clone()), Value::Str(b.clone())]
} else {
let va = if arr_name == "SYMTAB" {
ctx.rt.symtab_elem_get(a.as_str())
} else {
ctx.rt.array_get(arr_name, a.as_str())
};
let vb = if arr_name == "SYMTAB" {
ctx.rt.symtab_elem_get(b.as_str())
} else {
ctx.rt.array_get(arr_name, b.as_str())
};
vec![Value::Str(a.clone()), va, Value::Str(b.clone()), vb]
};
match exec_call_user_inner(ctx, fname, vals) {
Ok(v) => {
let n = v.as_number();
if n < 0.0 {
Ordering::Less
} else if n > 0.0 {
Ordering::Greater
} else {
Ordering::Equal
}
}
Err(e) => {
*err.borrow_mut() = Some(e);
Ordering::Equal
}
}
});
if let Some(e) = err.into_inner() {
return Err(e);
}
Ok(())
}
pub(crate) fn exec_call_user_inner(
ctx: &mut VmCtx<'_>,
name: &str,
mut vals: Vec<Value>,
) -> Result<Value> {
let func = ctx
.cp
.functions
.get(name)
.ok_or_else(|| Error::Runtime(format!("unknown function `{name}`")))?
.clone();
if ctx.locals.len() >= crate::limits::MAX_USER_CALL_DEPTH {
return Err(Error::Runtime(format!(
"maximum user function call depth ({}) exceeded",
crate::limits::MAX_USER_CALL_DEPTH
)));
}
while vals.len() < func.params.len() {
vals.push(Value::Uninit);
}
vals.truncate(func.params.len());
let mut frame = AwkMap::default();
for (p, v) in func.params.iter().zip(vals) {
frame.insert(p.clone(), v);
}
ctx.locals.push(frame);
let was_fn = ctx.in_function;
ctx.in_function = true;
let result = match execute(&func.body, ctx) {
Ok(VmSignal::Normal) => Value::Uninit,
Ok(VmSignal::Return(v)) => v,
Ok(VmSignal::Next) => {
ctx.locals.pop();
ctx.in_function = was_fn;
return Err(Error::Runtime("invalid jump out of function (next)".into()));
}
Ok(VmSignal::NextFile) => {
ctx.locals.pop();
ctx.in_function = was_fn;
return Err(Error::Runtime(
"invalid jump out of function (nextfile)".into(),
));
}
Ok(VmSignal::ExitPending) => {
ctx.locals.pop();
ctx.in_function = was_fn;
return Err(Error::Exit(ctx.rt.exit_code));
}
Err(e) => {
ctx.locals.pop();
ctx.in_function = was_fn;
return Err(e);
}
};
ctx.locals.pop();
ctx.in_function = was_fn;
Ok(result)
}