use std::fmt::{Display, Write};
use std::str::FromStr;
use codex::numeral_systems::{NamedNumeralSystem, RepresentationError};
use comemo::Tracked;
use ecow::{EcoString, EcoVec};
use typst_syntax::Span;
use crate::diag::{At, SourceResult, StrResult, bail, warning};
use crate::engine::Engine;
use crate::foundations::{Context, Func, Str, Value, cast, func};
#[func]
pub fn numbering(
engine: &mut Engine,
context: Tracked<Context>,
span: Span,
numbering: Numbering,
#[variadic]
numbers: Vec<u64>,
) -> SourceResult<Value> {
numbering.apply(engine, context, span, &numbers)
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Numbering {
Pattern(NumberingPattern),
Func(Func),
}
impl Numbering {
pub fn apply(
&self,
engine: &mut Engine,
context: Tracked<Context>,
span: Span,
numbers: &[u64],
) -> SourceResult<Value> {
Ok(match self {
Self::Pattern(pattern) => {
Value::Str(pattern.apply(Some((engine, span)), numbers).at(span)?.into())
}
Self::Func(func) => func.call(engine, context, numbers.iter().copied())?,
})
}
pub fn trimmed(mut self) -> Self {
if let Self::Pattern(pattern) = &mut self {
pattern.trimmed = true;
}
self
}
}
impl From<NumberingPattern> for Numbering {
fn from(pattern: NumberingPattern) -> Self {
Self::Pattern(pattern)
}
}
cast! {
Numbering,
self => match self {
Self::Pattern(pattern) => pattern.into_value(),
Self::Func(func) => func.into_value(),
},
v: NumberingPattern => Self::Pattern(v),
v: Func => Self::Func(v),
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct NumberingPattern {
pub pieces: EcoVec<(EcoString, NamedNumeralSystem)>,
pub suffix: EcoString,
trimmed: bool,
}
impl NumberingPattern {
pub fn apply(
&self,
warning_context: Option<(&mut Engine, Span)>,
numbers: &[u64],
) -> StrResult<EcoString> {
if let Some((engine, span)) = warning_context {
self.apply_with(numbers, |system, n| {
Ok(apply_system_with_fallback(engine, span, system, n))
})
} else {
self.apply_with(numbers, apply_system)
}
}
fn apply_with<D: Display>(
&self,
numbers: &[u64],
mut apply_system: impl FnMut(NamedNumeralSystem, u64) -> StrResult<D>,
) -> StrResult<EcoString> {
let mut fmt = EcoString::new();
let mut numbers = numbers.iter();
for (i, ((prefix, system), &n)) in
self.pieces.iter().zip(&mut numbers).enumerate()
{
if i > 0 || !self.trimmed {
fmt.push_str(prefix);
}
write!(fmt, "{}", apply_system(*system, n)?).unwrap();
}
for ((prefix, system), &n) in self.pieces.last().into_iter().cycle().zip(numbers)
{
if prefix.is_empty() {
fmt.push_str(&self.suffix);
} else {
fmt.push_str(prefix);
}
write!(fmt, "{}", apply_system(*system, n)?).unwrap();
}
if !self.trimmed {
fmt.push_str(&self.suffix);
}
Ok(fmt)
}
pub fn apply_kth(
&self,
engine: &mut Engine,
span: Span,
k: usize,
number: u64,
) -> EcoString {
let mut fmt = EcoString::new();
if let Some((prefix, _)) = self.pieces.first() {
fmt.push_str(prefix);
}
if let Some((_, system)) = self
.pieces
.iter()
.chain(self.pieces.last().into_iter().cycle())
.nth(k)
{
let represented_number =
apply_system_with_fallback(engine, span, *system, number);
write!(fmt, "{represented_number}").unwrap()
}
fmt.push_str(&self.suffix);
fmt
}
pub fn pieces(&self) -> usize {
self.pieces.len()
}
}
fn apply_system(system: NamedNumeralSystem, number: u64) -> StrResult<impl Display> {
match system.system().represent(number) {
Ok(represented) => Ok(represented),
Err(RepresentationError::Zero) => {
bail!("the numeral system `{}` cannot represent zero", system.name())
}
Err(RepresentationError::TooLarge) => {
bail!(
"the number {} is too large to be represented with the `{}` numeral system",
number,
system.name(),
)
}
}
}
fn apply_system_with_fallback(
engine: &mut Engine,
span: Span,
system: NamedNumeralSystem,
number: u64,
) -> impl Display + use<> {
apply_system(system, number).unwrap_or_else(|err| {
engine.sink.warn(warning!(
span,
"{err}";
hint: "this will become a hard error in the future";
));
apply_system(NamedNumeralSystem::Arabic, number)
.unwrap_or_else(|_| panic!("`arabic` should be able to represent {number}"))
})
}
impl FromStr for NumberingPattern {
type Err = &'static str;
fn from_str(pattern: &str) -> Result<Self, Self::Err> {
let mut pieces = EcoVec::new();
let mut handled = 0;
for (i, c) in pattern.char_indices() {
let Some(kind) =
NamedNumeralSystem::from_shorthand(c.encode_utf8(&mut [0; 4]))
else {
continue;
};
let prefix = pattern[handled..i].into();
pieces.push((prefix, kind));
handled = c.len_utf8() + i;
}
let suffix = pattern[handled..].into();
if pieces.is_empty() {
return Err("invalid numbering pattern");
}
Ok(Self { pieces, suffix, trimmed: false })
}
}
cast! {
NumberingPattern,
self => {
let mut pat = EcoString::new();
for (prefix, system) in &self.pieces {
pat.push_str(prefix);
pat.push_str(
system
.shorthand()
.expect("it is not possible to construct numbering systems that don't have a shorthand within Typst for now"),
);
}
pat.push_str(&self.suffix);
pat.into_value()
},
v: Str => v.parse()?,
}