use crate::check_args;
use crate::commands;
use crate::dict::dict_new;
use crate::expr;
use crate::list::list_to_string;
use crate::molt_err;
use crate::molt_ok;
use crate::parser;
use crate::parser::Script;
use crate::parser::Word;
use crate::scope::ScopeStack;
use crate::types::*;
use crate::value::Value;
use std::any::Any;
use std::collections::HashMap;
use std::rc::Rc;
use std::time::Instant;
const OPT_CODE: &str = "-code";
const OPT_LEVEL: &str = "-level";
const OPT_ERRORCODE: &str = "-errorcode";
const OPT_ERRORINFO: &str = "-errorinfo";
const ZERO: &str = "0";
#[derive(Default)]
pub struct Interp {
commands: HashMap<String, Rc<Command>>,
scopes: ScopeStack,
last_context_id: u64,
context_map: HashMap<ContextID, ContextBox>,
recursion_limit: usize,
num_levels: usize,
profile_map: HashMap<String, ProfileRecord>,
}
enum Command {
Native(CommandFunc, ContextID),
Proc(Procedure),
}
impl Command {
fn execute(&self, interp: &mut Interp, argv: &[Value]) -> MoltResult {
match self {
Command::Native(func, context_id) => func(interp, *context_id, argv),
Command::Proc(proc) => proc.execute(interp, argv),
}
}
fn cmdtype(&self) -> Value {
match self {
Command::Native(_, _) => Value::from("native"),
Command::Proc(_) => Value::from("proc"),
}
}
fn context_id(&self) -> ContextID {
match self {
Command::Native(_, context_id) => *context_id,
_ => NULL_CONTEXT,
}
}
fn is_proc(&self) -> bool {
if let Command::Proc(_) = self {
true
} else {
false
}
}
}
const NULL_CONTEXT: ContextID = ContextID(0);
struct ContextBox {
data: Box<dyn Any>,
ref_count: usize,
}
impl ContextBox {
fn new<T: 'static>(data: T) -> Self {
Self {
data: Box::new(data),
ref_count: 0,
}
}
fn increment(&mut self) {
self.ref_count += 1;
}
fn decrement(&mut self) -> bool {
assert!(
self.ref_count != 0,
"attempted to decrement context ref count below zero"
);
self.ref_count -= 1;
self.ref_count == 0
}
}
struct ProfileRecord {
count: u128,
nanos: u128,
}
impl ProfileRecord {
fn new() -> Self {
Self { count: 0, nanos: 0 }
}
}
impl Interp {
pub fn empty() -> Self {
let mut interp = Self {
recursion_limit: 1000,
commands: HashMap::new(),
last_context_id: 0,
context_map: HashMap::new(),
scopes: ScopeStack::new(),
num_levels: 0,
profile_map: HashMap::new(),
};
interp.set_scalar("errorInfo", Value::empty()).unwrap();
interp
}
pub fn new() -> Self {
let mut interp = Interp::empty();
interp.add_command("append", commands::cmd_append);
interp.add_command("array", commands::cmd_array);
interp.add_command("assert_eq", commands::cmd_assert_eq);
interp.add_command("break", commands::cmd_break);
interp.add_command("catch", commands::cmd_catch);
interp.add_command("continue", commands::cmd_continue);
interp.add_command("dict", commands::cmd_dict);
interp.add_command("error", commands::cmd_error);
interp.add_command("expr", commands::cmd_expr);
interp.add_command("for", commands::cmd_for);
interp.add_command("foreach", commands::cmd_foreach);
interp.add_command("global", commands::cmd_global);
interp.add_command("if", commands::cmd_if);
interp.add_command("incr", commands::cmd_incr);
interp.add_command("info", commands::cmd_info);
interp.add_command("join", commands::cmd_join);
interp.add_command("lappend", commands::cmd_lappend);
interp.add_command("lindex", commands::cmd_lindex);
interp.add_command("list", commands::cmd_list);
interp.add_command("llength", commands::cmd_llength);
interp.add_command("proc", commands::cmd_proc);
interp.add_command("puts", commands::cmd_puts);
interp.add_command("rename", commands::cmd_rename);
interp.add_command("return", commands::cmd_return);
interp.add_command("set", commands::cmd_set);
interp.add_command("string", commands::cmd_string);
interp.add_command("throw", commands::cmd_throw);
interp.add_command("time", commands::cmd_time);
interp.add_command("unset", commands::cmd_unset);
interp.add_command("while", commands::cmd_while);
interp.add_command("source", commands::cmd_source);
interp.add_command("exit", commands::cmd_exit);
interp.add_command("parse", parser::cmd_parse);
interp.add_command("pdump", commands::cmd_pdump);
interp.add_command("pclear", commands::cmd_pclear);
interp.populate_env();
interp
}
fn populate_env(&mut self) {
for (key, value) in std::env::vars() {
let _ = self.set_element("env", &key, value.into());
}
}
pub fn eval(&mut self, script: &str) -> MoltResult {
let value = Value::from(script);
self.eval_value(&value)
}
pub fn eval_value(&mut self, value: &Value) -> MoltResult {
self.num_levels += 1;
if self.num_levels > self.recursion_limit {
self.num_levels -= 1;
return molt_err!("too many nested calls to Interp::eval (infinite loop?)");
}
let mut result = self.eval_script(&*value.as_script()?);
self.num_levels -= 1;
if self.num_levels == 0 {
if let Err(mut exception) = result {
if exception.code() == ResultCode::Return {
exception.decrement_level();
}
result = match exception.code() {
ResultCode::Okay => Ok(exception.value()),
ResultCode::Error => Err(exception),
ResultCode::Return => Err(exception), ResultCode::Break => molt_err!("invoked \"break\" outside of a loop"),
ResultCode::Continue => molt_err!("invoked \"continue\" outside of a loop"),
ResultCode::Other(_) => molt_err!("unexpected result code."),
};
}
}
if let Err(exception) = &result {
if exception.is_error() {
self.set_global_error_data(exception.error_data())?;
}
}
result
}
fn set_global_error_data(&mut self, error_data: Option<&ErrorData>) -> Result<(), Exception> {
if let Some(data) = error_data {
self.scopes.set_global("errorInfo", data.error_info())?;
self.scopes.set_global("errorCode", data.error_code())?;
}
Ok(())
}
pub(crate) fn eval_script(&mut self, script: &Script) -> MoltResult {
let mut result_value = Value::empty();
for word_vec in script.commands() {
let words = self.eval_word_vec(word_vec.words())?;
if words.is_empty() {
break;
}
let name = words[0].as_str();
if let Some(cmd) = self.commands.get(name) {
let cmd = Rc::clone(cmd);
let result = cmd.execute(self, words.as_slice());
if let Ok(v) = result {
result_value = v;
} else if let Err(mut exception) = result {
match exception.code() {
ResultCode::Error => {
if exception.is_new_error() {
exception.add_error_info(" while executing");
} else if cmd.is_proc() {
exception.add_error_info(" invoked from within");
exception.add_error_info(&format!(
" (procedure \"{}\" line TODO)",
name
));
} else {
return Err(exception);
}
exception.add_error_info(&format!("\"{}\"", &list_to_string(&words)));
return Err(exception);
}
_ => return Err(exception),
}
} else {
unreachable!();
}
} else {
return molt_err!("invalid command name \"{}\"", name);
}
}
Ok(result_value)
}
fn eval_word_vec(&mut self, words: &[Word]) -> Result<MoltList, Exception> {
let mut list: MoltList = Vec::new();
for word in words {
if let Word::Expand(word_to_expand) = word {
let value = self.eval_word(word_to_expand)?;
for val in &*value.as_list()? {
list.push(val.clone());
}
} else {
list.push(self.eval_word(word)?);
}
}
Ok(list)
}
pub(crate) fn eval_word(&mut self, word: &Word) -> MoltResult {
match word {
Word::Value(val) => Ok(val.clone()),
Word::VarRef(name) => self.scalar(name),
Word::ArrayRef(name, index_word) => {
let index = self.eval_word(index_word)?;
self.element(name, index.as_str())
}
Word::Script(script) => self.eval_script(script),
Word::Tokens(tokens) => {
let tlist = self.eval_word_vec(tokens)?;
let string: String = tlist.iter().map(|i| i.as_str()).collect();
Ok(Value::from(string))
}
Word::Expand(_) => panic!("recursive Expand!"),
Word::String(str) => Ok(Value::from(str)),
}
}
pub(crate) fn return_options(&self, result: &MoltResult) -> Value {
let mut opts = dict_new();
match result {
Ok(_) => {
opts.insert(OPT_CODE.into(), ZERO.into());
opts.insert(OPT_LEVEL.into(), ZERO.into());
}
Err(exception) => {
match exception.code() {
ResultCode::Okay => unreachable!(), ResultCode::Error => {
let data = exception.error_data().expect("Error has no error data");
opts.insert(OPT_CODE.into(), "1".into());
opts.insert(OPT_ERRORCODE.into(), data.error_code());
opts.insert(OPT_ERRORINFO.into(), data.error_info());
}
ResultCode::Return => {
opts.insert(OPT_CODE.into(), exception.next_code().as_int().into());
if let Some(data) = exception.error_data() {
opts.insert(OPT_ERRORCODE.into(), data.error_code());
opts.insert(OPT_ERRORINFO.into(), data.error_info());
}
}
ResultCode::Break => {
opts.insert(OPT_CODE.into(), "3".into());
}
ResultCode::Continue => {
opts.insert(OPT_CODE.into(), "4".into());
}
ResultCode::Other(num) => {
opts.insert(OPT_CODE.into(), num.into());
}
}
opts.insert(OPT_LEVEL.into(), Value::from(exception.level() as MoltInt));
}
}
Value::from(opts)
}
pub fn complete(&mut self, script: &str) -> bool {
parser::parse(script).is_ok()
}
pub fn expr(&mut self, expr: &Value) -> MoltResult {
let result = expr::expr(self, expr);
if let Err(exception) = &result {
self.set_global_error_data(exception.error_data())?;
}
result
}
pub fn expr_bool(&mut self, expr: &Value) -> Result<bool, Exception> {
self.expr(expr)?.as_bool()
}
pub fn expr_int(&mut self, expr: &Value) -> Result<MoltInt, Exception> {
self.expr(expr)?.as_int()
}
pub fn expr_float(&mut self, expr: &Value) -> Result<MoltFloat, Exception> {
self.expr(expr)?.as_float()
}
pub fn var(&self, var_name: &Value) -> MoltResult {
let var_name = &*var_name.as_var_name();
match var_name.index() {
Some(index) => self.element(var_name.name(), index),
None => self.scalar(var_name.name()),
}
}
pub fn var_exists(&self, var_name: &Value) -> bool {
let var_name = &*var_name.as_var_name();
match var_name.index() {
Some(index) => self.scopes.elem_exists(var_name.name(), index),
None => self.scopes.exists(var_name.name()),
}
}
pub fn set_var(&mut self, var_name: &Value, value: Value) -> Result<(), Exception> {
let var_name = &*var_name.as_var_name();
match var_name.index() {
Some(index) => self.set_element(var_name.name(), index, value),
None => self.set_scalar(var_name.name(), value),
}
}
pub fn set_var_return(&mut self, var_name: &Value, value: Value) -> MoltResult {
let var_name = &*var_name.as_var_name();
match var_name.index() {
Some(index) => self.set_element_return(var_name.name(), index, value),
None => self.set_scalar_return(var_name.name(), value),
}
}
pub fn scalar(&self, name: &str) -> MoltResult {
self.scopes.get(name)
}
pub fn set_scalar(&mut self, name: &str, value: Value) -> Result<(), Exception> {
self.scopes.set(name, value)
}
pub fn set_scalar_return(&mut self, name: &str, value: Value) -> MoltResult {
self.scopes.set(name, value.clone())?;
Ok(value)
}
pub fn element(&self, name: &str, index: &str) -> MoltResult {
self.scopes.get_elem(name, index)
}
pub fn set_element(&mut self, name: &str, index: &str, value: Value) -> Result<(), Exception> {
self.scopes.set_elem(name, index, value)
}
pub fn set_element_return(&mut self, name: &str, index: &str, value: Value) -> MoltResult {
self.scopes.set_elem(name, index, value.clone())?;
Ok(value)
}
pub fn unset(&mut self, name: &str) {
self.scopes.unset(name);
}
pub fn unset_var(&mut self, name: &Value) {
let var_name = name.as_var_name();
if let Some(index) = var_name.index() {
self.unset_element(var_name.name(), index);
} else {
self.unset(var_name.name());
}
}
pub fn unset_element(&mut self, array_name: &str, index: &str) {
self.scopes.unset_element(array_name, index);
}
pub fn vars_in_scope(&self) -> MoltList {
self.scopes.vars_in_scope()
}
pub fn vars_in_global_scope(&self) -> MoltList {
self.scopes.vars_in_global_scope()
}
pub fn vars_in_local_scope(&self) -> MoltList {
self.scopes.vars_in_local_scope()
}
pub fn upvar(&mut self, level: usize, name: &str) {
assert!(level <= self.scopes.current(), "Invalid scope level");
self.scopes.upvar(level, name);
}
pub fn push_scope(&mut self) {
self.scopes.push();
}
pub fn pop_scope(&mut self) {
self.scopes.pop();
}
pub fn scope_level(&self) -> usize {
self.scopes.current()
}
pub(crate) fn array_unset(&mut self, array_name: &str) {
self.scopes.array_unset(array_name);
}
pub fn array_exists(&self, array_name: &str) -> bool {
self.scopes.array_exists(array_name)
}
pub fn array_get(&self, array_name: &str) -> MoltList {
self.scopes.array_get(array_name)
}
pub fn array_set(&mut self, array_name: &str, kvlist: &[Value]) -> MoltResult {
if kvlist.len() % 2 == 0 {
self.scopes.array_set(array_name, kvlist)?;
molt_ok!()
} else {
molt_err!("list must have an even number of elements")
}
}
pub fn array_names(&self, array_name: &str) -> MoltList {
self.scopes.array_indices(array_name)
}
pub fn array_size(&self, array_name: &str) -> usize {
self.scopes.array_size(array_name)
}
pub fn add_command(&mut self, name: &str, func: CommandFunc) {
self.add_context_command(name, func, NULL_CONTEXT);
}
pub fn add_context_command(&mut self, name: &str, func: CommandFunc, context_id: ContextID) {
if context_id != NULL_CONTEXT {
self.context_map
.get_mut(&context_id)
.expect("unknown context ID")
.increment();
}
self.commands
.insert(name.into(), Rc::new(Command::Native(func, context_id)));
}
pub(crate) fn add_proc(&mut self, name: &str, parms: &[Value], body: &Value) {
let proc = Procedure {
parms: parms.to_owned(),
body: body.clone(),
};
self.commands
.insert(name.into(), Rc::new(Command::Proc(proc)));
}
pub fn has_command(&self, name: &str) -> bool {
self.commands.contains_key(name)
}
pub fn rename_command(&mut self, old_name: &str, new_name: &str) {
if let Some(cmd) = self.commands.get(old_name) {
let cmd = Rc::clone(cmd);
self.commands.remove(old_name);
self.commands.insert(new_name.into(), cmd);
}
}
pub fn remove_command(&mut self, name: &str) {
let context_id = self
.commands
.get(name)
.expect("undefined command")
.context_id();
if context_id != NULL_CONTEXT
&& self
.context_map
.get_mut(&context_id)
.expect("unknown context ID")
.decrement()
{
self.context_map.remove(&context_id);
}
self.commands.remove(name);
}
pub fn command_names(&self) -> MoltList {
let vec: MoltList = self
.commands
.keys()
.cloned()
.map(|x| Value::from(&x))
.collect();
vec
}
pub fn command_type(&self, command: &str) -> MoltResult {
if let Some(cmd) = self.commands.get(command) {
molt_ok!(cmd.cmdtype())
} else {
molt_err!("\"{}\" isn't a command", command)
}
}
pub fn proc_names(&self) -> MoltList {
let vec: MoltList = self
.commands
.iter()
.filter(|(_, cmd)| cmd.is_proc())
.map(|(name, _)| Value::from(name))
.collect();
vec
}
pub fn proc_body(&self, procname: &str) -> MoltResult {
if let Some(cmd) = self.commands.get(procname) {
if let Command::Proc(proc) = &**cmd {
return molt_ok!(proc.body.clone());
}
}
molt_err!("\"{}\" isn't a procedure", procname)
}
pub fn proc_args(&self, procname: &str) -> MoltResult {
if let Some(cmd) = self.commands.get(procname) {
if let Command::Proc(proc) = &**cmd {
let vec: MoltList = proc
.parms
.iter()
.map(|item| item.as_list().expect("invalid proc parms")[0].clone())
.collect();
return molt_ok!(Value::from(vec));
}
}
molt_err!("\"{}\" isn't a procedure", procname)
}
pub fn proc_default(&self, procname: &str, arg: &str) -> Result<Option<Value>, Exception> {
if let Some(cmd) = self.commands.get(procname) {
if let Command::Proc(proc) = &**cmd {
for argvec in &proc.parms {
let argvec = argvec.as_list()?; if argvec[0].as_str() == arg {
if argvec.len() == 2 {
return Ok(Some(argvec[1].clone()));
} else {
return Ok(None);
}
}
}
return molt_err!(
"procedure \"{}\" doesn't have an argument \"{}\"",
procname,
arg
);
}
}
molt_err!("\"{}\" isn't a procedure", procname)
}
pub fn call_subcommand(
&mut self,
context_id: ContextID,
argv: &[Value],
subc: usize,
subcommands: &[Subcommand],
) -> MoltResult {
check_args(subc, argv, subc + 1, 0, "subcommand ?arg ...?")?;
let rec = Subcommand::find(subcommands, argv[subc].as_str())?;
(rec.1)(self, context_id, argv)
}
pub fn recursion_limit(&self) -> usize {
self.recursion_limit
}
pub fn set_recursion_limit(&mut self, limit: usize) {
self.recursion_limit = limit;
}
pub fn save_context<T: 'static>(&mut self, data: T) -> ContextID {
let id = self.context_id();
self.context_map.insert(id, ContextBox::new(data));
id
}
pub fn context<T: 'static>(&mut self, id: ContextID) -> &mut T {
self.context_map
.get_mut(&id)
.expect("unknown context ID")
.data
.downcast_mut::<T>()
.expect("context type mismatch")
}
pub fn context_id(&mut self) -> ContextID {
self.last_context_id += 1;
ContextID(self.last_context_id)
}
pub fn set_context<T: 'static>(&mut self, id: ContextID, data: T) {
self.context_map.insert(id, ContextBox::new(data));
}
pub fn profile_save(&mut self, name: &str, start: std::time::Instant) {
let dur = Instant::now().duration_since(start).as_nanos();
let rec = self
.profile_map
.entry(name.into())
.or_insert_with(ProfileRecord::new);
rec.count += 1;
rec.nanos += dur;
}
pub fn profile_clear(&mut self) {
self.profile_map.clear();
}
pub fn profile_dump(&self) {
if self.profile_map.is_empty() {
println!("no profile data");
} else {
for (name, rec) in &self.profile_map {
let avg = rec.nanos / rec.count;
println!("{} nanos {}, count={}", avg, name, rec.count);
}
}
}
}
struct Procedure {
parms: MoltList,
body: Value,
}
impl Procedure {
fn execute(&self, interp: &mut Interp, argv: &[Value]) -> MoltResult {
interp.push_scope();
let mut argi = 1;
for (speci, spec) in self.parms.iter().enumerate() {
let vec = &*spec.as_list()?; assert!(vec.len() == 1 || vec.len() == 2);
if vec[0].as_str() == "args" && speci == self.parms.len() - 1 {
interp.set_scalar("args", Value::from(&argv[argi..]))?;
argi = argv.len();
break;
}
if argi < argv.len() {
interp.set_scalar(vec[0].as_str(), argv[argi].clone())?;
argi += 1;
continue;
}
if vec.len() == 2 {
interp.set_scalar(vec[0].as_str(), vec[1].clone())?;
} else {
return self.wrong_num_args(&argv[0]);
}
}
if argi != argv.len() {
return self.wrong_num_args(&argv[0]);
}
let result = interp.eval_value(&self.body);
interp.pop_scope();
if let Err(mut exception) = result {
if exception.code() == ResultCode::Return {
exception.decrement_level();
}
return match exception.code() {
ResultCode::Okay => Ok(exception.value()),
ResultCode::Error => Err(exception),
ResultCode::Return => Err(exception), ResultCode::Break => molt_err!("invoked \"break\" outside of a loop"),
ResultCode::Continue => molt_err!("invoked \"continue\" outside of a loop"),
ResultCode::Other(_) => molt_err!("unexpected result code."),
};
}
result
}
fn wrong_num_args(&self, name: &Value) -> MoltResult {
let mut msg = String::new();
msg.push_str("wrong # args: should be \"");
msg.push_str(name.as_str());
for (i, arg) in self.parms.iter().enumerate() {
msg.push(' ');
if arg.as_str() == "args" && i == self.parms.len() - 1 {
msg.push_str("?arg ...?");
break;
}
let vec = arg.as_list().expect("error in proc arglist validation!");
if vec.len() == 1 {
msg.push_str(vec[0].as_str());
} else {
msg.push('?');
msg.push_str(vec[0].as_str());
msg.push('?');
}
}
msg.push_str("\"");
molt_err!(&msg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty() {
let interp = Interp::empty();
assert!(interp.command_names().is_empty());
}
#[test]
fn test_new() {
let interp = Interp::new();
assert!(!interp.command_names().is_empty());
}
#[test]
fn test_eval() {
let mut interp = Interp::new();
assert_eq!(interp.eval("set a 1"), Ok(Value::from("1")));
assert!(ex_match(
&interp.eval("error 2"),
Exception::molt_err(Value::from("2"))
));
assert_eq!(interp.eval("return 3"), Ok(Value::from("3")));
assert!(ex_match(
&interp.eval("break"),
Exception::molt_err(Value::from("invoked \"break\" outside of a loop"))
));
assert!(ex_match(
&interp.eval("continue"),
Exception::molt_err(Value::from("invoked \"continue\" outside of a loop"))
));
}
fn ex_match(r: &MoltResult, expected: Exception) -> bool {
if let Err(e) = r {
e.code() == expected.code() && e.value() == expected.value()
} else {
false
}
}
#[test]
fn test_eval_value() {
let mut interp = Interp::new();
assert_eq!(
interp.eval_value(&Value::from("set a 1")),
Ok(Value::from("1"))
);
assert!(ex_match(
&interp.eval_value(&Value::from("error 2")),
Exception::molt_err(Value::from("2"))
));
assert_eq!(
interp.eval_value(&Value::from("return 3")),
Ok(Value::from("3"))
);
assert!(ex_match(
&interp.eval_value(&Value::from("break")),
Exception::molt_err(Value::from("invoked \"break\" outside of a loop"))
));
assert!(ex_match(
&interp.eval_value(&Value::from("continue")),
Exception::molt_err(Value::from("invoked \"continue\" outside of a loop"))
));
}
#[test]
fn test_complete() {
let mut interp = Interp::new();
assert!(interp.complete("abc"));
assert!(interp.complete("a {bc} [def] \"ghi\" xyz"));
assert!(!interp.complete("a {bc"));
assert!(!interp.complete("a [bc"));
assert!(!interp.complete("a \"bc"));
}
#[test]
fn test_expr() {
let mut interp = Interp::new();
assert_eq!(interp.expr(&Value::from("1 + 2")), Ok(Value::from(3)));
assert_eq!(
interp.expr(&Value::from("a + b")),
Err(Exception::molt_err(Value::from(
"unknown math function \"a\""
)))
);
}
#[test]
fn test_expr_bool() {
let mut interp = Interp::new();
assert_eq!(interp.expr_bool(&Value::from("1")), Ok(true));
assert_eq!(interp.expr_bool(&Value::from("0")), Ok(false));
assert_eq!(
interp.expr_bool(&Value::from("a")),
Err(Exception::molt_err(Value::from(
"unknown math function \"a\""
)))
);
}
#[test]
fn test_expr_int() {
let mut interp = Interp::new();
assert_eq!(interp.expr_int(&Value::from("1 + 2")), Ok(3));
assert_eq!(
interp.expr_int(&Value::from("a")),
Err(Exception::molt_err(Value::from(
"unknown math function \"a\""
)))
);
}
#[test]
fn test_expr_float() {
let mut interp = Interp::new();
let val = interp
.expr_float(&Value::from("1.1 + 2.2"))
.expect("floating point value");
assert!((val - 3.3).abs() < 0.001);
assert_eq!(
interp.expr_float(&Value::from("a")),
Err(Exception::molt_err(Value::from(
"unknown math function \"a\""
)))
);
}
#[test]
fn test_recursion_limit() {
let mut interp = Interp::new();
assert_eq!(interp.recursion_limit(), 1000);
interp.set_recursion_limit(100);
assert_eq!(interp.recursion_limit(), 100);
assert!(dbg!(interp.eval("proc myproc {} { myproc }")).is_ok());
assert!(ex_match(
&interp.eval("myproc"),
Exception::molt_err(Value::from(
"too many nested calls to Interp::eval (infinite loop?)"
))
));
}
#[test]
fn context_basic_use() {
let mut interp = Interp::new();
let id = interp.save_context(String::from("ABC"));
let ctx = interp.context::<String>(id);
assert_eq!(*ctx, "ABC");
ctx.push_str("DEF");
let ctx = interp.context::<String>(id);
assert_eq!(*ctx, "ABCDEF");
}
#[test]
fn context_advanced_use() {
let mut interp = Interp::new();
let id = interp.context_id();
interp.set_context(id, String::from("ABC"));
let ctx = interp.context::<String>(id);
assert_eq!(*ctx, "ABC");
}
#[test]
#[should_panic]
fn context_unknown() {
let mut interp = Interp::new();
let id = interp.context_id();
let _ctx = interp.context::<String>(id);
}
#[test]
#[should_panic]
fn context_wrong_type() {
let mut interp = Interp::new();
let id = interp.save_context(String::from("ABC"));
let _ctx = interp.context::<Vec<String>>(id);
}
#[test]
#[should_panic]
fn context_forgotten_1_command() {
let mut interp = Interp::new();
let id = interp.save_context(String::from("ABC"));
interp.add_context_command("dummy", dummy_cmd, id);
interp.remove_command("dummy");
let _ctx = interp.context::<String>(id);
}
#[test]
#[should_panic(expected = "unknown context ID")]
fn context_forgotten_2_commands() {
let mut interp = Interp::new();
let id = interp.save_context(String::from("ABC"));
interp.add_context_command("dummy", dummy_cmd, id);
interp.add_context_command("dummy2", dummy_cmd, id);
interp.remove_command("dummy");
assert_eq!(interp.context::<String>(id), "ABC");
interp.remove_command("dummy2");
let _ctx = interp.context::<String>(id);
}
fn dummy_cmd(_: &mut Interp, _: ContextID, _: &[Value]) -> MoltResult {
molt_err!("Not really meant to be called")
}
}