packages feed

aasam-0.2.0.0: lib/Aasam.hs

module Aasam
    ( m
    , module Grammars
    , AasamError(..)
    ) where

import Data.Function (on)
import Data.List (groupBy)
import qualified Data.List.NonEmpty as DLNe
import Data.List.NonEmpty (NonEmpty((:|)))
import Data.Set (Set, insert, union)
import qualified Data.Set as Set
import Grammars
    ( CfgProduction
    , CfgString
    , ContextFree
    , NonTerminal(..)
    , Precedence
    , PrecedenceProduction(..)
    , Terminal(..)
    )
import Util ((>.), (|>), unwrapOr)

import Data.Bifunctor (Bifunctor(bimap, second))
import Data.Data (toConstr)
import qualified Data.Foldable
import qualified Data.List as List

import qualified Data.Text as Text
import Data.Text (Text)

doGeneric :: PrecedenceProduction -> (Int -> NonEmpty Text -> a) -> a
doGeneric (Prefix prec words) f = f prec words
doGeneric (Postfix prec words) f = f prec words
doGeneric (Infixl prec words) f = f prec words
doGeneric (Infixr prec words) f = f prec words
doGeneric (Closed words) f = f 0 words

getWords :: PrecedenceProduction -> [Text]
getWords = flip doGeneric (const DLNe.toList)

prec :: PrecedenceProduction -> Int
prec = flip doGeneric const

nt :: Int -> Int -> Int -> NonTerminal
nt prec p q = (NonTerminal . Text.pack) (show prec ++ show p ++ show q)

-- TODO: write a proper implementation of this that doesn't depend on List
groupSetBy :: Ord a => (a -> a -> Bool) -> Set a -> Set (Set a)
groupSetBy projection = Set.toList >. groupBy projection >. map Set.fromList >. Set.fromList

makeClasses :: Precedence -> Set Precedence
makeClasses = groupSetBy fixeq
  where
    fixeq = on (==) toConstr
        -- equivalence relation of fixity on precedence productions

type UniquenessPair = (PrecedenceProduction, Precedence)

-- This function returns a set of upairs. A upair contains a production of a single precedence on the left,
-- and the set of all productions of that precedence on the right (including the one on the left).
classToPairSet :: Precedence -> Set UniquenessPair
classToPairSet = groupSetBy preceq >. Set.map pair
  where
    pair :: Precedence -> UniquenessPair
    pair prec = (Set.elemAt 0 prec, prec)
    preceq :: PrecedenceProduction -> PrecedenceProduction -> Bool
    preceq a b = prec a == prec b

pairifyClasses :: Set Precedence -> Set (Set UniquenessPair)
pairifyClasses = Set.map classToPairSet

type PqQuad = (Int, Int, PrecedenceProduction, Precedence)

pqboundUPair :: Set UniquenessPair -> Set UniquenessPair -> UniquenessPair -> PqQuad
pqboundUPair pre post (r, s) = (greater pre $ prec r, greater post $ prec r, r, s)
  where
    greater :: Set UniquenessPair -> Int -> Int
    greater upairs n = Set.size $ Set.filter ((n <) . prec . fst) upairs

pqboundClasses :: Set UniquenessPair -> Set UniquenessPair -> Set (Set UniquenessPair) -> Set (Set PqQuad)
pqboundClasses pre post = Set.map (Set.map (pqboundUPair pre post))

intersperseStart :: NonEmpty Text -> CfgString
intersperseStart =
    DLNe.map (Left . Terminal) >. DLNe.intersperse (Right ((NonTerminal . Text.pack) "!start")) >. DLNe.toList

fill :: Precedence -> Set CfgProduction -> Set CfgProduction
fill s cfgprods = Set.union withTerminals withoutTerminals
  where
    (left, withoutTerminals) = Set.partition hasTerminal cfgprods
      where
        hasTerminal :: CfgProduction -> Bool
        hasTerminal (_, words) = List.any isTerminal words
    isTerminal :: Either Terminal NonTerminal -> Bool
    isTerminal (Right (NonTerminal _)) = False
    isTerminal (Left (Terminal _)) = True
    withTerminals = fill' s left
        -- TODO: write a proper implementation of this composition that doesn't depend on List
      where
        fill' :: Precedence -> Set CfgProduction -> Set CfgProduction
        fill' s = Set.toList >. repeat >. zipWith reset (Set.toList s) >. concat >. Set.fromList
          where
            reset :: PrecedenceProduction -> [CfgProduction] -> [CfgProduction]
            reset pp = map (second re)
              where
                re :: CfgString -> CfgString
                re str =
                    case pp of
                        Infixl prec words -> kansas str words
                        Infixr prec words -> kansas str words
                        _ ->
                            error
                                "This is a bug in Aasam. Somehow, I got a CfgProduction that hasn't any terminals, or a Closed production."
                  where
                    kansas :: CfgString -> NonEmpty Text -> CfgString
                    kansas str words = List.head str : intersperseStart words ++ [List.last str]

