use std::cmp::Reverse;
use std::collections::BTreeSet;
use std::fmt::{self, Debug, Display, Formatter, Write};
use std::sync::Arc;
use ecow::{eco_format, EcoString};
use serde::{Serialize, Serializer};
use crate::diag::{bail, SourceResult, StrResult};
use crate::foundations::{cast, func, scope, ty, Array, Func};
use crate::syntax::{Span, Spanned};
#[doc(inline)]
pub use typst_macros::symbols;
#[ty(scope, cast)]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Symbol(Repr);
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct SymChar(char, Option<fn() -> Func>);
#[derive(Clone, Eq, PartialEq, Hash)]
enum Repr {
Single(SymChar),
Const(&'static [(&'static str, SymChar)]),
Multi(Arc<(List, EcoString)>),
}
#[derive(Clone, Eq, PartialEq, Hash)]
enum List {
Static(&'static [(&'static str, SymChar)]),
Runtime(Box<[(EcoString, SymChar)]>),
}
impl Symbol {
pub const fn single(c: SymChar) -> Self {
Self(Repr::Single(c))
}
#[track_caller]
pub const fn list(list: &'static [(&'static str, SymChar)]) -> Self {
debug_assert!(!list.is_empty());
Self(Repr::Const(list))
}
#[track_caller]
pub fn runtime(list: Box<[(EcoString, SymChar)]>) -> Self {
debug_assert!(!list.is_empty());
Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new()))))
}
pub fn get(&self) -> char {
self.sym().char()
}
pub fn sym(&self) -> SymChar {
match &self.0 {
Repr::Single(c) => *c,
Repr::Const(_) => find(self.variants(), "").unwrap(),
Repr::Multi(arc) => find(self.variants(), &arc.1).unwrap(),
}
}
pub fn func(&self) -> StrResult<Func> {
self.sym()
.func()
.ok_or_else(|| eco_format!("symbol {self} is not callable"))
}
pub fn modified(mut self, modifier: &str) -> StrResult<Self> {
if let Repr::Const(list) = self.0 {
self.0 = Repr::Multi(Arc::new((List::Static(list), EcoString::new())));
}
if let Repr::Multi(arc) = &mut self.0 {
let (list, modifiers) = Arc::make_mut(arc);
if !modifiers.is_empty() {
modifiers.push('.');
}
modifiers.push_str(modifier);
if find(list.variants(), modifiers).is_some() {
return Ok(self);
}
}
bail!("unknown symbol modifier")
}
pub fn variants(&self) -> impl Iterator<Item = (&str, SymChar)> {
match &self.0 {
Repr::Single(c) => Variants::Single(Some(*c).into_iter()),
Repr::Const(list) => Variants::Static(list.iter()),
Repr::Multi(arc) => arc.0.variants(),
}
}
pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
let mut set = BTreeSet::new();
let modifiers = match &self.0 {
Repr::Multi(arc) => arc.1.as_str(),
_ => "",
};
for modifier in self.variants().flat_map(|(name, _)| name.split('.')) {
if !modifier.is_empty() && !contained(modifiers, modifier) {
set.insert(modifier);
}
}
set.into_iter()
}
}
#[scope]
impl Symbol {
#[func(constructor)]
pub fn construct(
span: Span,
#[variadic]
variants: Vec<Spanned<SymbolVariant>>,
) -> SourceResult<Symbol> {
let mut list = Vec::new();
if variants.is_empty() {
bail!(span, "expected at least one variant");
}
for Spanned { v, span } in variants {
if list.iter().any(|(prev, _)| &v.0 == prev) {
bail!(span, "duplicate variant");
}
list.push((v.0, SymChar::pure(v.1)));
}
Ok(Symbol::runtime(list.into_boxed_slice()))
}
}
impl Display for Symbol {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_char(self.get())
}
}
impl SymChar {
pub const fn pure(c: char) -> Self {
Self(c, None)
}
pub const fn with_func(c: char, func: fn() -> Func) -> Self {
Self(c, Some(func))
}
pub const fn char(&self) -> char {
self.0
}
pub fn func(&self) -> Option<Func> {
self.1.map(|f| f())
}
}
impl From<char> for SymChar {
fn from(c: char) -> Self {
SymChar(c, None)
}
}
impl Debug for Repr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Single(c) => Debug::fmt(c, f),
Self::Const(list) => list.fmt(f),
Self::Multi(lists) => lists.fmt(f),
}
}
}
impl Debug for List {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Static(list) => list.fmt(f),
Self::Runtime(list) => list.fmt(f),
}
}
}
impl crate::foundations::Repr for Symbol {
fn repr(&self) -> EcoString {
eco_format!("\"{}\"", self.get())
}
}
impl Serialize for Symbol {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_char(self.get())
}
}
impl List {
fn variants(&self) -> Variants<'_> {
match self {
List::Static(list) => Variants::Static(list.iter()),
List::Runtime(list) => Variants::Runtime(list.iter()),
}
}
}
pub struct SymbolVariant(EcoString, char);
cast! {
SymbolVariant,
c: char => Self(EcoString::new(), c),
array: Array => {
let mut iter = array.into_iter();
match (iter.next(), iter.next(), iter.next()) {
(Some(a), Some(b), None) => Self(a.cast()?, b.cast()?),
_ => Err("point array must contain exactly two entries")?,
}
},
}
enum Variants<'a> {
Single(std::option::IntoIter<SymChar>),
Static(std::slice::Iter<'static, (&'static str, SymChar)>),
Runtime(std::slice::Iter<'a, (EcoString, SymChar)>),
}
impl<'a> Iterator for Variants<'a> {
type Item = (&'a str, SymChar);
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Single(iter) => Some(("", iter.next()?)),
Self::Static(list) => list.next().copied(),
Self::Runtime(list) => list.next().map(|(s, c)| (s.as_str(), *c)),
}
}
}
fn find<'a>(
variants: impl Iterator<Item = (&'a str, SymChar)>,
modifiers: &str,
) -> Option<SymChar> {
let mut best = None;
let mut best_score = None;
'outer: for candidate in variants {
for modifier in parts(modifiers) {
if !contained(candidate.0, modifier) {
continue 'outer;
}
}
let mut matching = 0;
let mut total = 0;
for modifier in parts(candidate.0) {
if contained(modifiers, modifier) {
matching += 1;
}
total += 1;
}
let score = (matching, Reverse(total));
if best_score.map_or(true, |b| score > b) {
best = Some(candidate.1);
best_score = Some(score);
}
}
best
}
fn parts(modifiers: &str) -> impl Iterator<Item = &str> {
modifiers.split('.').filter(|s| !s.is_empty())
}
fn contained(modifiers: &str, m: &str) -> bool {
parts(modifiers).any(|part| part == m)
}