use super::error::{EvalResult, Flow, signal};
use super::intern::resolve_sym;
use super::value::*;
use std::cell::RefCell;
use std::collections::HashMap;
thread_local! {
static STANDARD_CASE_TABLE_OBJECT: RefCell<Option<Value>> = const { RefCell::new(None) };
}
pub fn reset_casetab_thread_locals() {
STANDARD_CASE_TABLE_OBJECT.with(|slot| *slot.borrow_mut() = None);
}
pub fn collect_casetab_gc_roots(roots: &mut Vec<Value>) {
STANDARD_CASE_TABLE_OBJECT.with(|slot| {
if let Some(v) = *slot.borrow() {
roots.push(v);
}
});
}
#[derive(Clone, Debug)]
pub struct CaseTable {
pub upcase: HashMap<char, char>,
pub downcase: HashMap<char, char>,
pub canonicalize: HashMap<char, char>,
pub equivalences: HashMap<char, char>,
}
impl CaseTable {
pub fn empty() -> Self {
Self {
upcase: HashMap::new(),
downcase: HashMap::new(),
canonicalize: HashMap::new(),
equivalences: HashMap::new(),
}
}
pub fn standard_ascii() -> Self {
let mut upcase = HashMap::new();
let mut downcase = HashMap::new();
let mut canonicalize = HashMap::new();
let mut equivalences = HashMap::new();
for lower in b'a'..=b'z' {
let upper = lower - b'a' + b'A';
let lc = lower as char;
let uc = upper as char;
upcase.insert(lc, uc);
downcase.insert(uc, lc);
canonicalize.insert(uc, lc);
canonicalize.insert(lc, lc);
equivalences.insert(uc, lc);
equivalences.insert(lc, uc);
}
Self {
upcase,
downcase,
canonicalize,
equivalences,
}
}
}
#[derive(Clone, Debug)]
pub struct CaseTableManager {
standard: CaseTable,
current: CaseTable,
}
impl CaseTableManager {
pub fn new() -> Self {
let table = CaseTable::standard_ascii();
Self {
standard: table.clone(),
current: table,
}
}
pub fn upcase_char(&self, c: char) -> char {
*self.current.upcase.get(&c).unwrap_or(&c)
}
pub fn downcase_char(&self, c: char) -> char {
*self.current.downcase.get(&c).unwrap_or(&c)
}
pub fn upcase_string(&self, s: &str) -> String {
s.chars().map(|c| self.upcase_char(c)).collect()
}
pub fn downcase_string(&self, s: &str) -> String {
s.chars().map(|c| self.downcase_char(c)).collect()
}
pub fn standard_table(&self) -> &CaseTable {
&self.standard
}
pub fn current_table(&self) -> &CaseTable {
&self.current
}
pub fn set_current(&mut self, table: CaseTable) {
self.current = table;
}
pub fn set_standard(&mut self, table: CaseTable) {
self.standard = table;
}
}
impl Default for CaseTableManager {
fn default() -> Self {
Self::new()
}
}
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 wrong_type(pred: &str, got: &Value) -> Flow {
signal("wrong-type-argument", vec![Value::symbol(pred), *got])
}
fn expect_char(value: &Value) -> Result<char, Flow> {
match value.kind() {
ValueKind::Fixnum(c) => Ok(char::from_u32(c as u32).unwrap_or('\0')),
other => Err(wrong_type("characterp", value)),
}
}
pub(crate) fn builtin_case_table_p(args: Vec<Value>) -> EvalResult {
expect_args("case-table-p", &args, 1)?;
Ok(Value::bool_val(is_case_table(&args[0])))
}
#[cfg(test)]
pub(crate) fn builtin_downcase_char(args: Vec<Value>) -> EvalResult {
expect_args("downcase", &args, 1)?;
let c = expect_char(&args[0])?;
let manager = CaseTableManager::new();
let result = manager.downcase_char(c);
Ok(Value::fixnum(result as i64))
}
const CT_CHAR_TABLE_TAG: &str = "--char-table--";
const CT_SUBTYPE: usize = 3;
const CT_EXTRA_COUNT: usize = 4;
const CT_EXTRA_START: usize = 5;
const CURRENT_CASE_TABLE_PROPERTY: &str = "case-table";
const STANDARD_CASE_TABLE_SYMBOL: &str = "neovm--standard-case-table-object";
fn build_char_table(
subtype: &str,
extra_slots: &[Value],
default: Value,
data_pairs: &[(i64, Value)],
) -> Value {
let extra_count = extra_slots.len();
let mut vec = Vec::with_capacity(CT_EXTRA_START + extra_count + data_pairs.len() * 2);
vec.push(Value::symbol(CT_CHAR_TABLE_TAG)); vec.push(default); vec.push(Value::NIL); vec.push(Value::symbol(subtype)); vec.push(Value::fixnum(extra_count as i64)); for slot in extra_slots {
vec.push(*slot);
}
for &(ch, val) in data_pairs {
vec.push(Value::fixnum(ch));
vec.push(val);
}
Value::vector(vec)
}
fn make_standard_case_table_value() -> Value {
let mut downcase_pairs = Vec::with_capacity(128);
let mut upcase_pairs = Vec::with_capacity(128);
let mut canon_pairs = Vec::with_capacity(128);
let mut eqv_pairs = Vec::with_capacity(128);
for i in 0i64..128 {
let down = if (b'A' as i64..=b'Z' as i64).contains(&i) {
i + (b'a' as i64 - b'A' as i64)
} else {
i
};
downcase_pairs.push((i, Value::fixnum(down)));
let up = if (b'a' as i64..=b'z' as i64).contains(&i) {
i + (b'A' as i64 - b'a' as i64)
} else {
i
};
upcase_pairs.push((i, Value::fixnum(up)));
canon_pairs.push((i, Value::fixnum(down)));
let eqv = if (b'A' as i64..=b'Z' as i64).contains(&i) {
i + (b'a' as i64 - b'A' as i64)
} else if (b'a' as i64..=b'z' as i64).contains(&i) {
i + (b'A' as i64 - b'a' as i64)
} else {
i
};
eqv_pairs.push((i, Value::fixnum(eqv)));
}
let upcase_ct = build_char_table("case-table", &[], Value::NIL, &upcase_pairs);
let canon_ct = build_char_table("case-table", &[], Value::NIL, &canon_pairs);
let eqv_ct = build_char_table("case-table", &[], Value::NIL, &eqv_pairs);
build_char_table(
"case-table",
&[upcase_ct, canon_ct, eqv_ct],
Value::NIL,
&downcase_pairs,
)
}
fn make_case_table_value() -> Value {
build_char_table(
"case-table",
&[Value::NIL, Value::NIL, Value::NIL],
Value::NIL,
&[],
)
}
fn ensure_standard_case_table_object() -> EvalResult {
STANDARD_CASE_TABLE_OBJECT.with(|slot| {
if let Some(value) = slot.borrow().as_ref() {
return Ok(*value);
}
let table = make_standard_case_table_value();
*slot.borrow_mut() = Some(table);
Ok(table)
})
}
pub(crate) fn builtin_current_case_table(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("current-case-table", &args, 0)?;
current_case_table_for_buffer_in_state(&mut ctx.obarray, &mut ctx.buffers)
}
pub(crate) fn builtin_standard_case_table(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("standard-case-table", &args, 0)?;
ensure_standard_case_table_object_in_state(&mut ctx.obarray)
}
pub(crate) fn builtin_set_case_table(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("set-case-table", &args, 1)?;
if !is_case_table(&args[0]) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("case-table-p"), args[0]],
));
}
let table = args[0];
let _ = ensure_standard_case_table_object_in_state(&mut ctx.obarray)?;
set_current_case_table_for_buffer_in_state(&mut ctx.buffers, table)?;
Ok(table)
}
pub(crate) fn builtin_set_standard_case_table(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("set-standard-case-table", &args, 1)?;
if !is_case_table(&args[0]) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("case-table-p"), args[0]],
));
}
STANDARD_CASE_TABLE_OBJECT.with(|slot| {
*slot.borrow_mut() = Some(args[0]);
});
let table = args[0];
ctx.obarray
.set_symbol_value(STANDARD_CASE_TABLE_SYMBOL, table);
Ok(table)
}
fn ensure_standard_case_table_object_in_state(obarray: &mut super::symbol::Obarray) -> EvalResult {
if let Some(value) = obarray.symbol_value(STANDARD_CASE_TABLE_SYMBOL).cloned() {
if is_case_table(&value) {
return Ok(value);
}
}
let table = make_standard_case_table_value();
obarray.set_symbol_value(STANDARD_CASE_TABLE_SYMBOL, table);
Ok(table)
}
fn current_case_table_for_buffer_in_state(
obarray: &mut super::symbol::Obarray,
buffers: &mut crate::buffer::BufferManager,
) -> Result<Value, Flow> {
let fallback = ensure_standard_case_table_object_in_state(obarray)?;
let current_id = buffers
.current_buffer_id()
.ok_or_else(|| signal("error", vec![Value::string("No current buffer")]))?;
let buf = buffers
.get(current_id)
.ok_or_else(|| signal("error", vec![Value::string("No current buffer")]))?;
if let Some(RuntimeBindingValue::Bound(value)) =
buf.get_buffer_local_binding(CURRENT_CASE_TABLE_PROPERTY)
{
if is_case_table(&value) {
return Ok(value);
}
}
let _ = buffers.set_buffer_local_property(current_id, CURRENT_CASE_TABLE_PROPERTY, fallback);
Ok(fallback)
}
pub(crate) fn sync_current_buffer_case_table_state(
ctx: &mut crate::emacs_core::eval::Context,
) -> Result<(), Flow> {
let _ = current_case_table_for_buffer_in_state(&mut ctx.obarray, &mut ctx.buffers)?;
Ok(())
}
fn set_current_case_table_for_buffer_in_state(
buffers: &mut crate::buffer::BufferManager,
table: Value,
) -> Result<(), Flow> {
let current_id = buffers
.current_buffer_id()
.ok_or_else(|| signal("error", vec![Value::string("No current buffer")]))?;
let _ = buffers.set_buffer_local_property(current_id, CURRENT_CASE_TABLE_PROPERTY, table);
Ok(())
}
pub fn is_case_table(v: &Value) -> bool {
use super::chartable::is_char_table;
use crate::emacs_core::value::ValueKind;
if !is_char_table(v) {
return false;
}
if v.is_vector() {
let vec = v.as_vector_data().unwrap().clone();
vec.len() > CT_SUBTYPE && vec[CT_SUBTYPE].is_symbol_named("case-table")
} else {
false
}
}
#[cfg(test)]
#[path = "casetab_test.rs"]
mod tests;