use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "tsify", derive(tsify_next::Tsify))]
pub struct Argument {
pub kind: ArgumentKind,
pub value: ArgumentValue,
}
pub type ArgumentSlot = Option<Argument>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "tsify", derive(tsify_next::Tsify))]
pub enum ArgumentKind {
Mandatory,
Optional,
Star,
Group,
Delimited { open: Delimiter, close: Delimiter },
Paired { open: Delimiter, close: Delimiter },
}
impl ArgumentKind {
#[inline]
pub const fn from_required(required: bool) -> Self {
if required {
ArgumentKind::Mandatory
} else {
ArgumentKind::Optional
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "tsify", derive(tsify_next::Tsify))]
pub enum ArgumentValue {
MathContent(SyntaxNode),
TextContent(SyntaxNode),
Delimiter(Delimiter),
CSName(String),
Dimension(String),
Integer(String),
KeyVal(String),
Column(String),
Boolean(bool),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "tsify", derive(tsify_next::Tsify))]
pub enum ContentMode {
Math,
Text,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "tsify", derive(tsify_next::Tsify))]
pub enum Delimiter {
None,
Char(char),
Control(&'static str),
}
impl<'de> Deserialize<'de> for Delimiter {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
enum DelimiterInput {
None,
Char(char),
Control(String),
}
match DelimiterInput::deserialize(deserializer)? {
DelimiterInput::None => Ok(Delimiter::None),
DelimiterInput::Char(ch) => Ok(Delimiter::Char(ch)),
DelimiterInput::Control(name) => {
Ok(Delimiter::Control(Box::leak(name.into_boxed_str())))
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "tsify", derive(tsify_next::Tsify))]
pub enum GroupKind {
Explicit,
Implicit,
Delimited { left: Delimiter, right: Delimiter },
InlineMath,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "tsify", derive(tsify_next::Tsify))]
pub enum SyntaxNode {
Root {
mode: ContentMode,
children: Vec<SyntaxNode>,
},
Group {
mode: ContentMode,
kind: GroupKind, children: Vec<SyntaxNode>,
},
Command {
name: String,
args: Vec<ArgumentSlot>,
known: bool,
},
Infix {
name: String,
args: Vec<ArgumentSlot>, left: Box<SyntaxNode>,
right: Box<SyntaxNode>,
},
Declarative {
name: String,
args: Vec<ArgumentSlot>,
},
Environment {
name: String,
args: Vec<ArgumentSlot>,
known: bool,
body: Box<SyntaxNode>, },
Scripted {
base: Box<SyntaxNode>,
subscript: Option<Box<SyntaxNode>>,
superscript: Option<Box<SyntaxNode>>,
},
Error { message: String, snippet: String },
Prime { count: usize },
Text(String),
Char(char),
ActiveSpace,
}
impl SyntaxNode {
pub fn is_group(&self) -> bool {
matches!(self, SyntaxNode::Root { .. } | SyntaxNode::Group { .. })
}
pub fn is_leaf(&self) -> bool {
matches!(
self,
SyntaxNode::Char(_)
| SyntaxNode::Text(_)
| SyntaxNode::Prime { .. }
| SyntaxNode::ActiveSpace
| SyntaxNode::Error { .. }
) || matches!(self, SyntaxNode::Command { args, .. } if args.iter().all(|slot| {
slot.as_ref().is_none_or(|arg| {
!matches!(
arg.value,
ArgumentValue::MathContent(_) | ArgumentValue::TextContent(_)
)
})
})) || matches!(self, SyntaxNode::Declarative { args, .. } if args.iter().all(|slot| {
slot.as_ref().is_none_or(|arg| {
!matches!(
arg.value,
ArgumentValue::MathContent(_) | ArgumentValue::TextContent(_)
)
})
}))
}
pub fn group_mode(&self) -> Option<ContentMode> {
match self {
SyntaxNode::Root { mode, .. } | SyntaxNode::Group { mode, .. } => Some(*mode),
_ => None,
}
}
pub fn root(mode: ContentMode, children: Vec<SyntaxNode>) -> Self {
SyntaxNode::Root { mode, children }
}
pub fn implicit_group(mode: ContentMode, children: Vec<SyntaxNode>) -> Self {
SyntaxNode::Group {
mode,
kind: GroupKind::Implicit,
children,
}
}
pub fn empty_group(mode: ContentMode) -> Self {
SyntaxNode::Group {
mode,
kind: GroupKind::Implicit,
children: Vec::new(),
}
}
pub fn prime(count: usize) -> Self {
SyntaxNode::Prime { count }
}
}
impl Argument {
pub fn from_value(kind: ArgumentKind, value: ArgumentValue) -> Self {
Argument { kind, value }
}
}
impl ContentMode {
pub const fn as_str(self) -> &'static str {
match self {
ContentMode::Math => "math",
ContentMode::Text => "text",
}
}
}
impl std::fmt::Display for ContentMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str((*self).as_str())
}
}
impl std::fmt::Display for SyntaxNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_with_indent(f, 0)
}
}
impl SyntaxNode {
fn fmt_with_indent(&self, f: &mut std::fmt::Formatter<'_>, indent: usize) -> std::fmt::Result {
let prefix = " ".repeat(indent);
match self {
SyntaxNode::Root { mode, children } => {
writeln!(f, "{}Root({:?}) [", prefix, mode)?;
Self::fmt_group_children_with_indent(f, children, indent + 1)?;
writeln!(f, "{}]", prefix)
}
SyntaxNode::Group {
mode,
kind,
children,
} => {
writeln!(f, "{}Group({:?}, {:?}) [", prefix, mode, kind)?;
Self::fmt_group_children_with_indent(f, children, indent + 1)?;
writeln!(f, "{}]", prefix)
}
SyntaxNode::Command { name, args, known } => {
writeln!(f, "{}Command(\\{}, known={}) [", prefix, name, known)?;
for arg in args {
fmt_argument_slot(f, arg, indent + 1)?;
}
writeln!(f, "{}]", prefix)
}
SyntaxNode::Infix {
name,
args,
left,
right,
} => {
writeln!(f, "{}Infix(\\{}) [", prefix, name)?;
writeln!(f, "{} left:", prefix)?;
left.fmt_with_indent(f, indent + 2)?;
writeln!(f, "{} right:", prefix)?;
right.fmt_with_indent(f, indent + 2)?;
if !args.is_empty() {
writeln!(f, "{} args:", prefix)?;
for arg in args {
fmt_argument_slot(f, arg, indent + 2)?;
}
}
writeln!(f, "{}]", prefix)
}
SyntaxNode::Declarative { name, args } => {
writeln!(f, "{}Declarative(\\{}) [", prefix, name)?;
if !args.is_empty() {
writeln!(f, "{} args:", prefix)?;
for arg in args {
fmt_argument_slot(f, arg, indent + 2)?;
}
}
writeln!(f, "{}]", prefix)
}
SyntaxNode::Environment {
name,
args,
known,
body,
} => {
writeln!(f, "{}Environment({}, known={}) [", prefix, name, known)?;
if !args.is_empty() {
writeln!(f, "{} args:", prefix)?;
for arg in args {
fmt_argument_slot(f, arg, indent + 2)?;
}
}
writeln!(f, "{} body:", prefix)?;
body.fmt_with_indent(f, indent + 2)?;
writeln!(f, "{}]", prefix)
}
SyntaxNode::Scripted {
base,
subscript,
superscript,
} => {
writeln!(f, "{}Scripted [", prefix)?;
writeln!(f, "{} base:", prefix)?;
base.fmt_with_indent(f, indent + 2)?;
if let Some(sub) = subscript {
writeln!(f, "{} subscript:", prefix)?;
sub.fmt_with_indent(f, indent + 2)?;
}
if let Some(sup) = superscript {
writeln!(f, "{} superscript:", prefix)?;
sup.fmt_with_indent(f, indent + 2)?;
}
writeln!(f, "{}]", prefix)
}
SyntaxNode::Error { message, snippet } => {
writeln!(
f,
"{}Error(message: {}, snippet: {})",
prefix, message, snippet
)
}
SyntaxNode::Prime { count } => writeln!(f, "{}Prime({})", prefix, count),
SyntaxNode::Text(s) => writeln!(f, "{}Text(\"{}\")", prefix, s),
SyntaxNode::Char(c) => writeln!(f, "{}Char('{}')", prefix, c),
SyntaxNode::ActiveSpace => writeln!(f, "{}ActiveSpace", prefix),
}
}
fn fmt_group_children_with_indent(
f: &mut std::fmt::Formatter<'_>,
children: &[SyntaxNode],
indent: usize,
) -> std::fmt::Result {
let prefix = " ".repeat(indent);
let mut i = 0;
while i < children.len() {
if let SyntaxNode::Char(_) = children[i] {
let mut merged = String::new();
while i < children.len() {
match &children[i] {
SyntaxNode::Char(c) => {
merged.push(*c);
i += 1;
}
_ => break,
}
}
if merged.chars().count() == 1 {
writeln!(f, "{}Char('{}')", prefix, merged.chars().next().unwrap())?;
} else {
writeln!(f, "{}Chars({:?})", prefix, merged)?;
}
continue;
}
children[i].fmt_with_indent(f, indent)?;
i += 1;
}
Ok(())
}
}
impl Argument {
fn fmt_with_indent(&self, f: &mut std::fmt::Formatter<'_>, indent: usize) -> std::fmt::Result {
let prefix = " ".repeat(indent);
writeln!(f, "{}Arg({:?}):", prefix, self.kind)?;
self.value.fmt_with_indent(f, indent + 1)
}
}
impl ArgumentValue {
fn fmt_with_indent(&self, f: &mut std::fmt::Formatter<'_>, indent: usize) -> std::fmt::Result {
let prefix = " ".repeat(indent);
match self {
ArgumentValue::MathContent(node) | ArgumentValue::TextContent(node) => {
node.fmt_with_indent(f, indent)
}
ArgumentValue::Delimiter(delim) => writeln!(f, "{}Delimiter({:?})", prefix, delim),
ArgumentValue::CSName(value) => writeln!(f, "{}CSName(\"{}\")", prefix, value),
ArgumentValue::Dimension(value) => writeln!(f, "{}Dimension(\"{}\")", prefix, value),
ArgumentValue::Integer(value) => writeln!(f, "{}Integer(\"{}\")", prefix, value),
ArgumentValue::KeyVal(value) => writeln!(f, "{}KeyVal(\"{}\")", prefix, value),
ArgumentValue::Column(value) => writeln!(f, "{}Column(\"{}\")", prefix, value),
ArgumentValue::Boolean(value) => writeln!(f, "{}Boolean({})", prefix, value),
}
}
}
fn fmt_argument_slot(
f: &mut std::fmt::Formatter<'_>,
slot: &ArgumentSlot,
indent: usize,
) -> std::fmt::Result {
let prefix = " ".repeat(indent);
match slot {
Some(argument) => argument.fmt_with_indent(f, indent),
None => writeln!(f, "{}Arg(None)", prefix),
}
}