use crate::check_args;
use crate::commands;
#[cfg(feature = "dict")]
use crate::dict::dict_new;
#[cfg(feature = "expr")]
use crate::expr;
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 alloc::rc::Rc;
use alloc::borrow::ToOwned as _;
use alloc::string::String;
use alloc::vec::Vec;
#[cfg(feature = "closure-commands")]
use alloc::boxed::Box;
use alloc::format;
use indexmap::IndexMap;
#[cfg(feature = "std")]
use std::time::Instant;
#[derive(Default)]
pub struct Interp {
commands: IndexMap<String, Rc<Command>, MoltHasher>,
scopes: ScopeStack,
recursion_limit: usize,
num_levels: usize,
#[cfg(feature = "std")]
profile_map: IndexMap<String, ProfileRecord, MoltHasher>,
}
enum Command {
Native(CommandFunc),
#[cfg(feature = "closure-commands")]
Closure(BoxedClosure),
Proc(Procedure),
}
#[cfg(feature = "closure-commands")]
type BoxedClosure = Box<dyn Fn(&mut Interp, &[Value]) -> Result<Option<Value>, Exception>>;
impl Command {
fn execute(&self, interp: &mut Interp, argv: &[Value]) -> MoltResult {
match self {
Command::Native(func) => {
Ok(func(interp, argv)?.unwrap_or_default())
}
#[cfg(feature = "closure-commands")]
Command::Closure(func) => Ok(func(interp, argv)?.unwrap_or_default()),
Command::Proc(proc) => proc.execute(interp, argv),
}
}
fn cmdtype(&self) -> Value {
match self {
Command::Native(_) => Value::from("native"),
#[cfg(feature = "closure-commands")]
Command::Closure(_) => Value::from("closure"),
Command::Proc(_) => Value::from("proc"),
}
}
fn is_proc(&self) -> bool {
matches!(self, Command::Proc(_))
}
}
#[cfg(feature = "std")]
struct ProfileRecord {
count: u128,
nanos: u128,
}
#[cfg(feature = "std")]
impl ProfileRecord {
fn new() -> Self {
Self { count: 0, nanos: 0 }
}
}
impl Interp {
pub fn empty() -> Self {
let mut interp = Self {
recursion_limit: 1000,
commands: IndexMap::default(),
scopes: ScopeStack::new(),
num_levels: 0,
#[cfg(feature = "std")]
profile_map: IndexMap::default(),
};
interp.set_scalar("errorInfo", Value::empty()).map_err(|_| ()).unwrap();
interp
}
pub fn new() -> Self {
let mut interp = Interp::empty();
static NEW_COMMANDS: &[(&str, CommandFunc)] = &[
("append", commands::cmd_append),
("break", commands::cmd_break),
("catch", commands::cmd_catch),
("continue", commands::cmd_continue),
("error", commands::cmd_error),
("global", commands::cmd_global),
("array", commands::cmd_array),
("assert_eq", commands::cmd_assert_eq),
("incr", commands::cmd_incr),
("join", commands::cmd_join),
("lappend", commands::cmd_lappend),
("lindex", commands::cmd_lindex),
("list", commands::cmd_list),
("llength", commands::cmd_llength),
("proc", commands::cmd_proc),
("rename", commands::cmd_rename),
("return", commands::cmd_return),
("set", commands::cmd_set),
("throw", commands::cmd_throw),
("unset", commands::cmd_unset),
("foreach", commands::cmd_foreach),
("for", commands::cmd_for),
("if", commands::cmd_if),
("while", commands::cmd_while),
#[cfg(feature = "string-command")]
("string", commands::cmd_string),
#[cfg(feature = "expr")]
("expr", commands::cmd_expr),
#[cfg(feature = "dict")]
("dict", commands::cmd_dict),
#[cfg(feature = "info")]
("info", commands::cmd_info),
#[cfg(feature = "std")]
("puts", commands::cmd_puts),
#[cfg(feature = "std")]
("time", commands::cmd_time),
#[cfg(feature = "std")]
("source", commands::cmd_source),
#[cfg(feature = "std")]
("exit", commands::cmd_exit),
#[cfg(feature = "internals")]
("parse", parser::cmd_parse),
#[cfg(all(feature = "std", feature = "internals"))]
("pdump", commands::cmd_pdump),
#[cfg(all(feature = "std", feature = "internals"))]
("pclear", commands::cmd_pclear),
];
for &(name, func) in NEW_COMMANDS {
interp.add_command(name, func);
}
#[cfg(feature = "std")]
interp.populate_env();
interp
}
#[cfg(feature = "std")]
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.to_owned());
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 = None;
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();
let cmd = self.commands.get(name)
.ok_or_else(|| Exception::molt_err(Value::from(format!("invalid command name \"{}\"", name))))?;
let cmd = Rc::clone(cmd);
let result = cmd.execute(self, words.as_slice());
match result {
Ok(v) => result_value = Some(v),
#[cfg(feature = "error-stack-trace")]
Err(mut exception) if 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!("\"{}\"", &crate::list::list_to_string(&words)));
return Err(exception);
}
Err(e) => return Err(e),
}
}
Ok(result_value.unwrap_or_default())
}
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)),
}
}
#[cfg(feature = "dict")]
pub(crate) fn return_options(&self, result: &MoltResult) -> Value {
const OPT_CODE: &str = "-code";
const OPT_LEVEL: &str = "-level";
const OPT_ERRORCODE: &str = "-errorcode";
const OPT_ERRORINFO: &str = "-errorinfo";
const ZERO: &str = "0";
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()
}
#[cfg(feature = "expr")]
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> {
cfg_if::cfg_if! {
if #[cfg(feature = "expr")] {
self.expr(expr)?.as_bool()
} else {
self.eval_value(expr)?.as_bool()
}
}
}
#[cfg(feature = "expr")]
pub fn expr_int(&mut self, expr: &Value) -> Result<MoltInt, Exception> {
self.expr(expr)?.as_int()
}
#[cfg(all(feature = "float", feature = "expr"))]
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 {
self.set_var(var_name, value.clone())?;
Ok(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 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 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]) -> Result<(), Exception> {
if kvlist.len() % 2 == 0 {
self.scopes.array_set(array_name, kvlist)?;
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)
}
#[cfg(feature = "closure-commands")]
pub fn add_command_closure(&mut self, name: &str, func: impl (Fn(&mut Interp, &[Value]) -> MoltOptResult) + 'static) {
self.commands.insert(name.into(), Rc::new(Command::Closure(Box::new(func))));
}
pub fn add_command(&mut self, name: &str, func: CommandFunc) {
self.commands
.insert(name.into(), Rc::new(Command::Native(func)));
}
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) {
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().map_err(|_|()).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,
argv: &[Value],
subc: usize,
subcommands: &[Subcommand],
) -> MoltOptResult {
check_args(subc, argv, subc + 1, 0, "subcommand ?arg ...?")?;
let rec = Subcommand::find(subcommands, argv[subc].as_str())?;
(rec.1)(self, argv)
}
pub fn recursion_limit(&self) -> usize {
self.recursion_limit
}
pub fn set_recursion_limit(&mut self, limit: usize) {
self.recursion_limit = limit;
}
#[cfg(feature = "std")]
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;
}
#[cfg(feature = "std")]
pub fn profile_clear(&mut self) {
self.profile_map.clear();
}
#[cfg(feature = "std")]
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().map_err(|_| ()).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('\"');
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?)"
))
));
}
}