use crate::interp::Interp;
pub use crate::value::Value;
use indexmap::IndexMap;
use std::fmt;
use std::str::FromStr;
pub type MoltInt = i64;
pub type MoltFloat = f64;
pub type MoltList = Vec<Value>;
pub type MoltDict = IndexMap<Value, Value>;
pub type MoltResult = Result<Value, Exception>;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ResultCode {
Okay,
Error,
Return,
Break,
Continue,
Other(MoltInt),
}
impl fmt::Display for ResultCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ResultCode::Okay => write!(f, "ok"),
ResultCode::Error => write!(f, "error"),
ResultCode::Return => write!(f, "return"),
ResultCode::Break => write!(f, "break"),
ResultCode::Continue => write!(f, "continue"),
ResultCode::Other(code) => write!(f, "{}", *code),
}
}
}
impl FromStr for ResultCode {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"ok" => return Ok(ResultCode::Okay),
"error" => return Ok(ResultCode::Error),
"return" => return Ok(ResultCode::Return),
"break" => return Ok(ResultCode::Break),
"continue" => return Ok(ResultCode::Continue),
_ => (),
}
match Value::get_int(value) {
Ok(num) => match num {
0 => Ok(ResultCode::Okay),
1 => Ok(ResultCode::Error),
2 => Ok(ResultCode::Return),
3 => Ok(ResultCode::Break),
4 => Ok(ResultCode::Continue),
_ => Ok(ResultCode::Other(num)),
},
Err(exception) => Err(exception.value().as_str().into()),
}
}
}
impl ResultCode {
pub fn from_value(value: &Value) -> Result<Self, Exception> {
if let Some(x) = value.as_copy::<ResultCode>() {
Ok(x)
} else {
molt_err!("invalid result code: \"{}\"", value)
}
}
pub fn as_int(&self) -> MoltInt {
match self {
ResultCode::Okay => 0,
ResultCode::Error => 1,
ResultCode::Return => 2,
ResultCode::Break => 3,
ResultCode::Continue => 4,
ResultCode::Other(num) => *num,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Exception {
code: ResultCode,
value: Value,
level: usize,
next_code: ResultCode,
error_data: Option<ErrorData>,
}
impl Exception {
pub fn is_error(&self) -> bool {
self.code == ResultCode::Error
}
pub fn error_code(&self) -> Value {
self.error_data()
.expect("exception is not an error")
.error_code()
}
pub fn error_info(&self) -> Value {
self.error_data()
.expect("exception is not an error")
.error_info()
}
pub fn error_data(&self) -> Option<&ErrorData> {
self.error_data.as_ref()
}
pub fn code(&self) -> ResultCode {
self.code
}
pub fn value(&self) -> Value {
self.value.clone()
}
pub fn level(&self) -> usize {
self.level
}
pub fn next_code(&self) -> ResultCode {
self.next_code
}
pub fn add_error_info(&mut self, line: &str) {
if let Some(data) = &mut self.error_data {
data.add_info(line);
} else {
panic!("add_error_info called for non-Error Exception");
}
}
pub fn molt_err(msg: Value) -> Self {
let data = ErrorData::new(Value::from("NONE"), msg.as_str());
Self {
code: ResultCode::Error,
value: msg,
level: 0,
next_code: ResultCode::Error,
error_data: Some(data),
}
}
pub fn molt_err2(error_code: Value, msg: Value) -> Self {
let data = ErrorData::new(error_code, msg.as_str());
Self {
code: ResultCode::Error,
value: msg,
level: 0,
next_code: ResultCode::Error,
error_data: Some(data),
}
}
pub fn molt_return(value: Value) -> Self {
Self {
code: ResultCode::Return,
value,
level: 1,
next_code: ResultCode::Okay,
error_data: None,
}
}
pub fn molt_return_ext(value: Value, level: usize, next_code: ResultCode) -> Self {
assert!(level > 0 || next_code != ResultCode::Okay);
Self {
code: if level > 0 {
ResultCode::Return
} else {
next_code
},
value,
level,
next_code,
error_data: None,
}
}
pub fn molt_return_err(
msg: Value,
level: usize,
error_code: Option<Value>,
error_info: Option<Value>,
) -> Self {
let error_code = error_code.unwrap_or_else(|| Value::from("NONE"));
let error_info = error_info.unwrap_or_else(Value::empty);
let data = ErrorData::rethrow(error_code, error_info.as_str());
Self {
code: if level == 0 {
ResultCode::Error
} else {
ResultCode::Return
},
value: msg,
level,
next_code: ResultCode::Error,
error_data: Some(data),
}
}
pub fn molt_break() -> Self {
Self {
code: ResultCode::Break,
value: Value::empty(),
level: 0,
next_code: ResultCode::Break,
error_data: None,
}
}
pub fn molt_continue() -> Self {
Self {
code: ResultCode::Continue,
value: Value::empty(),
level: 0,
next_code: ResultCode::Continue,
error_data: None,
}
}
pub(crate) fn decrement_level(&mut self) {
assert!(self.code == ResultCode::Return && self.level > 0);
self.level -= 1;
if self.level == 0 {
self.code = self.next_code;
}
}
pub(crate) fn is_new_error(&self) -> bool {
if let Some(data) = &self.error_data {
data.is_new()
} else {
false
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ErrorData {
error_code: Value,
stack_trace: Vec<String>,
is_new: bool,
}
impl ErrorData {
fn new(error_code: Value, error_msg: &str) -> Self {
Self {
error_code,
stack_trace: vec![error_msg.into()],
is_new: true,
}
}
fn rethrow(error_code: Value, error_info: &str) -> Self {
Self {
error_code,
stack_trace: vec![error_info.into()],
is_new: false,
}
}
pub fn error_code(&self) -> Value {
self.error_code.clone()
}
pub(crate) fn is_new(&self) -> bool {
self.is_new
}
pub fn error_info(&self) -> Value {
Value::from(self.stack_trace.join("\n"))
}
pub(crate) fn add_info(&mut self, info: &str) {
self.stack_trace.push(info.into());
self.is_new = false;
}
}
#[derive(Eq, PartialEq, Debug, Hash, Copy, Clone)]
pub struct ContextID(pub(crate) u64);
pub type CommandFunc = fn(&mut Interp, ContextID, &[Value]) -> MoltResult;
pub struct Subcommand(pub &'static str, pub CommandFunc);
impl Subcommand {
pub fn find<'a>(
ensemble: &'a [Subcommand],
sub_name: &str,
) -> Result<&'a Subcommand, Exception> {
for subcmd in ensemble {
if subcmd.0 == sub_name {
return Ok(subcmd);
}
}
let mut names = String::new();
names.push_str(ensemble[0].0);
let last = ensemble.len() - 1;
if ensemble.len() > 1 {
names.push_str(", ");
}
if ensemble.len() > 2 {
let vec: Vec<&str> = ensemble[1..last].iter().map(|x| x.0).collect();
names.push_str(&vec.join(", "));
}
if ensemble.len() > 1 {
names.push_str(", or ");
names.push_str(ensemble[last].0);
}
molt_err!(
"unknown or ambiguous subcommand \"{}\": must be {}",
sub_name,
&names
)
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct VarName {
name: String,
index: Option<String>,
}
impl VarName {
pub fn scalar(name: String) -> Self {
Self { name, index: None }
}
pub fn array(name: String, index: String) -> Self {
Self {
name,
index: Some(index),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn index(&self) -> Option<&str> {
self.index.as_ref().map(|x| &**x)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_result_code_as_string() {
assert_eq!(Value::from_other(ResultCode::Okay).as_str(), "ok");
assert_eq!(Value::from_other(ResultCode::Error).as_str(), "error");
assert_eq!(Value::from_other(ResultCode::Return).as_str(), "return");
assert_eq!(Value::from_other(ResultCode::Break).as_str(), "break");
assert_eq!(Value::from_other(ResultCode::Continue).as_str(), "continue");
assert_eq!(Value::from_other(ResultCode::Other(5)).as_str(), "5");
}
#[test]
fn test_result_code_from_value() {
assert_eq!(ResultCode::from_value(&"ok".into()), Ok(ResultCode::Okay));
assert_eq!(
ResultCode::from_value(&"error".into()),
Ok(ResultCode::Error)
);
assert_eq!(
ResultCode::from_value(&"return".into()),
Ok(ResultCode::Return)
);
assert_eq!(
ResultCode::from_value(&"break".into()),
Ok(ResultCode::Break)
);
assert_eq!(
ResultCode::from_value(&"continue".into()),
Ok(ResultCode::Continue)
);
assert_eq!(
ResultCode::from_value(&"5".into()),
Ok(ResultCode::Other(5))
);
assert!(ResultCode::from_value(&"nonesuch".into()).is_err());
}
#[test]
fn test_result_code_as_int() {
assert_eq!(ResultCode::Okay.as_int(), 0);
assert_eq!(ResultCode::Error.as_int(), 1);
assert_eq!(ResultCode::Return.as_int(), 2);
assert_eq!(ResultCode::Break.as_int(), 3);
assert_eq!(ResultCode::Continue.as_int(), 4);
assert_eq!(ResultCode::Other(5).as_int(), 5);
}
#[test]
fn test_error_data_new() {
let data = ErrorData::new("CODE".into(), "error message");
assert_eq!(data.error_code(), "CODE".into());
assert_eq!(data.error_info(), "error message".into());
assert!(data.is_new());
}
#[test]
fn test_error_data_rethrow() {
let data = ErrorData::rethrow("CODE".into(), "stack trace");
assert_eq!(data.error_code(), "CODE".into());
assert_eq!(data.error_info(), "stack trace".into());
assert!(!data.is_new());
}
#[test]
fn test_error_data_add_info() {
let mut data = ErrorData::new("CODE".into(), "error message");
assert_eq!(data.error_info(), "error message".into());
assert!(data.is_new());
data.add_info("next line");
assert_eq!(data.error_info(), "error message\nnext line".into());
assert!(!data.is_new());
}
#[test]
fn test_exception_molt_err() {
let mut exception = Exception::molt_err("error message".into());
assert_eq!(exception.code(), ResultCode::Error);
assert_eq!(exception.value(), "error message".into());
assert!(exception.is_error());
assert!(exception.error_data().is_some());
if let Some(data) = exception.error_data() {
assert_eq!(data.error_code(), "NONE".into());
assert_eq!(data.error_info(), "error message".into());
}
exception.add_error_info("from unit test");
if let Some(data) = exception.error_data() {
assert_eq!(data.error_info(), "error message\nfrom unit test".into());
}
}
#[test]
fn test_exception_molt_err2() {
let exception = Exception::molt_err2("CODE".into(), "error message".into());
assert_eq!(exception.code(), ResultCode::Error);
assert_eq!(exception.value(), "error message".into());
assert!(exception.is_error());
assert!(exception.error_data().is_some());
if let Some(data) = exception.error_data() {
assert_eq!(data.error_code(), "CODE".into());
assert_eq!(data.error_info(), "error message".into());
}
}
#[test]
fn test_exception_molt_return_err_level0() {
let exception = Exception::molt_return_err(
"error message".into(),
0,
Some("MYERR".into()),
Some("stack trace".into()),
);
assert_eq!(exception.code(), ResultCode::Error);
assert_eq!(exception.next_code(), ResultCode::Error);
assert_eq!(exception.level(), 0);
assert_eq!(exception.value(), "error message".into());
assert!(exception.is_error());
assert!(exception.error_data().is_some());
if let Some(data) = exception.error_data() {
assert_eq!(data.error_code(), "MYERR".into());
assert_eq!(data.error_info(), "stack trace".into());
}
}
#[test]
fn test_exception_molt_return_err_level2() {
let exception = Exception::molt_return_err(
"error message".into(),
2,
Some("MYERR".into()),
Some("stack trace".into()),
);
assert_eq!(exception.code(), ResultCode::Return);
assert_eq!(exception.next_code(), ResultCode::Error);
assert_eq!(exception.level(), 2);
assert_eq!(exception.value(), "error message".into());
assert!(!exception.is_error());
assert!(exception.error_data().is_some());
if let Some(data) = exception.error_data() {
assert_eq!(data.error_code(), "MYERR".into());
assert_eq!(data.error_info(), "stack trace".into());
}
}
#[test]
#[should_panic]
fn text_exception_add_error_info() {
let mut exception = Exception::molt_break();
exception.add_error_info("should panic; not an error exception");
}
#[test]
fn test_exception_molt_return() {
let exception = Exception::molt_return("result".into());
assert_eq!(exception.code(), ResultCode::Return);
assert_eq!(exception.value(), "result".into());
assert_eq!(exception.level(), 1);
assert_eq!(exception.next_code(), ResultCode::Okay);
assert!(!exception.is_error());
assert!(!exception.error_data().is_some());
}
#[test]
fn test_exception_molt_return_ext() {
let exception = Exception::molt_return_ext("result".into(), 2, ResultCode::Break);
assert_eq!(exception.code(), ResultCode::Return);
assert_eq!(exception.value(), "result".into());
assert_eq!(exception.level(), 2);
assert_eq!(exception.next_code(), ResultCode::Break);
assert!(!exception.is_error());
assert!(!exception.error_data().is_some());
}
#[test]
fn test_exception_molt_break() {
let exception = Exception::molt_break();
assert_eq!(exception.code(), ResultCode::Break);
assert_eq!(exception.value(), "".into());
assert!(!exception.is_error());
assert!(!exception.error_data().is_some());
}
#[test]
fn test_exception_molt_continue() {
let exception = Exception::molt_continue();
assert_eq!(exception.code(), ResultCode::Continue);
assert_eq!(exception.value(), "".into());
assert!(!exception.is_error());
assert!(!exception.error_data().is_some());
}
}