feat(messages): implement custom parser for message files

This commit is contained in:
Gregor Kleen 2021-03-06 20:13:55 +01:00
parent f50665b08c
commit bb877eb813
8 changed files with 409 additions and 10 deletions

View File

@ -17,7 +17,7 @@ BtnExamSwitchOccurrence: Zu Prüfungstermin/-raum wechseln
BtnExamDeregister: Von der Prüfung abmelden
BtnHijack: Sitzung übernehmen
BtnSave: Speichern
PressSaveToSave: Änderungen werden erst durch Drücken des Knopfes "Speichern" gespeichert.
PressSaveToSave: Änderungen werden erst durch Drücken des Knopfes „_{MsgBtnSave}“ gespeichert.
BtnHandIn: Abgeben
BtnNameCandidatesInfer: Studienfach-Namens-Zuordnung automatisch lernen
BtnNameCandidatesDeleteConflicts: Namenskonflikte löschen
@ -151,7 +151,7 @@ CourseMemberOf: Teilnehmer von
CourseAssociatedWith: assoziiert mit
CourseMembersCount n@Int: #{n}
CourseMembersCountLimited n@Int max@Int: #{n}/#{max}
CourseMembersCountOf n@Int mbNum@IntMaybe: #{n} Kursanmeldungen #{maybeToMessage " von " mbNum " möglichen"}
CourseMembersCountOf n@Int mbNum@(Maybe Int): #{n} Kursanmeldungen #{maybeToMessage " von " mbNum " möglichen"}
CourseName: Kurstitel
CourseDescription: Beschreibung
CourseHomepageExternal: Externe Homepage
@ -2146,8 +2146,8 @@ CsvImportExplanationLabel: Hinweise zum CSV-Import
CsvExampleData: Beispiel-Datei
CsvExportExample: Beispiel-CSV exportieren
Proportion c@Text of@Text prop@Rational: #{c}/#{of} (#{rationalToFixed2 (100 * prop)}%)
ProportionNoRatio c@Text of@Text: #{c}/#{of}
Proportion c@Text of'@Text prop@Rational: #{c}/#{of'} (#{rationalToFixed2 (100 * prop)}%)
ProportionNoRatio c@Text of'@Text: #{c}/#{of'}
CourseUserCsvName tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-teilnehmer
ExamUserCsvName tid@TermId ssh@SchoolId csh@CourseShorthand examn@ExamName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase examn}-teilnehmer

View File

@ -2145,8 +2145,8 @@ CsvImportExplanationLabel: Informating regarding CSV import
CsvExampleData: Example data
CsvExportExample: Export example CSV
Proportion c of prop: #{c}/#{of} (#{rationalToFixed2 (100 * prop)}%)
ProportionNoRatio c of: #{c}/#{of}
Proportion c of' prop: #{c}/#{of'} (#{rationalToFixed2 (100 * prop)}%)
ProportionNoRatio c of': #{c}/#{of'}
CourseUserCsvName tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-participants
ExamUserCsvName tid ssh csh examn: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase examn}-participants

View File

@ -161,6 +161,7 @@ dependencies:
- psqueues
- nonce
- IntervalMap
- haskell-src-meta
other-extensions:
- GeneralizedNewtypeDeriving
- IncoherentInstances

View File

@ -52,6 +52,8 @@ import qualified Data.Scientific as Scientific
import Utils.Workflow (RouteWorkflowScope)
import Foundation.I18n.TH
pluralDE :: (Eq a, Num a)
=> a -- ^ Count
@ -123,16 +125,13 @@ ordinalEN (toMessage -> numStr) = case lastChar of
lastChar = last <$> fromNullable numStr
-- Convenience Type for Messages, since Yesod messages cannot deal with compound type identifiers
type IntMaybe = Maybe Int
-- | Convenience function for i18n messages definitions
maybeToMessage :: ToMessage m => Text -> Maybe m -> Text -> Text
maybeToMessage _ Nothing _ = mempty
maybeToMessage before (Just x) after = before <> toMessage x <> after
-- Messages creates type UniWorXMessage and RenderMessage UniWorX instance
mkMessage "UniWorX" "messages/uniworx" "de-de-formal"
mkMessage ''UniWorX "messages/uniworx" "de-de-formal"
mkMessageVariant "UniWorX" "Campus" "messages/campus" "de"
mkMessageVariant "UniWorX" "Dummy" "messages/dummy" "de"
mkMessageVariant "UniWorX" "PWHash" "messages/pw-hash" "de"

