From 4ff3bb9d4fc5dcd38c4129b8e83b543212ee2a49 Mon Sep 17 00:00:00 2001
From: Lukas Fleischer <calcurse@cryptocrack.de>
Date: Wed, 5 Oct 2011 11:19:17 +0200
Subject: src/utils.c: Support more powerful duration strings

Add support for "1d23h42m"-like duration strings to parse_duration().

Also, switch to using a deterministic finite automaton to parse duration
strings, as things tend to get unclear.

Signed-off-by: Lukas Fleischer <calcurse@cryptocrack.de>
---
 src/utils.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 100 insertions(+), 23 deletions(-)

(limited to 'src')

diff --git a/src/utils.c b/src/utils.c
index ce4b0f7..2501d9b 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -696,50 +696,127 @@ parse_time (const char *string, unsigned *hour, unsigned *minute)
 }
 
 /*
- * Converts a duration string into minutes. Both "hh:mm" (e.g.  "23:42") and
- * "mmm" (e.g. "100") formats are accepted. Short forms like "23:" (23:00) or
- * ":45" (0:45) are allowed as well.
+ * Converts a duration string into minutes.
+ *
+ * Allowed formats (noted as regular expressions):
+ *
+ * - \d*:\d*
+ * - (\d*m|\d*h(|\d*m)|\d*d(|\d*m|\d*h(|\d*m)))
+ * - \d+
+ *
+ * "\d" is used as a placeholder for "(0|1|2|3|4|5|6|7|8|9)".
+ *
+ * Note that this function performs an additional range check on each token to
+ * ensure we do not accept semantically invalid strings such as "42:23".
  *
  * Returns 1 on success and 0 on failure.
  */
 int
 parse_duration (const char *string, unsigned *duration)
 {
+  enum {
+    STATE_INITIAL,
+    STATE_HHMM_MM,
+    STATE_DDHHMM_HH,
+    STATE_DDHHMM_MM,
+    STATE_DONE
+  } state = STATE_INITIAL;
+
   const char *p;
-  unsigned in[2] = {0, 0}, n = 0;
+  unsigned in = 0;
+  unsigned dur = 0;
 
   if (!string || *string == '\0')
     return 0;
 
-  /* parse string into in[], read up to two integers */
+  /* parse string using a simple state machine */
   for (p = string; *p; p++)
     {
-      if (*p == ':')
+      if ((*p >= '0') && (*p <= '9'))
         {
-          if ((++n) > 1)
+          if (state == STATE_DONE)
             return 0;
+          else
+            in = in * 10 + (int)(*p - '0');
         }
-      else if ((*p >= '0') && (*p <= '9'))
-        in[n] = in[n] * 10 + (int)(*p - '0');
       else
-        return 0;
-    }
+        {
+          switch (state)
+            {
+            case STATE_INITIAL:
+              if (*p == ':')
+                {
+                  dur += in * HOURINMIN;
+                  state = STATE_HHMM_MM;
+                }
+              else if (*p == 'd')
+                {
+                  dur += in * DAYINMIN;
+                  state = STATE_DDHHMM_HH;
+                }
+              else if (*p == 'h')
+                {
+                  if (in >= DAYINHOURS)
+                    return 0;
+                  dur += in * HOURINMIN;
+                  state = STATE_DDHHMM_MM;
+                }
+              else if (*p == 'm')
+                {
+                  if (in >= HOURINMIN)
+                    return 0;
+                  dur += in;
+                  state = STATE_DONE;
+                }
+              else
+                return 0;
+              break;
+            case STATE_DDHHMM_HH:
+              if (*p == 'h')
+                {
+                  if (in >= DAYINHOURS)
+                    return 0;
+                  dur += in * HOURINMIN;
+                  state = STATE_DDHHMM_MM;
+                }
+              else if (*p == 'm')
+                {
+                  if (in >= HOURINMIN)
+                    return 0;
+                  dur += in;
+                  state = STATE_DONE;
+                }
+              else
+                return 0;
+              break;
+            case STATE_DDHHMM_MM:
+              if (*p == 'm')
+                {
+                  if (in >= HOURINMIN)
+                    return 0;
+                  dur += in;
+                  state = STATE_DONE;
+                }
+              else
+                return 0;
+              break;
+            case STATE_HHMM_MM:
+            case STATE_DONE:
+              return 0;
+              break;
+            }
 
-  if (n == 0)
-    {
-      if (in[0] > 999)
-        return 0;
-      *duration = in[0];
-    }
-  else if (n == 1)
-    {
-      if (in[0] > DAYINHOURS || in[1] >= HOURINMIN)
-        return 0;
-      *duration = in[0] * HOURINMIN + in[1];
+          in = 0;
+        }
     }
-  else
+
+  if ((state == STATE_HHMM_MM && in >= HOURINMIN) ||
+      ((state == STATE_DDHHMM_HH || state == STATE_DDHHMM_MM) && in > 0))
     return 0;
 
+  dur += in;
+  *duration = dur;
+
   return 1;
 }
 
-- 
cgit v1.2.3-70-g09d2