use std::{
fmt,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::Instant,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SourceRange {
pub(crate) start: usize,
pub(crate) end: usize,
}
impl SourceRange {
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub const fn start(self) -> usize {
self.start
}
pub const fn end(self) -> usize {
self.end
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AmbiguityPolicy {
#[default]
Preserve,
Reject,
Fallback,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HintKind {
Cardinal,
Digits,
Date,
Time,
Ordinal,
Roman,
Range,
Telephone,
Electronic,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Hint {
pub(crate) range: SourceRange,
pub(crate) kind: HintKind,
}
impl Hint {
pub const fn new(range: SourceRange, kind: HintKind) -> Self {
Self { range, kind }
}
pub const fn range(self) -> SourceRange {
self.range
}
pub const fn kind(self) -> HintKind {
self.kind
}
}
#[derive(Clone, Debug, Default)]
pub struct NormalizeOptions {
pub ambiguity_policy: AmbiguityPolicy,
pub hints: Vec<Hint>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum SegmentKind {
Verbatim,
Unresolved,
Cardinal,
Ordinal,
Decimal,
Digits,
Percent,
Money,
Unit,
Date,
Time,
Abbreviation,
Range,
Telephone,
Iban,
Roman,
Electronic,
Symbol,
Fallback,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum FallbackClass {
Number,
Date,
Time,
Percent,
Quantity,
Abbreviation,
Identifier,
Roman,
Electronic,
Expression,
Symbol,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum FallbackReason {
MissingIntent,
LeadingZeroes,
InvalidForm,
ProtectedIdentifier,
UnsupportedForm,
UnapprovedAbbreviation,
UnhandledSymbol,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum FallbackStrategy {
PreferredNumber,
PreferredDate,
PreferredTime,
SurfaceDate,
SurfaceTime,
Literal,
UnicodeCodePoint,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FallbackDiagnostic {
pub(crate) range: SourceRange,
pub(crate) attempted_class: FallbackClass,
pub(crate) reason: FallbackReason,
pub(crate) original_category: Option<IssueCategory>,
pub(crate) strategy: FallbackStrategy,
}
impl FallbackDiagnostic {
pub const fn range(&self) -> SourceRange {
self.range
}
pub const fn attempted_class(&self) -> FallbackClass {
self.attempted_class
}
pub const fn reason(&self) -> FallbackReason {
self.reason
}
pub const fn original_category(&self) -> Option<IssueCategory> {
self.original_category
}
pub const fn strategy(&self) -> FallbackStrategy {
self.strategy
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum IssueCategory {
Ambiguous,
InvalidExpression,
ProtectedIdentifier,
Unsupported,
UnknownAbbreviation,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Issue {
pub(crate) range: SourceRange,
pub(crate) category: IssueCategory,
pub(crate) explanation: &'static str,
}
impl Issue {
pub const fn range(&self) -> SourceRange {
self.range
}
pub const fn category(&self) -> IssueCategory {
self.category
}
pub const fn explanation(&self) -> &'static str {
self.explanation
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Segment {
pub(crate) range: SourceRange,
pub(crate) kind: SegmentKind,
pub(crate) text: String,
pub(crate) rule_id: &'static str,
}
impl Segment {
pub const fn range(&self) -> SourceRange {
self.range
}
pub const fn kind(&self) -> SegmentKind {
self.kind
}
pub fn text(&self) -> &str {
&self.text
}
pub const fn rule_id(&self) -> &'static str {
self.rule_id
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NormalizeResult {
pub(crate) normalized_text: String,
pub(crate) locale: &'static str,
pub(crate) normalizer_id: &'static str,
pub(crate) complete: bool,
pub(crate) segments: Vec<Segment>,
pub(crate) issues: Vec<Issue>,
pub(crate) fallbacks: Vec<FallbackDiagnostic>,
}
impl NormalizeResult {
pub fn normalized_text(&self) -> &str {
&self.normalized_text
}
pub const fn locale(&self) -> &'static str {
self.locale
}
pub const fn normalizer_id(&self) -> &'static str {
self.normalizer_id
}
pub fn complete(&self) -> bool {
self.complete
}
pub fn segments(&self) -> &[Segment] {
&self.segments
}
pub fn issues(&self) -> &[Issue] {
&self.issues
}
pub fn fallbacks(&self) -> &[FallbackDiagnostic] {
&self.fallbacks
}
pub fn fallback_used(&self) -> bool {
!self.fallbacks.is_empty()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for NormalizeResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut result = serializer.serialize_struct("NormalizeResult", 8)?;
result.serialize_field("normalized_text", &self.normalized_text)?;
result.serialize_field("locale", &self.locale)?;
result.serialize_field("normalizer_id", &self.normalizer_id)?;
result.serialize_field("complete", &self.complete)?;
result.serialize_field("segments", &self.segments)?;
result.serialize_field("issues", &self.issues)?;
result.serialize_field("fallbacks", &self.fallbacks)?;
result.serialize_field("fallback_used", &self.fallback_used())?;
result.end()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LimitKind {
Input,
Hints,
Candidates,
Result,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum NormalizeError {
InvalidInput,
InvalidHint,
InvalidConfiguration,
LimitExceeded(LimitKind),
Cancelled,
Unresolved(Vec<Issue>),
Internal,
}
impl fmt::Display for NormalizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::InvalidInput => "input is empty, whitespace-only, or contains forbidden controls",
Self::InvalidHint => "hint is not a valid whole-expression original-source range",
Self::InvalidConfiguration => "built-in normalizer configuration is invalid",
Self::LimitExceeded(_) => "normalization resource limit exceeded",
Self::Cancelled => "normalization was cancelled or its deadline expired",
Self::Unresolved(_) => "strict normalization contains unresolved linguistic work",
Self::Internal => "normalization invariant failed",
};
f.write_str(message)
}
}
impl std::error::Error for NormalizeError {}
#[derive(Clone, Debug, Default)]
pub struct WorkControl {
cancelled: Arc<AtomicBool>,
deadline: Option<Instant>,
}
impl WorkControl {
pub fn new(deadline: Option<Instant>) -> Self {
Self {
deadline,
..Self::default()
}
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Relaxed);
}
pub(crate) fn check(&self) -> Result<(), NormalizeError> {
if self.cancelled.load(Ordering::Relaxed)
|| self
.deadline
.is_some_and(|deadline| Instant::now() >= deadline)
{
Err(NormalizeError::Cancelled)
} else {
Ok(())
}
}
}