lib: refactor some journal, period expression parsers

This commit is contained in:
Simon Michael 2018-06-08 19:35:27 -07:00
parent 1287081ffd
commit dfcafc2cdf
4 changed files with 58 additions and 39 deletions

View File

@ -45,6 +45,7 @@ module Hledger.Data.Dates (
elapsedSeconds, elapsedSeconds,
prevday, prevday,
parsePeriodExpr, parsePeriodExpr,
parsePeriodExpr',
nulldatespan, nulldatespan,
failIfInvalidYear, failIfInvalidYear,
failIfInvalidMonth, failIfInvalidMonth,
@ -313,7 +314,13 @@ earliest (Just d1) (Just d2) = Just $ min d1 d2
-- | Parse a period expression to an Interval and overall DateSpan using -- | Parse a period expression to an Interval and overall DateSpan using
-- the provided reference date, or return a parse error. -- the provided reference date, or return a parse error.
parsePeriodExpr :: Day -> Text -> Either (ParseError Char CustomErr) (Interval, DateSpan) parsePeriodExpr :: Day -> Text -> Either (ParseError Char CustomErr) (Interval, DateSpan)
parsePeriodExpr refdate s = parsewith (periodexpr refdate <* eof) (T.toLower s) parsePeriodExpr refdate s = parsewith (periodexprp refdate <* eof) (T.toLower s)
-- | Like parsePeriodExpr, but call error' on failure.
parsePeriodExpr' :: Day -> Text -> (Interval, DateSpan)
parsePeriodExpr' refdate s =
either (error' . ("failed to parse:" ++) . parseErrorPretty) id $
parsePeriodExpr refdate s
maybePeriod :: Day -> Text -> Maybe (Interval,DateSpan) maybePeriod :: Day -> Text -> Maybe (Interval,DateSpan)
maybePeriod refdate = either (const Nothing) Just . parsePeriodExpr refdate maybePeriod refdate = either (const Nothing) Just . parsePeriodExpr refdate
@ -910,23 +917,23 @@ lastthisnextthing = do
-- Right (DayOfMonth 2,DateSpan 2009/01/01-) -- Right (DayOfMonth 2,DateSpan 2009/01/01-)
-- >>> p "every 2nd day of month 2009-" -- >>> p "every 2nd day of month 2009-"
-- Right (DayOfMonth 2,DateSpan 2009/01/01-) -- Right (DayOfMonth 2,DateSpan 2009/01/01-)
periodexpr :: Day -> SimpleTextParser (Interval, DateSpan) periodexprp :: Day -> SimpleTextParser (Interval, DateSpan)
periodexpr rdate = surroundedBy (skipMany spacenonewline) . choice $ map try [ periodexprp rdate = surroundedBy (skipMany spacenonewline) . choice $ map try [
intervalanddateperiodexpr rdate, intervalanddateperiodexprp rdate,
(,) NoInterval <$> periodexprdatespan rdate (,) NoInterval <$> periodexprdatespanp rdate
] ]
intervalanddateperiodexpr :: Day -> SimpleTextParser (Interval, DateSpan) intervalanddateperiodexprp :: Day -> SimpleTextParser (Interval, DateSpan)
intervalanddateperiodexpr rdate = do intervalanddateperiodexprp rdate = do
i <- reportinginterval i <- reportingintervalp
s <- option def . try $ do s <- option def . try $ do
skipMany spacenonewline skipMany spacenonewline
periodexprdatespan rdate periodexprdatespanp rdate
return (i,s) return (i,s)
-- Parse a reporting interval. -- Parse a reporting interval.
reportinginterval :: SimpleTextParser Interval reportingintervalp :: SimpleTextParser Interval
reportinginterval = choice' [ reportingintervalp = choice' [
tryinterval "day" "daily" Days, tryinterval "day" "daily" Days,
tryinterval "week" "weekly" Weeks, tryinterval "week" "weekly" Weeks,
tryinterval "month" "monthly" Months, tryinterval "month" "monthly" Months,
@ -1007,19 +1014,19 @@ reportinginterval = choice' [
singular' = T.pack singular singular' = T.pack singular
plural' = T.pack $ singular ++ "s" plural' = T.pack $ singular ++ "s"
periodexprdatespan :: Day -> SimpleTextParser DateSpan periodexprdatespanp :: Day -> SimpleTextParser DateSpan
periodexprdatespan rdate = choice $ map try [ periodexprdatespanp rdate = choice $ map try [
doubledatespan rdate, doubledatespanp rdate,
fromdatespan rdate, fromdatespanp rdate,
todatespan rdate, todatespanp rdate,
justdatespan rdate justdatespanp rdate
] ]
-- | -- |
-- -- >>> parsewith (doubledatespan (parsedate "2018/01/01") <* eof) "20180101-201804" -- -- >>> parsewith (doubledatespan (parsedate "2018/01/01") <* eof) "20180101-201804"
-- Right DateSpan 2018/01/01-2018/04/01 -- Right DateSpan 2018/01/01-2018/04/01
doubledatespan :: Day -> SimpleTextParser DateSpan doubledatespanp :: Day -> SimpleTextParser DateSpan
doubledatespan rdate = do doubledatespanp rdate = do
optional (string "from" >> skipMany spacenonewline) optional (string "from" >> skipMany spacenonewline)
b <- smartdate b <- smartdate
skipMany spacenonewline skipMany spacenonewline
@ -1027,8 +1034,8 @@ doubledatespan rdate = do
e <- smartdate e <- smartdate
return $ DateSpan (Just $ fixSmartDate rdate b) (Just $ fixSmartDate rdate e) return $ DateSpan (Just $ fixSmartDate rdate b) (Just $ fixSmartDate rdate e)
fromdatespan :: Day -> SimpleTextParser DateSpan fromdatespanp :: Day -> SimpleTextParser DateSpan
fromdatespan rdate = do fromdatespanp rdate = do
b <- choice [ b <- choice [
do do
string "from" >> skipMany spacenonewline string "from" >> skipMany spacenonewline
@ -1041,14 +1048,14 @@ fromdatespan rdate = do
] ]
return $ DateSpan (Just $ fixSmartDate rdate b) Nothing return $ DateSpan (Just $ fixSmartDate rdate b) Nothing
todatespan :: Day -> SimpleTextParser DateSpan todatespanp :: Day -> SimpleTextParser DateSpan
todatespan rdate = do todatespanp rdate = do
choice [string "to", string "-"] >> skipMany spacenonewline choice [string "to", string "-"] >> skipMany spacenonewline
e <- smartdate e <- smartdate
return $ DateSpan Nothing (Just $ fixSmartDate rdate e) return $ DateSpan Nothing (Just $ fixSmartDate rdate e)
justdatespan :: Day -> SimpleTextParser DateSpan justdatespanp :: Day -> SimpleTextParser DateSpan
justdatespan rdate = do justdatespanp rdate = do
optional (string "in" >> skipMany spacenonewline) optional (string "in" >> skipMany spacenonewline)
d <- smartdate d <- smartdate
return $ spanFromSmartDate rdate d return $ spanFromSmartDate rdate d

View File

@ -223,8 +223,8 @@ instance Eq Posting where
-- TODO: needs renaming, or removal if no longer needed. See also TextPosition in Hledger.UI.Editor -- TODO: needs renaming, or removal if no longer needed. See also TextPosition in Hledger.UI.Editor
-- | The position of parse errors (eg), like parsec's SourcePos but generic. -- | The position of parse errors (eg), like parsec's SourcePos but generic.
data GenericSourcePos = GenericSourcePos FilePath Int Int -- ^ name, 1-based line number and 1-based column number. data GenericSourcePos = GenericSourcePos FilePath Int Int -- ^ file path, 1-based line number and 1-based column number.
| JournalSourcePos FilePath (Int, Int) -- ^ file name, inclusive range of 1-based line numbers (first, last). | JournalSourcePos FilePath (Int, Int) -- ^ file path, inclusive range of 1-based line numbers (first, last).
deriving (Eq, Read, Show, Ord, Data, Generic, Typeable) deriving (Eq, Read, Show, Ord, Data, Generic, Typeable)
instance NFData GenericSourcePos instance NFData GenericSourcePos

View File

@ -86,7 +86,11 @@ module Hledger.Read.Common (
postingcommentp, postingcommentp,
-- ** bracketed dates -- ** bracketed dates
bracketeddatetagsp bracketeddatetagsp,
-- ** misc
singlespacedtextp,
singlespacep
) )
where where
--- * imports --- * imports
@ -193,6 +197,7 @@ rjp = runJournalParser
genericSourcePos :: SourcePos -> GenericSourcePos genericSourcePos :: SourcePos -> GenericSourcePos
genericSourcePos p = GenericSourcePos (sourceName p) (fromIntegral . unPos $ sourceLine p) (fromIntegral . unPos $ sourceColumn p) genericSourcePos p = GenericSourcePos (sourceName p) (fromIntegral . unPos $ sourceLine p) (fromIntegral . unPos $ sourceColumn p)
-- | Construct a generic start & end line parse position from start and end megaparsec SourcePos's.
journalSourcePos :: SourcePos -> SourcePos -> GenericSourcePos journalSourcePos :: SourcePos -> SourcePos -> GenericSourcePos
journalSourcePos p p' = JournalSourcePos (sourceName p) (fromIntegral . unPos $ sourceLine p, fromIntegral $ line') journalSourcePos p p' = JournalSourcePos (sourceName p) (fromIntegral . unPos $ sourceLine p, fromIntegral $ line')
where line' where line'
@ -438,19 +443,26 @@ modifiedaccountnamep = do
joinAccountNames parent joinAccountNames parent
a a
-- | Parse an account name. Account names start with a non-space, may -- | Parse an account name, plus one following space if present.
-- have single spaces inside them, and are terminated by two or more -- Account names start with a non-space, may have single spaces inside them,
-- spaces (or end of input). Also they have one or more components of -- and are terminated by two or more spaces (or end of input).
-- at least one character, separated by the account separator char. -- (Also they have one or more components of at least one character,
-- (This parser will also consume one following space, if present.) -- separated by the account separator character, but we don't check that here.)
accountnamep :: TextParser m AccountName accountnamep :: TextParser m AccountName
accountnamep = do accountnamep = singlespacedtextp
-- | Parse any text beginning with a non-whitespace character, until a double space or the end of input.
-- Consumes one of the following spaces, if present.
singlespacedtextp :: TextParser m T.Text
singlespacedtextp = do
firstPart <- part firstPart <- part
otherParts <- many $ try $ singleSpace *> part otherParts <- many $ try $ singlespacep *> part
pure $! T.unwords $ firstPart : otherParts pure $! T.unwords $ firstPart : otherParts
where where
part = takeWhile1P Nothing (not . isSpace) part = takeWhile1P Nothing (not . isSpace)
singleSpace = void spacenonewline *> notFollowedBy spacenonewline
-- | Parse one non-newline whitespace character that is not followed by another one.
singlespacep = void spacenonewline *> notFollowedBy spacenonewline
--- ** amounts --- ** amounts

View File

@ -470,7 +470,7 @@ periodictransactionp = do
transactionp :: JournalParser m Transaction transactionp :: JournalParser m Transaction
transactionp = do transactionp = do
-- ptrace "transactionp" -- ptrace "transactionp"
pos <- getPosition startpos <- getPosition
date <- datep <?> "transaction" date <- datep <?> "transaction"
edate <- optional (lift $ secondarydatep date) <?> "secondary date" edate <- optional (lift $ secondarydatep date) <?> "secondary date"
lookAhead (lift spacenonewline <|> newline) <?> "whitespace or newline" lookAhead (lift spacenonewline <|> newline) <?> "whitespace or newline"
@ -480,8 +480,8 @@ transactionp = do
(comment, tags) <- lift transactioncommentp (comment, tags) <- lift transactioncommentp
let year = first3 $ toGregorian date let year = first3 $ toGregorian date
postings <- postingsp (Just year) postings <- postingsp (Just year)
pos' <- getPosition endpos <- getPosition
let sourcepos = journalSourcePos pos pos' let sourcepos = journalSourcePos startpos endpos
return $ txnTieKnot $ Transaction 0 sourcepos date edate status code description comment tags postings "" return $ txnTieKnot $ Transaction 0 sourcepos date edate status code description comment tags postings ""
#ifdef TESTS #ifdef TESTS