use super::error::{EvalResult, Flow, signal};
use super::value::*;
use crate::emacs_core::SymId;
use std::cell::RefCell;
use std::collections::HashMap;
fn is_integer(value: &Value) -> bool {
value.is_fixnum()
}
fn is_valid_ccl_program(program: &Value) -> bool {
if !program.is_vector() {
return false;
};
let program = program.as_vector_data().unwrap().clone();
if program.len() < 3 {
return false;
}
if !program.iter().all(is_integer) {
return false;
}
let buf_magnification = program[0].as_int().unwrap();
let eof_ic = program[1].as_int().unwrap();
buf_magnification >= 0 && (0..=program.len() as i64).contains(&eof_ic)
}
#[derive(Default)]
struct CclRegistry {
programs: HashMap<SymId, (i64, Value)>,
code_conversion_maps: HashMap<SymId, (i64, Value)>,
next_program_id: i64,
next_code_conversion_map_id: i64,
}
impl CclRegistry {
fn with_defaults() -> Self {
Self {
programs: HashMap::new(),
code_conversion_maps: HashMap::new(),
next_program_id: 1,
next_code_conversion_map_id: 0,
}
}
fn register_program(&mut self, name: SymId, program: Value) -> i64 {
if let Some((id, slot)) = self.programs.get_mut(&name) {
*slot = program;
return *id;
}
let id = self.next_program_id;
self.next_program_id = self.next_program_id.saturating_add(1);
self.programs.insert(name, (id, program));
id
}
fn lookup_program(&self, name: SymId) -> Option<Value> {
self.programs.get(&name).map(|(_, program)| *program)
}
fn register_code_conversion_map(&mut self, name: SymId, value: Value) -> i64 {
if let Some((id, slot)) = self.code_conversion_maps.get_mut(&name) {
*slot = value;
return *id;
}
let id = self.next_code_conversion_map_id;
self.next_code_conversion_map_id = self.next_code_conversion_map_id.saturating_add(1);
self.code_conversion_maps.insert(name, (id, value));
id
}
}
thread_local! {
static CCL_REGISTRY: RefCell<CclRegistry> = RefCell::new(CclRegistry::with_defaults());
}
fn with_ccl_registry<R>(f: impl FnOnce(&CclRegistry) -> R) -> R {
CCL_REGISTRY.with(|r| f(&r.borrow()))
}
fn with_ccl_registry_mut<R>(f: impl FnOnce(&mut CclRegistry) -> R) -> R {
CCL_REGISTRY.with(|r| f(&mut r.borrow_mut()))
}
pub(crate) fn reset_ccl_registry() {
CCL_REGISTRY.with(|r| *r.borrow_mut() = CclRegistry::with_defaults());
}
pub(crate) fn collect_ccl_gc_roots(roots: &mut Vec<Value>) {
CCL_REGISTRY.with(|r| {
let reg = r.borrow();
for (_, v) in reg.programs.values() {
roots.push(*v);
}
for (_, v) in reg.code_conversion_maps.values() {
roots.push(*v);
}
});
}
pub(crate) fn unregister_registered_ccl_program(name: SymId) {
with_ccl_registry_mut(|registry| {
let _ = registry.programs.remove(&name);
});
}
pub(crate) fn is_registered_ccl_program(name: SymId) -> bool {
with_ccl_registry(|registry| registry.programs.contains_key(&name))
}
enum CclProgramDesignatorKind {
Inline,
RegisteredSymbol,
}
fn resolve_ccl_program_designator(value: &Value) -> Option<(Value, CclProgramDesignatorKind)> {
if value.is_vector() {
return Some((*value, CclProgramDesignatorKind::Inline));
}
let name = value.as_symbol_id()?;
with_ccl_registry(|registry| {
registry
.lookup_program(name)
.map(|program| (program, CclProgramDesignatorKind::RegisteredSymbol))
})
}
fn ccl_program_code_index_message(
program: &Value,
designator_kind: CclProgramDesignatorKind,
) -> String {
let base_len = match program.kind() {
ValueKind::Veclike(VecLikeType::Vector) => program.as_vector_data().unwrap().len() as i64,
_ => 0,
};
let index = match designator_kind {
CclProgramDesignatorKind::Inline => base_len.saturating_add(1),
CclProgramDesignatorKind::RegisteredSymbol => base_len.saturating_add(2),
};
format!("Error in CCL program at {index}th code")
}
fn expect_args(name: &str, args: &[Value], n: usize) -> Result<(), Flow> {
if args.len() != n {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_min_args(name: &str, args: &[Value], min: usize) -> Result<(), Flow> {
if args.len() < min {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_max_args(name: &str, args: &[Value], max: usize) -> Result<(), Flow> {
if args.len() > max {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
pub(crate) fn builtin_ccl_program_p_impl(args: Vec<Value>) -> EvalResult {
expect_args("ccl-program-p", &args, 1)?;
let is_program = resolve_ccl_program_designator(&args[0])
.map(|(program, _)| is_valid_ccl_program(&program))
.unwrap_or(false);
Ok(Value::bool_val(is_program))
}
pub(crate) fn builtin_ccl_execute_impl(args: Vec<Value>) -> EvalResult {
expect_args("ccl-execute", &args, 2)?;
if !args[1].is_vector() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("vectorp"), args[1]],
));
}
let status_len = match args[1].kind() {
ValueKind::Veclike(VecLikeType::Vector) => args[1].as_vector_data().unwrap().len(),
_ => unreachable!("status already validated as vector"),
};
if status_len != 8 {
return Err(signal(
"error",
vec![Value::string("Length of vector REGISTERS is not 8")],
));
}
let Some((program, designator_kind)) = resolve_ccl_program_designator(&args[0]) else {
return Err(signal("error", vec![Value::string("Invalid CCL program")]));
};
if !is_valid_ccl_program(&program) {
return Err(signal("error", vec![Value::string("Invalid CCL program")]));
}
let message = ccl_program_code_index_message(&program, designator_kind);
Err(signal("error", vec![Value::string(message)]))
}
pub(crate) fn builtin_ccl_execute_on_string_impl(args: Vec<Value>) -> EvalResult {
expect_min_args("ccl-execute-on-string", &args, 3)?;
expect_max_args("ccl-execute-on-string", &args, 5)?;
if !args[1].is_vector() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("vectorp"), args[1]],
));
}
let status_len = match args[1].kind() {
ValueKind::Veclike(VecLikeType::Vector) => args[1].as_vector_data().unwrap().len(),
_ => unreachable!("status already validated as vector"),
};
if status_len != 9 {
return Err(signal(
"error",
vec![Value::string("Length of vector STATUS is not 9")],
));
}
let Some((program, designator_kind)) = resolve_ccl_program_designator(&args[0]) else {
return Err(signal("error", vec![Value::string("Invalid CCL program")]));
};
if !is_valid_ccl_program(&program) {
return Err(signal("error", vec![Value::string("Invalid CCL program")]));
}
match args[2].kind() {
ValueKind::String => {
let message = ccl_program_code_index_message(&program, designator_kind);
Err(signal("error", vec![Value::string(message)]))
}
other => {
Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[2]],
))
}
}
}
pub(crate) fn builtin_register_ccl_program_impl(args: Vec<Value>) -> EvalResult {
expect_args("register-ccl-program", &args, 2)?;
if !args[0].is_symbol() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), args[0]],
));
}
let program = if args[1].is_nil() {
Value::vector(vec![Value::fixnum(0), Value::fixnum(0), Value::fixnum(0)])
} else {
if !args[1].is_vector() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("vectorp"), args[1]],
));
}
args[1]
};
if !is_valid_ccl_program(&program) {
return Err(signal("error", vec![Value::string("Error in CCL program")]));
}
let name = args[0]
.as_symbol_id()
.expect("symbol already validated by is_symbol");
let program_id = with_ccl_registry_mut(|registry| registry.register_program(name, program));
Ok(Value::fixnum(program_id))
}
pub(crate) fn builtin_register_code_conversion_map_impl(args: Vec<Value>) -> EvalResult {
expect_args("register-code-conversion-map", &args, 2)?;
if !args[0].is_symbol() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), args[0]],
));
}
if !args[1].is_vector() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("vectorp"), args[1]],
));
}
let name = args[0]
.as_symbol_id()
.expect("symbol already validated by is_symbol");
let map_id =
with_ccl_registry_mut(|registry| registry.register_code_conversion_map(name, args[1]));
Ok(Value::fixnum(map_id))
}
#[cfg(test)]
#[path = "ccl_test.rs"]
mod tests;