Skip to content

Commit

Permalink
Improve pattern parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
zbraniecki committed Sep 29, 2020
1 parent d8a9bc4 commit 0535b38
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 195 deletions.
27 changes: 23 additions & 4 deletions components/datetime/src/date.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::ops::{Add, Sub};
use std::str::FromStr;

#[derive(Debug)]
pub enum DateTimeError {
Parse(std::num::ParseIntError),
Overflow(&'static str),
Overflow { field: &'static str, max: usize },
}

impl fmt::Display for DateTimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Parse(err) => write!(f, "{}", err),
Self::Overflow { field, max } => write!(f, "{} must be between 0-{}", field, max),
}
}
}

impl From<std::num::ParseIntError> for DateTimeError {
Expand Down Expand Up @@ -157,7 +167,10 @@ macro_rules! dt_unit {
fn from_str(input: &str) -> Result<Self, Self::Err> {
let val: u8 = input.parse()?;
if val > $value {
Err(DateTimeError::Overflow("$name must be between 0-$value"))
Err(DateTimeError::Overflow {
field: "$name",
max: $value,
})
} else {
Ok(Self(val))
}
Expand All @@ -169,7 +182,10 @@ macro_rules! dt_unit {

fn try_from(input: u8) -> Result<Self, Self::Error> {
if input > $value {
Err(DateTimeError::Overflow("$name must be between 0-$value"))
Err(DateTimeError::Overflow {
field: "$name",
max: $value,
})
} else {
Ok(Self(input))
}
Expand All @@ -181,7 +197,10 @@ macro_rules! dt_unit {

fn try_from(input: usize) -> Result<Self, Self::Error> {
if input > $value {
Err(DateTimeError::Overflow("$name must be between 0-$value"))
Err(DateTimeError::Overflow {
field: "$name",
max: $value,
})
} else {
Ok(Self(input as u8))
}
Expand Down
33 changes: 21 additions & 12 deletions components/datetime/src/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,29 @@ pub enum FieldLength {
Six = 6,
}

impl FieldLength {
pub fn try_from(idx: usize) -> Result<Self, Error> {
Ok(match idx {
1 => Self::One,
2 => Self::TwoDigit,
3 => Self::Abbreviated,
4 => Self::Wide,
5 => Self::Narrow,
6 => Self::Six,
_ => return Err(Error::TooLong),
})
}
macro_rules! try_field_length {
($i:ty) => {
impl TryFrom<$i> for FieldLength {
type Error = Error;

fn try_from(input: $i) -> Result<Self, Self::Error> {
Ok(match input {
1 => Self::One,
2 => Self::TwoDigit,
3 => Self::Abbreviated,
4 => Self::Wide,
5 => Self::Narrow,
6 => Self::Six,
_ => return Err(Error::TooLong),
})
}
}
};
}

try_field_length!(u8);
try_field_length!(usize);

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum FieldSymbol {
Year(Year),
Expand Down
179 changes: 0 additions & 179 deletions components/datetime/src/pattern.rs

This file was deleted.

17 changes: 17 additions & 0 deletions components/datetime/src/pattern/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use crate::fields;

#[derive(Debug)]
pub enum Error {
Unknown,
FieldTooLong,
UnknownSubstitution(u8),
}

impl From<fields::Error> for Error {
fn from(input: fields::Error) -> Self {
match input {
fields::Error::Unknown => Self::Unknown,
fields::Error::TooLong => Self::FieldTooLong,
}
}
}
94 changes: 94 additions & 0 deletions components/datetime/src/pattern/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
mod error;
mod parser;

use crate::fields::{self, Field, FieldLength, FieldSymbol};
pub use error::Error;
use parser::Parser;
use std::iter::FromIterator;

#[derive(Debug, PartialEq, Clone)]
pub enum PatternItem {
Field(fields::Field),
Literal(String),
}

impl From<Field> for PatternItem {
fn from(input: Field) -> Self {
Self::Field(input)
}
}

impl From<(FieldSymbol, FieldLength)> for PatternItem {
fn from(input: (FieldSymbol, FieldLength)) -> Self {
Field {
symbol: input.0,
length: input.1,
}
.into()
}
}

impl<'p> From<&str> for PatternItem {
fn from(input: &str) -> Self {
Self::Literal(input.into())
}
}

#[derive(Default, Debug, Clone, PartialEq)]
pub struct Pattern(pub Vec<PatternItem>);

impl Pattern {
pub fn from_bytes(input: &[u8]) -> Result<Self, Error> {
Parser::new(input).parse().map(Pattern)
}

// TODO(#277): This should be turned into a utility for all ICU4X.
pub fn from_bytes_combination(
input: &[u8],
date: Pattern,
time: Pattern,
) -> Result<Self, Error> {
Parser::new(input).parse_placeholders(vec![time, date]).map(Pattern)
}
}

impl FromIterator<PatternItem> for Pattern {
fn from_iter<I: IntoIterator<Item = PatternItem>>(iter: I) -> Self {
let items: Vec<PatternItem> = iter.into_iter().collect();
Self(items)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn pattern_parse() {
assert_eq!(
Pattern::from_bytes(b"dd/MM/y").expect("Parsing pattern failed."),
vec![
(fields::Day::DayOfMonth.into(), FieldLength::TwoDigit).into(),
"/".into(),
(fields::Month::Format.into(), FieldLength::TwoDigit).into(),
"/".into(),
(fields::Year::Calendar.into(), FieldLength::One).into(),
]
.into_iter()
.collect()
);

assert_eq!(
Pattern::from_bytes(b"HH:mm:ss").expect("Parsing pattern failed."),
vec![
(fields::Hour::H23.into(), FieldLength::TwoDigit).into(),
":".into(),
(FieldSymbol::Minute, FieldLength::TwoDigit).into(),
":".into(),
(fields::Second::Second.into(), FieldLength::TwoDigit).into(),
]
.into_iter()
.collect()
);
}
}
Loading

0 comments on commit 0535b38

Please sign in to comment.