-- The CE production on `closedrule` must go to a non-terminal.
-- Relevant terminals in these rules are all added by `fill`. Those added immediately in the rule bodies are just to signal to fill.
--   If an "evil" non-terminal appears anywhere in the output of a *rule fuctions, that's a bug.
prerule :: Int -> Int -> PqQuad -> Set CfgProduction
prerule p q (_, _, r, s) = fill s $ Set.singleton (nt (prec r) p q, [Right (nt (prec r - 1) (p + 1) q)])

postrule :: Int -> Int -> PqQuad -> Set CfgProduction
postrule p q (_, _, r, s) = fill s $ Set.singleton (nt (prec r) p q, [Right (nt (prec r - 1) p (q + 1))])

inlrule :: Int -> Int -> PqQuad -> Set CfgProduction
inlrule p q (_, _, r, s) = fill s $ Set.fromList [a, b]
  where
    a =
        ( nt (prec r) p q
        , [Right (nt (prec r) 0 q), Left ((Terminal . Text.pack) "evil"), Right (nt (prec r - 1) p 0)])
    b = (nt (prec r) p q, [Right (nt (prec r - 1) p q)])

inrrule :: Int -> Int -> PqQuad -> Set CfgProduction
inrrule p q (_, _, r, s) = fill s $ Set.fromList [a, b]
  where
    a =
        ( nt (prec r) p q
        , [Right (nt (prec r - 1) 0 q), Left ((Terminal . Text.pack) "evil"), Right (nt (prec r) p 0)])
    b = (nt (prec r) p q, [Right (nt (prec r - 1) p q)])

closedrule :: Set UniquenessPair -> Set UniquenessPair -> Int -> Int -> PqQuad -> Set CfgProduction
closedrule pres posts p q (_, _, r, s) = insert ae isets `union` jsets
  where
    ae = (nt 0 p q, [Right ((NonTerminal . Text.pack) "CE")])
    isets :: Set CfgProduction
    isets = foldl (flip (union . ido)) Set.empty (zip (Set.toList pres) [1 .. p])
      where
        ido :: (UniquenessPair, Int) -> Set CfgProduction
        ido ((r, s), i) =
            Set.singleton
                (nt 0 p q, intersperseStart (getWords r |> DLNe.fromList) ++ [Right (nt (prec r) (p - i) 0)])
    jsets :: Set CfgProduction
    jsets = foldl (flip (union . jdo)) Set.empty (zip (Set.toList posts) [1 .. q])
      where
        jdo :: (UniquenessPair, Int) -> Set CfgProduction
        jdo ((r, s), j) =
            Set.singleton
                (nt 0 p q, Right (nt (prec r) 0 (q - j)) : intersperseStart (getWords r |> DLNe.fromList))

convertClass :: (Int -> Int -> PqQuad -> Set CfgProduction) -> Set PqQuad -> Set CfgProduction
convertClass rule = foldl (flip (union . psets)) Set.empty
  where
    psets (pbound, qbound, r, s) = foldl (flip (union . qsets)) Set.empty [0 .. pbound]
      where
        qsets p = foldl ((. flip (rule p) (pbound, qbound, r, s)) . union) Set.empty [0 .. qbound]

convertClasses :: Set UniquenessPair -> Set UniquenessPair -> Set (Set PqQuad) -> Set CfgProduction
convertClasses pres posts = Set.map convertClassBranching >. foldl union Set.empty
  where
    convertClassBranching :: Set PqQuad -> Set CfgProduction
    convertClassBranching quads = convertClass rule quads
      where
        rule =
            case Set.elemAt 0 quads of
                (_, _, Infixl _ _, _) -> inlrule
                (_, _, Infixr _ _, _) -> inrrule
                (_, _, Prefix _ _, _) -> prerule
                (_, _, Postfix _ _, _) -> postrule
                (_, _, Closed _, _) -> closedrule pres posts

-- |The type of errors. Contains a list of strings, each of which describes an error of the input grammar.
newtype AasamError =
    AasamError [Text]
    deriving (Show, Eq, Ord)

