ebnf-bff-0.1.1.0: src/Text/EBNF/Build/Parser/Except.hs
module Text.EBNF.Build.Parser.Except where
import Text.Parsec.Pos
import Text.EBNF.SyntaxTree
import Text.EBNF.Helper
import Data.Function
import Data.List
{-
A number of exception structures for reporting
warnings or invalid structures in EBNF grammars
-}
data FailData = FailData {
failtype :: String,
description :: String,
pos :: SourcePos
} deriving (Eq)
instance Show FailData where
show fd = concat [failtype fd, " in " , show $ pos fd, ":\n", description fd]
data Report = Clean
| Warning {warnings :: [FailData]}
| Failed {failures :: [FailData]} deriving (Eq)
instance Show Report where
show Clean = "Clean Report"
show (Warning w) = intercalate "\n" . map show $ w
show (Failed f) = intercalate "\n" . map show $ f
concatReports :: [Report] -> Report
concatReports = foldl combineReports Clean
{-|
Combining reports is a symmetric operation where @Clean@s
are overridden by @Warning@s and @Failed@s, whereas @Warning@s
are overridden only by @Failed@s.
-}
combineReports :: Report -> Report -> Report
combineReports Clean Clean = Clean
combineReports Clean a = a
combineReports a Clean = combineReports Clean a
combineReports (Warning w) (Failed f) = Failed (sortBy (compare `on` pos) $ f ++ w)
combineReports (Failed f) (Warning w) = combineReports (Warning w) (Failed f)
combineReports (Failed f) (Failed f') = Failed (sortBy (compare `on` pos) $ f ++ f')
combineReports (Warning w) (Warning w') = Warning (sortBy (compare `on` pos) $ w ++ w')
{-|
Will analyse a syntax tree, returning reports to be combined
together.
-}
generateReport :: SyntaxTree -> Report
generateReport st = concatReports . map (($ st) . reporter) $ reports
reports :: [SyntaxTree -> Report]
reports = [] -- [neverTerminating]
reporter :: (SyntaxTree -> Report) -> SyntaxTree -> Report
reporter fn st = let rep = fn st
reps = map fn $ children st
in concatReports (rep:reps)
{-|
@Failed@ when a repeat sequence with only optional sequences or has
an optional sequence as it's first subsequence
@Warning@ when a repeat sequence contains a definitions list that
contains an optional sequence.
@Clean@ otherwise
-}
neverTerminating :: SyntaxTree -> Report
neverTerminating (SyntaxTree _ _ _ []) = Clean
neverTerminating st
| opInRepeat = Failed [reportf]
| hasOptionalInTail = Warning [reportw]
| otherwise = Clean
where
reportf =
FailData "failure"
"sequence will never terminate (repeat sequence only contains or favours optionals)"
(position st)
reportw =
FailData "warning"
"sequence may never terminate (contains optional sequence in a repeat sequence)"
(position st)
opInRepeat = (isRepeatSeq && isOnlyOptionals) || opIsFirstInDef
opIsFirstInDef = isRepeatSeq && ((identifier . head . children . head $ children st) == "optional sequence")
{- Optional sequence is in a repeat sequence -}
isRepeatSeq = identifier st == "repeated sequence"
hasOptionalInTail = any (\a -> identifier (sk a) == "optional sequence")
. tail' . children . head . children $ st
isOnlyOptionals = all (\a -> identifier (sk a) == "optional sequence")
. children . head . children $ st
{- skip term -> factor -> primary -}
sk :: SyntaxTree -> SyntaxTree
sk st' = st'
tail' :: [a] -> [a]
tail' [] = []
tail' a = tail a