1use std::{
2 fmt,
3 sync::{
4 Arc,
5 atomic::{AtomicBool, Ordering},
6 },
7 time::Instant,
8};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize))]
13pub struct SourceRange {
14 pub(crate) start: usize,
15 pub(crate) end: usize,
16}
17
18impl SourceRange {
19 pub const fn new(start: usize, end: usize) -> Self {
21 Self { start, end }
22 }
23 pub const fn start(self) -> usize {
25 self.start
26 }
27 pub const fn end(self) -> usize {
29 self.end
30 }
31}
32
33#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub enum AmbiguityPolicy {
37 #[default]
39 Preserve,
40 Reject,
42 Fallback,
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub enum HintKind {
50 Cardinal,
52 Digits,
54 Date,
56 Time,
58 Ordinal,
60 Roman,
62 Range,
64 Telephone,
66 Electronic,
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub struct Hint {
73 pub(crate) range: SourceRange,
74 pub(crate) kind: HintKind,
75}
76
77impl Hint {
78 pub const fn new(range: SourceRange, kind: HintKind) -> Self {
80 Self { range, kind }
81 }
82 pub const fn range(self) -> SourceRange {
84 self.range
85 }
86 pub const fn kind(self) -> HintKind {
88 self.kind
89 }
90}
91
92#[derive(Clone, Debug, Default)]
94pub struct NormalizeOptions {
95 pub ambiguity_policy: AmbiguityPolicy,
97 pub hints: Vec<Hint>,
99}
100
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize))]
104pub enum SegmentKind {
105 Verbatim,
107 Unresolved,
109 Cardinal,
111 Ordinal,
113 Decimal,
115 Digits,
117 Percent,
119 Money,
121 Unit,
123 Date,
125 Time,
127 Abbreviation,
129 Range,
131 Telephone,
133 Iban,
135 Roman,
137 Electronic,
139 Symbol,
141 Fallback,
143}
144
145#[derive(Clone, Copy, Debug, Eq, PartialEq)]
147#[cfg_attr(feature = "serde", derive(serde::Serialize))]
148pub enum FallbackClass {
149 Number,
151 Date,
153 Time,
155 Percent,
157 Quantity,
159 Abbreviation,
161 Identifier,
163 Roman,
165 Electronic,
167 Expression,
169 Symbol,
171}
172
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
175#[cfg_attr(feature = "serde", derive(serde::Serialize))]
176pub enum FallbackReason {
177 MissingIntent,
179 LeadingZeroes,
181 InvalidForm,
183 ProtectedIdentifier,
185 UnsupportedForm,
187 UnapprovedAbbreviation,
189 UnhandledSymbol,
191}
192
193#[derive(Clone, Copy, Debug, Eq, PartialEq)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize))]
196pub enum FallbackStrategy {
197 PreferredNumber,
199 PreferredDate,
201 PreferredTime,
203 SurfaceDate,
205 SurfaceTime,
207 Literal,
209 UnicodeCodePoint,
211}
212
213#[derive(Clone, Debug, Eq, PartialEq)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize))]
216pub struct FallbackDiagnostic {
217 pub(crate) range: SourceRange,
218 pub(crate) attempted_class: FallbackClass,
219 pub(crate) reason: FallbackReason,
220 pub(crate) original_category: Option<IssueCategory>,
221 pub(crate) strategy: FallbackStrategy,
222}
223
224impl FallbackDiagnostic {
225 pub const fn range(&self) -> SourceRange {
227 self.range
228 }
229 pub const fn attempted_class(&self) -> FallbackClass {
231 self.attempted_class
232 }
233 pub const fn reason(&self) -> FallbackReason {
235 self.reason
236 }
237 pub const fn original_category(&self) -> Option<IssueCategory> {
239 self.original_category
240 }
241 pub const fn strategy(&self) -> FallbackStrategy {
243 self.strategy
244 }
245}
246
247#[derive(Clone, Copy, Debug, Eq, PartialEq)]
249#[cfg_attr(feature = "serde", derive(serde::Serialize))]
250pub enum IssueCategory {
251 Ambiguous,
253 InvalidExpression,
255 ProtectedIdentifier,
257 Unsupported,
259 UnknownAbbreviation,
261}
262
263#[derive(Clone, Debug, Eq, PartialEq)]
265#[cfg_attr(feature = "serde", derive(serde::Serialize))]
266pub struct Issue {
267 pub(crate) range: SourceRange,
268 pub(crate) category: IssueCategory,
269 pub(crate) explanation: &'static str,
270}
271
272impl Issue {
273 pub const fn range(&self) -> SourceRange {
275 self.range
276 }
277 pub const fn category(&self) -> IssueCategory {
279 self.category
280 }
281 pub const fn explanation(&self) -> &'static str {
283 self.explanation
284 }
285}
286
287#[derive(Clone, Debug, Eq, PartialEq)]
289#[cfg_attr(feature = "serde", derive(serde::Serialize))]
290pub struct Segment {
291 pub(crate) range: SourceRange,
292 pub(crate) kind: SegmentKind,
293 pub(crate) text: String,
294 pub(crate) rule_id: &'static str,
295}
296
297impl Segment {
298 pub const fn range(&self) -> SourceRange {
300 self.range
301 }
302 pub const fn kind(&self) -> SegmentKind {
304 self.kind
305 }
306 pub fn text(&self) -> &str {
308 &self.text
309 }
310 pub const fn rule_id(&self) -> &'static str {
312 self.rule_id
313 }
314}
315
316#[derive(Clone, Debug, Eq, PartialEq)]
318pub struct NormalizeResult {
319 pub(crate) normalized_text: String,
320 pub(crate) locale: &'static str,
321 pub(crate) normalizer_id: &'static str,
322 pub(crate) complete: bool,
323 pub(crate) segments: Vec<Segment>,
324 pub(crate) issues: Vec<Issue>,
325 pub(crate) fallbacks: Vec<FallbackDiagnostic>,
326}
327
328impl NormalizeResult {
329 pub fn normalized_text(&self) -> &str {
331 &self.normalized_text
332 }
333 pub const fn locale(&self) -> &'static str {
335 self.locale
336 }
337 pub const fn normalizer_id(&self) -> &'static str {
339 self.normalizer_id
340 }
341 pub fn complete(&self) -> bool {
343 self.complete
344 }
345 pub fn segments(&self) -> &[Segment] {
347 &self.segments
348 }
349 pub fn issues(&self) -> &[Issue] {
351 &self.issues
352 }
353 pub fn fallbacks(&self) -> &[FallbackDiagnostic] {
355 &self.fallbacks
356 }
357 pub fn fallback_used(&self) -> bool {
359 !self.fallbacks.is_empty()
360 }
361}
362
363#[cfg(feature = "serde")]
364impl serde::Serialize for NormalizeResult {
365 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
366 where
367 S: serde::Serializer,
368 {
369 use serde::ser::SerializeStruct;
370 let mut result = serializer.serialize_struct("NormalizeResult", 8)?;
371 result.serialize_field("normalized_text", &self.normalized_text)?;
372 result.serialize_field("locale", &self.locale)?;
373 result.serialize_field("normalizer_id", &self.normalizer_id)?;
374 result.serialize_field("complete", &self.complete)?;
375 result.serialize_field("segments", &self.segments)?;
376 result.serialize_field("issues", &self.issues)?;
377 result.serialize_field("fallbacks", &self.fallbacks)?;
378 result.serialize_field("fallback_used", &self.fallback_used())?;
379 result.end()
380 }
381}
382
383#[derive(Clone, Copy, Debug, Eq, PartialEq)]
385pub enum LimitKind {
386 Input,
388 Hints,
390 Candidates,
392 Result,
394}
395
396#[derive(Clone, Debug, Eq, PartialEq)]
398pub enum NormalizeError {
399 InvalidInput,
401 InvalidHint,
403 InvalidConfiguration,
405 LimitExceeded(LimitKind),
407 Cancelled,
409 Unresolved(Vec<Issue>),
411 Internal,
413}
414
415impl fmt::Display for NormalizeError {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 let message = match self {
418 Self::InvalidInput => "input is empty, whitespace-only, or contains forbidden controls",
419 Self::InvalidHint => "hint is not a valid whole-expression original-source range",
420 Self::InvalidConfiguration => "built-in normalizer configuration is invalid",
421 Self::LimitExceeded(_) => "normalization resource limit exceeded",
422 Self::Cancelled => "normalization was cancelled or its deadline expired",
423 Self::Unresolved(_) => "strict normalization contains unresolved linguistic work",
424 Self::Internal => "normalization invariant failed",
425 };
426 f.write_str(message)
427 }
428}
429
430impl std::error::Error for NormalizeError {}
431
432#[derive(Clone, Debug, Default)]
434pub struct WorkControl {
435 cancelled: Arc<AtomicBool>,
436 deadline: Option<Instant>,
437}
438
439impl WorkControl {
440 pub fn new(deadline: Option<Instant>) -> Self {
442 Self {
443 deadline,
444 ..Self::default()
445 }
446 }
447 pub fn cancel(&self) {
449 self.cancelled.store(true, Ordering::Relaxed);
450 }
451 pub(crate) fn check(&self) -> Result<(), NormalizeError> {
452 if self.cancelled.load(Ordering::Relaxed)
453 || self
454 .deadline
455 .is_some_and(|deadline| Instant::now() >= deadline)
456 {
457 Err(NormalizeError::Cancelled)
458 } else {
459 Ok(())
460 }
461 }
462}