pub mod value;
pub mod eval;
pub use value::{Value, Env};
pub use eval::{Evaluator, EvalError};
use synoema_diagnostic::{Diagnostic, codes};
use synoema_types::{TypeError, TypeErrorKind};
fn parse_err(e: impl std::fmt::Display) -> Diagnostic {
Diagnostic::error(codes::PARSE_UNEXPECTED_TOKEN, format!("{}", e))
}
fn type_err(e: TypeError) -> Diagnostic {
let code = match &e.kind {
TypeErrorKind::Mismatch { .. } => codes::TYPE_MISMATCH,
TypeErrorKind::InfiniteType { .. } => codes::TYPE_INFINITE,
TypeErrorKind::Unbound { .. } => codes::TYPE_UNBOUND_VAR,
TypeErrorKind::UnboundType { .. } => codes::TYPE_UNBOUND_TYPE,
TypeErrorKind::ArityMismatch { .. } => codes::TYPE_ARITY,
TypeErrorKind::PatternMismatch { .. } => codes::TYPE_PATTERN,
TypeErrorKind::Other(_) => codes::TYPE_OTHER,
};
let mut diag = Diagnostic::error(code, format!("{}", e));
if let TypeErrorKind::Mismatch { expected, found } = &e.kind {
diag = diag
.with_note(format!("expected: {}", expected))
.with_note(format!("found: {}", found));
}
diag.maybe_span(e.span)
}
fn eval_err(e: EvalError) -> Diagnostic {
let msg = &e.message;
let code = if msg.contains("Undefined") || msg.contains("undefined") {
codes::EVAL_UNDEFINED
} else if msg.contains("No matching pattern") || msg.contains("no matching") {
codes::EVAL_NO_MATCH
} else if msg.contains("Division by zero") || msg.contains("division by zero") {
codes::EVAL_DIV_ZERO
} else if msg.contains("readline") || msg.contains("IO") {
codes::EVAL_IO
} else {
codes::EVAL_TYPE
};
Diagnostic::error(code, e.message)
}
pub fn run(source: &str) -> Result<Env, Diagnostic> {
let program = synoema_parser::parse(source)
.map_err(|e| parse_err(&e))?;
let program = synoema_types::resolve_modules(program);
let _types = synoema_types::typecheck(source)
.map_err(type_err)?;
let mut evaluator = Evaluator::new();
evaluator.eval_program(&program)
.map_err(eval_err)
}
pub fn eval_main(source: &str) -> Result<(Value, Vec<String>), Diagnostic> {
let source = source.to_string();
std::thread::Builder::new()
.stack_size(64 * 1024 * 1024) .spawn(move || eval_main_inner(&source))
.expect("Failed to spawn eval thread")
.join()
.expect("Eval thread panicked")
}
fn eval_main_inner(source: &str) -> Result<(Value, Vec<String>), Diagnostic> {
let program = synoema_parser::parse(source)
.map_err(|e| parse_err(&e))?;
let program = synoema_types::resolve_modules(program);
let _types = synoema_types::typecheck(source)
.map_err(type_err)?;
let mut evaluator = Evaluator::new();
let env = evaluator.eval_program(&program)
.map_err(eval_err)?;
let main_name = program.decls.iter().rev()
.find_map(|d| match d {
synoema_parser::Decl::Func { name, .. } => Some(name.clone()),
_ => None,
})
.ok_or_else(|| Diagnostic::error(codes::EVAL_UNDEFINED, "No function defined"))?;
let val = env.lookup(&main_name)
.cloned()
.ok_or_else(|| Diagnostic::error(codes::EVAL_UNDEFINED, format!("Function '{}' not found", main_name)))?;
let result = match &val {
Value::Func { equations, .. } if equations.iter().all(|eq| eq.pats.is_empty()) => {
let eq = &equations[0];
evaluator.eval(&env, &eq.body)
.map_err(eval_err)?
}
other => other.clone(),
};
Ok((result, evaluator.output))
}
pub fn eval_expr(source: &str) -> Result<Value, Diagnostic> {
let wrapped = format!("__expr = {}", source);
let program = synoema_parser::parse(&wrapped)
.map_err(|e| parse_err(&e))?;
let mut evaluator = Evaluator::new();
let env = evaluator.eval_program(&program)
.map_err(eval_err)?;
match env.lookup("__expr") {
Some(Value::Func { equations, .. }) if !equations.is_empty() => {
let eq = &equations[0];
if eq.pats.is_empty() {
evaluator.eval(&env, &eq.body)
.map_err(eval_err)
} else {
Ok(env.lookup("__expr").unwrap().clone())
}
}
Some(v) => Ok(v.clone()),
None => Err(Diagnostic::error(codes::EVAL_UNDEFINED, "Expression not found")),
}
}
#[cfg(test)]
mod tests;