use std::{collections::BTreeMap, fmt, iter::once, mem::discriminant};
use ecow::EcoString;
use serde::*;
use crate::{
BindingCounts, CodeSpan, Complex, Ident, Primitive, SemanticComment, Signature, Sp, Subscript,
parse::ident_modifier_args,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum Item {
Words(Vec<Sp<Word>>),
Binding(Binding),
Import(Import),
Module(Sp<ScopedModule>),
Data(Vec<DataDef>),
}
impl PartialEq for Item {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Item::Words(a), Item::Words(b)) => words_eq(a, b),
(Item::Binding(a), Item::Binding(b)) => a == b,
(Item::Import(a), Item::Import(b)) => a == b,
(Item::Module(a), Item::Module(b)) => a == b,
(Item::Data(a), Item::Data(b)) => a == b,
_ => false,
}
}
}
fn words_eq(a: &[Sp<Word>], b: &[Sp<Word>]) -> bool {
a.iter().map(|w| &w.value).eq(b.iter().map(|w| &w.value))
}
impl Item {
pub fn span(&self) -> Option<CodeSpan> {
match self {
Item::Words(words) => (words.first().zip(words.last()))
.map(|(first, last)| first.span.clone().merge(last.span.clone())),
Item::Binding(binding) => Some(binding.span()),
Item::Import(import) => Some(import.span()),
Item::Module(module) => Some(module.span.clone()),
Item::Data(data) => {
(data.first().zip(data.last())).map(|(first, last)| first.span().merge(last.span()))
}
}
}
pub fn kind_str(&self) -> &'static str {
match self {
Item::Words(_) => "words",
Item::Binding(_) => "binding",
Item::Import(_) => "import",
Item::Module(_) => "module",
Item::Data(_) => "data definition",
}
}
pub fn words_or<'a, T>(&'a self, default: T, on_words: impl FnOnce(&'a [Sp<Word>]) -> T) -> T {
match self {
Item::Words(words) => on_words(words),
_ => default,
}
}
pub fn is_empty_line(&self) -> bool {
self.words_or(false, |words| {
words.iter().all(|w| matches!(w.value, Word::Spaces))
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Binding {
pub name: Sp<Ident>,
pub arrow_span: CodeSpan,
pub public: bool,
pub code_macro: bool,
pub signature: Option<Sp<Signature>>,
pub words: Vec<Sp<Word>>,
pub counts: BindingCounts,
}
impl Binding {
pub fn span(&self) -> CodeSpan {
(self.name.span.clone()).merge(if let Some(last_word) = self.words.last() {
last_word.span.clone()
} else {
self.arrow_span.clone()
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ScopedModule {
pub open_span: CodeSpan,
pub public: bool,
pub kind: ModuleKind,
pub items: Vec<Item>,
pub imports: Option<ImportLine>,
pub close_span: Option<CodeSpan>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ModuleKind {
Named(Sp<Ident>),
Test,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Import {
pub name: Option<Sp<Ident>>,
pub tilde_span: CodeSpan,
pub public: bool,
pub path: Sp<String>,
pub lines: Vec<Option<ImportLine>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ImportLine {
pub tilde_span: CodeSpan,
pub public: bool,
pub items: Vec<Sp<Ident>>,
}
impl Import {
pub fn span(&self) -> CodeSpan {
let first = (self.name.as_ref())
.map(|n| n.span.clone())
.unwrap_or_else(|| self.path.span.clone());
let last = (self.items().last())
.map(|(i, _)| i.span.clone())
.unwrap_or_else(|| self.path.span.clone());
first.merge(last)
}
pub fn items(&self) -> impl Iterator<Item = (&Sp<Ident>, bool)> {
(self.lines.iter().flatten())
.flat_map(|line| line.items.iter().map(|item| (item, line.public)))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DataDef {
pub init_span: CodeSpan,
pub public: bool,
pub variant: bool,
pub name: Option<Sp<Ident>>,
pub fields: Option<DataFields>,
pub func: Option<Vec<Sp<Word>>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DataFields {
pub boxed: bool,
pub open_span: CodeSpan,
pub fields: Vec<DataField>,
pub post_comments: Option<Comments>,
pub trailing_newline: bool,
pub close_span: Option<CodeSpan>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DataField {
pub comments: Option<Comments>,
pub name: Sp<Ident>,
pub validator: Option<FieldValidator>,
pub eol_comment: Option<Sp<EcoString>>,
pub init: Option<FieldInit>,
pub bar_span: Option<CodeSpan>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldValidator {
pub open_span: CodeSpan,
pub words: Vec<Sp<Word>>,
pub close_span: Option<CodeSpan>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldInit {
pub arrow_span: CodeSpan,
pub words: Vec<Sp<Word>>,
}
impl DataDef {
pub fn span(&self) -> CodeSpan {
let end = self
.fields
.as_ref()
.map(|fields| fields.span())
.unwrap_or_else(|| {
self.name
.as_ref()
.map(|name| name.span.clone())
.unwrap_or_else(|| self.init_span.clone())
});
let mut span = (self.init_span.clone()).merge(end);
if let Some(words) = &self.func
&& let Some(word) = words.last()
{
span = span.merge(word.span.clone());
}
span
}
}
impl DataFields {
pub fn span(&self) -> CodeSpan {
let end = self
.close_span
.clone()
.or_else(|| {
self.fields.last().map(|field| {
field
.bar_span
.clone()
.unwrap_or_else(|| field.name.span.clone())
})
})
.unwrap_or_else(|| self.open_span.clone());
self.open_span.clone().merge(end)
}
}
impl DataField {
pub fn span(&self) -> CodeSpan {
let Some(end) = self.bar_span.clone().or_else(|| {
self.init.as_ref().map(|d| {
d.words
.last()
.map(|w| w.span.clone())
.unwrap_or_else(|| d.arrow_span.clone())
})
}) else {
return self.name.span.clone();
};
self.name.span.clone().merge(end)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Comments {
pub lines: Vec<Sp<EcoString>>,
pub semantic: BTreeMap<SemanticComment, CodeSpan>,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct InlineMacro {
pub func: Sp<Func>,
pub caret_span: Option<CodeSpan>,
pub ident: Sp<Ident>,
}
#[derive(Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
#[serde(tag = "type", content = "value")]
pub enum Word {
Number(NumWord, String),
Char(String),
String(String),
MultilineString(Vec<Sp<String>>),
FormatString(Vec<String>),
MultilineFormatString(Vec<Sp<Vec<String>>>),
Label(String),
Ref(Ref, Vec<ChainComponent>),
IncompleteRef(Vec<RefComponent>),
Strand(Vec<Sp<Word>>),
Array(Arr),
Func(Func),
Pack(FunctionPack),
Primitive(Primitive),
Modified(Box<Modified>),
Placeholder(Option<usize>),
Comment(EcoString),
Spaces,
BreakLine,
FlipLine,
SemanticComment(SemanticComment),
OutputComment { i: usize, n: usize },
Subscripted(Box<Subscripted>),
InlineMacro(InlineMacro),
}
impl PartialEq for Word {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Number(a_n, a_s), Self::Number(b_n, b_s)) => a_n == b_n && a_s == b_s,
(Self::Char(a), Self::Char(b)) => a == b,
(Self::String(a), Self::String(b)) => a == b,
(Self::Label(a), Self::Label(b)) => a == b,
(Self::FormatString(a), Self::FormatString(b)) => a == b,
(Self::MultilineFormatString(a), Self::MultilineFormatString(b)) => a == b,
(Self::Ref(a, ach), Self::Ref(b, bch)) => a == b && ach == bch,
(Self::Strand(a), Self::Strand(b)) => words_eq(a, b),
(Self::Array(a), Self::Array(b)) => a.lines == b.lines,
(Self::Func(a), Self::Func(b)) => a.lines == b.lines,
(Self::Pack(a), Self::Pack(b)) => (a.branches.iter().flat_map(|br| &br.value.lines))
.eq(b.branches.iter().flat_map(|br| &br.value.lines)),
(Self::Primitive(a), Self::Primitive(b)) => a == b,
(Self::Modified(a), Self::Modified(b)) => {
a.modifier == b.modifier
&& a.code_operands()
.map(|w| &w.value)
.eq(b.code_operands().map(|w| &w.value))
}
(Self::Placeholder(_), Self::Placeholder(_)) => false,
(Self::Comment(a), Self::Comment(b)) => a == b,
_ => discriminant(self) == discriminant(other),
}
}
}
impl Word {
pub fn is_code(&self) -> bool {
!matches!(
self,
Word::Comment(_) | Word::Spaces | Word::BreakLine | Word::FlipLine
)
}
pub fn is_literal(&self) -> bool {
match self {
Word::Number(..) | Word::Char(_) | Word::String(_) => true,
Word::Array(arr) => arr.lines.iter().all(|item| {
item.words_or(false, |words| {
(words.iter())
.filter(|w| w.value.is_code())
.all(|w| w.value.is_literal())
})
}),
Word::Strand(items) => items.iter().all(|w| w.value.is_literal()),
_ => false,
}
}
pub fn is_end_of_line(&self) -> bool {
match self {
Word::Comment(_)
| Word::SemanticComment(_)
| Word::OutputComment { .. }
| Word::MultilineString(_)
| Word::MultilineFormatString(_) => true,
Word::Modified(m) => m.operands.last().is_some_and(|w| w.value.is_end_of_line()),
_ => false,
}
}
}
impl fmt::Debug for Word {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Word::Number(s, ..) => write!(f, "{s:?}"),
Word::Char(char) => write!(f, "{char:?}"),
Word::String(string) => write!(f, "{string:?}"),
Word::MultilineString(string) => write!(f, "$ {string:?}"),
Word::FormatString(parts) => {
write!(f, "$\"")?;
for part in parts {
let escaped = format!("{part:?}");
let part = &escaped[1..escaped.len() - 1];
write!(f, "{part}")?;
}
write!(f, "\"")
}
Word::MultilineFormatString(lines) => {
for line in lines {
write!(f, "$ ")?;
for part in &line.value {
let escaped = format!("{part:?}");
let part = &escaped[1..escaped.len() - 1];
write!(f, "{part}")?;
}
}
Ok(())
}
Word::Label(label) => write!(f, "${label}"),
Word::Ref(r, chained) => {
write!(f, "ref({r}")?;
for comp in chained {
write!(f, "≈{}", comp.item)?;
}
write!(f, ")")
}
Word::IncompleteRef(path) => {
write!(f, "incomplete_ref({}~...)", path[0].module.value)
}
Word::Array(arr) => arr.fmt(f),
Word::Strand(items) => write!(f, "strand({items:?})"),
Word::Func(func) => func.fmt(f),
Word::Pack(pack) => pack.fmt(f),
Word::Primitive(prim) => prim.fmt(f),
Word::Modified(modified) => modified.fmt(f),
Word::Spaces => write!(f, "' '"),
Word::Comment(comment) => write!(f, "# {comment}"),
Word::Placeholder(Some(i)) => write!(f, "^{i}"),
Word::Placeholder(None) => write!(f, "^"),
Word::BreakLine => write!(f, "break_line"),
Word::FlipLine => write!(f, "unbreak_line"),
Word::SemanticComment(comment) => write!(f, "{comment}"),
Word::OutputComment { i, n, .. } => write!(f, "output_comment({i}/{n})"),
Word::Subscripted(sub) => sub.fmt(f),
Word::InlineMacro(InlineMacro { ident, func, .. }) => {
write!(f, "inline_macro({:?}{}))", func.value, ident.value)
}
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Ref {
pub path: Vec<RefComponent>,
pub name: Sp<Ident>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefComponent {
pub module: Sp<Ident>,
pub tilde_span: CodeSpan,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChainComponent {
pub tilde_span: CodeSpan,
pub item: Ref,
}
impl Ref {
pub fn span(&self) -> CodeSpan {
if let Some(comp) = self.path.first() {
comp.module.span.clone().merge(self.name.span.clone())
} else {
self.name.span.clone()
}
}
pub fn modifier_args(&self) -> usize {
ident_modifier_args(&self.name.value)
}
pub fn root_module(&self) -> Option<&Ident> {
self.path.first().map(|c| &c.module.value)
}
pub fn chain_refs(
self,
chained: impl IntoIterator<Item = ChainComponent>,
) -> impl Iterator<Item = Self> {
let mut prev = self.name.clone();
once(self).chain(chained.into_iter().map(move |comp| {
let mut path = vec![RefComponent {
module: prev.clone(),
tilde_span: comp.tilde_span,
}];
path.extend(comp.item.path);
let r = Ref {
path,
name: comp.item.name,
};
prev = r.name.clone();
r
}))
}
}
impl PartialEq for Ref {
fn eq(&self, other: &Self) -> bool {
self.path
.iter()
.map(|c| &c.module.value)
.eq(other.path.iter().map(|c| &c.module.value))
&& self.name.value == other.name.value
}
}
impl Eq for Ref {}
impl fmt::Debug for Ref {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for comp in &self.path {
write!(f, "{}~", comp.module.value)?;
}
write!(f, "{}", self.name.value)
}
}
impl fmt::Display for Ref {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for comp in &self.path {
write!(f, "{}~", comp.module.value)?;
}
write!(f, "{}", self.name.value)
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Arr {
pub down_span: Option<CodeSpan>,
pub lines: Vec<Item>,
pub boxes: bool,
pub closed: bool,
}
impl Arr {
pub fn word_lines(&self) -> impl DoubleEndedIterator<Item = &[Sp<Word>]> {
self.lines
.iter()
.filter_map(|line| match line {
Item::Words(words) => Some(words),
_ => None,
})
.map(|v| v.as_slice())
}
pub fn word_lines_mut(&mut self) -> impl Iterator<Item = &mut Vec<Sp<Word>>> {
self.lines.iter_mut().filter_map(|line| match line {
Item::Words(words) => Some(words),
_ => None,
})
}
}
impl fmt::Debug for Arr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_tuple("arr");
for line in &self.lines {
match line {
Item::Words(line) => {
for word in line {
d.field(&word.value);
}
if line.is_empty() {
d.field(&"newline");
}
}
item => {
d.field(item);
}
}
}
d.finish()
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct Func {
pub signature: Option<Sp<Signature>>,
pub lines: Vec<Item>,
pub closed: bool,
}
impl Func {
pub fn trimmed_lines(&self) -> &[Item] {
let mut lines = self.lines.as_slice();
while lines.first().is_some_and(Item::is_empty_line) {
lines = &lines[1..];
}
while lines.last().is_some_and(Item::is_empty_line) {
lines = &lines[..lines.len() - 1];
}
lines
}
pub fn is_multiline(&self) -> bool {
self.trimmed_lines().len() > 1
}
pub fn word_lines(&self) -> impl Iterator<Item = &[Sp<Word>]> {
self.lines
.iter()
.filter_map(|line| match line {
Item::Words(words) => Some(words),
_ => None,
})
.map(|v| v.as_slice())
}
pub fn word_lines_mut(&mut self) -> impl Iterator<Item = &mut Vec<Sp<Word>>> {
self.lines.iter_mut().filter_map(|line| match line {
Item::Words(words) => Some(words),
_ => None,
})
}
}
impl fmt::Debug for Func {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_tuple("func");
for line in &self.lines {
match line {
Item::Words(line) => {
for word in line {
d.field(&word.value);
}
if line.is_empty() {
d.field(&"newline");
}
}
item => {
d.field(item);
}
}
}
d.finish()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionPack {
pub down_span: Option<CodeSpan>,
pub is_array: Option<bool>,
pub branches: Vec<Sp<Func>>,
pub closed: bool,
}
impl FunctionPack {
pub fn lexical_order(&self) -> impl DoubleEndedIterator<Item = &Sp<Func>> {
let mut branches: Vec<_> = self.branches.iter().collect();
branches.sort_by_key(|func| func.span.start.col);
if self.down_span.is_some() {
branches.sort_by_key(|func| -(func.span.start.line as i32));
} else {
branches.sort_by_key(|func| func.span.start.line);
}
branches.into_iter()
}
pub fn into_lexical_order(self) -> impl DoubleEndedIterator<Item = Sp<Func>> {
let mut branches = self.branches;
branches.sort_by_key(|func| func.span.start.col);
if self.down_span.is_some() {
branches.sort_by_key(|func| -(func.span.start.line as i32));
} else {
branches.sort_by_key(|func| func.span.start.line);
}
branches.into_iter()
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Modified {
pub modifier: Sp<Modifier>,
pub operands: Vec<Sp<Word>>,
pub pack_expansion: bool,
}
impl Modified {
pub fn code_operands(&self) -> impl DoubleEndedIterator<Item = &Sp<Word>> {
self.operands.iter().filter(|word| word.value.is_code())
}
}
impl fmt::Debug for Modified {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.modifier.value)?;
for word in &self.operands {
write!(f, "({:?})", word.value)?;
}
Ok(())
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum Modifier {
Primitive(Primitive),
Ref(Ref),
Macro(InlineMacro),
}
impl fmt::Debug for Modifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Modifier::Primitive(prim) => prim.fmt(f),
Modifier::Ref(refer) => write!(f, "ref({refer:?})"),
Modifier::Macro(mac) => write!(f, "macro({:?}{})", mac.func, mac.ident),
}
}
}
impl fmt::Display for Modifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Modifier::Primitive(prim) => prim.format().fmt(f),
Modifier::Ref(refer) => write!(f, "{refer}"),
Modifier::Macro(mac) => match ident_modifier_args(&mac.ident.value) {
0 | 1 => write!(f, "monadic inline macro"),
2 => write!(f, "dyadic inline macro"),
3 => write!(f, "triadic inline macro"),
4 => write!(f, "tetradic inline macro"),
_ => write!(f, "inline macro"),
},
}
}
}
impl Modifier {
pub fn args(&self) -> usize {
match self {
Modifier::Primitive(prim) => prim.modifier_args().unwrap_or(0),
Modifier::Ref(r) => r.modifier_args(),
Modifier::Macro(mac) => ident_modifier_args(&mac.ident.value),
}
}
pub fn subscript_margs(&self, sub: Option<&Subscript>) -> usize {
match self {
Modifier::Primitive(prim) => prim.subscript_margs(sub).unwrap_or_else(|| self.args()),
m => m.args(),
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ArgSetter {
pub ident: Sp<Ident>,
pub colon_span: CodeSpan,
}
impl fmt::Debug for ArgSetter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:", self.ident.value)
}
}
impl ArgSetter {
pub fn span(&self) -> CodeSpan {
self.ident.span.clone().merge(self.colon_span.clone())
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Subscripted {
pub script: Sp<Subscript>,
pub word: Sp<Word>,
}
impl fmt::Debug for Subscripted {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.word.value.fmt(f)?;
write!(f, "{}", self.script.value)
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub enum NumWord {
Real(f64),
Infinity(bool),
Complex(Complex),
Err(String),
}
impl From<f64> for NumWord {
fn from(value: f64) -> Self {
Self::Real(value).normalize()
}
}
impl From<Complex> for NumWord {
fn from(value: Complex) -> Self {
Self::Complex(value)
}
}
impl From<Result<f64, String>> for NumWord {
fn from(value: Result<f64, String>) -> Self {
match value {
Ok(v) => v.into(),
Err(e) => Self::Err(e),
}
}
}
impl From<Result<Complex, String>> for NumWord {
fn from(value: Result<Complex, String>) -> Self {
match value {
Ok(v) => Self::Complex(v),
Err(e) => Self::Err(e),
}
}
}
impl NumWord {
fn normalize(self) -> Self {
match self {
Self::Real(f64::INFINITY) => Self::Infinity(false),
Self::Real(f64::NEG_INFINITY) => Self::Infinity(true),
_ => self,
}
}
pub fn map<R, C>(self, real: impl FnOnce(f64) -> R, complex: impl FnOnce(Complex) -> C) -> Self
where
C: Into<Self>,
R: Into<Self>,
{
match self {
Self::Real(r) => real(r).into(),
Self::Infinity(false) => real(f64::INFINITY).into(),
Self::Infinity(true) => real(f64::NEG_INFINITY).into(),
Self::Complex(c) => complex(c).into(),
Self::Err(e) => Self::Err(e),
}
.normalize()
}
pub fn map_with<R, C>(
self,
other: Self,
real: impl FnOnce(f64, f64) -> R,
complex: impl FnOnce(Complex, Complex) -> C,
) -> Self
where
C: Into<Self>,
R: Into<Self>,
{
match (self, other) {
(Self::Real(a), Self::Real(b)) => real(a, b).into(),
(Self::Infinity(false), b) => Self::Real(f64::INFINITY).map_with(b, real, complex),
(Self::Infinity(true), b) => Self::Real(f64::NEG_INFINITY).map_with(b, real, complex),
(a, Self::Infinity(false)) => a.map_with(Self::Real(f64::INFINITY), real, complex),
(a, Self::Infinity(true)) => a.map_with(Self::Real(f64::NEG_INFINITY), real, complex),
(Self::Complex(a), Self::Complex(b)) => complex(a, b).into(),
(Self::Real(a), Self::Complex(b)) => complex(a.into(), b).into(),
(Self::Complex(a), Self::Real(b)) => complex(a, b.into()).into(),
(Self::Err(e), _) | (_, Self::Err(e)) => Self::Err(e),
}
.normalize()
}
}
impl fmt::Debug for NumWord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl fmt::Display for NumWord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NumWord::Real(r) => write!(f, "{r}"),
NumWord::Infinity(false) => write!(f, "∞"),
NumWord::Infinity(true) => write!(f, "-∞"),
NumWord::Complex(c) => write!(f, "{c}"),
NumWord::Err(e) => write!(f, "error({e})"),
}
}
}