lib: fix backtracking in periodexpr parser

- Simplify doctests for periodexpr.
- Besides consuming leading space consume ending space for periodexpr also.
- Drop implicit option (def, def) behaviour of periodexpr. I.e. disallow
  hledger reg -p '' and auto-transaction with heading just '~'.
- Slightly re-factor periodexpr.
- Ensure that reportinginterval doesn't consume trailing space.
  Useful if  we'll start disallowing periods like "every1stjan2009-".
This commit is contained in:
Mykola Orliuk 2017-11-26 04:58:53 +01:00 committed by Simon Michael
parent 8ab1911345
commit 48623b4ceb
4 changed files with 25 additions and 27 deletions

View File

@ -219,6 +219,12 @@ renderPostingCommentDates p = p { pcomment = comment' }
-- 2018/11/29 -- 2018/11/29
-- hi $1.00 -- hi $1.00
-- <BLANKLINE> -- <BLANKLINE>
-- >>> gen "2017/1"
-- 2017/01/01
-- hi $1.00
-- <BLANKLINE>
-- >>> gen ""
-- ... Failed to parse ...
-- >>> gen "weekly from 2017" -- >>> gen "weekly from 2017"
-- *** Exception: Unable to generate transactions according to "weekly from 2017" as 2017-01-01 is not a first day of the week -- *** Exception: Unable to generate transactions according to "weekly from 2017" as 2017-01-01 is not a first day of the week
-- >>> gen "monthly from 2017/5/4" -- >>> gen "monthly from 2017/5/4"

View File

@ -73,6 +73,7 @@ import Prelude ()
import Prelude.Compat import Prelude.Compat
import Control.Monad import Control.Monad
import Data.List.Compat import Data.List.Compat
import Data.Default
import Data.Maybe import Data.Maybe
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
@ -627,11 +628,6 @@ parsedate s = fromMaybe (error' $ "could not parse date \"" ++ s ++ "\"")
-- -- 2008-02-29 -- -- 2008-02-29
-- #endif -- #endif
-- | Parse a time string to a time type using the provided pattern, or
-- return the default.
_parsetimewith :: ParseTime t => String -> String -> t -> t
_parsetimewith pat s def = fromMaybe def $ parsetime defaultTimeLocale pat s
{-| {-|
Parse a date in any of the formats allowed in ledger's period expressions, Parse a date in any of the formats allowed in ledger's period expressions,
and maybe some others: and maybe some others:
@ -781,7 +777,7 @@ lastthisnextthing = do
return ("", T.unpack r, T.unpack p) return ("", T.unpack r, T.unpack p)
-- | -- |
-- >>> let p s = parsewith (periodexpr (parsedate "2008/11/26") <* eof) (T.toLower s) :: Either (ParseError Char MPErr) (Interval, DateSpan) -- >>> let p = parsePeriodExpr (parsedate "2008/11/26")
-- >>> p "from Aug to Oct" -- >>> p "from Aug to Oct"
-- Right (NoInterval,DateSpan 2008/08/01-2008/09/30) -- Right (NoInterval,DateSpan 2008/08/01-2008/09/30)
-- >>> p "aug to oct" -- >>> p "aug to oct"
@ -816,36 +812,28 @@ lastthisnextthing = do
-- Right (DayOfWeek 2,DateSpan -) -- Right (DayOfWeek 2,DateSpan -)
-- >>> p "every 2nd day of week" -- >>> p "every 2nd day of week"
-- Right (DayOfWeek 2,DateSpan -) -- Right (DayOfWeek 2,DateSpan -)
-- >>> p "every 2nd day of month"
-- Right (DayOfMonth 2,DateSpan -)
-- >>> p "every 2nd day"
-- Right (DayOfMonth 2,DateSpan -)
-- >>> p "every 2nd day 2009-" -- >>> p "every 2nd day 2009-"
-- Right (DayOfMonth 2,DateSpan 2009/01/01-) -- Right (DayOfMonth 2,DateSpan 2009/01/01-)
-- >>> p "every 2nd day of month 2009-"
-- Right (DayOfMonth 2,DateSpan 2009/01/01-)
periodexpr :: Day -> SimpleTextParser (Interval, DateSpan) periodexpr :: Day -> SimpleTextParser (Interval, DateSpan)
periodexpr rdate = choice $ map try [ periodexpr rdate = surroundedBy (many spacenonewline) . choice $ map try [
intervalanddateperiodexpr rdate, intervalanddateperiodexpr rdate,
intervalperiodexpr, (,) NoInterval <$> periodexprdatespan rdate
dateperiodexpr rdate,
(return (NoInterval,DateSpan Nothing Nothing))
] ]
intervalanddateperiodexpr :: Day -> SimpleTextParser (Interval, DateSpan) intervalanddateperiodexpr :: Day -> SimpleTextParser (Interval, DateSpan)
intervalanddateperiodexpr rdate = do intervalanddateperiodexpr rdate = do
many spacenonewline
i <- reportinginterval i <- reportinginterval
s <- option def . try $ do
many spacenonewline many spacenonewline
s <- periodexprdatespan rdate periodexprdatespan rdate
return (i,s) return (i,s)
intervalperiodexpr :: SimpleTextParser (Interval, DateSpan)
intervalperiodexpr = do
many spacenonewline
i <- reportinginterval
return (i, DateSpan Nothing Nothing)
dateperiodexpr :: Day -> SimpleTextParser (Interval, DateSpan)
dateperiodexpr rdate = do
many spacenonewline
s <- periodexprdatespan rdate
return (NoInterval, s)
-- Parse a reporting interval. -- Parse a reporting interval.
reportinginterval :: SimpleTextParser Interval reportinginterval :: SimpleTextParser Interval
reportinginterval = choice' [ reportinginterval = choice' [
@ -877,9 +865,8 @@ reportinginterval = choice' [
optOf_ "month" optOf_ "month"
return $ DayOfMonth n, return $ DayOfMonth n,
do string "every" do string "every"
many spacenonewline
let mnth = choice' [month, mon] >>= \(_,m,_) -> return (read m) let mnth = choice' [month, mon] >>= \(_,m,_) -> return (read m)
d_o_y <- makePermParser $ DayOfYear <$$> (mnth <* many spacenonewline) <||> (nth <* many spacenonewline) d_o_y <- makePermParser $ DayOfYear <$$> try (many spacenonewline *> mnth) <||> try (many spacenonewline *> nth)
optOf_ "year" optOf_ "year"
return d_o_y, return d_o_y,
do string "every" do string "every"

View File

@ -43,6 +43,8 @@ data WhichDate = PrimaryDate | SecondaryDate deriving (Eq,Show)
data DateSpan = DateSpan (Maybe Day) (Maybe Day) deriving (Eq,Ord,Data,Generic,Typeable) data DateSpan = DateSpan (Maybe Day) (Maybe Day) deriving (Eq,Ord,Data,Generic,Typeable)
instance Default DateSpan where def = DateSpan Nothing Nothing
instance NFData DateSpan instance NFData DateSpan
-- synonyms for various date-related scalars -- synonyms for various date-related scalars

View File

@ -38,6 +38,9 @@ choice' = choice . map try
choiceInState :: [StateT s (ParsecT MPErr Text m) a] -> StateT s (ParsecT MPErr Text m) a choiceInState :: [StateT s (ParsecT MPErr Text m) a] -> StateT s (ParsecT MPErr Text m) a
choiceInState = choice . map try choiceInState = choice . map try
surroundedBy :: Applicative m => m openclose -> m a -> m a
surroundedBy p = between p p
parsewith :: Parsec e Text a -> Text -> Either (ParseError Char e) a parsewith :: Parsec e Text a -> Text -> Either (ParseError Char e) a
parsewith p = runParser p "" parsewith p = runParser p ""