use alloc::vec::Vec;
use kstring::KString;
use rustc_hash::FxBuildHasher;
use crate::FxHashMap;
use crate::character_class::Class;
use crate::token::Token;
#[derive(Clone, Debug)]
pub(crate) enum RecordedToken {
Token(Token),
CommandName(KString),
}
#[cfg(target_arch = "wasm32")]
static_assertions::assert_eq_size!(RecordedToken, [usize; 4]);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CmdSource {
Config,
Document,
Local,
}
#[derive(Debug)]
struct CmdDef {
num_args: u8,
class: Option<Class>,
start: usize,
end: usize,
}
#[derive(Debug, Default)]
pub(crate) struct CustomCmds {
tokens: Vec<RecordedToken>,
map: FxHashMap<KString, CmdDef>,
}
impl CustomCmds {
pub(crate) fn with_capacity(capacity: usize) -> Self {
CustomCmds {
tokens: Vec::new(),
map: FxHashMap::with_capacity_and_hasher(capacity, FxBuildHasher),
}
}
pub(crate) fn get(&self, name: &str, source: CmdSource) -> Option<Token> {
let def = self.map.get(name)?;
Some(Token::CustomCmdRef(
source,
def.num_args,
def.class,
def.start,
def.end,
))
}
pub(crate) fn body(&self, start: usize, end: usize) -> Option<&[RecordedToken]> {
self.tokens.get(start..end)
}
pub(crate) fn insert(
&mut self,
name: &str,
num_args: u8,
body: &[RecordedToken],
first_class: Option<Class>,
) -> bool {
if self.map.contains_key(name) {
return false;
}
self.insert_or_replace(name, num_args, body, first_class);
true
}
pub(crate) fn insert_or_replace(
&mut self,
name: &str,
num_args: u8,
body: &[RecordedToken],
first_class: Option<Class>,
) {
let start = self.tokens.len();
self.tokens.extend_from_slice(body);
let end = self.tokens.len();
self.register(name, num_args, first_class, start, end);
}
pub(crate) fn insert_and_copy_local(
&mut self,
local: &CustomCmds,
name: &str,
num_args: u8,
body: &[RecordedToken],
first_class: Option<Class>,
) -> bool {
if self.map.contains_key(name) {
return false;
}
self.insert_or_replace_and_copy_local(local, name, num_args, body, first_class);
true
}
pub(crate) fn insert_or_replace_and_copy_local(
&mut self,
local: &CustomCmds,
name: &str,
num_args: u8,
body: &[RecordedToken],
first_class: Option<Class>,
) {
let (start, end) = self.absorb(local, body);
self.register(name, num_args, first_class, start, end);
}
fn register(
&mut self,
name: &str,
num_args: u8,
first_class: Option<Class>,
start: usize,
end: usize,
) {
self.map.insert(
KString::from_ref(name),
CmdDef {
num_args,
class: first_class,
start,
end,
},
);
}
fn absorb(&mut self, local: &CustomCmds, body: &[RecordedToken]) -> (usize, usize) {
let start = self.tokens.len();
self.tokens.extend_from_slice(body);
let end = self.tokens.len();
let mut i = start;
while i < self.tokens.len() {
if let RecordedToken::Token(Token::CustomCmdRef(
CmdSource::Local,
num_args,
class,
body_start,
body_end,
)) = self.tokens[i]
{
let new_start = self.tokens.len();
if let Some(body) = local.body(body_start, body_end) {
self.tokens.extend_from_slice(body);
}
let new_end = self.tokens.len();
self.tokens[i] = RecordedToken::Token(Token::CustomCmdRef(
CmdSource::Document,
num_args,
class,
new_start,
new_end,
));
}
i += 1;
}
(start, end)
}
pub(crate) fn remove(&mut self, name: &str) {
self.map.remove(name);
}
pub(crate) fn clear(&mut self) {
self.tokens.clear();
self.map.clear();
}
}
pub(crate) fn is_valid_macro_name(s: &str) -> bool {
if s.is_empty() {
return false;
}
s.chars().count() == 1
|| s.bytes().all(|b| b.is_ascii_alphabetic())
}