packages feed

hydra-0.5.0: src/main/haskell/Hydra/Tools/GrammarToModule.hs

-- | A utility for converting a BNF grammar to a Hydra module

module Hydra.Tools.GrammarToModule where

import Hydra.Kernel
import Hydra.Dsl.Annotations
import Hydra.Dsl.Bootstrap as Bootstrap
import Hydra.Dsl.Types as Types
import Hydra.Dsl.Terms as Terms
import qualified Hydra.Grammar as G

import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Maybe as Y

import Hydra.Sources.Core


grammarToModule :: Namespace -> G.Grammar -> Maybe String -> Module
grammarToModule ns (G.Grammar prods) desc = Module ns elements [hydraCoreModule] [hydraCoreModule] desc
  where
    elements = pairToElement <$> L.concat (L.zipWith (makeElements False) (capitalize . fst <$> prodPairs) (snd <$> prodPairs))
      where
        prodPairs = (\(G.Production (G.Symbol s) pat) -> (s, pat)) <$> prods
        pairToElement (lname, typ) = Bootstrap.typeElement (toName lname) typ

    toName local = unqualifyName $ QualifiedName (Just ns) local

    findNames pats = L.reverse $ fst (L.foldl nextName ([], M.empty) pats)
      where
        nextName (names, nameMap) pat = (nn:names, M.insert rn ni nameMap)
          where
            rn = rawName pat
            (nn, ni) = case M.lookup rn nameMap of
              Nothing -> (rn, 1)
              Just i -> (rn ++ show (i+1), i+1)

        rawName pat = case pat of
          G.PatternNil -> "none"
          G.PatternIgnored _ -> "ignored"
          G.PatternLabeled (G.LabeledPattern (G.Label l) _) -> l
          G.PatternConstant (G.Constant c) -> decapitalize $ withCharacterAliases c
          G.PatternRegex _ -> "regex"
          G.PatternNonterminal (G.Symbol s) -> decapitalize s
          G.PatternSequence _ -> "sequence"
          G.PatternAlternatives _ -> "alts"
          G.PatternOption p -> decapitalize (rawName p)
          G.PatternStar p -> "listOf" ++ capitalize (rawName p)
          G.PatternPlus p -> "listOf" ++ capitalize (rawName p)

    isComplex pat = case pat of
      G.PatternLabeled (G.LabeledPattern _ p) -> isComplex p
      G.PatternSequence pats -> isNontrivial True pats
      G.PatternAlternatives pats -> isNontrivial False pats
      _ -> False

    isNontrivial isRecord pats = if L.length minPats == 1
        then case L.head minPats of
          G.PatternLabeled _ -> True
          _ -> False
        else True
      where
        minPats = simplify isRecord pats

    -- Remove trivial patterns from records
    simplify isRecord pats = if isRecord then L.filter (not . isConstant) pats else pats
      where
        isConstant p = case p of
          G.PatternConstant _ -> True
          _ -> False

    makeElements omitTrivial lname pat = forPat pat
      where
        forPat pat = case pat of
          G.PatternNil -> trivial
          G.PatternIgnored _ -> []
          G.PatternLabeled (G.LabeledPattern (G.Label l) p) -> forPat p
          G.PatternConstant _ -> trivial
          G.PatternRegex _ -> [(lname, Types.string)]
          G.PatternNonterminal (G.Symbol other) -> [(lname, TypeVariable $ toName other)]
          G.PatternSequence pats -> forRecordOrUnion True Types.record pats
          G.PatternAlternatives pats -> forRecordOrUnion False Types.union pats
          G.PatternOption p -> mod "Option" Types.optional p
          G.PatternStar p -> mod "Elmt" Types.list p
          G.PatternPlus p -> mod "Elmt" nonemptyList p

        trivial = if omitTrivial then [] else [(lname, Types.unit)]

        forRecordOrUnion isRecord construct pats = if isNontrivial isRecord pats
            then (lname, construct fields):els
            -- Eliminate single-field record and union types, unless the field has a user-defined name
            else forPat $ L.head minPats
          where
            fieldPairs = L.zipWith toField (findNames minPats) minPats
            fields = fst <$> fieldPairs
            els = L.concat (snd <$> fieldPairs)
            minPats = simplify isRecord pats

        toField n p = descend n f2 p
          where
            f2 ((lname, typ):rest) = (FieldType (Name n) typ, rest)

        mod n f p = descend n f2 p
          where
            f2 ((lname, typ):rest) = (lname, f typ):rest

        descend n f p = f $ if isComplex p
            then (lname, TypeVariable (toName $ fst $ L.head cpairs)):cpairs
            else if L.null cpairs
              then [(lname, Types.unit)]
              else (lname, snd (L.head cpairs)):L.tail cpairs
          where
            cpairs = makeElements False (childName lname n) p

    childName lname n = lname ++ "." ++ capitalize n