packages feed

grm-0.1.0: grm.hs

{-# OPTIONS -Wall #-}
{-# LANGUAGE DeriveDataTypeable #-}

-- The grm grammar generator
-- Copyright 2011-2012, Brett Letner

module Main where

import Control.Monad
import Data.Char
import Data.List
import Data.Maybe
import Distribution.Text
import Grm.Prims
import Paths_grm
import System.Console.CmdArgs
import System.Directory
import System.FilePath.Posix
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Language
import Text.PrettyPrint.Leijen (braces,brackets,text,hsep,(<+>),empty,vcat,Doc,indent,(<>),parens,int,list)
import qualified Text.ParserCombinators.Parsec.Token as P

type P a = Parser a

data Args = Args
  { files :: [FilePath]
  , locations :: Bool
  } deriving (Show, Data, Typeable)

argsDesc :: Args
argsDesc = Args
  { files = def &= args
  , locations = def &= help "generate location information"
  } &= summary summ &= program prog
  where
  summ = prog ++ " v" ++ display version ++ ", " ++ copyright
  prog = "grm"
  copyright = "(C) Brett Letner 2011-2012"

main :: IO ()
main = do
  a <- cmdArgs argsDesc
  mapM_ (doit (locations a)) $ files a

doit :: Bool -> String -> IO ()
doit gen_locs fn = do
  eea <- parseFromFile grm fn
  case eea of
    Left e -> error $ show e
    Right xs -> do
      let bn = takeBaseName fn
      let dir = combine "Language" bn
      createDirectoryIfMissing True dir
      absGrm gen_locs dir bn xs
      parGrm gen_locs dir bn xs

absGrm :: Bool -> FilePath -> String -> [Decl] -> IO ()
absGrm gen_locs dir bn xs = writeFile (combine dir "Abs.hs") $ show d
  where
  d = vcat $
    [ text "{-# LANGUAGE DeriveDataTypeable #-}"
    , hsep [ text "module"
           , text $ "Language." ++ bn ++ ".Abs"
           , text "where"]
    , vcat [ text "import" <+> text imp | imp <- imps ]
    , lexDecls xs
    ] ++
    map (dataDecl gen_locs) xs ++ 
    (if gen_locs then map metaDecl xs else []) ++
    map (ppDecl gen_locs) xs
  imps =
    [ "Control.DeepSeq"
    , "Data.Generics"
    , "Grm.Prims"
    , "Grm.Lex"
    , "Text.PrettyPrint.Leijen"
    ]

lexDecls :: [Decl] -> Doc
lexDecls xs = vcat
  [ hsep [text "myLexemes =", text $ show $ resDecls xs]
  , text "grmLexFilePath = lexFilePath myLexemes"
  , text "grmLexContents = lexContents myLexemes"
  ]

absName :: FilePath -> String
absName fn = takeBaseName fn

hdr :: String
hdr = unlines
  [ "{-# OPTIONS -w #-}"
  , "-- Haskell module generated by grm *** DO NOT EDIT ***"
  ]

parGrm :: Bool -> FilePath -> String -> [Decl] -> IO ()
parGrm gen_locs dir bn xs = do
  let fn1 = dir </> "Par.y"
  writeFile fn1 $ show d
  where
  d = vcat $
    [ parHdr
    ] ++
    map (parDecl gen_locs [ s | List s _ _ _ _ _ <- xs ]) xs
  parHdr = vcat $ map text
    [ "{"
    , hdr
    , ""
    , "module Language." ++ bn ++ ".Par where"
    , "import Grm.Prims"
    , "import Grm.Lex"
    , "import Language." ++ bn ++ ".Abs"
    , "}"
    , "%tokentype { (Token Point) }"
    , "%name grmParse"
    , "%token"
    , unlines [ "  " ++ show sym ++ " " ++ "{ TSymbol _ " ++ show sym ++ " }"
                | sym <- resDecls xs ]
    , "  uident { TUident _ _ }"
    , "  usym { TUsym _ _ }"
    , "  lident { TLident _ _ }"
    , "  string { TString _ _ }"
    , "  char { TChar _ _ }"
    , "  number { TNumber _ _ }"
    , "%%"
    ]

capitalize :: String -> String
capitalize "" = ""
capitalize (c:cs) = toUpper c : cs

grm :: P [Decl]
grm = do
  whiteSpace
  ds <- many decl
  eof
  return ds

pGroup :: P Decl
pGroup = do
  reserved "group"
  liftM Group $ pBraces $ many1 pData

pData :: P DataD
pData = do
  reserved "data"
  n <- identifier
  xs <- many1 alt
  return $ DataD n xs

pList :: P Decl
pList = do
  reserved "list"
  liftM6 List identifier identifier pEmpty terminator stringLiteral horiz

decl :: P Decl
decl = choice [pGroup, liftM (Group . singleton) pData, pType, pList]

vcatStr :: String -> [Doc] -> Doc
vcatStr s xs = vcat $ intersperse (text s) xs

altList :: [Doc] -> Doc
altList (x0:xs) = vcat $
  [text "=" <+> x0] ++ [ text "|" <+> x | x <- xs ] ++
  [text "deriving (Show,Eq,Ord,Data,Typeable)"]
altList [] = unreachable

dataDecl :: Bool -> Decl -> Doc
dataDecl gen_locs x = case x of
  Group [] -> unreachable
  Group xs@(DataD n0 _ : _) -> vcat $
    [text "data" <+> text n0 <+> locs, indent 2 $ altList $ concatMap (dataData gen_locs) xs]
    ++ types (dataDecl gen_locs) xs
  Type a b -> dataType gen_locs a $ text (capitalize b) <+> locs
  List a b _ _ _ _ -> dataType gen_locs a $ brackets $ text b <+> locs
  where
    locs = if gen_locs then text "a" else empty
    
types :: (Decl -> Doc) -> [DataD] -> [Doc]
types f (DataD b _ : xs) = [ f $ Type a b | DataD a _ <- xs ]
types _ [] = unreachable

dataType :: Bool -> String -> Doc -> Doc
dataType gen_locs a b = hsep [text "type", text a, locs, text "=", b]
  where locs = if gen_locs then text "a" else empty
        
dataData :: Bool -> DataD -> [Doc]
dataData gen_locs (DataD _ xs) =
  [ hsep $ map text $ s : locs : (catMaybes $ map (nameAltTok gen_locs) ts)
    | Alt s ts <- xs ]
  where locs = if gen_locs then "a" else ""
        
nameAltTok :: Bool -> AltTok -> Maybe String
nameAltTok gen_locs x = case x of
  StringT{} -> Nothing
  IdentT s -> Just $ if gen_locs then "(" ++ s ++ " a)" else s
  PrimT s -> Just $ capitalize s

metaDecl :: Decl -> Doc
metaDecl x = case x of
  Group [] -> unreachable
  Group xs@(DataD n0 _ : _) -> vcat $
    [ hsep [text "instance HasMeta", text n0, text "where"]
    , indent 2 $ text $ "meta x = case x of"
    , indent 4 $ vcat $ concatMap metaData xs
    ]
  Type{} -> empty
  List{} -> empty

metaData :: DataD -> [Doc]
metaData (DataD _ xs) = [ f s ts | Alt s ts <- xs ]
  where
  f s ts =
    hsep [text s, text "a", hsep $ replicate (length ys) (text "_"), text "-> a" ]
    where
    ys = filter (not . isStringT) ts

ppDecl :: Bool -> Decl -> Doc
ppDecl gen_locs x = case x of
  Group [] -> unreachable
  Group xs@(DataD n0 _ : _) -> vcat $
    [ hsep [ text "instance Pretty"
           , parens (text n0 <+> if gen_locs then text "a" else empty)
           , text "where" ]
    , indent 2 $ text "pretty = pp" <> text n0
    , text $ "pp" ++ n0 ++ " x = case x of"
    , indent 2 $ vcat $ concatMap (ppData gen_locs) xs
    ] ++ types (ppDecl gen_locs) xs
  Type a b -> hsep $ map text ["pp" ++ a, "=", "pp" ++ capitalize b]
  List a b _ d e f ->
    hsep $ map text ["pp" ++ a, "=", "ppList", "pp" ++ b, show d, show e, show f]

ppData :: Bool -> DataD -> [Doc]
ppData gen_locs (DataD _ xs) = [ f s ts | Alt s ts <- xs ]
  where
  f s ts =
    hsep [ text s
         , if gen_locs then text "_" else empty
         , hsep $ map text [ v | (Just v, _) <- ys ], text "->", ppAltToks ys ]
    where
    ys = nameToks "v" $ numberToks (not . isStringT) ts

isStringT :: AltTok -> Bool
isStringT (StringT{}) = True
isStringT _ = False

nameToks :: String -> [(Maybe Int, AltTok)] -> [(Maybe String, AltTok)]
nameToks s xs = [ (fmap (\i -> s ++ show i) mi, t) | (mi,t) <- xs ]

resDecls :: [Decl] -> [String]
resDecls xs = filter (not . null) $ sort $ nub $ concatMap resDecl xs

numberToks :: (AltTok -> Bool) -> [AltTok] -> [(Maybe Int, AltTok)]
numberToks f = loop 1
  where
  loop _ [] = []
  loop i (x:xs)
    | f x = (Just i, x) : loop (succ i) xs
    | otherwise = (Nothing, x) : loop i xs

ppAltToks :: [(Maybe String, AltTok)] -> Doc
ppAltToks [] = text "Text.PrettyPrint.Leijen.empty"
ppAltToks [x] = ppAltTok x
ppAltToks (x:y:xs) = ppAltTok x <+> rest
  where
  rest = case y of
    (_, StringT "") -> text "<>" <+> ppAltToks xs
    _ -> text "<+>" <+> ppAltToks (y:xs)

ppAltTok :: (Maybe String, AltTok) -> Doc
ppAltTok x = case x of
  (_, StringT "") -> error "unexpected empty string"
  (_, StringT s) -> hsep [text "text", text $ show s]
  (Just v, IdentT s) -> text $ "pp" ++ s ++ " " ++ v
  (mv, PrimT s) -> ppAltTok (mv, IdentT $ capitalize s)
  _ -> unreachable

pEmpty :: P Empty
pEmpty = choice
  [ reserved "empty" >> return Empty
  , reserved "nonempty" >> return NonEmpty
  ]

terminator :: P Terminator
terminator = choice
  [ reserved "separator" >> return Separator
  , reserved "terminator" >> return Terminator
  ]

horiz :: P Horiz
horiz = choice
  [ reserved "vert" >> return Vert
  , reserved "horiz" >> return Horiz
  ]

alt :: P Alt
alt = do
  reservedOp "|"
  choice [pAlt,defAlt]

pAlt :: P Alt
pAlt = do
  c <- identifier
  ts <- many altTok
  return $ Alt c ts

defAlt :: P Alt
defAlt = do
  reserved "_"
  liftM DefAlt identifier

pType :: P Decl
pType = do
  reserved "type"
  c <- identifier
  reservedOp "="
  p <- identifier
  return $ Type c p

altTok :: P AltTok
altTok = choice
  [ liftM StringT stringLiteral
  , liftM IdentT identifier
  , liftM PrimT prim
  ]

prim :: P String
prim = choice $ map res primNames

res :: String -> P String
res s = reserved s >> return s

primNames :: [String]
primNames = ["string","number","char","uident","lident","usym"]

data Decl
  = Group [DataD]
  | Type String String
  | List String String Empty Terminator String Horiz
  deriving Show

resDecl :: Decl -> [String]
resDecl x = case x of
  Group xs -> concatMap resData xs
  Type{} -> []
  List _ _ _ _ s _ -> [s]

resData :: DataD -> [String]
resData (DataD _ xs) = concatMap resAlt xs

resAlt :: Alt -> [String]
resAlt x = case x of
  Alt _ ys -> concatMap resAltTok ys
  DefAlt{} -> []

data DataD = DataD String [Alt] deriving Show

data Alt
  = Alt String [AltTok]
  | DefAlt String
  deriving Show

resAltTok :: AltTok -> [String]
resAltTok x = case x of
  StringT s -> [s]
  _ -> []

data AltTok
  = StringT String
  | IdentT String
  | PrimT String
  deriving (Show, Eq)

parData :: Bool -> [String] -> DataD -> Doc
parData gen_locs ss (DataD c xs0) = case filter f xs0 of
  [] -> unreachable
  x0:xs -> vcat
    [ text c
    , indent 2 $ vcat
        [ hsep [ text ":", parAlt gen_locs ss x0 ]
        , vcat [ text "|" <+> parAlt gen_locs ss x | x <- xs ]
        ]
    ]
  where
  f x = case x of
    Alt a _ -> last a /= '_'
    DefAlt{} -> True

parAlt :: Bool -> [String] -> Alt -> Doc
parAlt gen_locs ss x = case x of
  Alt c ts -> hsep $
    map parAltTokL ts ++
    [braces $ hsep $ text c : (if gen_locs then loc else empty) : map parAltTokR ns ]
    where
    ns = nameToks "$" $ numberToks ((/=) (StringT "")) ts
    loc = case filter ((/=) (StringT "")) ts of
      [] -> text "noPoint"
      [_] -> parens (text "point $1")
      zs -> parens $ text "lrPoint" <+> list (map (pointAltTok ss) (zip zs [ 1 .. ]))
  DefAlt c -> hsep [text c, text "{ $1 }"]

pointAltTok :: [String] -> (AltTok,Int) -> Doc
pointAltTok ss (x,i) = case x of
  IdentT s | s `elem` ss -> text "lrPointList" <+> text "$" <> int i
  _ -> text "point" <+> text "$" <> int i

parAltTokR :: (Maybe String, AltTok) -> Doc
parAltTokR x = case x of
  (Just i, IdentT _) -> text i
  (Just i, PrimT s) -> parens (text ("unT" ++ capitalize s) <+> text i)
  _ -> empty

parAltTokL :: AltTok -> Doc
parAltTokL x = case x of
  StringT "" -> empty
  StringT s -> text $ show s
  IdentT s -> text s
  PrimT s -> text s

parDecl :: Bool -> [String] -> Decl -> Doc
parDecl gen_locs ss x = case x of
  Type a b -> hsep [text a, text ":", text b, braces $ text "$1"]
  Group xs -> vcat $ map (parData gen_locs ss) xs
  List prodsStr prodStr empt term sepStr _ -> vcat
    [ prods
    , indent 2 $ vcat
      [ hsep [text ":", rev_prods, braces $ text "reverse $1"]
      , case empt of
          Empty -> text "| {- empty -} { [] }"
          NonEmpty -> empty
      ]
    , rev_prods
    , indent 2 $ vcat
      [ hsep [text ":", a, braces $ brackets $ text "$1"]
      , hsep [text "|", rev_prods, b, braces $ hsep [c, text ": $1"]]
      ]
    ]
    where
    a = case term of
      Terminator | sepStr /= "" -> prod <+> sp
      _ -> prod
    b = case (sepStr, term) of
      ("", _) -> prod
      (_, Terminator) -> prod <+> sp
      (_, Separator) -> sp <+> prod
    c = case term of
      Separator | sepStr /= "" -> text "$3"
      _ -> text "$2"
    sp = text $ show sepStr
    rev_prods = text $ "REV_" ++ prodsStr
    prod = text prodStr
    prods = text prodsStr


lexer :: P.TokenParser a
lexer = P.makeTokenParser grmStyle

reserved :: String -> P ()
reserved = P.reserved lexer

pBraces :: P a -> P a
pBraces = P.braces lexer

identifier :: P String
identifier = P.identifier lexer

reservedOp :: String -> P ()
reservedOp = P.reservedOp lexer

stringLiteral :: P String
stringLiteral = P.stringLiteral lexer

whiteSpace :: P ()
whiteSpace = P.whiteSpace lexer

grmStyle :: LanguageDef a
grmStyle = haskellStyle
  { P.identStart = upper
  }

liftM6 :: Monad m =>
  (t -> t1 -> t2 -> t3 -> t4 -> t5 -> b) ->
  m t -> m t1 -> m t2 -> m t3 -> m t4 -> m t5 -> m b
liftM6 z ma mb mc md me mf = do
  a <- ma
  b <- mb
  c <- mc
  d <- md
  e <- me
  f <- mf
  return $ z a b c d e f