packages feed

happy-arbitrary-0.0.1: src/Language/Happy/Arbitrary.hs

{-# OPTIONS_GHC -Wwarn #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Strict            #-}
module Language.Happy.Arbitrary where

import           Control.Applicative  ((<|>))
import           Control.Monad.Extra  (concatMapM)
import           Data.Fix             (foldFix)
import           Data.Map             (Map)
import qualified Data.Map             as Map
import           Data.Maybe           (fromJust)
import           Data.Text            (Text)
import qualified Data.Text            as Text
import           Language.Happy.Ast   (Node, NodeF (..))
import           Language.Happy.Lexer (Lexeme, lexemeText)
import qualified Test.QuickCheck.Gen  as Gen
import           Test.QuickCheck.Gen  (Gen)

genTokens :: Text -> Node (Lexeme Text) -> Gen [Text]
genTokens start g = do
    case Map.lookup start rules of
        Nothing -> error $ "no such rule: " <> Text.unpack start
        Just r  -> expand tokens rules r
  where
    tokens = foldFix terminals g
    rules = foldFix nonterminals g

expand :: Map Text Text -> Map Text [[Text]] -> [[Text]] -> Gen [Text]
expand tokens rules r = do
    rule <- Gen.elements r
    let expanded = map (resolve tokens rules) rule
    concatMapM (continue tokens rules) expanded

continue :: Map Text Text -> Map Text [[Text]] -> Either Text [[Text]] -> Gen [Text]
continue _ _ (Left token)          = return [token]
continue tokens rules (Right rule) = expand tokens rules rule

resolve :: Map Text Text -> Map Text [[Text]] -> Text -> Either Text [[Text]]
resolve tokens rules sym =
    fromJust $ (Left <$> Map.lookup sym tokens) <|> (Right <$> Map.lookup sym rules)

terminals :: NodeF (Lexeme Text) (Map Text Text) -> Map Text Text
terminals node = case node of
    Token k v -> Map.singleton (lexemeText k) (lexemeText v)
    n         -> Map.unions n

nonterminals :: NodeF (Lexeme Text) (Map Text [[Text]]) -> Map Text [[Text]]
nonterminals node = case node of
    RuleLine syms _     -> Map.singleton "" [map lexemeText syms]
    RuleDefn name rules -> Map.singleton (lexemeText name) (merge rules)
    n                   -> Map.unionsWith (++) n
  where
    merge = concat . concatMap Map.elems