use crate::{
dict::{dict_new, dict_path_insert, dict_path_remove, list_to_dict},
interp::Interp,
types::*,
util, *,
};
use std::fs;
cfg_if::cfg_if! {
if #[cfg(feature = "wasm")] {
use wasm_timer::Instant;
}else{
use std::time::Instant;
}
}
pub const _APPEND: &str = "append";
pub const _ARRAY: &str = "array";
pub const _ASSERT_EQ: &str = "assert_eq";
pub const _BREAK: &str = "break";
pub const _CATCH: &str = "catch";
pub const _CONTINUE: &str = "continue";
pub const _DICT: &str = "dict";
pub const _ERROR: &str = "error";
pub const _EXPR: &str = "expr";
pub const _FOR: &str = "for";
pub const _FOREACH: &str = "foreach";
pub const _GLOBAL: &str = "global";
pub const _IF: &str = "if";
pub const _INCR: &str = "incr";
pub const _INFO: &str = "info";
pub const _JOIN: &str = "join";
pub const _LAPPEND: &str = "lappend";
pub const _LINDEX: &str = "lindex";
pub const _LIST: &str = "list";
pub const _LLENGTH: &str = "llength";
pub const _PROC: &str = "proc";
pub const _PUTS: &str = "puts";
pub const _RENAME: &str = "rename";
pub const _RETURN: &str = "return";
pub const _SET: &str = "set";
pub const _STRING: &str = "string";
pub const _THROW: &str = "throw";
pub const _TIME: &str = "time";
pub const _UNSET: &str = "unset";
pub const _WHILE: &str = "while";
pub const _SOURCE: &str = "source";
pub const _EXIT: &str = "exit";
pub const _PARSE: &str = "parse";
pub const _PDUMP: &str = "pdump";
pub const _PCLEAR: &str = "pclear";
pub fn cmd_append<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 0, "varName ?value value ...?")?;
let mut new_string: String = interp
.var(&argv[1])
.and_then(|val| Ok(val.to_string()))
.unwrap_or_else(|_| String::new());
for item in &argv[2..] {
new_string.push_str(item.as_str());
}
interp.set_var_return(&argv[1], new_string.into())
}
pub fn cmd_array<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
let f = _gen_subcommand_generic!(
1,
[
("exists", cmd_array_exists),
("get", cmd_array_get),
("names", cmd_array_names),
("set", cmd_array_set),
("size", cmd_array_size),
("unset", cmd_array_unset),
],
);
f(interp, argv)
}
pub fn cmd_array_exists<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "arrayName")?;
molt_ok!(Value::from(interp.array_exists(argv[2].as_str())))
}
pub fn cmd_array_names<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "arrayName")?;
molt_ok!(Value::from(interp.array_names(argv[2].as_str())))
}
pub fn cmd_array_get<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "arrayName")?;
molt_ok!(Value::from(interp.array_get(argv[2].as_str())))
}
pub fn cmd_parse<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 2, "script")?;
let script = &argv[1];
molt_ok!(format!("{:?}", parser::parse(script.as_str())?))
}
pub fn cmd_array_set<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 4, 4, "arrayName list")?;
let var_name = argv[2].as_var_name();
if var_name.index().is_none() {
interp.array_set(var_name.name(), &*argv[3].as_list()?)
} else {
interp.array_set(var_name.name(), &*Value::empty().as_list()?)?;
molt_err!("can't set \"{}\": variable isn't array", &argv[2])
}
}
pub fn cmd_array_size<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "arrayName")?;
molt_ok!(Value::from(interp.array_size(argv[2].as_str()) as MoltInt))
}
pub fn cmd_array_unset<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 4, "arrayName ?index?")?;
if argv.len() == 3 {
interp.array_unset(argv[2].as_str());
} else {
interp.unset_element(argv[2].as_str(), argv[3].as_str());
}
molt_ok!()
}
pub fn cmd_assert_eq<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 3, 3, "received expected")?;
if argv[1] == argv[2] {
molt_ok!()
} else {
molt_err!("assertion failed: received \"{}\", expected \"{}\".", argv[1], argv[2])
}
}
pub fn cmd_break<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 1, 1, "")?;
Err(Exception::molt_break())
}
pub fn cmd_catch<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 4, "script ?resultVarName? ?optionsVarName?")?;
let result = interp.eval_value(&argv[1]);
let (code, value) = match &result {
Ok(val) => (0, val.clone()),
Err(exception) => match exception.code() {
ResultCode::Okay => unreachable!(), ResultCode::Error => (1, exception.value()),
ResultCode::Return => (2, exception.value()),
ResultCode::Break => (3, exception.value()),
ResultCode::Continue => (4, exception.value()),
ResultCode::Other(_) => unimplemented!(), },
};
if argv.len() >= 3 {
interp.set_var(&argv[2], value)?;
}
if argv.len() == 4 {
interp.set_var(&argv[3], interp.return_options(&result))?;
}
Ok(Value::from(code))
}
pub fn cmd_continue<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 1, 1, "")?;
Err(Exception::molt_continue())
}
pub fn cmd_dict<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
let f = _gen_subcommand_generic!(
1,
[
("create", cmd_dict_new),
("exists", cmd_dict_exists),
("get", cmd_dict_get),
("keys", cmd_dict_keys),
("remove", cmd_dict_remove),
("set", cmd_dict_set),
("size", cmd_dict_size),
("unset", cmd_dict_unset),
("values", cmd_dict_values),
],
);
f(interp, argv)
}
fn cmd_dict_new<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
if argv.len() % 2 != 0 {
return molt_err!(
"wrong # args: should be \"{} {}\"",
Value::from(&argv[0..2]).to_string(),
"?key value?"
);
}
if argv.len() > 2 {
molt_ok!(Value::from(list_to_dict(&argv[2..])))
} else {
molt_ok!(Value::from(dict_new()))
}
}
fn cmd_dict_exists<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 4, 0, "dictionary key ?key ...?")?;
let mut value: Value = argv[2].clone();
let indices = &argv[3..];
for index in indices {
if let Ok(dict) = value.as_dict() {
if let Some(val) = dict.get(index) {
value = val.clone();
} else {
return molt_ok!(false);
}
} else {
return molt_ok!(false);
}
}
molt_ok!(true)
}
fn cmd_dict_get<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 0, "dictionary ?key ...?")?;
let mut value: Value = argv[2].clone();
let indices = &argv[3..];
for index in indices {
let dict = value.as_dict()?;
if let Some(val) = dict.get(index) {
value = val.clone();
} else {
return molt_err!("key \"{}\" not known in dictionary", index);
}
}
molt_ok!(value)
}
fn cmd_dict_keys<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "dictionary")?;
let dict = argv[2].as_dict()?;
let keys: MoltList = dict.keys().cloned().collect();
molt_ok!(keys)
}
fn cmd_dict_remove<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 0, "dictionary ?key ...?")?;
let mut dict = (&*argv[2].as_dict()?).clone();
for key in &argv[3..] {
dict.shift_remove(key);
}
molt_ok!(dict)
}
fn cmd_dict_set<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 5, 0, "dictVarName key ?key ...? value")?;
let value = &argv[argv.len() - 1];
let keys = &argv[3..(argv.len() - 1)];
if let Ok(old_dict_val) = interp.var(&argv[2]) {
interp.set_var_return(&argv[2], dict_path_insert(&old_dict_val, keys, value)?)
} else {
let new_val = Value::from(dict_new());
interp.set_var_return(&argv[2], dict_path_insert(&new_val, keys, value)?)
}
}
fn cmd_dict_size<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "dictionary")?;
let dict = argv[2].as_dict()?;
molt_ok!(dict.len() as MoltInt)
}
fn cmd_dict_unset<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 4, 0, "dictVarName key ?key ...?")?;
let keys = &argv[3..];
if let Ok(old_dict_val) = interp.var(&argv[2]) {
interp.set_var_return(&argv[2], dict_path_remove(&old_dict_val, keys)?)
} else {
let new_val = Value::from(dict_new());
interp.set_var_return(&argv[2], dict_path_remove(&new_val, keys)?)
}
}
fn cmd_dict_values<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "dictionary")?;
let dict = argv[2].as_dict()?;
let values: MoltList = dict.values().cloned().collect();
molt_ok!(values)
}
pub fn cmd_error<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 2, "message")?;
molt_err!(argv[1].clone())
}
pub fn cmd_exit<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 1, 2, "?returnCode?")?;
let return_code: MoltInt = if argv.len() == 1 { 0 } else { argv[1].as_int()? };
std::process::exit(return_code as i32)
}
pub fn cmd_expr<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 0, "expr")?;
if argv.len() == 2 {
interp.expr(&argv[1])
} else {
let values = argv[1..].iter().map(|v| v.as_str()).collect::<Vec<_>>();
interp.expr(&Value::from(values.join(" ")))
}
}
pub fn cmd_for<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 5, 5, "start test next command")?;
let start = &argv[1];
let test = &argv[2];
let next = &argv[3];
let command = &argv[4];
interp.eval_value(start)?;
while interp.expr_bool(test)? {
let result = interp.eval_value(command);
if let Err(exception) = result {
match exception.code() {
ResultCode::Break => break,
ResultCode::Continue => (),
_ => return Err(exception),
}
}
let result = interp.eval_value(next);
if let Err(exception) = result {
match exception.code() {
ResultCode::Break => break,
ResultCode::Continue => {
return molt_err!("invoked \"continue\" outside of a loop");
}
_ => return Err(exception),
}
}
}
molt_ok!()
}
pub fn cmd_foreach<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 4, 4, "varList list body")?;
let var_list = &*argv[1].as_list()?;
let list = &*argv[2].as_list()?;
let body = &argv[3];
let mut i = 0;
while i < list.len() {
for var in var_list {
if i < list.len() {
interp.set_var(&var, list[i].clone())?;
i += 1;
} else {
interp.set_var(&var, Value::empty())?;
}
}
let result = interp.eval_value(body);
if let Err(exception) = result {
match exception.code() {
ResultCode::Break => break,
ResultCode::Continue => (),
_ => return Err(exception),
}
}
}
molt_ok!()
}
pub fn cmd_global<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
if interp.scope_level() > 0 {
for name in &argv[1..] {
interp.upvar(0, name.as_str());
}
}
molt_ok!()
}
#[derive(Eq, PartialEq, Debug)]
enum IfWants {
Expr,
ThenBody,
SkipThenClause,
ElseClause,
ElseBody,
}
pub fn cmd_if<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
let mut argi = 1;
let mut wants = IfWants::Expr;
while argi < argv.len() {
match wants {
IfWants::Expr => {
wants = if interp.expr_bool(&argv[argi])? {
IfWants::ThenBody
} else {
IfWants::SkipThenClause
};
}
IfWants::ThenBody => {
if argv[argi].as_str() == "then" {
argi += 1;
}
if argi < argv.len() {
return interp.eval_value(&argv[argi]);
} else {
break;
}
}
IfWants::SkipThenClause => {
if argv[argi].as_str() == "then" {
argi += 1;
}
if argi < argv.len() {
argi += 1;
wants = IfWants::ElseClause;
}
continue;
}
IfWants::ElseClause => {
if argv[argi].as_str() == "elseif" {
wants = IfWants::Expr;
} else {
wants = IfWants::ElseBody;
continue;
}
}
IfWants::ElseBody => {
if argv[argi].as_str() == "else" {
argi += 1;
if argi == argv.len() {
return molt_err!(
"wrong # args: no script following after \"{}\" argument",
argv[argi - 1]
);
}
}
if argi < argv.len() {
return interp.eval_value(&argv[argi]);
} else {
break;
}
}
}
argi += 1;
}
if argi < argv.len() {
return molt_err!(
"wrong # args: extra words after \"else\" clause in \"if\" command"
);
} else if wants == IfWants::Expr {
return molt_err!(
"wrong # args: no expression after \"{}\" argument",
argv[argi - 1]
);
} else if wants == IfWants::ThenBody || wants == IfWants::SkipThenClause {
return molt_err!(
"wrong # args: no script following after \"{}\" argument",
argv[argi - 1]
);
} else {
molt_ok!() }
}
pub fn cmd_incr<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 3, "varName ?increment?")?;
let increment: MoltInt = if argv.len() == 3 { argv[2].as_int()? } else { 1 };
let new_value = increment
+ interp
.var(&argv[1])
.and_then(|val| Ok(val.as_int()?))
.unwrap_or_else(|_| 0);
interp.set_var_return(&argv[1], new_value.into())
}
pub fn cmd_info<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
let f = _gen_subcommand_generic!(
1,
[
("args", cmd_info_args),
("body", cmd_info_body),
("cmdtype", cmd_info_cmdtype),
("commands", cmd_info_commands),
("complete", cmd_info_complete),
("default", cmd_info_default),
("exists", cmd_info_exists),
("globals", cmd_info_globals),
("locals", cmd_info_locals),
("procs", cmd_info_procs),
("vars", cmd_info_vars),
],
);
f(interp, argv)
}
pub fn cmd_info_args<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "procname")?;
interp.proc_args(&argv[2].as_str())
}
pub fn cmd_info_body<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "procname")?;
interp.proc_body(&argv[2].as_str())
}
pub fn cmd_info_cmdtype<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "command")?;
interp.command_type(&argv[2].as_str())
}
pub fn cmd_info_commands<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
molt_ok!(Value::from(interp.command_names()))
}
pub fn cmd_info_default<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 5, 5, "procname arg varname")?;
if let Some(val) = interp.proc_default(&argv[2].as_str(), &argv[3].as_str())? {
interp.set_var(&argv[4], val)?;
molt_ok!(1)
} else {
interp.set_var(&argv[4], Value::empty())?;
molt_ok!(0)
}
}
pub fn cmd_info_exists<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "varname")?;
Ok(interp.var_exists(&argv[2]).into())
}
pub fn cmd_info_complete<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "command")?;
if interp.complete(argv[2].as_str()) {
molt_ok!(true)
} else {
molt_ok!(false)
}
}
pub fn cmd_info_globals<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
molt_ok!(Value::from(interp.vars_in_global_scope()))
}
pub fn cmd_info_locals<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
molt_ok!(Value::from(interp.vars_in_local_scope()))
}
pub fn cmd_info_procs<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
molt_ok!(Value::from(interp.proc_names()))
}
pub fn cmd_info_vars<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
molt_ok!(Value::from(interp.vars_in_scope()))
}
pub fn cmd_join<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 3, "list ?joinString?")?;
let list = &argv[1].as_list()?;
let join_string = if argv.len() == 3 { argv[2].to_string() } else { " ".to_string() };
let list: Vec<String> = list.iter().map(|v| v.to_string()).collect();
molt_ok!(list.join(&join_string))
}
pub fn cmd_lappend<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 0, "varName ?value ...?")?;
let var_result = interp.var(&argv[1]);
let mut list: MoltList = if var_result.is_ok() {
var_result.expect("got value").to_list()?
} else {
Vec::new()
};
let mut values = argv[2..].to_owned();
list.append(&mut values);
interp.set_var_return(&argv[1], Value::from(list))
}
pub fn cmd_lindex<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 0, "list ?index ...?")?;
if argv.len() != 3 {
lindex_into(&argv[1], &argv[2..])
} else {
lindex_into(&argv[1], &*argv[2].as_list()?)
}
}
pub fn lindex_into(list: &Value, indices: &[Value]) -> MoltResult {
let mut value: Value = list.clone();
for index_val in indices {
let list = value.as_list()?;
let index = index_val.as_int()?;
value = if index < 0 || index as usize >= list.len() {
Value::empty()
} else {
list[index as usize].clone()
};
}
molt_ok!(value)
}
pub fn cmd_list<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
molt_ok!(&argv[1..])
}
pub fn cmd_llength<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 2, "list")?;
molt_ok!(argv[1].as_list()?.len() as MoltInt)
}
pub fn cmd_pdump<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 1, 1, "")?;
interp.profile_dump();
molt_ok!()
}
pub fn cmd_pclear<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 1, 1, "")?;
interp.profile_clear();
molt_ok!()
}
pub fn cmd_proc<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 4, 4, "name args body")?;
let name = argv[1].as_str();
let args = &*argv[2].as_list()?;
for arg in args {
let vec = arg.as_list()?;
if vec.is_empty() {
return molt_err!("argument with no name");
} else if vec.len() > 2 {
return molt_err!("too many fields in argument specifier \"{}\"", arg);
}
}
interp.add_proc(name, args, &argv[3]);
molt_ok!()
}
pub fn cmd_puts<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 2, "string")?;
cfg_if::cfg_if! {
if #[cfg(feature = "std_buff")] {
interp.std_buff.push(Ok(argv[1].clone()));
} else {
println!("{}", argv[1]);
}
}
molt_ok!()
}
pub fn cmd_rename<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 3, 3, "oldName newName")?;
let old_name = argv[1].as_str();
let new_name = argv[2].as_str();
if !interp.has_proc(old_name) {
return molt_err!("can't rename \"{}\": command doesn't exist", old_name);
}
if new_name.is_empty() {
interp.remove_proc(old_name);
} else {
interp.rename_proc(old_name, new_name);
}
molt_ok!()
}
pub fn cmd_return<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 1, 0, "?options...? ?value?")?;
let mut code = ResultCode::Okay;
let mut level: MoltInt = 1;
let mut error_code: Option<Value> = None;
let mut error_info: Option<Value> = None;
if argv.len() == 1 {
return Err(Exception::molt_return_ext(Value::empty(), level as usize, code));
}
let return_value: Value;
let opt_args: &[Value] = if argv.len() % 2 == 0 {
return_value = argv[argv.len() - 1].clone();
&argv[1..argv.len() - 1]
} else {
return_value = Value::empty();
&argv[1..argv.len()]
};
let mut queue = opt_args.iter();
while let Some(opt) = queue.next() {
let val = queue
.next()
.expect("missing option value: coding error in cmd_return");
match opt.as_str() {
"-code" => {
code = ResultCode::from_value(val)?;
}
"-errorcode" => {
error_code = Some(val.clone());
}
"-errorinfo" => {
error_info = Some(val.clone());
}
"-level" => {
level = val.as_int()?;
}
_ => return molt_err!("invalid return option: \"{}\"", opt),
}
}
if code == ResultCode::Error {
Err(Exception::molt_return_err(
return_value,
level as usize,
error_code,
error_info,
))
} else if level == 0 && code == ResultCode::Okay {
Ok(return_value)
} else {
Err(Exception::molt_return_ext(return_value, level as usize, code))
}
}
pub fn cmd_set<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 3, "varName ?newValue?")?;
if argv.len() == 3 {
interp.set_var_return(&argv[1], argv[2].clone())
} else {
molt_ok!(interp.var(&argv[1])?)
}
}
pub fn cmd_source<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 2, "filename")?;
let filename = argv[1].as_str();
match fs::read_to_string(filename) {
Ok(script) => interp.eval(&script),
Err(e) => molt_err!("couldn't read file \"{}\": {}", filename, e),
}
}
pub fn cmd_string<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
let f = _gen_subcommand_generic!(
1,
[
("cat", cmd_string_cat),
("compare", cmd_string_compare),
("equal", cmd_string_equal),
("first", cmd_string_first),
("last", cmd_string_last),
("length", cmd_string_length),
("map", cmd_string_map),
("range", cmd_string_range),
("tolower", cmd_string_tolower),
("toupper", cmd_string_toupper),
("trim", cmd_string_trim),
("trimleft", cmd_string_trim),
("trimright", cmd_string_trim),
],
);
f(interp, argv)
}
pub fn cmd_todo<Ctx>(_interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
molt_err!("TODO")
}
pub fn cmd_string_cat<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
let mut buff = String::new();
for arg in &argv[2..] {
buff.push_str(arg.as_str());
}
molt_ok!(buff)
}
pub fn cmd_string_compare<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 4, 7, "?-nocase? ?-length length? string1 string2")?;
let arglen = argv.len();
let mut nocase = false;
let mut length: Option<MoltInt> = None;
let opt_args = &argv[2..arglen - 2];
let mut queue = opt_args.iter();
while let Some(opt) = queue.next() {
match opt.as_str() {
"-nocase" => nocase = true,
"-length" => {
if let Some(val) = queue.next() {
length = Some(val.as_int()?);
} else {
return molt_err!("wrong # args: should be \"string compare ?-nocase? ?-length length? string1 string2\"");
}
}
_ => return molt_err!("bad option \"{}\": must be -nocase or -length", opt),
}
}
if nocase {
let val1 = &argv[arglen - 2];
let val2 = &argv[arglen - 1];
let val1 = Value::from(val1.as_str().to_lowercase());
let val2 = Value::from(val2.as_str().to_lowercase());
molt_ok!(util::compare_len(val1.as_str(), val2.as_str(), length)?)
} else {
molt_ok!(util::compare_len(
argv[arglen - 2].as_str(),
argv[arglen - 1].as_str(),
length
)?)
}
}
pub fn cmd_string_equal<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 4, 7, "?-nocase? ?-length length? string1 string2")?;
let arglen = argv.len();
let mut nocase = false;
let mut length: Option<MoltInt> = None;
let opt_args = &argv[2..arglen - 2];
let mut queue = opt_args.iter();
while let Some(opt) = queue.next() {
match opt.as_str() {
"-nocase" => nocase = true,
"-length" => {
if let Some(val) = queue.next() {
length = Some(val.as_int()?);
} else {
return molt_err!("wrong # args: should be \"string equal ?-nocase? ?-length length? string1 string2\"");
}
}
_ => return molt_err!("bad option \"{}\": must be -nocase or -length", opt),
}
}
if nocase {
let val1 = &argv[arglen - 2];
let val2 = &argv[arglen - 1];
let val1 = Value::from(val1.as_str().to_lowercase());
let val2 = Value::from(val2.as_str().to_lowercase());
let flag = util::compare_len(val1.as_str(), val2.as_str(), length)? == 0;
molt_ok!(flag)
} else {
let flag = util::compare_len(
argv[arglen - 2].as_str(),
argv[arglen - 1].as_str(),
length,
)? == 0;
molt_ok!(flag)
}
}
pub fn cmd_string_first<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 4, 5, "needleString haystackString ?startIndex?")?;
let needle = argv[2].as_str();
let haystack = argv[3].as_str();
let start_char: usize = if argv.len() == 5 {
let arg = argv[4].as_int()?;
if arg < 0 {
0
} else {
arg as usize
}
} else {
0
};
let pos_byte: Option<usize> = haystack
.char_indices()
.nth(start_char)
.and_then(|(start_byte, _)| haystack[start_byte..].find(needle));
let pos_char: MoltInt = match pos_byte {
None => -1,
Some(b) => {
haystack[b..].char_indices().take_while(|(i, _)| *i < b).count() as MoltInt
+ start_char as MoltInt
}
};
molt_ok!(pos_char)
}
pub fn cmd_string_last<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 4, 5, "needleString haystackString ?lastIndex?")?;
let needle = argv[2].as_str();
let haystack = argv[3].as_str();
let count = haystack.chars().count();
let last: Option<usize> = if argv.len() == 5 {
let arg = argv[4].as_int()?;
if arg < 0 {
return molt_ok!(-1);
}
if arg as usize >= count {
None
} else {
Some(arg as usize)
}
} else {
None
};
let slice = match last {
None => haystack,
Some(n) => match haystack.char_indices().nth(n + 1) {
None => haystack,
Some((byte, _)) => &haystack[..byte],
},
};
let pos_byte = slice.rfind(needle);
let pos_char: MoltInt = match pos_byte {
None => -1,
Some(b) => haystack.char_indices().take_while(|(i, _)| *i < b).count() as MoltInt,
};
molt_ok!(pos_char)
}
pub fn cmd_string_length<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "string")?;
let len: MoltInt = argv[2].as_str().chars().count() as MoltInt;
molt_ok!(len)
}
pub fn cmd_string_map<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 4, 5, "?-nocase? charMap string")?;
let mut nocase = false;
if argv.len() == 5 {
let opt = argv[2].as_str();
if opt == "-nocase" {
nocase = true;
} else {
return molt_err!("bad option \"{}\": must be -nocase", opt);
}
}
let char_map = argv[argv.len() - 2].as_dict()?;
let s = argv[argv.len() - 1].as_str();
let filtered_keys = char_map
.iter()
.map(|(k, v)| {
let new_k =
if nocase { Value::from(k.as_str().to_lowercase()) } else { k.clone() };
let count = new_k.as_str().chars().count();
(new_k, count, v.clone())
})
.filter(|(_, count, _)| *count > 0)
.collect::<Vec<_>>();
let string_lower: Option<String> = if nocase { Some(s.to_lowercase()) } else { None };
let mut result = String::new();
let mut skip = 0;
for (i, c) in s.char_indices() {
if skip > 0 {
skip -= 1;
continue;
}
let mut matched = false;
for (from, from_char_count, to) in &filtered_keys {
let haystack: &str = match &string_lower {
Some(x) => &x[i..],
None => &s[i..],
};
if haystack.starts_with(&from.as_str()) {
matched = true;
result.push_str(to.as_str());
skip = from_char_count - 1;
break;
}
}
if !matched {
result.push(c);
}
}
molt_ok!(result)
}
pub fn cmd_string_range<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 5, 5, "string first last")?;
let s = argv[2].as_str();
let first = argv[3].as_int()?;
let last = argv[4].as_int()?;
if last < 0 {
return molt_ok!("");
}
let clamp = { |i: MoltInt| if i < 0 { 0 } else { i } };
let substr = s
.chars()
.skip(clamp(first) as usize)
.take((clamp(last) - clamp(first) + 1) as usize)
.collect::<String>();
molt_ok!(substr)
}
pub fn cmd_string_tolower<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "string")?;
let lower = argv[2].as_str().to_lowercase();
molt_ok!(lower)
}
pub fn cmd_string_toupper<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "string")?;
let upper = argv[2].as_str().to_uppercase();
molt_ok!(upper)
}
pub fn cmd_string_trim<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(2, argv, 3, 3, "string")?;
let s = argv[2].as_str();
let trimmed = match argv[1].as_str() {
"trimleft" => s.trim_start(),
"trimright" => s.trim_end(),
_ => s.trim(),
};
molt_ok!(trimmed)
}
pub fn cmd_throw<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 3, 3, "type message")?;
Err(Exception::molt_err2(argv[1].clone(), argv[2].clone()))
}
pub fn cmd_time<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 2, 3, "command ?count?")?;
let command = &argv[1];
let count = if argv.len() == 3 { argv[2].as_int()? } else { 1 };
let start = Instant::now();
for _i in 0..count {
let result = interp.eval_value(command);
if result.is_err() {
return result;
}
}
let span = start.elapsed();
let avg = if count > 0 { span.as_nanos() / (count as u128) } else { 0 } as MoltInt;
molt_ok!("{} nanoseconds per iteration", avg)
}
pub fn cmd_unset<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 1, 0, "?-nocomplain? ?--? ?name name name...?")?;
let mut options_ok = true;
for arg in argv {
let var = arg.as_str();
if options_ok {
if var == "--" {
options_ok = false;
continue;
} else if var == "-nocomplain" {
continue;
}
}
interp.unset_var(arg);
}
molt_ok!()
}
pub fn cmd_while<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
check_args(1, argv, 3, 3, "test command")?;
while interp.expr_bool(&argv[1])? {
let result = interp.eval_value(&argv[2]);
if let Err(exception) = result {
match exception.code() {
ResultCode::Break => break,
ResultCode::Continue => (),
_ => return Err(exception),
}
}
}
molt_ok!()
}