use super::Error;
use crate::value::UclValue;
use std::cell::Cell;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
pub type MacroHandler = dyn Fn(&mut MacroCall<'_>) -> Result<(), MacroError>;
#[derive(Clone)]
pub(crate) struct Registered {
pub(crate) handler: Rc<MacroHandler>,
pub(crate) context: bool,
}
#[derive(Clone, Default)]
pub(crate) struct MacroTable {
macros: HashMap<String, Registered>,
pub(crate) ran: Cell<bool>,
}
impl MacroTable {
pub(crate) fn insert(&mut self, name: String, macro_: Registered) {
self.macros.insert(name, macro_);
}
pub(crate) fn get(&self, name: &[u8]) -> Option<&Registered> {
if name.is_empty() {
return None;
}
std::str::from_utf8(name)
.ok()
.and_then(|name| self.macros.get(name))
}
pub(crate) fn is_empty(&self) -> bool {
self.macros.is_empty()
}
pub(crate) fn names(&self) -> Vec<&str> {
let mut names: Vec<&str> = self.macros.keys().map(String::as_str).collect();
names.sort_unstable();
names
}
}
pub(crate) trait Host {
fn add_entry(
&mut self,
key: String,
value: UclValue,
priority: u8,
at: usize,
) -> Result<(), MacroError>;
fn parse_text(&mut self, text: &[u8], at: usize) -> Result<(), Error>;
}
pub struct MacroCall<'a> {
host: &'a mut dyn Host,
name: &'a str,
value: &'a [u8],
arguments: Option<&'a UclValue>,
root: Option<(&'a UclValue, u8)>,
at: usize,
text_error: Option<Error>,
}
impl<'a> MacroCall<'a> {
pub(crate) fn new(
host: &'a mut dyn Host,
name: &'a str,
value: &'a [u8],
arguments: Option<&'a UclValue>,
root: Option<(&'a UclValue, u8)>,
at: usize,
) -> Self {
Self {
host,
name,
value,
arguments,
root,
at,
text_error: None,
}
}
pub(crate) fn take_text_error(&mut self) -> Option<Error> {
self.text_error.take()
}
pub fn name(&self) -> &str {
self.name
}
pub fn value(&self) -> &[u8] {
self.value
}
pub fn value_str(&self) -> Option<&str> {
std::str::from_utf8(self.value).ok()
}
pub fn arguments(&self) -> Option<&UclValue> {
self.arguments
}
pub fn root(&self) -> Option<&UclValue> {
self.root.map(|(root, _)| root)
}
pub fn root_priority(&self) -> Option<u8> {
self.root.map(|(_, priority)| priority)
}
pub fn add(&mut self, key: impl Into<String>, value: UclValue) -> Result<(), MacroError> {
self.host.add_entry(key.into(), value, 0, self.at)
}
pub fn add_with_priority(
&mut self,
key: impl Into<String>,
value: UclValue,
priority: u8,
) -> Result<(), MacroError> {
let priority = priority & crate::value::MAX_PRIORITY;
self.host.add_entry(key.into(), value, priority, self.at)
}
pub fn parse(&mut self, text: impl AsRef<[u8]>) -> Result<(), MacroError> {
if let Some(error) = &self.text_error {
return Err(MacroError(Repr::Text(Box::new(error.clone()))));
}
match self.host.parse_text(text.as_ref(), self.at) {
Ok(()) => Ok(()),
Err(error) => {
self.text_error = Some(error.clone());
Err(MacroError(Repr::Text(Box::new(error))))
}
}
}
}
impl fmt::Debug for MacroCall<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MacroCall")
.field("name", &self.name)
.field("value", &String::from_utf8_lossy(self.value))
.field("arguments", &self.arguments)
.field("root", &self.root.is_some())
.field("root_priority", &self.root_priority())
.finish()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MacroError(pub(crate) Repr);
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Repr {
Stop,
Failed(String),
Text(Box<Error>),
}
impl MacroError {
pub fn stop() -> Self {
Self(Repr::Stop)
}
pub fn new(message: impl Into<String>) -> Self {
Self(Repr::Failed(message.into()))
}
pub fn is_stop(&self) -> bool {
match &self.0 {
Repr::Stop => true,
Repr::Failed(_) => false,
Repr::Text(error) => error.is_stopped(),
}
}
pub fn parse_error(&self) -> Option<&Error> {
match &self.0 {
Repr::Text(error) => Some(error),
_ => None,
}
}
}
impl fmt::Display for MacroError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Repr::Stop => f.write_str("the macro stops the parse"),
Repr::Failed(message) => f.write_str(message),
Repr::Text(error) => write!(f, "text parsed in place of the macro: {error}"),
}
}
}
impl std::error::Error for MacroError {}