305
src/Foundation/I18n/TH.hs Normal file
View File

@ -0,0 +1,305 @@
{-# LANGUAGE UndecidableInstances #-}
module Foundation.I18n.TH
( mkMessage
) where
import Import.NoFoundation
import qualified Language.Haskell.TH.Lib as TH
import qualified Language.Haskell.TH.Syntax as TH
import qualified System.Directory.Tree as DirTree
import Data.HashMap.Strict.InsOrd (InsOrdHashMap)
import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
import Data.HashSet.InsOrd (InsOrdHashSet)
import qualified Data.HashSet.InsOrd as InsOrdHashSet
import qualified Data.Foldable as F
import qualified Data.HashMap.Strict as HashMap
import qualified Data.HashSet as HashSet
import qualified Data.Map.Strict as Map
import qualified Text.Parsec as P
import qualified Text.Parsec.Text.Lazy as P
import qualified Language.Haskell.Meta.Parse as Meta
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Text as Text
import Utils.TH.AlphaConversion (alphaConvE)
newtype MsgFile f g = MsgFile
{ msgFileContent :: InsOrdHashMap String (f (MsgDef f g))
} deriving (Generic, Typeable)
deriving stock instance Eq (f (MsgDef f g)) => Eq (MsgFile f g)
deriving stock instance Show (f (MsgDef f g)) => Show (MsgFile f g)
instance Semigroup (f (MsgDef f g)) => Monoid (MsgFile f g) where
mempty = MsgFile InsOrdHashMap.empty
instance Semigroup (f (MsgDef f g)) => Semigroup (MsgFile f g) where
MsgFile a <> MsgFile b = MsgFile $ InsOrdHashMap.unionWith (<>) a b
data MsgDef f g = MsgDef
{ msgDefVars :: InsOrdHashMap String (f (g TH.Type))
, msgDefContent :: [MsgDefContent]
} deriving (Generic, Typeable)
deriving stock instance Eq (f (g TH.Type)) => Eq (MsgDef f g)
deriving stock instance Show (f (g TH.Type)) => Show (MsgDef f g)
data MsgDefContent = MsgDefContentLiteral String
| MsgDefContentSplice Bool {- Recurse? -} TH.Exp
deriving (Eq, Ord, Show, Generic, Typeable)
disambiguateMsgFile :: MsgFile NonEmpty Maybe -> Either (InsOrdHashSet String, InsOrdHashMap String (InsOrdHashSet String)) (MsgFile Identity Maybe)
disambiguateMsgFile MsgFile{..}
| not (InsOrdHashSet.null duplicateDefs) || not (InsOrdHashMap.null duplicateVars)
= Left (duplicateDefs, duplicateVars)
| otherwise
= Right $ MsgFile{ msgFileContent = fmap msgDefToSingletons <$> toSingletons msgFileContent, .. }
where
toDuplicates :: forall k v. (Eq k, Hashable k) => InsOrdHashMap k (NonEmpty v) -> InsOrdHashSet k
toDuplicates = InsOrdHashSet.fromList . InsOrdHashMap.keys . InsOrdHashMap.filter (minLength 2)
duplicateDefs = toDuplicates msgFileContent
duplicateVars = InsOrdHashMap.mapMaybe (assertM' (not . InsOrdHashSet.null) . toDuplicates . msgDefVars . NonEmpty.head) msgFileContent
toSingletons :: forall k v. InsOrdHashMap k (NonEmpty v) -> InsOrdHashMap k (Identity v)
toSingletons = InsOrdHashMap.map $ \case
x NonEmpty.:| [] -> Identity x
xs -> error $ "toSingletons: Expected length 1, but got: " <> show (NonEmpty.length xs)
msgDefToSingletons :: MsgDef NonEmpty Maybe -> MsgDef Identity Maybe
msgDefToSingletons MsgDef{..} = MsgDef
{ msgDefVars = toSingletons msgDefVars
, ..
}
ensureTypesMsgFile :: MsgFile Identity Maybe -> Either (InsOrdHashMap String (InsOrdHashSet String)) (MsgFile Identity Identity)
ensureTypesMsgFile MsgFile{..}
| not $ InsOrdHashMap.null untyped
= Left untyped
| otherwise
= Right $ MsgFile{ msgFileContent = over _Wrapped msgDefToTyped <$> msgFileContent, .. }
where
untyped = InsOrdHashMap.mapMaybe (assertM' (not . InsOrdHashSet.null) . InsOrdHashSet.fromList . InsOrdHashMap.keys . InsOrdHashMap.filter (is _Nothing . runIdentity) . msgDefVars . runIdentity) msgFileContent
msgDefToTyped MsgDef{..} = MsgDef
{ msgDefVars = flip InsOrdHashMap.map msgDefVars $ \case
Identity (Just x) -> Identity $ Identity x
_other -> error "msgDefToTyped got Nothing"
, ..
}
accumInsOrdHashMap :: (Foldable f, Eq k, Hashable k)
=> f (k, v)
-> InsOrdHashMap k (NonEmpty v)
accumInsOrdHashMap = F.foldl' (\acc (k, v) -> InsOrdHashMap.insertWith (<>) k (pure v) acc) InsOrdHashMap.empty
unionsInsOrdHashMap :: (Foldable f, Eq k, Hashable k)
=> f (InsOrdHashMap k (NonEmpty v))
-> InsOrdHashMap k (NonEmpty v)
unionsInsOrdHashMap = F.foldl' (InsOrdHashMap.unionWith (<>)) InsOrdHashMap.empty
insOrdHashMapKeysSet :: InsOrdHashMap k v -> HashSet k
insOrdHashMapKeysSet = HashMap.keysSet . InsOrdHashMap.toHashMap
mkMessage :: TH.Name -- ^ Foundation type
-> FilePath -- ^ Base directory of translation files
-> Lang -- ^ Default translation language
-> TH.DecsQ
mkMessage dt folder lang = mkMessageCommon True "Msg" "Message" dt (TH.nameBase dt) folder lang
mkMessageCommon :: Bool -- ^ Generate new datatype
-> String -- ^ String to prepend to constructor names
-> String -- ^ String to append to datatype name
-> TH.Name -- ^ Name of master datatype
-> String -- ^ Basename of translation datatype
-> FilePath -- ^ Base directory of translation files
-> Lang -- ^ Default translation language
-> TH.DecsQ
mkMessageCommon genType prefix postfix master dt folder defLang = do
files <- fmap DirTree.zipPaths . liftIO $ DirTree.readDirectoryWith (runExceptT . parseMsgFile) folder
forMOf_ (folded . _1) files TH.addDependentFile
let (errors, successes) = flip (foldMapOf $ folded . _2) files $ \case
Left err -> ([err], mempty)
Right (lang, x) -> (mempty, MergeHashMap $ HashMap.singleton lang x)
unless (null errors) . fail $ "Errors occurred while parsing message files:\n" <> indent 2 (unlines $ map show errors)
let (ambiguous, disambiguated) = flip ifoldMap successes $ \lang x -> case disambiguateMsgFile x of
Left errs -> ([(lang, errs)], mempty)
Right x' -> (mempty, HashMap.singleton lang x')
ambiguousError (lang, (ambigDefs, ambigVars)) = "Language " <> unpack lang <> ":\n" <> unlines errs
where
errs = bool (pure $ "Duplicate message definitions:\n" <> indent 1 (unlines ambigDefsErrs)) [] (InsOrdHashSet.null ambigDefs)
++ bool (pure $ "Duplicate variable names:\n" <> indent 1 (unlines ambigVarsErrs)) [] (InsOrdHashMap.null ambigVars)
ambigDefsErrs = InsOrdHashSet.toList ambigDefs
ambigVarsErrs = map (\(defn, InsOrdHashSet.toList -> vars) -> defn <> ": " <> intercalate ", " vars) $ InsOrdHashMap.toList ambigVars
unless (null ambiguous) . fail . indent' 2 . unlines $ map ambiguousError ambiguous
defMsgFile <- case HashMap.lookup defLang disambiguated of
Nothing -> fail $ "Default language (" <> unpack defLang <> ") not found; found instead: " <> intercalate ", " (unpack <$> HashMap.keys disambiguated)
Just x -> return x
let allDefns = insOrdHashMapKeysSet $ msgFileContent defMsgFile
extraDefns = flip HashMap.mapMaybe disambiguated $ \MsgFile{..} -> assertM' (not . HashSet.null) $ insOrdHashMapKeysSet msgFileContent `HashSet.difference` allDefns
extraDefnsErrs = flip map (HashMap.toList extraDefns) $ \(lang, extra) -> "Language " <> unpack lang <> ":\n" <> indent 1 (intercalate ", " $ HashSet.toList extra)
unless (null extraDefns) . fail $ "Extraneous message definitions:\n" <> indent 2 (unlines extraDefnsErrs)
let defnName defn = TH.mkName $ prefix <> defn
datName = TH.mkName $ dt <> postfix
execWriterT @_ @[TH.Dec] $ do
when genType $ do
typedDefMsgFile <- case ensureTypesMsgFile defMsgFile of
Left untypedVars -> fail $ "Default language (" <> unpack defLang <> ") contains untyped variables:\n" <> indent 2 (unlines . flip map (InsOrdHashMap.toList $ InsOrdHashSet.toList <$> untypedVars) $ \(defn, vs) -> defn <> ": " <> intercalate ", " vs)
Right x -> return x
let
datCons :: [TH.ConQ]
datCons = flip foldMap (InsOrdHashMap.toList $ msgFileContent typedDefMsgFile) $ \(defn, Identity MsgDef{ msgDefVars }) ->
pure . TH.normalC (defnName defn) . flip foldMap (InsOrdHashMap.toList msgDefVars) $ \(_, Identity (Identity varT)) ->
pure . TH.bangType (TH.bang TH.noSourceUnpackedness TH.sourceStrict) $ return varT
tellMPoint $ TH.dataD (TH.cxt []) datName [] Nothing datCons []
renderLangs <- iforM disambiguated $ \lang MsgFile{..} -> do
let missing = allDefns `HashSet.difference` insOrdHashMapKeysSet msgFileContent
complete = HashSet.null missing
unless complete $
lift . TH.reportWarning $ "Language " <> unpack lang <> " is not complete, missing:\n" <> indent 2 (unlines $ HashSet.toList missing)
funName <- lift $ newUniqueName "renderLang"
tellMPoint $ TH.sigD funName [t| $(TH.conT master) -> [Lang] -> $(TH.conT datName) -> $(bool [t|Maybe Text|] [t|Text|] complete) |]
masterN <- lift $ TH.newName "_master"
langsN <- lift $ TH.newName "_langs"
let
lamExp :: TH.ExpQ
lamExp = TH.lamCaseE $ bool (++ [TH.match TH.wildP (TH.normalB [e|Nothing|]) []]) id complete matches
where matches :: [TH.MatchQ]
matches = flip map (InsOrdHashMap.toList msgFileContent) $ \(defn, Identity MsgDef{..}) -> do
varns <- flip foldMapM (InsOrdHashMap.toList msgDefVars) $ \(varn, Identity mType) -> InsOrdHashMap.singleton varn . (, mType) <$> TH.newName ("_" <> varn)
let transE :: TH.ExpQ
transE
| Just (x NonEmpty.:| xs) <- NonEmpty.nonEmpty msgDefContent = go x xs
| otherwise = [e|Text.empty|]
where
go' (MsgDefContentLiteral (pack -> t)) = TH.lift (t :: Text)
go' (MsgDefContentSplice isRec spliceE)
| isRec = [e|renderMessage $(TH.varE masterN) $(TH.varE langsN) $(return $ alphaConv spliceE)|]
| otherwise = [e|toMessage $(return $ alphaConv spliceE)|]
go c [] = go' c
go (MsgDefContentLiteral t1) (MsgDefContentLiteral t2 : cs) = go (MsgDefContentLiteral $ t1 <> t2) cs
go c1 (c2:cs) = [e|$(go' c1) `Text.append` $(go c2 cs)|]
alphaConv = alphaConvE . Map.fromList . map ((,) <$> views _1 TH.mkName <*> view (_2 . _1)) $ InsOrdHashMap.toList varns
defnP :: TH.PatQ
defnP = TH.conP (defnName defn) . map varP $ F.toList varns
where varP (varn, Nothing) = TH.varP varn
varP (varn, Just typ) = TH.sigP (varP (varn, Nothing)) $ return typ
TH.match defnP (TH.normalB $ bool [e|Just $(transE)|] transE complete) []
tellMPoint . TH.funD funName . pure $ TH.clause [TH.varP masterN, TH.varP langsN] (TH.normalB lamExp) []
tellMPoint $ TH.pragInlD funName TH.Inlinable TH.FunLike TH.AllPhases
return (complete, funName)
allRenderers <- lift $ newUniqueName "langRendereres"
tellMPoint $ TH.sigD allRenderers [t|HashMap Lang (Either ($(TH.conT master) -> [Lang] -> $(TH.conT datName) -> Maybe Text) ($(TH.conT master) -> [Lang] -> $(TH.conT datName) -> Text))|]
let allRenderers' = TH.listE . flip map (HashMap.toList renderLangs) $ \(lang, (complete, funName)) -> [e|($(TH.lift lang), $(bool [e|Left|] [e|Right|] complete) $(TH.varE funName))|]
in tellMPoint . TH.funD allRenderers . pure $ TH.clause [] (TH.normalB [e|HashMap.fromList $(allRenderers')|]) []
tellMPoint $ TH.pragInlD allRenderers TH.NoInline TH.FunLike TH.AllPhases
let defRender = views _2 TH.varE $ HashMap.findWithDefault (error "could not find default language in renderLangs") defLang renderLangs
in tellMPoint . TH.instanceD (TH.cxt []) [t|RenderMessage $(TH.conT master) $(TH.conT datName)|] . pure $
TH.funD 'renderMessage . pure $
TH.clause [] (TH.normalB [e|renderMessageDispatch $(TH.lift defLang) $(defRender) $(TH.varE allRenderers)|]) []
where
indent, indent' :: Int -> String -> String
indent n = unlines . map (replicate (4 * n) ' ' <>) . lines
indent' n = unlines . over (_tail . traverse) (replicate (4 * n) ' ' <>) . lines
parseMsgFile :: FilePath -> ExceptT P.ParseError IO (Lang, MsgFile NonEmpty Maybe)
parseMsgFile fPath = do
let msgFileLang = pack $ takeBaseName fPath
msgFileContent <- either throwE return <=< liftIO $ P.parseFromFile (pMsgFile <* P.eof) fPath
return (msgFileLang, MsgFile{..})
pMsgFile, pMsgLine :: P.Parser (InsOrdHashMap String (NonEmpty (MsgDef NonEmpty Maybe)))
pMsgFile = flip P.label ".msg file" . fmap unionsInsOrdHashMap $ pMsgLine `P.sepEndBy` P.try (P.many1 P.endOfLine)
pMsgLine = flip P.label ".msg line" $ do
spaces
P.choice
[ flip P.label "comment line" $ InsOrdHashMap.empty <$ P.char '#' <* P.manyTill P.anyChar (P.lookAhead . P.try $ void P.endOfLine <|> P.eof)
, flip P.label "empty line" $ InsOrdHashMap.empty <$ P.try P.endOfLine
, flip P.label "message line" $ do
constrBase <- (:) <$> P.upper <*> P.many (P.upper <|> P.lower <|> P.digit <|> P.char '\'')
msgDefVars <- P.option InsOrdHashMap.empty $ do
P.skipMany1 P.space
accumInsOrdHashMap <$> P.sepEndBy pMsgDefVar (P.many1 P.space)
spaces
void $ P.char ':'
spaces
msgDefContent <- P.manyTill pMsgDefContent . P.lookAhead . P.try $ void P.endOfLine <|> P.eof
return . InsOrdHashMap.singleton constrBase $ (NonEmpty.:| []) MsgDef{..}
]
pMsgDefVar :: P.Parser (String, Maybe TH.Type)
pMsgDefVar = do
varBase <- (:) <$> P.lower <*> P.many (P.upper <|> P.lower <|> P.digit <|> P.char '\'')
varTyp <- P.optionMaybe $
P.char '@'
*> parseToSeparator Meta.parseType (fmap pure $ P.char ':' <|> P.space)
return (varBase, varTyp)
pMsgDefContent :: P.Parser MsgDefContent
pMsgDefContent = flip P.label "message definition content" $ choice
[ MsgDefContentLiteral . pure <$ P.char '\\' <*> (P.char '_' <|> P.char '#' <|> P.char '\\')
, do
isRecurse <- P.try $
(True <$ P.string "_{")
<|> (False <$ P.string "#{")
spliceExp <- parseToSeparator Meta.parseExp (pure <$> P.char '}')
void $ P.char '}'
return $ MsgDefContentSplice isRecurse spliceExp
, MsgDefContentLiteral <$> many1Till P.anyChar (P.lookAhead . P.try $ void (P.char '_') <|> void (P.char '#') <|> void P.endOfLine <|> P.eof)
]
parseToSeparator :: (String -> Either String a) -> P.Parser String -> P.Parser a
parseToSeparator innerP sepP = do
w <- many1Till P.anyChar $ P.lookAhead endOfWord
let cont mErrStr = do
endStr <- endOfWord
case endStr of
Nothing -> maybe mzero fail mErrStr
Just sepStr -> parseToSeparator (innerP . ((w <> sepStr) <>)) sepP
case innerP w of
Right res -> return res
<|> cont Nothing
Left errStr -> cont $ Just errStr
where
endOfWord = (Just <$> P.try sepP )
<|> (Nothing <$ P.try P.endOfLine)
many1Till :: P.Stream s m t => P.ParsecT s u m a -> P.ParsecT s u m end -> P.ParsecT s u m [a]
many1Till p end = (:) <$> p <*> P.manyTill p end
notLookAhead :: P.Stream s m t => P.ParsecT s u m a -> P.ParsecT s u m not -> P.ParsecT s u m a
notLookAhead p n = do
followingNot <- (True <$ P.lookAhead n) <|> pure False
bool p mzero followingNot
spaces :: P.Parser ()
spaces = P.skipMany $ notLookAhead P.space P.endOfLine
-- | Stolen from Richard Eisenberg: <https://gitlab.haskell.org/ghc/ghc/-/issues/13054>
newUniqueName :: TH.Quasi q => String -> q TH.Name
newUniqueName str = do
n <- TH.qNewName str
TH.qNewName $ show n

View File

@ -22,6 +22,7 @@ import ClassyPrelude.Yesod as Import
, sinkFile, sourceFile
, defaultYesodMiddleware
, authorizationCheck
, mkMessage
)
import UnliftIO.Async.Utils as Import

View File

@ -21,6 +21,8 @@ import qualified Network.Wai.Parse as NWP
import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Monad.Reader.Class (local)
import qualified Data.HashMap.Strict as HashMap
selectLanguage :: MonadHandler m
=> NonEmpty Lang -- ^ Available translations, first is default
@ -71,3 +73,23 @@ languagesMiddleware avL act = do
let langs = toList $ selectLanguages avL pLangs
setLangs hData = hData{ handlerRequest = (handlerRequest hData){ reqLangs = langs } }
local setLangs $ ($logDebugS "languages" . tshow . (pLangs,langs,) =<< languages) *> act
renderMessageDispatch :: forall msg site.
Lang -- ^ Default language
-> (site -> [Lang] -> msg -> Text) -- ^ Default renderer
-> HashMap Lang (Either (site -> [Lang] -> msg -> Maybe Text) (site -> [Lang] -> msg -> Text)) -- ^ All renderers
-> site -> [Lang] -> msg -> Text
renderMessageDispatch defLang defRender extraRenders app langs msg = go $ selectLanguages avLangs langs
where
avLangs = defLang :| HashMap.keys extraRenders
go (l :| []) = case HashMap.lookup l extraRenders of
Nothing -> defRender app langs msg
Just (Left pRender) -> fromMaybe (defRender app langs msg) $ pRender app langs msg
Just (Right tRender) -> tRender app langs msg
go (l1 :| l2 : ls) = case HashMap.lookup l1 extraRenders of
Nothing -> go (l2 :| ls)
Just (Left pRender) -> fromMaybe (go $ l2 :| ls) $ pRender app langs msg
Just (Right tRender) -> tRender app langs msg
{-# INLINE renderMessageDispatch #-}

View File

@ -0,0 +1,71 @@
module Utils.TH.AlphaConversion
( alphaConvE
) where
import Import.NoModel
import Language.Haskell.TH
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
alphaConvE :: Map Name Name -> Exp -> Exp
alphaConvE ns = alphaConvE' Set.empty
where
alphaConvE' bound = \case
VarE varn
| Just varn' <- Map.lookup varn $ ns `Map.withoutKeys` bound
-> VarE varn'
o@(VarE _) -> o
o@(ConE _) -> o
o@(LitE _) -> o
AppE f x -> AppE (alphaConvE' bound f) (alphaConvE' bound x)
AppTypeE f t -> AppTypeE (alphaConvE' bound f) t
InfixE mA o mB -> InfixE (alphaConvE' bound <$> mA) (alphaConvE' bound o) (alphaConvE' bound <$> mB)
UInfixE a o b -> UInfixE (alphaConvE' bound a) (alphaConvE' bound o) (alphaConvE' bound b)
ParensE e -> ParensE $ alphaConvE' bound e
LamE ps e -> LamE ps $ alphaConvE' (bound `Set.union` foldMap pVars ps) e
LamCaseE _ -> error "alphaConvE: LamCaseE not implemented"
TupE es -> TupE $ over (traverse . _Just) (alphaConvE' bound) es
UnboxedTupE es -> UnboxedTupE $ over (traverse . _Just) (alphaConvE' bound) es
UnboxedSumE e sAlt sAry -> UnboxedSumE (alphaConvE' bound e) sAlt sAry
CondE i t e -> CondE (alphaConvE' bound i) (alphaConvE' bound t) (alphaConvE' bound e)
MultiIfE _ -> error "alphaConvE: MultiIfE not implemented"
LetE _ _ -> error "alphaConvE: LetE not implemented"
CaseE _ _ -> error "alphaConvE: CaseE not implemented"
DoE _ -> error "alphaConvE: DoE not implemented"
MDoE _ -> error "alphaConvE: MDoE not implemented"
CompE _ -> error "alphaConvE: CompE not implemented"
ArithSeqE (FromR e) -> ArithSeqE . FromR $ alphaConvE' bound e
ArithSeqE (FromThenR e1 e2) -> ArithSeqE $ FromThenR (alphaConvE' bound e1) (alphaConvE' bound e2)
ArithSeqE (FromToR e1 e2) -> ArithSeqE $ FromToR (alphaConvE' bound e1) (alphaConvE' bound e2)
ArithSeqE (FromThenToR e1 e2 e3) -> ArithSeqE $ FromThenToR (alphaConvE' bound e1) (alphaConvE' bound e2) (alphaConvE' bound e3)
ListE es -> ListE $ map (alphaConvE' bound) es
SigE e t -> SigE (alphaConvE' bound e) t
RecConE rN es -> RecConE rN $ over (traverse . _2) (alphaConvE' bound) es
RecUpdE e es -> RecUpdE (alphaConvE' bound e) $ over (traverse . _2) (alphaConvE' bound) es
StaticE e -> StaticE $ alphaConvE' bound e
o@(UnboundVarE _) -> o
o@(LabelE _) -> o
o@(ImplicitParamVarE _) -> o
pVars :: Pat -> Set Name
pVars = \case
LitP _ -> Set.empty
VarP n -> Set.singleton n
TupP ps -> foldMap pVars ps
UnboxedTupP ps -> foldMap pVars ps
UnboxedSumP p _ _ -> pVars p
ConP _ ps -> foldMap pVars ps
InfixP p1 _ p2 -> pVars p1 `Set.union` pVars p2
UInfixP p1 _ p2 -> pVars p1 `Set.union` pVars p2
ParensP p -> pVars p
TildeP p -> pVars p
BangP p -> pVars p
AsP n p -> Set.insert n $ pVars p
WildP -> Set.empty
RecP _ ps -> foldMapOf (folded . _2) pVars ps
ListP ps -> foldMap pVars ps
SigP p _ -> pVars p
ViewP _ p -> pVars p