enum State
{
Start,
Number,
Unit
}
struct StateMachine
{
state : State,
negative : bool,
total : i64,
current : Option<i64>,
unit : String
}
impl StateMachine
{
pub fn new() -> StateMachine
{
StateMachine {
state : State::Start,
negative : false,
total : 0,
current : None,
unit : String::new()
}
}
fn incorporate_current(
&mut self
) -> bool
{
if let Some(current) = self.current.take()
{
let by = match self.unit.as_str()
{
"d" => 24 * 60 * 60 * 1000,
"h" => 60 * 60 * 1000,
"m" => 60 * 1000,
"s" => 1000,
"ms" => 1,
_ => return false
};
if self.negative == false
{
self.total = current.saturating_mul(by).saturating_add(self.total);
}
else
{
self.total = self.total.saturating_sub(current.saturating_mul(by));
}
self.unit.clear();
true
}
else
{
false
}
}
fn incorporate_val(
&mut self,
by : char
)
{
let digit = by.to_digit(10).unwrap() as i64;
if let Some(current) = self.current
{
self.current = Some(current.saturating_mul(10).saturating_add(digit));
}
else
{
self.current = Some(digit);
}
}
fn start(
&mut self,
ch : char
) -> bool
{
if ch == '-'
{
self.negative = true;
self.state = State::Number;
true
}
else if ch == ' '
{
true
}
else if ch.is_digit(10) == true
{
self.incorporate_val(ch);
self.state = State::Number;
true
}
else
{
false
}
}
fn number(
&mut self,
ch : char
) -> bool
{
if ch.is_digit(10) == true
{
self.incorporate_val(ch);
}
else if ch != ' '
{
self.unit.push(ch);
self.state = State::Unit;
}
true
}
fn unit(
&mut self,
ch : char
) -> bool
{
if ch == ' '
{
if self.incorporate_current() == true
{
self.state = State::Number;
true
}
else
{
false
}
}
else if ch.is_digit(10)
{
if self.incorporate_current()
{
self.incorporate_val(ch);
self.state = State::Number;
true
}
else
{
false
}
}
else
{
self.unit.push(ch);
true
}
}
fn process(
&mut self,
ch : char
) -> bool
{
match self.state
{
State::Start => self.start(ch),
State::Number => self.number(ch),
State::Unit => self.unit(ch)
}
}
pub fn decode<T : AsRef<str>>(
mut self,
value : T
) -> Option<i64>
{
for ch in value.as_ref().chars()
{
if self.process(ch) == false
{
return None;
}
}
if (self.current.is_some() || self.unit.len() > 0) && self.incorporate_current()
{
Some(self.total)
}
else
{
None
}
}
}
pub fn process<T : AsRef<str>>(
value : T
) -> Option<i64>
{
let decoder = StateMachine::new();
decoder.decode(value)
}
#[cfg(test)]
mod tests
{
fn verify(
neg : bool,
days : i64,
hours : i64,
minutes : i64,
seconds : i64,
milli : i64
) -> i64
{
if neg
{
days.saturating_mul(-24 * 60 * 60 * 1000)
.saturating_sub(hours.saturating_mul(60 * 60 * 1000))
.saturating_sub(minutes.saturating_mul(60 * 1000))
.saturating_sub(seconds.saturating_mul(1000))
.saturating_sub(milli)
}
else
{
days.saturating_mul(24 * 60 * 60 * 1000)
.saturating_add(hours.saturating_mul(60 * 60 * 1000))
.saturating_add(minutes.saturating_mul(60 * 1000))
.saturating_add(seconds.saturating_mul(1000))
.saturating_add(milli)
}
}
#[test]
fn days()
{
assert_eq!(super::process("4d").unwrap(), verify(false, 4, 0, 0, 0, 0));
assert_eq!(super::process("-8d").unwrap(), verify(true, 8, 0, 0, 0, 0));
}
#[test]
fn hours()
{
assert_eq!(super::process("4h").unwrap(), verify(false, 0, 4, 0, 0, 0));
assert_eq!(super::process("-8h").unwrap(), verify(true, 0, 8, 0, 0, 0));
}
#[test]
fn minutes()
{
assert_eq!(super::process("4m").unwrap(), verify(false, 0, 0, 4, 0, 0));
assert_eq!(super::process("-8m").unwrap(), verify(true, 0, 0, 8, 0, 0));
}
#[test]
fn seconds()
{
assert_eq!(super::process("4s").unwrap(), verify(false, 0, 0, 0, 4, 0));
assert_eq!(super::process("-8s").unwrap(), verify(true, 0, 0, 0, 8, 0));
}
#[test]
fn milliseconds()
{
assert_eq!(super::process("4ms").unwrap(), verify(false, 0, 0, 0, 0, 4));
assert_eq!(super::process("-8ms").unwrap(), verify(true, 0, 0, 0, 0, 8));
}
#[test]
fn combo()
{
assert_eq!(super::process("12d 10h 8m 6s 4ms").unwrap(), verify(false, 12, 10, 8, 6, 4));
assert_eq!(super::process("-12d 10h 8m 6s 4ms").unwrap(), verify(true, 12, 10, 8, 6, 4));
assert_eq!(super::process("12d 4ms").unwrap(), verify(false, 12, 0, 0, 0, 4));
assert_eq!(super::process("-10h 6s").unwrap(), verify(true, 0, 10, 0, 6, 0));
}
#[test]
fn spaces()
{
assert_eq!(super::process(" -6s3ms").unwrap(), verify(true, 0, 0, 0, 6, 3));
assert_eq!(super::process("12d10h8m6s4ms").unwrap(), verify(false, 12, 10, 8, 6, 4));
assert_eq!(super::process("12 d 4 ms").unwrap(), verify(false, 12, 0, 0, 0, 4));
assert_eq!(super::process("- 10h 6 s").unwrap(), verify(true, 0, 10, 0, 6, 0));
}
#[test]
fn bad()
{
assert!(super::process("abc").is_none());
assert!(super::process("123").is_none());
assert!(super::process("123u").is_none());
assert!(super::process("12h 123").is_none());
assert!(super::process("12u 123m").is_none());
assert!(super::process("12u123m").is_none());
assert!(super::process("h0").is_none());
assert!(super::process("-3").is_none());
assert!(super::process("-h").is_none());
assert!(super::process("h").is_none());
assert!(super::process("-3h-6m").is_none());
assert!(super::process("-3h m").is_none());
}
}