use super::error::{EvalResult, Flow, signal};
use super::eval::{Context, push_scratch_gc_root, restore_scratch_gc_roots, save_scratch_gc_roots};
use super::intern::resolve_sym;
use super::value::*;
use std::collections::{BTreeMap, BTreeSet};
const CHAR_TABLE_TAG: &str = "--char-table--";
const SUB_CHAR_TABLE_TAG: &str = "--sub-char-table--";
const BOOL_VECTOR_TAG: &str = "--bool-vector--";
const CT_DEFAULT: usize = 1; const CT_PARENT: usize = 2; const CT_SUBTYPE: usize = 3; const CT_EXTRA_COUNT: usize = 4; const CT_EXTRA_START: usize = 5; const CT_LOGICAL_LENGTH: i64 = 0x3F_FFFF;
const MAX_CHAR: i64 = 0x3F_FFFF;
const GNU_CHAR_TABLE_STANDARD_SLOTS: usize = 4 + GNU_CHAR_TABLE_CONTENT_BLOCKS_USIZE;
const GNU_CHAR_TABLE_CONTENT_BLOCKS_USIZE: usize = 64;
const GNU_CHAR_TABLE_CONTENT_START: usize = 4;
const GNU_CHAR_TABLE_ASCII_SLOT: usize = 3;
const GNU_CHARTAB_SIZE: [usize; 4] = [64, 16, 32, 128];
const GNU_CHARTAB_CHARS: [i64; 4] = [65_536, 4_096, 128, 1];
const BV_SIZE: usize = 1;
pub fn is_char_table(v: &Value) -> bool {
if v.is_vector() {
let vec = v.as_vector_data().unwrap();
vec.len() >= CT_EXTRA_START
&& vec[0]
.as_symbol_id()
.map_or(false, |id| resolve_sym(id) == CHAR_TABLE_TAG)
} else {
false
}
}
pub fn is_bool_vector(v: &Value) -> bool {
if v.is_vector() {
let vec = v.as_vector_data().unwrap();
vec.len() >= 2
&& vec[0]
.as_symbol_id()
.map_or(false, |id| resolve_sym(id) == BOOL_VECTOR_TAG)
} else {
false
}
}
pub(crate) fn bool_vector_length(v: &Value) -> Option<i64> {
if !v.is_vector() {
return None;
};
let vec = v.as_vector_data().unwrap();
if vec.len() < 2
|| !vec[0]
.as_symbol_id()
.map_or(false, |id| resolve_sym(id) == BOOL_VECTOR_TAG)
{
return None;
}
Some(match vec[BV_SIZE].kind() {
ValueKind::Fixnum(n) => n,
_ => 0,
})
}
pub(crate) fn char_table_length(v: &Value) -> Option<i64> {
if !v.is_vector() {
return None;
};
let vec = v.as_vector_data().unwrap();
if vec.len() >= CT_EXTRA_START
&& vec[0]
.as_symbol_id()
.map_or(false, |id| resolve_sym(id) == CHAR_TABLE_TAG)
{
Some(CT_LOGICAL_LENGTH)
} else {
None
}
}
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(())
}
}
fn wrong_type(pred: &str, got: &Value) -> Flow {
signal("wrong-type-argument", vec![Value::symbol(pred), *got])
}
fn expect_int(value: &Value) -> Result<i64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n),
_other => Err(wrong_type("integerp", value)),
}
}
fn expect_wholenump(value: &Value) -> Result<i64, Flow> {
let n = match value.kind() {
ValueKind::Fixnum(n) => n,
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("wholenump"), *value],
));
}
};
if n < 0 {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("wholenump"), *value],
));
}
Ok(n)
}
fn ct_data_start(vec: &[Value]) -> usize {
let extra_count = match vec[CT_EXTRA_COUNT].kind() {
ValueKind::Fixnum(n) => n as usize,
_ => 0,
};
CT_EXTRA_START + extra_count
}
fn is_sub_char_table_literal(v: &Value) -> bool {
if !v.is_vector() {
return false;
}
let vec = v.as_vector_data().unwrap();
vec.len() >= 3
&& vec[0]
.as_symbol_id()
.is_some_and(|id| resolve_sym(id) == SUB_CHAR_TABLE_TAG)
}
fn sub_char_table_depth_min_contents(v: &Value) -> Option<(usize, i64, Vec<Value>)> {
if !is_sub_char_table_literal(v) {
return None;
}
let vec = v.as_vector_data().unwrap();
let depth = vec.get(1)?.as_fixnum()?;
let min_char = vec.get(2)?.as_fixnum()?;
if !(1..=3).contains(&depth) || !(0..=MAX_CHAR).contains(&min_char) {
return None;
}
Some((depth as usize, min_char, vec[3..].to_vec()))
}
pub(crate) fn make_sub_char_table_from_external_slots(items: &[Value]) -> Result<Value, String> {
if items.len() < 2 {
return Err("Invalid size of sub-char-table".to_string());
}
let depth = items[0]
.as_fixnum()
.ok_or_else(|| "Invalid depth in sub-char-table".to_string())?;
if !(1..=3).contains(&depth) {
return Err("Invalid depth in sub-char-table".to_string());
}
let min_char = items[1]
.as_fixnum()
.ok_or_else(|| "Invalid minimum character in sub-char-table".to_string())?;
if !(0..=MAX_CHAR).contains(&min_char) {
return Err("Invalid minimum character in sub-char-table".to_string());
}
let expected = 2 + GNU_CHARTAB_SIZE[depth as usize];
if items.len() != expected {
return Err("Invalid size in sub-char-table".to_string());
}
let mut vec = Vec::with_capacity(items.len() + 1);
vec.push(Value::symbol(SUB_CHAR_TABLE_TAG));
vec.extend_from_slice(items);
Ok(Value::vector(vec))
}
fn char_table_extra_count(vec: &[Value]) -> usize {
match vec.get(CT_EXTRA_COUNT).map(|v| v.kind()) {
Some(ValueKind::Fixnum(n)) if n >= 0 => n as usize,
_ => 0,
}
}
fn char_table_extra_slot_value(table: &Value, idx: usize) -> Option<Value> {
if !is_char_table(table) {
return None;
}
let vec = table.as_vector_data().unwrap();
let extra_count = char_table_extra_count(vec);
(idx < extra_count).then(|| vec[CT_EXTRA_START + idx])
}
fn is_char_code_property_table(table: &Value) -> bool {
if !is_char_table(table) {
return false;
}
let vec = table.as_vector_data().unwrap();
vec.get(CT_SUBTYPE)
.is_some_and(|v| v.is_symbol_named("char-code-property-table"))
&& char_table_extra_count(vec) == 5
}
fn uniprop_compressed_string(value: Value) -> Option<Vec<u32>> {
let string = value.as_lisp_string()?;
let codes = crate::emacs_core::builtins::lisp_string_char_codes(string);
matches!(codes.first(), Some(1 | 2)).then_some(codes)
}
fn flatten_uniprop_compressed_string(vec: &mut Vec<Value>, start: i64, codes: &[u32]) {
match codes.first().copied() {
Some(1) => {
let mut cursor = 1;
let Some(mut idx) = codes.get(cursor).copied().map(i64::from) else {
return;
};
cursor += 1;
while cursor < codes.len() && idx < GNU_CHARTAB_CHARS[2] {
let value = codes[cursor] as i64;
if value > 0 {
ct_set_char(vec, start + idx, Value::fixnum(value));
}
idx += 1;
cursor += 1;
}
}
Some(2) => {
let mut cursor = 1;
let mut idx = 0_i64;
while cursor < codes.len() && idx < GNU_CHARTAB_CHARS[2] {
let value = codes[cursor] as i64;
cursor += 1;
let count = if cursor < codes.len() && codes[cursor] >= 128 {
let count = codes[cursor] as i64 - 128;
cursor += 1;
count
} else {
1
};
for _ in 0..count {
if idx >= GNU_CHARTAB_CHARS[2] {
break;
}
ct_set_char(vec, start + idx, Value::fixnum(value));
idx += 1;
}
}
}
_ => {}
}
}
fn flatten_char_table_slot(
vec: &mut Vec<Value>,
value: Value,
start: i64,
span: i64,
is_uniprop: bool,
) {
if value.is_nil() {
return;
}
if let Some((depth, min_char, contents)) = sub_char_table_depth_min_contents(&value) {
flatten_sub_char_table(vec, depth, min_char, &contents, is_uniprop);
return;
}
if is_uniprop
&& span == GNU_CHARTAB_CHARS[2]
&& let Some(codes) = uniprop_compressed_string(value)
{
flatten_uniprop_compressed_string(vec, start, &codes);
return;
}
let end = (start + span - 1).min(MAX_CHAR);
if start == end {
ct_set_char(vec, start, value);
} else {
ct_set_range(vec, start, end, value);
}
}
fn flatten_sub_char_table(
vec: &mut Vec<Value>,
depth: usize,
min_char: i64,
contents: &[Value],
is_uniprop: bool,
) {
if depth > 3 || contents.len() != GNU_CHARTAB_SIZE[depth] {
return;
}
let span = GNU_CHARTAB_CHARS[depth];
for (idx, value) in contents.iter().copied().enumerate() {
flatten_char_table_slot(vec, value, min_char + idx as i64 * span, span, is_uniprop);
}
}
pub(crate) fn make_char_table_from_external_slots(items: &[Value]) -> Result<Value, String> {
if items.len() < GNU_CHAR_TABLE_STANDARD_SLOTS {
return Err("Invalid size char-table".to_string());
}
let default = items[0];
let parent = items[1];
let purpose = items[2];
let extra_count = items.len() - GNU_CHAR_TABLE_STANDARD_SLOTS;
let mut vec = vec![
Value::symbol(CHAR_TABLE_TAG),
default,
parent,
purpose,
Value::fixnum(extra_count as i64),
];
vec.extend_from_slice(&items[GNU_CHAR_TABLE_STANDARD_SLOTS..]);
let is_uniprop = purpose.is_symbol_named("char-code-property-table") && extra_count == 5;
for block in 0..GNU_CHAR_TABLE_CONTENT_BLOCKS_USIZE {
let start = block as i64 * GNU_CHAR_TABLE_BLOCK_CHARS;
flatten_char_table_slot(
&mut vec,
items[GNU_CHAR_TABLE_CONTENT_START + block],
start,
GNU_CHAR_TABLE_BLOCK_CHARS,
is_uniprop,
);
}
flatten_char_table_slot(
&mut vec,
items[GNU_CHAR_TABLE_ASCII_SLOT],
0,
128,
is_uniprop,
);
Ok(Value::vector(vec))
}
pub fn make_char_table_value(sub_type: Value, default: Value) -> Value {
make_char_table_with_extra_slots(sub_type, default, 0)
}
pub fn make_char_table_with_extra_slots(sub_type: Value, default: Value, n_extras: i64) -> Value {
let mut vec = vec![
Value::symbol(CHAR_TABLE_TAG),
default, Value::NIL, sub_type, Value::fixnum(n_extras), ];
for _ in 0..n_extras {
vec.push(Value::NIL);
}
Value::vector(vec)
}
pub fn ct_set_single(table: &Value, ch: i64, value: Value) {
if table.is_vector() {
let mut vec = table
.as_vector_data()
.map(|items| items.to_vec())
.unwrap_or_default();
ct_set_char(&mut vec, ch, value);
let _ = table.replace_vector_data(vec);
} else {
panic!("ct_set_single: expected char-table Vector");
}
}
pub(crate) fn builtin_make_char_table(eval: &mut Context, args: Vec<Value>) -> EvalResult {
expect_min_args("make-char-table", &args, 1)?;
expect_max_args("make-char-table", &args, 2)?;
let sub_type = args[0];
let default = if args.len() > 1 { args[1] } else { Value::NIL };
let n_extras = if let Some(name) = sub_type.as_symbol_name() {
eval.obarray
.get_property(name, "char-table-extra-slots")
.and_then(|v| v.as_int())
.unwrap_or(0)
} else {
0
};
Ok(make_char_table_with_extra_slots(
sub_type, default, n_extras,
))
}
pub(crate) fn builtin_char_table_p(args: Vec<Value>) -> EvalResult {
expect_args("char-table-p", &args, 1)?;
Ok(Value::bool_val(is_char_table(&args[0])))
}
pub(crate) fn builtin_set_char_table_range(args: Vec<Value>) -> EvalResult {
expect_args("set-char-table-range", &args, 3)?;
let table = &args[0];
let range = &args[1];
let value = &args[2];
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
let mut vec = table.as_vector_data().unwrap().clone();
match range.kind() {
ValueKind::Nil => {
vec[CT_DEFAULT] = *value;
}
ValueKind::T => {
ct_set_range(&mut vec, 0, MAX_CHAR, *value);
}
ValueKind::Fixnum(_) => {
let ch = expect_int(range)?;
ct_set_char(&mut vec, ch, *value);
}
ValueKind::Cons => {
let pair_car = range.cons_car();
let pair_cdr = range.cons_cdr();
let min = expect_int(&pair_car)?;
let max = expect_int(&pair_cdr)?;
if min > max {
return Err(signal(
"args-out-of-range",
vec![Value::fixnum(min), Value::fixnum(max)],
));
}
ct_set_range(&mut vec, min, max, *value);
}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("char-table-range"), *range],
));
}
}
let _ = table.replace_vector_data(vec);
Ok(*value)
}
fn ct_set_char(vec: &mut Vec<Value>, ch: i64, value: Value) {
vec.push(Value::fixnum(ch));
vec.push(value);
}
fn ct_set_range(vec: &mut Vec<Value>, min: i64, max: i64, value: Value) {
vec.push(Value::cons(Value::fixnum(min), Value::fixnum(max)));
vec.push(value);
}
fn ct_get_char(vec: &[Value], ch: i64) -> Option<Value> {
let start = ct_data_start(vec);
let len = vec.len();
if len < start + 2 {
return None;
}
let mut i = len; while i >= start + 2 {
i -= 2;
let key = vec[i];
match key.kind() {
ValueKind::Fixnum(existing) => {
if existing == ch {
return Some(vec[i + 1]);
}
}
ValueKind::Cons => {
let pair_car = key.cons_car();
let pair_cdr = key.cons_cdr();
if let (Some(min), Some(max)) = (pair_car.as_fixnum(), pair_cdr.as_fixnum()) {
if ch >= min && ch <= max {
return Some(vec[i + 1]);
}
}
}
_ => {}
}
}
None
}
pub(crate) fn builtin_char_table_range(args: Vec<Value>) -> EvalResult {
expect_args("char-table-range", &args, 2)?;
let table = &args[0];
let range = &args[1];
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
match range.kind() {
ValueKind::Nil => {
let vec = table.as_vector_data().unwrap();
Ok(vec[CT_DEFAULT])
}
ValueKind::Fixnum(_) => {
let ch = expect_int(range)?;
ct_lookup(table, ch)
}
ValueKind::Cons => {
let pair_car = range.cons_car();
let pair_cdr = range.cons_cdr();
let from = expect_int(&pair_car)?;
let _to = expect_int(&pair_cdr)?;
let (value, _run_from, _run_to) = ct_lookup_and_range(table, from)?;
Ok(value)
}
_ => Err(signal(
"error",
vec![Value::string(
"Invalid RANGE argument to `char-table-range'",
)],
)),
}
}
pub(crate) fn ct_lookup(table: &Value, ch: i64) -> EvalResult {
if !table.is_vector() {
return Err(wrong_type("char-table-p", table));
}
let vec_ref = table.as_vector_data().unwrap();
if let Some(val) = ct_get_char(vec_ref, ch) {
if !val.is_nil() {
return Ok(val);
}
}
let default = vec_ref[CT_DEFAULT];
let parent = vec_ref[CT_PARENT];
if !default.is_nil() {
Ok(default)
} else if is_char_table(&parent) {
ct_lookup(&parent, ch)
} else {
Ok(Value::NIL)
}
}
fn ct_lookup_and_range(table: &Value, ch: i64) -> Result<(Value, i64, i64), Flow> {
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
for run in ct_effective_runs(table) {
if ch >= run.start && ch <= run.end {
return Ok((run.value, run.start, run.end));
}
}
Ok((Value::NIL, 0, MAX_CHAR))
}
fn key_span(key: Value) -> Option<(i64, i64)> {
match key.kind() {
ValueKind::Fixnum(ch) => Some((ch, ch)),
ValueKind::Cons => {
let start = key.cons_car().as_fixnum()?;
let end = key.cons_cdr().as_fixnum()?;
Some((start, end))
}
_ => None,
}
}
fn refine_atomic_boundary(start: i64, end: i64, ch: i64, lo: &mut i64, hi: &mut i64) {
let domain_end = MAX_CHAR.saturating_add(1);
let start = start.clamp(0, domain_end);
let end_exclusive = end.saturating_add(1).clamp(0, domain_end);
for boundary in [start, end_exclusive] {
if boundary <= ch {
*lo = (*lo).max(boundary);
} else {
*hi = (*hi).min(boundary);
}
}
}
fn ct_lookup_atomic_range(table: &Value, ch: i64) -> Result<(Value, i64, i64), Flow> {
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
if !(0..=MAX_CHAR).contains(&ch) {
return Ok((Value::NIL, 0, MAX_CHAR));
}
let vec = table.as_vector_data().unwrap();
let start = ct_data_start(vec);
let mut lo = 0;
let mut hi = MAX_CHAR.saturating_add(1);
let mut found_local = false;
let mut local_value = Value::NIL;
let mut i = vec.len();
while i >= start + 2 {
i -= 2;
if let Some((entry_start, entry_end)) = key_span(vec[i]) {
refine_atomic_boundary(entry_start, entry_end, ch, &mut lo, &mut hi);
if !found_local && ch >= entry_start && ch <= entry_end {
found_local = true;
local_value = vec[i + 1];
}
}
}
let atomic_end = hi.saturating_sub(1).min(MAX_CHAR);
if found_local && !local_value.is_nil() {
return Ok((local_value, lo, atomic_end));
}
let default = vec[CT_DEFAULT];
if !default.is_nil() {
return Ok((default, lo, atomic_end));
}
let parent = vec[CT_PARENT];
if is_char_table(&parent) {
let (parent_value, parent_start, parent_end) = ct_lookup_atomic_range(&parent, ch)?;
return Ok((
parent_value,
lo.max(parent_start),
atomic_end.min(parent_end),
));
}
Ok((Value::NIL, lo, atomic_end))
}
pub(crate) fn char_table_ref_and_range(table: &Value, ch: i64) -> Result<(Value, i64, i64), Flow> {
ct_lookup_and_range(table, ch)
}
pub(crate) fn char_table_ref_and_atomic_range(
table: &Value,
ch: i64,
) -> Result<(Value, i64, i64), Flow> {
ct_lookup_atomic_range(table, ch)
}
pub(crate) fn builtin_char_table_parent(args: Vec<Value>) -> EvalResult {
expect_args("char-table-parent", &args, 1)?;
let table = &args[0];
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
let vec = table.as_vector_data().unwrap();
Ok(vec[CT_PARENT])
}
pub(crate) fn char_table_local_entries(table: &Value) -> Result<Vec<(Value, Value)>, Flow> {
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
let vec = table.as_vector_data().unwrap().clone();
let start = ct_data_start(&vec);
let mut out = Vec::new();
let mut i = start;
while i + 1 < vec.len() {
match vec[i].kind() {
ValueKind::Fixnum(_) | ValueKind::Cons => out.push((vec[i], vec[i + 1])),
_ => {}
}
i += 2;
}
Ok(out)
}
pub(crate) fn builtin_set_char_table_parent(args: Vec<Value>) -> EvalResult {
expect_args("set-char-table-parent", &args, 2)?;
let table = &args[0];
let parent = &args[1];
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
if !parent.is_nil() && !is_char_table(parent) {
return Err(wrong_type("char-table-p", parent));
}
if !parent.is_nil() {
let mut cursor = *parent;
while is_char_table(&cursor) {
if cursor.is_vector() && table.is_vector() {
if std::ptr::eq(
cursor.as_vector_data().unwrap() as *const _,
table.as_vector_data().unwrap() as *const _,
) {
return Err(signal(
"error",
vec![Value::string(
"Attempt to make a chartable be its own parent",
)],
));
}
}
let vec = cursor.as_vector_data().unwrap().clone();
cursor = vec[CT_PARENT];
}
}
let _ = table.set_vector_slot(CT_PARENT, *parent);
Ok(*parent)
}
pub(crate) fn for_each_char_table_mapping(
table: &Value,
mut f: impl FnMut(Value, Value) -> Result<(), Flow>,
) -> Result<(), Flow> {
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
let shared_range = Value::cons(Value::fixnum(0), Value::fixnum(MAX_CHAR));
let saved = save_scratch_gc_roots();
push_scratch_gc_root(shared_range);
let result = (|| {
for run in ct_effective_runs(table) {
shared_range.set_car(Value::fixnum(run.start));
shared_range.set_cdr(Value::fixnum(run.end));
if run.value.is_nil() {
continue;
}
let key = if run.start == run.end {
Value::fixnum(run.start)
} else {
shared_range
};
let value = decode_unicode_property_map_value(*table, run.value);
f(key, value)?;
}
Ok(())
})();
restore_scratch_gc_roots(saved);
result
}
pub(crate) fn builtin_map_char_table(eval: &mut Context, args: Vec<Value>) -> EvalResult {
expect_args("map-char-table", &args, 2)?;
let func = args[0];
let table = args[1];
for_each_char_table_mapping(&table, |key, value| {
let _ = eval.apply(func, vec![key, value])?;
Ok(())
})?;
Ok(Value::NIL)
}
fn ct_resolved_entries(table: &Value) -> Vec<(Value, Value)> {
ct_effective_runs(table)
.into_iter()
.filter(|run| !run.value.is_nil())
.map(|run| (run_key(run.start, run.end), run.value))
.collect()
}
#[derive(Clone, Copy)]
struct RawEntry {
start: i64,
end: i64,
value: Value,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct EffectiveRun {
start: i64,
end: i64,
value: Value,
}
fn ct_collect_raw_entries(vec: &[Value]) -> Vec<RawEntry> {
let start = ct_data_start(vec);
let mut raws = Vec::new();
let mut i = start;
while i + 1 < vec.len() {
match vec[i].kind() {
ValueKind::Fixnum(ch) => raws.push(RawEntry {
start: ch,
end: ch,
value: vec[i + 1],
}),
ValueKind::Cons => {
let pair_car = vec[i].cons_car();
let pair_cdr = vec[i].cons_cdr();
if let (Some(min), Some(max)) = (pair_car.as_fixnum(), pair_cdr.as_fixnum()) {
raws.push(RawEntry {
start: min,
end: max,
value: vec[i + 1],
});
}
}
_ => {}
}
i += 2;
}
raws
}
fn ct_effective_runs(table: &Value) -> Vec<EffectiveRun> {
if !table.is_vector() {
return vec![EffectiveRun {
start: 0,
end: MAX_CHAR,
value: Value::NIL,
}];
};
let vec = table.as_vector_data().unwrap().clone();
let raws = ct_collect_raw_entries(&vec);
let default = vec[CT_DEFAULT];
let parent = vec[CT_PARENT];
let domain_end = MAX_CHAR.saturating_add(1);
let parent_runs = if is_char_table(&parent) {
ct_effective_runs(&parent)
} else {
vec![EffectiveRun {
start: 0,
end: MAX_CHAR,
value: Value::NIL,
}]
};
let mut boundaries = BTreeSet::new();
let mut starts: BTreeMap<i64, Vec<usize>> = BTreeMap::new();
let mut ends: BTreeMap<i64, Vec<usize>> = BTreeMap::new();
boundaries.insert(0);
boundaries.insert(domain_end);
for (idx, raw) in raws.iter().enumerate() {
let end_exclusive = raw.end.saturating_add(1).min(domain_end);
boundaries.insert(raw.start);
boundaries.insert(end_exclusive);
starts.entry(raw.start).or_default().push(idx);
ends.entry(end_exclusive).or_default().push(idx);
}
for run in &parent_runs {
boundaries.insert(run.start);
boundaries.insert(run.end.saturating_add(1).min(domain_end));
}
let boundary_vec = boundaries.into_iter().collect::<Vec<_>>();
let mut runs: Vec<EffectiveRun> = Vec::new();
let mut active_raws = BTreeSet::new();
let mut parent_idx = 0usize;
for window in boundary_vec.windows(2) {
let start = window[0];
let end_exclusive = window[1];
if let Some(indices) = ends.get(&start) {
for idx in indices {
active_raws.remove(idx);
}
}
if let Some(indices) = starts.get(&start) {
for idx in indices {
active_raws.insert(*idx);
}
}
if start > MAX_CHAR || end_exclusive <= start {
continue;
}
let end = end_exclusive.saturating_sub(1).min(MAX_CHAR);
while parent_idx + 1 < parent_runs.len() && start > parent_runs[parent_idx].end {
parent_idx += 1;
}
let local = active_raws.iter().next_back().map(|idx| raws[*idx].value);
let value = match local {
Some(local) if !local.is_nil() => local,
_ if !default.is_nil() => default,
_ => parent_runs
.get(parent_idx)
.filter(|run| start >= run.start && start <= run.end)
.map(|run| run.value)
.unwrap_or(Value::NIL),
};
if let Some(previous) = runs.last_mut()
&& previous.end.saturating_add(1) == start
&& eq_value(&previous.value, &value)
{
previous.end = end;
} else {
runs.push(EffectiveRun { start, end, value });
}
}
if runs.is_empty() {
vec![EffectiveRun {
start: 0,
end: MAX_CHAR,
value: Value::NIL,
}]
} else {
runs
}
}
fn run_key(start: i64, end: i64) -> Value {
if start == end {
Value::fixnum(start)
} else {
Value::cons(Value::fixnum(start), Value::fixnum(end))
}
}
pub(crate) fn for_each_non_nil_char_table_run<F>(table: &Value, mut f: F)
where
F: FnMut(Value, Value),
{
if !is_char_table(table) {
return;
}
for run in ct_effective_runs(table) {
if run.value.is_nil() {
continue;
}
f(run_key(run.start, run.end), run.value);
}
}
const GNU_CHAR_TABLE_CONTENT_BLOCKS: i64 = 64;
const GNU_CHAR_TABLE_BLOCK_CHARS: i64 = 1 << 16;
fn uniform_run_value(runs: &[EffectiveRun], start: i64, end: i64) -> Option<Value> {
runs.iter()
.find(|run| start >= run.start && end <= run.end)
.map(|run| run.value)
}
pub(crate) fn char_table_external_slots(table: &Value) -> Option<Vec<Value>> {
if !is_char_table(table) {
return None;
}
if !table.is_vector() {
return None;
};
let vec = table.as_vector_data().unwrap().clone();
let runs = ct_effective_runs(table);
let extra_count = match vec[CT_EXTRA_COUNT].kind() {
ValueKind::Fixnum(n) if n >= 0 => n as usize,
_ => 0,
};
let mut slots = Vec::with_capacity(4 + GNU_CHAR_TABLE_CONTENT_BLOCKS as usize + extra_count);
slots.push(vec[CT_DEFAULT]);
slots.push(vec[CT_PARENT]);
slots.push(vec[CT_SUBTYPE]);
slots.push(uniform_run_value(&runs, 0, 127).unwrap_or(Value::NIL));
for idx in 0..GNU_CHAR_TABLE_CONTENT_BLOCKS {
let start = idx * GNU_CHAR_TABLE_BLOCK_CHARS;
let end = (start + GNU_CHAR_TABLE_BLOCK_CHARS - 1).min(MAX_CHAR);
slots.push(uniform_run_value(&runs, start, end).unwrap_or(Value::NIL));
}
for extra_idx in 0..extra_count {
slots.push(vec[CT_EXTRA_START + extra_idx]);
}
Some(slots)
}
pub(crate) fn builtin_char_table_extra_slot(args: Vec<Value>) -> EvalResult {
expect_args("char-table-extra-slot", &args, 2)?;
let table = &args[0];
let n = expect_int(&args[1])?;
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
let v = table.as_vector_data().unwrap().clone();
let extra_count = match v[CT_EXTRA_COUNT].kind() {
ValueKind::Fixnum(c) => c,
_ => 0,
};
if n < 0 || n >= extra_count {
return Err(signal("args-out-of-range", vec![args[0], args[1]]));
}
Ok(v[CT_EXTRA_START + n as usize])
}
pub(crate) fn builtin_set_char_table_extra_slot(args: Vec<Value>) -> EvalResult {
expect_args("set-char-table-extra-slot", &args, 3)?;
let table = &args[0];
let n = expect_int(&args[1])?;
let value = &args[2];
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
let v = table.as_vector_data().unwrap();
let extra_count = match v[CT_EXTRA_COUNT].kind() {
ValueKind::Fixnum(c) => c,
_ => 0,
};
if n < 0 || n >= extra_count {
return Err(signal("args-out-of-range", vec![args[0], args[1]]));
}
let slot_idx = CT_EXTRA_START + n as usize;
let _ = table.set_vector_slot(slot_idx, *value);
Ok(*value)
}
pub(crate) fn builtin_char_table_subtype(args: Vec<Value>) -> EvalResult {
expect_args("char-table-subtype", &args, 1)?;
let table = &args[0];
if !is_char_table(table) {
return Err(wrong_type("char-table-p", table));
}
let vec = table.as_vector_data().unwrap();
Ok(vec[CT_SUBTYPE])
}
fn assq_cell_eq(key: Value, list: Value) -> Result<Value, Flow> {
let mut cursor = list;
loop {
match cursor.kind() {
ValueKind::Nil => return Ok(Value::NIL),
ValueKind::Cons => {
let entry = cursor.cons_car();
if entry.is_cons() && eq_value(&entry.cons_car(), &key) {
return Ok(entry);
}
cursor = cursor.cons_cdr();
}
_ => return Err(wrong_type("listp", &list)),
}
}
}
fn char_code_property_cell(eval: &Context, prop: Value) -> Result<Value, Flow> {
let alist = eval
.obarray
.symbol_value("char-code-property-alist")
.copied()
.unwrap_or(Value::NIL);
assq_cell_eq(prop, alist)
}
pub(crate) fn builtin_unicode_property_table_internal(
eval: &mut Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("unicode-property-table-internal", &args, 1)?;
let prop = args[0];
let mut cell = char_code_property_cell(eval, prop)?;
if cell.is_nil() {
return Ok(Value::NIL);
}
let table = cell.cons_cdr();
if table.is_string() {
let Some(file_name) = table.as_runtime_string_owned() else {
return Ok(table);
};
let load_name = Value::string(format!("international/{file_name}"));
let _ = crate::emacs_core::load::builtin_load_in_vm_runtime(
eval,
&[load_name, Value::T, Value::T, Value::T, Value::T],
)?;
cell = char_code_property_cell(eval, prop)?;
if cell.is_nil() {
return Ok(Value::NIL);
}
}
Ok(cell.cons_cdr())
}
fn expect_character(value: &Value) -> Result<i64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) if (0..=MAX_CHAR).contains(&n) => Ok(n),
_ => Err(signal(
"wrong-type-argument",
vec![Value::symbol("characterp"), *value],
)),
}
}
fn invalid_unicode_property_table() -> Flow {
signal(
"error",
vec![Value::string("Invalid Unicode property table")],
)
}
fn decode_uniprop_run_length(table: Value, value: Value) -> Value {
let Some(ValueKind::Fixnum(index)) = Some(value.kind()) else {
return value;
};
if index < 0 {
return value;
}
let Some(value_table) = char_table_extra_slot_value(&table, 4) else {
return value;
};
if !value_table.is_vector() {
return value;
}
value_table
.as_vector_data()
.and_then(|values| values.get(index as usize).copied())
.unwrap_or(value)
}
fn decode_unicode_property_map_value(table: Value, value: Value) -> Value {
if !is_char_code_property_table(&table) {
return value;
}
match char_table_extra_slot_value(&table, 1).map(|v| v.kind()) {
Some(ValueKind::Fixnum(0)) => decode_uniprop_run_length(table, value),
_ => value,
}
}
pub(crate) fn builtin_get_unicode_property_internal(args: Vec<Value>) -> EvalResult {
expect_args("get-unicode-property-internal", &args, 2)?;
let table = args[0];
let ch = expect_character(&args[1])?;
if !is_char_table(&table) {
return Err(wrong_type("char-table-p", &table));
}
if !is_char_code_property_table(&table) {
return Err(invalid_unicode_property_table());
}
let decoder = char_table_extra_slot_value(&table, 1).unwrap_or(Value::NIL);
let value = ct_lookup(&table, ch)?;
match decoder.kind() {
ValueKind::Fixnum(0) => Ok(decode_uniprop_run_length(table, value)),
ValueKind::Nil => Ok(value),
ValueKind::Fixnum(_) => Err(invalid_unicode_property_table()),
_ => Ok(value),
}
}
pub(crate) fn builtin_make_bool_vector(args: Vec<Value>) -> EvalResult {
expect_args("make-bool-vector", &args, 2)?;
let length = expect_int(&args[0])?;
if length < 0 {
return Err(signal("args-out-of-range", vec![args[0]]));
}
let init_val = if args[1].is_truthy() {
Value::fixnum(1)
} else {
Value::fixnum(0)
};
let len = length as usize;
let mut vec = Vec::with_capacity(2 + len);
vec.push(Value::symbol(BOOL_VECTOR_TAG));
vec.push(Value::fixnum(length));
for _ in 0..len {
vec.push(init_val);
}
Ok(Value::vector(vec))
}
pub(crate) fn builtin_bool_vector(args: Vec<Value>) -> EvalResult {
let bits: Vec<bool> = args.into_iter().map(|v| v.is_truthy()).collect();
Ok(bv_from_bits(&bits))
}
pub(crate) fn builtin_bool_vector_p(args: Vec<Value>) -> EvalResult {
expect_args("bool-vector-p", &args, 1)?;
Ok(Value::bool_val(is_bool_vector(&args[0])))
}
fn bv_length(vec: &[Value]) -> i64 {
match vec[BV_SIZE].kind() {
ValueKind::Fixnum(n) => n,
_ => 0,
}
}
fn bv_bits(vec: &[Value]) -> Vec<bool> {
let len = bv_length(vec) as usize;
let mut bits = Vec::with_capacity(len);
for i in 0..len {
let v = &vec[2 + i];
bits.push(v.as_fixnum().map_or(false, |n| n != 0));
}
bits
}
pub(crate) fn builtin_bool_vector_count_population(args: Vec<Value>) -> EvalResult {
expect_args("bool-vector-count-population", &args, 1)?;
let (bits, _len) = extract_bv_bits(&args[0])?;
let count = bits.iter().filter(|&&b| b).count();
Ok(Value::fixnum(count as i64))
}
fn extract_bv_bits(value: &Value) -> Result<(Vec<bool>, i64), Flow> {
if !is_bool_vector(value) {
return Err(wrong_type("bool-vector-p", value));
}
let vec = value.as_vector_data().unwrap().clone();
let len = bv_length(&vec);
let bits = bv_bits(&vec);
Ok((bits, len))
}
fn bv_from_bits(bits: &[bool]) -> Value {
let len = bits.len();
let mut vec = Vec::with_capacity(2 + len);
vec.push(Value::symbol(BOOL_VECTOR_TAG));
vec.push(Value::fixnum(len as i64));
for &b in bits {
vec.push(Value::fixnum(if b { 1 } else { 0 }));
}
Value::vector(vec)
}
pub(crate) fn builtin_bool_vector_intersection(args: Vec<Value>) -> EvalResult {
expect_min_args("bool-vector-intersection", &args, 2)?;
expect_max_args("bool-vector-intersection", &args, 3)?;
let (bits_a, len_a) = extract_bv_bits(&args[0])?;
let (bits_b, len_b) = extract_bv_bits(&args[1])?;
if len_a != len_b {
return Err(signal(
"wrong-length-argument",
vec![Value::fixnum(len_a), Value::fixnum(len_b)],
));
}
let result_bits: Vec<bool> = bits_a
.iter()
.zip(bits_b.iter())
.map(|(&a, &b)| a && b)
.collect();
if args.len() == 3 {
store_bv_result_with_expected_lengths(&args[2], &result_bits, &[len_a, len_b])?;
Ok(args[2])
} else {
Ok(bv_from_bits(&result_bits))
}
}
pub(crate) fn builtin_bool_vector_union(args: Vec<Value>) -> EvalResult {
expect_min_args("bool-vector-union", &args, 2)?;
expect_max_args("bool-vector-union", &args, 3)?;
let (bits_a, len_a) = extract_bv_bits(&args[0])?;
let (bits_b, len_b) = extract_bv_bits(&args[1])?;
if len_a != len_b {
return Err(signal(
"wrong-length-argument",
vec![Value::fixnum(len_a), Value::fixnum(len_b)],
));
}
let result_bits: Vec<bool> = bits_a
.iter()
.zip(bits_b.iter())
.map(|(&a, &b)| a || b)
.collect();
if args.len() == 3 {
store_bv_result_with_expected_lengths(&args[2], &result_bits, &[len_a, len_b])?;
Ok(args[2])
} else {
Ok(bv_from_bits(&result_bits))
}
}
pub(crate) fn builtin_bool_vector_exclusive_or(args: Vec<Value>) -> EvalResult {
expect_min_args("bool-vector-exclusive-or", &args, 2)?;
expect_max_args("bool-vector-exclusive-or", &args, 3)?;
let (bits_a, len_a) = extract_bv_bits(&args[0])?;
let (bits_b, len_b) = extract_bv_bits(&args[1])?;
if len_a != len_b {
return Err(signal(
"wrong-length-argument",
vec![Value::fixnum(len_a), Value::fixnum(len_b)],
));
}
let result_bits: Vec<bool> = bits_a
.iter()
.zip(bits_b.iter())
.map(|(&a, &b)| a ^ b)
.collect();
if args.len() == 3 {
store_bv_result_with_expected_lengths(&args[2], &result_bits, &[len_a, len_b])?;
Ok(args[2])
} else {
Ok(bv_from_bits(&result_bits))
}
}
pub(crate) fn builtin_bool_vector_not(args: Vec<Value>) -> EvalResult {
expect_min_args("bool-vector-not", &args, 1)?;
expect_max_args("bool-vector-not", &args, 2)?;
let (bits, len_a) = extract_bv_bits(&args[0])?;
let result_bits: Vec<bool> = bits.into_iter().map(|b| !b).collect();
if args.len() == 2 {
store_bv_result_with_expected_lengths(&args[1], &result_bits, &[len_a])?;
Ok(args[1])
} else {
Ok(bv_from_bits(&result_bits))
}
}
pub(crate) fn builtin_bool_vector_set_difference(args: Vec<Value>) -> EvalResult {
expect_min_args("bool-vector-set-difference", &args, 2)?;
expect_max_args("bool-vector-set-difference", &args, 3)?;
let (bits_a, len_a) = extract_bv_bits(&args[0])?;
let (bits_b, len_b) = extract_bv_bits(&args[1])?;
if len_a != len_b {
return Err(signal(
"wrong-length-argument",
vec![Value::fixnum(len_a), Value::fixnum(len_b)],
));
}
let result_bits: Vec<bool> = bits_a
.iter()
.zip(bits_b.iter())
.map(|(&a, &b)| a && !b)
.collect();
if args.len() == 3 {
store_bv_result_with_expected_lengths(&args[2], &result_bits, &[len_a, len_b])?;
Ok(args[2])
} else {
Ok(bv_from_bits(&result_bits))
}
}
pub(crate) fn builtin_bool_vector_count_consecutive(args: Vec<Value>) -> EvalResult {
expect_args("bool-vector-count-consecutive", &args, 3)?;
let (bits, len) = extract_bv_bits(&args[0])?;
let target = args[1].is_truthy();
let start = expect_wholenump(&args[2])?;
if start > len {
return Err(signal(
"args-out-of-range",
vec![args[0], Value::fixnum(start)],
));
}
let mut count = 0usize;
for bit in bits.iter().skip(start as usize) {
if *bit != target {
break;
}
count += 1;
}
Ok(Value::fixnum(count as i64))
}
pub(crate) fn builtin_bool_vector_subsetp(args: Vec<Value>) -> EvalResult {
expect_args("bool-vector-subsetp", &args, 2)?;
let (bits_a, len_a) = extract_bv_bits(&args[0])?;
let (bits_b, len_b) = extract_bv_bits(&args[1])?;
if len_a != len_b {
return Err(signal(
"wrong-length-argument",
vec![
Value::fixnum(len_a),
Value::fixnum(len_b),
Value::fixnum(len_b),
],
));
}
let is_subset = bits_a.iter().zip(bits_b.iter()).all(|(&a, &b)| !a || b);
Ok(Value::bool_val(is_subset))
}
fn store_bv_result_with_expected_lengths(
dest: &Value,
bits: &[bool],
expected_lengths: &[i64],
) -> Result<(), Flow> {
if !is_bool_vector(dest) {
return Err(wrong_type("bool-vector-p", dest));
}
let v = dest.as_vector_data().unwrap().clone();
let len = bv_length(&v) as usize;
if len != bits.len() {
let mut payload: Vec<Value> = expected_lengths
.iter()
.copied()
.map(Value::fixnum)
.collect();
payload.push(Value::fixnum(len as i64));
return Err(signal("wrong-length-argument", payload));
}
let mut slots = dest
.as_vector_data()
.map(|items| items.to_vec())
.unwrap_or_default();
for (i, &b) in bits.iter().enumerate() {
slots[2 + i] = Value::fixnum(if b { 1 } else { 0 });
}
let _ = dest.replace_vector_data(slots);
Ok(())
}
#[cfg(test)]
#[path = "chartable_test.rs"]
mod tests;