-- |Takes a distfix precedence grammar. If there is an error, produces an 'AasamError', else produces a corresponding unambiguous context-free grammar.
--
-- All possible errors are enumerated in the documentation for 'Precedence'.
m :: Precedence -> Either AasamError ContextFree
m precg =
    if null errors
        then Right (nt highestPrecedence 0 0, assignStart (addCes prods))
        else Left (AasamError errors)
  where
    errors = foldl fn [] [positive, noInitSubseq, noInitWhole, classesPrecDisjoint, precContinue]
      where
        fn :: [Text] -> Maybe Text -> [Text]
        fn a e =
            case e of
                Nothing -> a
                Just err -> err : a
        positive =
            if all fn precg
                then Nothing
                else Just errstr
          where
            fn (Closed _) = True
            fn x = prec x > 0
            errstr = Text.pack "All precedences must be positive integers."
        noInitSubseq =
            if Set.disjoint initials subsequents
                then Nothing
                else Just errstr
          where
            (initials, subsequents) = foldl fn (Set.empty, Set.empty) precg
              where
                fn (i, s) e = (insert (head words) i, (tail words |> Set.fromList) `union` s)
                  where
                    words = getWords e
            errstr = Text.pack "No initial word may also be a subsequent word of another production."
        noInitWhole =
            if all fx precg
                then Nothing
                else Just errstr
          where
            fx x = all fy precg
              where
                fy y = getWords x `notPrefixedBy` getWords y || x == y
                  where
                    notPrefixedBy :: Eq a => [a] -> [a] -> Bool
                    notPrefixedBy [] [] = False
                    notPrefixedBy (_:_) [] = False
                    notPrefixedBy [] (_:_) = True
                    notPrefixedBy (x:xs) (y:ys) = x /= y || notPrefixedBy xs ys
            errstr =
                Text.pack "No initial sequence of words may also be the whole sequence of another production."
        classesPrecDisjoint =
            if allDisjoint precGroups
                then Nothing
                else Just errstr
          where
            allDisjoint :: Ord a => [Set a] -> Bool
            allDisjoint (x:xs) = all (Set.disjoint x) xs && allDisjoint xs
            allDisjoint [] = True
            precGroups :: [Set Int]
            precGroups = List.map (foldl (flip (insert . prec)) Set.empty) (Set.toList classes)
            errstr =
                Text.pack
                    "No precedence of a production of one fixity may also be the precedence of a production of another fixity."
        precContinue =
            if precedences == Set.fromList [lowestPrecedence .. highestPrecedence]
                then Nothing
                else Just errstr
          where
            errstr =
                Text.pack
                    "The set of precedences must be either empty or the set of integers between 1 and greatest precedence, inclusive."
    classes = makeClasses precg
    upairClasses = pairifyClasses classes
    (pre, post) = (findBy isPre, findBy isPost)
      where
        isPre clas =
            case Set.elemAt 0 clas of
                (Prefix _ _, _) -> True
                _ -> False
        isPost clas =
            case Set.elemAt 0 clas of
                (Postfix _ _, _) -> True
                _ -> False
        findBy f = unwrapOr Set.empty $ Data.Foldable.find f upairClasses
    prods = pqboundClasses pre post upairClasses |> convertClasses pre post
    addCes :: Set CfgProduction -> Set CfgProduction
    addCes = union ces
      where
        ces :: Set CfgProduction
        ces =
            Set.filter isClosed precg |>
            Set.map (\(Closed words) -> ((NonTerminal . Text.pack) "CE", intersperseStart words))
          where
            isClosed :: PrecedenceProduction -> Bool
            isClosed (Closed _) = True
            isClosed _ = False
    assignStart :: Set CfgProduction -> Set CfgProduction
    assignStart = Set.map $ bimap lhsMap rhsMap
      where
        lhsMap :: NonTerminal -> NonTerminal
        lhsMap lhs =
            if lhs == (NonTerminal . Text.pack) "!start"
                then nt highestPrecedence 0 0
                else lhs
        rhsMap = map submap
          where
            submap :: Either Terminal NonTerminal -> Either Terminal NonTerminal
            submap (Right x) = Right $ lhsMap x
            submap y = y
    (highestPrecedence, lowestPrecedence, precedences) =
        foldl
            (\(ha, la, pa) e -> (max (prec e) ha, min (prec e) la, prec e `insert` pa))
            (0, 0, Set.singleton 0)
            precg