use super::error::{
EvalResult, Flow, signal, signal_suppressed, signal_with_data, signal_with_data_suppressed,
};
use super::expr::Expr;
use super::intern::resolve_sym;
use super::symbol::Obarray;
use super::value::*;
use crate::emacs_core::value::ValueKind;
fn put_error_properties(obarray: &mut Obarray, name: &str, message: &str, conditions: Vec<&str>) {
let cond_list = Value::list(conditions.iter().map(|s| Value::symbol(*s)).collect());
obarray.put_property(name, "error-conditions", cond_list);
obarray.put_property(name, "error-message", Value::string(message));
}
fn build_conditions_from_obarray(obarray: &Obarray, name: &str, parents: &[&str]) -> Vec<String> {
let mut conditions = vec![name.to_string()];
for &parent in parents {
if let Some(parent_conds) = obarray.get_property(parent, "error-conditions") {
for sym in iter_symbol_list(parent_conds) {
if !conditions.contains(&sym) {
conditions.push(sym);
}
}
} else {
if !conditions.contains(&parent.to_string()) {
conditions.push(parent.to_string());
}
}
}
conditions
}
fn iter_symbol_list(value: &Value) -> Vec<String> {
let mut result = Vec::new();
if let Some(items) = list_to_vec(value) {
for item in items {
if let Some(name) = item.as_symbol_name() {
result.push(name.to_string());
}
}
}
result
}
pub fn signal_matches_hierarchical(
obarray: &Obarray,
signal_sym: &str,
condition_sym: &str,
) -> bool {
if condition_sym == "t" {
return true;
}
if signal_sym == condition_sym {
return true;
}
if let Some(conds) = obarray.get_property(signal_sym, "error-conditions") {
for sym_name in iter_symbol_list(conds) {
if sym_name == condition_sym {
return true;
}
}
}
false
}
pub fn signal_matches_condition_pattern(
obarray: &Obarray,
signal_sym: &str,
pattern: &Expr,
) -> bool {
match pattern {
Expr::Symbol(id) => signal_matches_hierarchical(obarray, signal_sym, resolve_sym(*id)),
Expr::List(items) => items
.iter()
.any(|item| signal_matches_condition_pattern(obarray, signal_sym, item)),
_ => false,
}
}
pub fn signal_matches_condition_value(
obarray: &Obarray,
signal_sym: &str,
pattern: &Value,
) -> bool {
match pattern.kind() {
ValueKind::Symbol(id) => signal_matches_hierarchical(obarray, signal_sym, resolve_sym(id)),
ValueKind::T => true,
ValueKind::Nil => false,
ValueKind::Cons => list_to_vec(pattern).is_some_and(|items| {
items
.iter()
.any(|item| signal_matches_condition_value(obarray, signal_sym, item))
}),
_ => false,
}
}
pub fn init_standard_errors(obarray: &mut Obarray) {
put_error_properties(obarray, "error", "error", vec!["error"]);
register_simple(obarray, "quit", "Quit", &["error"]);
register_simple(obarray, "user-error", "User error", &["error"]);
register_simple(
obarray,
"args-out-of-range",
"Args out of range",
&["error"],
);
register_simple(
obarray,
"beginning-of-buffer",
"Beginning of buffer",
&["error"],
);
register_simple(obarray, "end-of-buffer", "End of buffer", &["error"]);
register_simple(
obarray,
"end-of-file",
"End of file during parsing",
&["error"],
);
register_simple(
obarray,
"buffer-read-only",
"Buffer is read-only",
&["error"],
);
register_simple(
obarray,
"coding-system-error",
"Invalid coding system",
&["error"],
);
register_simple(
obarray,
"cyclic-function-indirection",
"Symbol's chain of function indirections contains a loop",
&["error"],
);
register_simple(
obarray,
"cyclic-variable-indirection",
"Symbol's chain of variable indirections contains a loop",
&["error"],
);
register_simple(obarray, "invalid-function", "Invalid function", &["error"]);
register_simple(
obarray,
"invalid-read-syntax",
"Invalid read syntax",
&["error"],
);
register_simple(obarray, "invalid-regexp", "Invalid regexp", &["error"]);
register_simple(
obarray,
"wrong-length-argument",
"Wrong length argument",
&["error"],
);
register_simple(
obarray,
"mark-inactive",
"The mark is not active now",
&["error"],
);
register_simple(obarray, "no-catch", "No catch for tag", &["error"]);
register_simple(obarray, "scan-error", "Scan error", &["error"]);
register_simple(obarray, "search-failed", "Search failed", &["error"]);
register_simple(
obarray,
"setting-constant",
"Attempt to set a constant symbol",
&["error"],
);
register_simple(obarray, "text-read-only", "Text is read-only", &["error"]);
register_simple(
obarray,
"void-function",
"Symbol\u{2019}s function definition is void",
&["error"],
);
register_simple(
obarray,
"void-variable",
"Symbol\u{2019}s value as variable is void",
&["error"],
);
register_simple(
obarray,
"wrong-number-of-arguments",
"Wrong number of arguments",
&["error"],
);
register_simple(
obarray,
"wrong-type-argument",
"Wrong type argument",
&["error"],
);
register_simple(
obarray,
"cl-assertion-failed",
"Assertion failed",
&["error"],
);
register_simple(obarray, "type-mismatch", "Type mismatch", &["error"]);
register_simple(
obarray,
"permission-denied",
"Permission denied",
&["error"],
);
register_simple(
obarray,
"recursion-error",
"Excessive recursive calling error",
&["error"],
);
register_simple(obarray, "arith-error", "Arithmetic error", &["error"]);
register_simple(
obarray,
"overflow-error",
"Arithmetic overflow error",
&["arith-error"],
);
register_simple(
obarray,
"range-error",
"Arithmetic range error",
&["arith-error"],
);
register_simple(
obarray,
"domain-error",
"Arithmetic domain error",
&["arith-error"],
);
register_simple(
obarray,
"underflow-error",
"Arithmetic underflow error",
&["arith-error"],
);
register_simple(obarray, "file-error", "File error", &["error"]);
register_simple(
obarray,
"file-already-exists",
"File already exists",
&["file-error"],
);
register_simple(
obarray,
"file-date-error",
"Cannot set file date",
&["file-error"],
);
register_simple(obarray, "file-locked", "File is locked", &["file-error"]);
register_simple(obarray, "file-missing", "File is missing", &["file-error"]);
register_simple(
obarray,
"file-notify-error",
"File notification error",
&["file-error"],
);
register_simple(obarray, "dbus-error", "D-Bus error", &["error"]);
register_simple(obarray, "json-error", "JSON error", &["error"]);
register_simple(
obarray,
"json-parse-error",
"JSON parse error",
&["json-error"],
);
register_simple(
obarray,
"json-serialize-error",
"JSON serialize error",
&["json-error"],
);
register_simple(
obarray,
"remote-file-error",
"Remote file error",
&["file-error"],
);
register_simple(
obarray,
"excessive-lisp-nesting",
"Lisp nesting exceeds `max-lisp-eval-depth'",
&["recursion-error"],
);
}
fn register_simple(obarray: &mut Obarray, name: &str, message: &str, parents: &[&str]) {
let conditions = build_conditions_from_obarray(obarray, name, parents);
let cond_refs: Vec<&str> = conditions.iter().map(|s| s.as_str()).collect();
put_error_properties(obarray, name, message, cond_refs);
}
fn extract_parent_symbols(value: &Value) -> Result<Vec<String>, Flow> {
match value.kind() {
ValueKind::Symbol(id) => Ok(vec![resolve_sym(id).to_owned()]),
ValueKind::Nil => Ok(vec!["error".to_string()]),
ValueKind::T => Ok(vec!["t".to_string()]),
ValueKind::Cons => {
let items = list_to_vec(value).ok_or_else(|| {
signal("wrong-type-argument", vec![Value::symbol("listp"), *value])
})?;
let mut parents = Vec::with_capacity(items.len());
for item in &items {
match item.as_symbol_name() {
Some(name) => parents.push(name.to_string()),
None => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), *item],
));
}
}
}
if parents.is_empty() {
Ok(vec!["error".to_string()])
} else {
Ok(parents)
}
}
_ => Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), *value],
)),
}
}
fn build_signal_flow(symbol_name: &str, data: Value) -> Flow {
match data.kind() {
ValueKind::Nil => signal(symbol_name, vec![]),
ValueKind::Cons => match list_to_vec(&data) {
Some(data) => signal(symbol_name, data),
None => signal_with_data(symbol_name, data),
},
_ => signal_with_data(symbol_name, data),
}
}
fn build_signal_flow_suppressed(symbol_name: &str, data: Value) -> Flow {
match data.kind() {
ValueKind::Nil => signal_suppressed(symbol_name, vec![]),
ValueKind::Cons => match list_to_vec(&data) {
Some(data) => signal_suppressed(symbol_name, data),
None => signal_with_data_suppressed(symbol_name, data),
},
_ => signal_with_data_suppressed(symbol_name, data),
}
}
fn build_peculiar_signal_flow(eval: &super::eval::Context, error_object: Value) -> Flow {
if !error_object.is_cons() {
unreachable!("peculiar signal error object must be a cons");
};
let pair_car = error_object.cons_car();
let pair_cdr = error_object.cons_cdr();
let error_symbol = pair_car;
let data = pair_cdr;
let Some(sym_name) = error_symbol.as_symbol_name() else {
return signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), error_symbol],
);
};
if sym_name != "error"
&& sym_name != "quit"
&& eval
.obarray
.get_property(sym_name, "error-conditions")
.is_none()
{
return signal_suppressed("error", vec![Value::string("Invalid error symbol")]);
}
build_signal_flow_suppressed(sym_name, data)
}
pub(crate) fn builtin_signal(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
if args.len() != 2 {
return Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol("signal"), Value::fixnum(args.len() as i64)],
));
}
if args[0].is_nil() {
let flow = if args[1].is_cons() {
build_peculiar_signal_flow(eval, args[1])
} else {
build_signal_flow("error", args[1])
};
return Err(flow);
}
let sym_name = match args[0].as_symbol_name() {
Some(name) => name.to_string(),
None => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), args[0]],
));
}
};
let flow = build_signal_flow(&sym_name, args[1]);
Err(flow)
}
pub(crate) fn builtin_error_message_string(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
if args.len() != 1 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("error-message-string"),
Value::fixnum(args.len() as i64),
],
));
}
let error_data = &args[0];
let (sym_name, data) = match error_data.kind() {
ValueKind::Cons => {
let pair_car = error_data.cons_car();
let pair_cdr = error_data.cons_cdr();
let sym = match pair_car.as_symbol_name() {
Some(name) => name.to_string(),
None => return Ok(Value::string("peculiar error")),
};
let rest = match pair_cdr.kind() {
ValueKind::Nil => vec![],
ValueKind::Cons => list_to_vec(&pair_cdr).unwrap_or_else(|| vec![pair_cdr]),
_ => vec![pair_cdr],
};
(sym, rest)
}
ValueKind::Nil => return Ok(Value::string("peculiar error")),
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("listp"), *error_data],
));
}
};
let base_message = eval
.obarray
.get_property(&sym_name, "error-message")
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| sym_name.clone());
let is_known_error = signal_matches_hierarchical(&eval.obarray, &sym_name, "error");
if !is_known_error {
if data.is_empty() {
return Ok(Value::string("peculiar error"));
}
let data_strs: Vec<String> = data
.iter()
.map(|v| format_error_arg(eval, v, true))
.collect();
return Ok(Value::string(format!(
"peculiar error: {}",
data_strs.join(", ")
)));
}
if data.is_empty() {
if sym_name == "error" {
return Ok(Value::string("peculiar error"));
}
if sym_name == "user-error" {
return Ok(Value::string(""));
}
return Ok(Value::string(base_message));
}
if sym_name == "user-error" {
if let Some(first_str) = data.first().and_then(|v| v.as_str().map(|s| s.to_string())) {
let rest = &data[1..];
if rest.is_empty() {
return Ok(Value::string(first_str));
}
let rest_strs: Vec<String> = rest
.iter()
.map(|v| format_error_arg(eval, v, false))
.collect();
return Ok(Value::string(format!(
"{first_str}, {}",
rest_strs.join(", ")
)));
}
let data_strs: Vec<String> = data
.iter()
.map(|v| format_error_arg(eval, v, false))
.collect();
return Ok(Value::string(data_strs.join(", ")));
}
let is_file_error_family = signal_matches_hierarchical(&eval.obarray, &sym_name, "file-error");
let is_file_locked = sym_name == "file-locked";
if is_file_locked {
let data_strs: Vec<String> = data
.iter()
.map(|v| format_error_arg(eval, v, true))
.collect();
return Ok(Value::string(format!(
"peculiar error: {}",
data_strs.join(", ")
)));
}
if sym_name == "error" || is_file_error_family {
if let Some(first_str) = data.first().and_then(|v| v.as_str().map(|s| s.to_string())) {
let rest = &data[1..];
if rest.is_empty() {
return Ok(Value::string(first_str));
}
let quote_strings = sym_name == "error";
let rest_strs: Vec<String> = rest
.iter()
.map(|v| format_error_arg(eval, v, quote_strings))
.collect();
return Ok(Value::string(format!(
"{first_str}: {}",
rest_strs.join(", ")
)));
}
if data.len() > 1 {
let detail: Vec<String> = data[1..]
.iter()
.map(|v| format_error_arg(eval, v, true))
.collect();
return Ok(Value::string(format!(
"peculiar error: {}",
detail.join(", ")
)));
}
return Ok(Value::string("peculiar error"));
}
let quote_strings = sym_name != "end-of-file";
let data_strs: Vec<String> = data
.iter()
.map(|v| format_error_arg(eval, v, quote_strings))
.collect();
Ok(Value::string(format!(
"{}: {}",
base_message,
data_strs.join(", ")
)))
}
fn format_error_arg(eval: &super::eval::Context, value: &Value, quote_strings: bool) -> String {
if !quote_strings {
if let Some(s) = value.as_str() {
return s.to_string();
}
}
super::error::print_value_with_eval(eval, value)
}
pub struct ErrorRegistry {
parents: std::collections::HashMap<String, Vec<String>>,
}
impl ErrorRegistry {
pub fn new() -> Self {
let mut reg = Self {
parents: std::collections::HashMap::new(),
};
reg.init_standard_hierarchy();
reg
}
pub fn define_error(&mut self, name: &str, _message: &str, parents: &[&str]) {
let parent_list = if parents.is_empty() {
vec!["error".to_string()]
} else {
parents.iter().map(|s| s.to_string()).collect()
};
self.parents.insert(name.to_string(), parent_list);
}
pub fn signal_matches_condition(&self, signal_sym: &str, condition: &str) -> bool {
if condition == "t" {
return true;
}
if signal_sym == condition {
return true;
}
let mut visited = std::collections::HashSet::new();
let mut stack = vec![signal_sym.to_string()];
while let Some(current) = stack.pop() {
if !visited.insert(current.clone()) {
continue;
}
if let Some(parents) = self.parents.get(¤t) {
for parent in parents {
if parent == condition {
return true;
}
stack.push(parent.clone());
}
}
}
false
}
pub fn conditions_for(&self, signal_sym: &str) -> Vec<String> {
let mut result = vec![signal_sym.to_string()];
let mut visited = std::collections::HashSet::new();
visited.insert(signal_sym.to_string());
let mut stack = vec![signal_sym.to_string()];
while let Some(current) = stack.pop() {
if let Some(parents) = self.parents.get(¤t) {
for parent in parents {
if visited.insert(parent.clone()) {
result.push(parent.clone());
stack.push(parent.clone());
}
}
}
}
result
}
fn init_standard_hierarchy(&mut self) {
self.parents.insert("error".to_string(), vec![]);
let simple_children_of_error = [
"quit",
"user-error",
"args-out-of-range",
"beginning-of-buffer",
"end-of-buffer",
"buffer-read-only",
"coding-system-error",
"invalid-function",
"invalid-read-syntax",
"invalid-regexp",
"mark-inactive",
"no-catch",
"scan-error",
"search-failed",
"setting-constant",
"text-read-only",
"void-function",
"void-variable",
"wrong-number-of-arguments",
"wrong-type-argument",
"cl-assertion-failed",
"permission-denied",
"recursion-error",
];
for name in &simple_children_of_error {
self.parents
.insert(name.to_string(), vec!["error".to_string()]);
}
self.parents
.insert("arith-error".to_string(), vec!["error".to_string()]);
for name in &[
"overflow-error",
"range-error",
"domain-error",
"underflow-error",
] {
self.parents
.insert(name.to_string(), vec!["arith-error".to_string()]);
}
self.parents
.insert("file-error".to_string(), vec!["error".to_string()]);
for name in &[
"file-already-exists",
"file-date-error",
"file-locked",
"file-missing",
"file-notify-error",
] {
self.parents
.insert(name.to_string(), vec!["file-error".to_string()]);
}
self.parents
.insert("dbus-error".to_string(), vec!["error".to_string()]);
self.parents
.insert("json-error".to_string(), vec!["error".to_string()]);
for name in &["json-parse-error", "json-serialize-error"] {
self.parents
.insert(name.to_string(), vec!["json-error".to_string()]);
}
self.parents.insert(
"remote-file-error".to_string(),
vec!["file-error".to_string()],
);
}
}
impl Default for ErrorRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[path = "errors_test.rs"]
mod tests;