grm (empty) → 0.1.0
raw patch · 9 files changed
+1414/−0 lines, 9 filesdep +Cabaldep +basedep +cmdargssetup-changed
Dependencies added: Cabal, base, cmdargs, directory, filepath, parsec, process, syb, wl-pprint
Files
- Grm/Layout.hs +69/−0
- Grm/Lex.hs +536/−0
- Grm/Prims.hs +199/−0
- LICENSE +30/−0
- README +31/−0
- Setup.hs +5/−0
- THANKS +14/−0
- grm.cabal +39/−0
- grm.hs +491/−0
+ Grm/Layout.hs view
@@ -0,0 +1,69 @@+{-# OPTIONS -Wall -fno-warn-name-shadowing #-}++-- The grm grammar generator+-- Copyright 2011-2012, Brett Letner++module Grm.Layout (layout) where++import Grm.Lex+import Grm.Prims++layout :: [Token Point] -> [Token Point]+layout ts = loop [] ts+ where+ loop [] [] = []++ loop (_:cs) [] = stopSyms eofLoc ++ loop cs []++ loop cs (x:y:xs) | isStopSym x && isStopWord y = x : y : loop cs xs++ loop (_:cs) (x:xs) | isStopWord x =+ stopSyms (startLoc x) ++ [x] ++ loop (filter ((>=) $ col x) cs) xs++ loop (c:cs) xs0@(x:_) | col x < c = stopSyms (startLoc x) ++ loop cs xs0++ loop (c:cs) (x:xs) | col x == c && (isWord ["where"] x) = stopSyms (startLoc x) ++ cont cs x xs++ loop cs0@(c:_) (x:xs) | col x == c = termSym (startLoc x) : cont cs0 x xs++ loop cs0 (x:xs) = cont cs0 x xs++ cont cs0 x0 [] | isStartWord x0 = x0 : startSym (stopLoc x0) : loop (col x0 : cs0) []++ cont cs0 x0 (x:xs) | isStartWord x0 && not (isStartSym x) =+ x0 : startSym (startLoc x) : cont (col x : cs0) x xs++ cont cs0 x xs0 = x : loop cs0 xs0++ eofLoc = stopLoc $ last ts++isStartWord :: Token Point -> Bool+isStartWord = isWord [ "where", "do", "of", "imports", "exports", "branch" ] -- , "let"++isStopWord :: Token Point -> Bool+isStopWord = isWord [ "until" ]++isStartSym :: Token Point -> Bool+isStartSym = isWord ["{"]++isStopSym :: Token Point -> Bool+isStopSym = isWord ["}"]++isWord :: [String] -> Token Point -> Bool+isWord ss (TSymbol _ s) = s `elem` ss+isWord _ _ = False++col :: Token Point -> Int+col = locColumn . startLoc++mkSym :: String -> Loc -> Token Point+mkSym s l = TSymbol (Point l l) s++startSym :: Loc -> Token Point+startSym = mkSym "{"++stopSyms :: Loc -> [Token Point]+stopSyms loc = [ termSym loc, mkSym "}" loc ]++termSym :: Loc -> Token Point+termSym = mkSym ";"
+ Grm/Lex.hs view
@@ -0,0 +1,536 @@+{-# OPTIONS -Wall #-}++-- The grm grammar generator+-- Copyright 2011-2012, Brett Letner++module Grm.Lex+ ( Token(..)+ , Point(..)+ , lexFilePath+ , lexContents+ , ppToken+ , ppTokenList+ , happyError+ , notWSToken+ , unTWhitespace+ , unTSLComment+ , unTMLComment+ , unTString+ , unTChar+ , unTNumber+ , unTSymbol+ , unTUsym+ , unTUident+ , unTLident+ ) where++import Control.Monad+import Data.Char+-- import Numeric+import Data.List+import Grm.Prims+import Text.PrettyPrint.Leijen++happyError :: [Token Point] -> a+happyError [] = error "unexpected end of tokens"+happyError (t:_) = error $ ppErr (startLoc t) ("unexpected token: " ++ show (show (ppToken t)))++-- fixme: doesn't work for empty modules (e.g. module Foo where)++notWSToken :: Token t -> Bool+notWSToken t = case t of+ TWhitespace{} -> False+ TSLComment{} -> False+ TMLComment{} -> False+ _ -> True++data Token a+ = TWhitespace a String+ | TSLComment a String+ | TMLComment a String+ | TString a String+ | TChar a Char+ | TNumber a String+ | TSymbol a String+ | TUsym a String+ | TUident a String+ | TLident a String+ deriving (Show)++unTWhitespace :: Token t -> String+unTWhitespace (TWhitespace _ b) = b+unTWhitespace _ = error "unTWhitespace"++unTSLComment :: Token t -> String+unTSLComment (TSLComment _ b) = b+unTSLComment _ = error "unTSLComment"++unTMLComment :: Token t -> String+unTMLComment (TMLComment _ b) = b+unTMLComment _ = error "unTMLComment"++unTString :: Token t -> String+unTString (TString _ b) = b+unTString _ = error "unTString"++unTChar :: Token t -> Char+unTChar (TChar _ b) = b+unTChar _ = error "unTChar"++unTNumber :: Token t -> String+unTNumber (TNumber _ b) = b+unTNumber _ = error "unTNumber"++unTSymbol :: Token t -> String+unTSymbol (TSymbol _ b) = b+unTSymbol _ = error "unTSymbol"++unTUsym :: Token t -> String+unTUsym (TUsym _ b) = b+unTUsym _ = error "unTUsym"++unTUident :: Token t -> String+unTUident (TUident _ b) = b+unTUident _ = error "unTUident"++unTLident :: Token t -> String+unTLident (TLident _ b) = b+unTLident _ = error "unTLident"++instance Eq (Token a) where+ (==) a b = case (a,b) of+ (TWhitespace _ x, TWhitespace _ y) -> x == y+ (TSLComment _ x, TSLComment _ y) -> x == y+ (TMLComment _ x, TMLComment _ y) -> x == y+ (TString _ x, TString _ y) -> x == y+ (TChar _ x, TChar _ y) -> x == y+ (TNumber _ x, TNumber _ y) -> x == y -- numbers are compared lexically+ (TSymbol _ x, TSymbol _ y) -> x == y+ (TUsym _ x, TUsym _ y) -> x == y+ (TUident _ x, TUident _ y) -> x == y+ (TLident _ x, TLident _ y) -> x == y+ _ -> False++ppTokenList :: [Token a] -> Doc+ppTokenList = sep . map ppToken++ppToken :: Token a -> Doc+ppToken t = text $ case t of+ TWhitespace _ s -> s+ TSLComment _ s -> s+ TMLComment _ s -> s+ TString _ a -> show a+ TChar _ a -> show a+ TNumber _ s -> s+ TSymbol _ s -> s+ TUsym _ s -> s+ TUident _ s -> s+ TLident _ s -> s++instance HasMeta Token where+ meta t = case t of+ TWhitespace a _ -> a+ TSLComment a _ -> a+ TMLComment a _ -> a+ TString a _ -> a+ TChar a _ -> a+ TNumber a _ -> a+ TSymbol a _ -> a+ TUsym a _ -> a+ TUident a _ -> a+ TLident a _ -> a++ -- mapMetaM f t = case t of+ -- TWhitespace a b -> f a >>= \x -> return (TWhitespace x b)+ -- TSLComment a b -> f a >>= \x -> return (TSLComment x b)+ -- TMLComment a b -> f a >>= \x -> return (TMLComment x b)+ -- TString a b -> f a >>= \x -> return (TString x b)+ -- TChar a b -> f a >>= \x -> return (TChar x b)+ -- TNumber a b -> f a >>= \x -> return (TNumber x b)+ -- TSymbol a b -> f a >>= \x -> return (TSymbol x b)+ -- TUsym a b -> f a >>= \x -> return (TUsym x b)+ -- TUident a b -> f a >>= \x -> return (TUident x b)+ -- TLident a b -> f a >>= \x -> return (TLident x b)++lexFilePath :: [String] -> FilePath -> IO [Token Point]+lexFilePath syms fn = do+ s <- readFile fn+ lexContents syms fn s++lexContents :: [String] -> FilePath -> String -> IO [Token Point]+lexContents syms fn s = do+ let st0 = initSt syms fn $ filter ((/=) '\r') s+ let M f = lexTokens+ case f st0 of+ (Left e, st) -> error $ ppErr (stLoc st) e+ (Right ts, st) -> case stInput st of+ "" -> return ts+ e -> error $ ppErr (stLoc st) $ "lexical error:" ++ show (head e)++initSt :: [String] -> FilePath -> String -> St+initSt syms fn s = St+ { stLoc = initLoc fn+ , stInput = s+ , stSymbols = syms+ }++instance Monad M where+ (M f) >>= g = M $ \st ->+ let (ma, st1) = f st+ in case ma of+ Left e -> (Left e, st)+ Right a ->+ let M h = g a+ in h st1++ return a = M $ \st -> (Right a, st)+ fail s = M $ \st -> (Left s, st)++data M a = M (St -> (Either String a, St))++getSt :: M St+getSt = M $ \st -> (Right st, st)++lexAnyChar :: M Char+lexAnyChar = M $ \st ->+ let+ loc0 = stLoc st+ in case stInput st of+ "" -> (Left "unexpected eof", st)+ (c:cs) | c == eolChar ->+ (Right c, st{ stInput = cs+ , stLoc = loc0{ locColumn = 0, locLine = succ (locLine loc0) }})+ (c:cs) ->+ (Right c, st{ stInput = cs+ , stLoc = loc0{ locColumn = succ (locColumn loc0) }})++tryLex :: M a -> M (Maybe a)+tryLex (M f) = M $ \st -> case f st of+ (Left _, _) -> (Right Nothing, st)+ (Right a, st1) -> (Right $ Just a, st1)++data St = St+ { stLoc :: Loc+ , stInput :: String+ , stSymbols :: [String]+ } deriving (Show)++qualChar :: Char+qualChar = '.'++escChar :: Char+escChar = '\\'++dQuoteChar :: Char+dQuoteChar = '"'++sQuoteChar :: Char+sQuoteChar = '\''++eolChar :: Char+eolChar = '\n'++wsChar :: String+wsChar = [ ' ', eolChar ]++slCommentStart :: String+slCommentStart = "//"++mlCommentStart :: String+mlCommentStart = "/*"++mlCommentEnd :: String+mlCommentEnd = "*/"++-- slCommentStart = "--", doesn't work if you support the decrement statement, e.g. i--+-- mlCommentStart = "{-"+-- mlCommentEnd = "-}"++uidentStart :: String+uidentStart = [ 'A' .. 'Z' ]++lidentStart :: String+lidentStart = '_' : [ 'a' .. 'z' ]++identEnd :: String+identEnd = uidentStart ++ lidentStart ++ [ '0' .. '9' ]++getLoc :: M Loc+getLoc = liftM stLoc getSt++getSymbols :: M [String]+getSymbols = liftM stSymbols getSt++lexStringLit :: String -> M String+lexStringLit s = do+ s1 <- sequence $ replicate (length s) lexAnyChar+ if s1 == s+ then return s+ else fail $ "unexpected string:" ++ s++lexCharPred :: (Char -> Bool) -> M Char+lexCharPred p = do+ c <- lexAnyChar+ if p c+ then return c+ else fail $ "unexpected char:" ++ show c++many :: M a -> M [a]+many m = do+ mx <- tryLex m+ case mx of+ Just x -> do+ xs <- many m+ return $ x : xs+ Nothing -> return []++oneof :: [M a] -> M a+oneof [] = fail "token doesn't match any alternatives"+oneof (m:ms) = do+ mx <- tryLex m+ case mx of+ Just x -> return x+ Nothing -> oneof ms++lexKeyword :: M (Token Point)+lexKeyword = do+ a <- getLoc+ s <- oneof [ lexUpperWord, lexLowerWord ]+ ss <- getSymbols+ b <- getLoc+ if s `elem` ss+ then return $ TSymbol (Point a b) s+ else fail $ "not a keyword:" ++ s++lexSymbol :: M (Token Point)+lexSymbol = do+ a <- getLoc+ ss <- getSymbols+ s <- oneof [ lexUsymTok, liftM singleton $ lexCharPred (flip elem symChar) ]+ b <- getLoc+ if s `elem` ss+ then return $ TSymbol (Point a b) s+ else fail $ "not a symbol:" ++ s++usymChar :: String+usymChar = "!#$%&*+-/:<=>?@\\^|~"++symChar :: String+symChar = "(),;`{}[]."++lexUsymTok :: M String+lexUsymTok = many1 (lexCharPred $ flip elem usymChar)++lexUsym :: M (Token Point)+lexUsym = do+ a <- getLoc+ s <- lexUsymTok+ b <- getLoc+ return $ TUsym (Point a b) s++lexUident :: M (Token Point)+lexUident = do+ a <- getLoc+ s <- lexQualified+ b <- getLoc+ return $ TUident (Point a b) s++lexLident :: M (Token Point)+lexLident = do+ a <- getLoc+ s <- oneof [ lexLowerQualified, lexLowerWord ]+ b <- getLoc+ return $ TLident (Point a b) s++lexLowerQualified :: M String+lexLowerQualified = do+ s1 <- lexQualified+ s2 <- do+ _ <- lexCharPred ((==) qualChar)+ lexLowerWord+ return $ qualify [s1,s2]++lexQualified :: M String+lexQualified = do+ s0 <- lexUpperWord+ ss <- many $ do+ _ <- lexCharPred ((==) qualChar)+ s <- lexUpperWord+ return s+ return $ qualify $ s0 : ss++lexUpperWord :: M String+lexUpperWord = do+ c <- lexCharPred (flip elem uidentStart)+ cs <- many $ lexCharPred (flip elem identEnd)+ return $ c : cs++lexLowerWord :: M String+lexLowerWord = do+ c <- lexCharPred (flip elem lidentStart)+ cs <- many $ lexCharPred (flip elem identEnd)+ return $ c : cs++qualify :: [String] -> String+qualify = concat . intersperse [qualChar]++many1 :: M a -> M [a]+many1 m = do+ x <- m+ xs <- many m+ return $ x : xs++lexTokens :: M [Token Point]+lexTokens = many lexToken++lexToken :: M (Token Point)+lexToken = oneof+ [ lexWhitespace+ , lexSingleLineComment+ , lexMultiLineComment+ , lexString+ , lexChar+ , lexNumber+ , lexKeyword+ , lexLident -- must be before lexUident for qualification+ , lexUident+ , lexSymbol+ , lexUsym+ ]++lexWhitespace :: M (Token Point)+lexWhitespace = do+ a <- getLoc+ s <- many1 $ lexCharPred (flip elem wsChar)+ b <- getLoc+ return $ TWhitespace (Point a b) s++lexSingleLineComment :: M (Token Point)+lexSingleLineComment = do+ a <- getLoc+ _ <- lexStringLit slCommentStart+ cs <- many $ lexCharPred ((/=) eolChar)+ b <- getLoc+ return $ TSLComment (Point a b) $ slCommentStart ++ cs++lexMultiLineComment :: M (Token Point)+lexMultiLineComment = do+ a <- getLoc+ s <- lexMLCommentStart+ b <- getLoc+ return $ TMLComment (Point a b) s++lexMLCommentStart :: M String+lexMLCommentStart = do+ sa <- lexStringLit mlCommentStart+ sb <- lexMLCommentEnd+ return $ sa ++ sb++lexMLCommentEnd :: M String+lexMLCommentEnd = do+ ms0 <- tryLex $ lexStringLit mlCommentEnd+ case ms0 of+ Just s -> return s+ Nothing -> do+ ms <- tryLex lexMLCommentStart+ sa <- case ms of+ Just s -> return s+ Nothing -> do+ c <- lexAnyChar+ return [c]+ sb <- lexMLCommentEnd+ return $ sa ++ sb++lexChar :: M (Token Point)+lexChar = do+ a <- getLoc+ _ <- lexCharPred ((==) sQuoteChar)+ s <- oneof [ lexEscChar, liftM singleton $ lexCharPred ((/=) sQuoteChar) ]+ _ <- lexCharPred ((==) sQuoteChar)+ b <- getLoc+ return $ TChar (Point a b) $ read ([sQuoteChar] ++ s ++ [sQuoteChar])++lexString :: M (Token Point)+lexString = do+ a <- getLoc+ _ <- lexCharPred ((==) dQuoteChar)+ s <- liftM concat $ many $ oneof [ lexEscChar, liftM singleton $ lexCharPred ((/=) dQuoteChar) ]+ _ <- lexCharPred ((==) dQuoteChar)+ b <- getLoc+ return $ TString (Point a b) $ read ([dQuoteChar] ++ s ++ [dQuoteChar])++lexDot :: M String+lexDot = liftM singleton $ lexCharPred ((==) '.')++lexSeq :: [M String] -> M String+lexSeq = liftM concat . sequence++lexExponent :: M String+lexExponent = do+ a <- liftM toLower $+ oneof [ lexCharPred ((==) 'e'), lexCharPred ((==) 'E') ]+ b <- oneof [ lexCharPred ((==) '+') >> return ""+ , liftM singleton $ lexCharPred ((==) '-')+ , return ""+ ]+ c <- lexDecimal+ return $ a : b ++ c+ +lexNumber :: M (Token Point)+lexNumber = do+ a <- getLoc+ c <- oneof+ [ liftM singleton $ lexCharPred ((==) '-')+ , return ""+ ]+ s <- oneof+ [ lex0x "0x" isHexit+ , lex0x "0X" isHexit+ , lex0x "0o" isOctit+ , lex0x "0O" isOctit+ , lex0x "0b" isBinit+ , lex0x "0B" isBinit+ , lexFloat+ , lexDecimal+ ]+ b <- getLoc+ return $ TNumber (Point a b) $ c ++ s++lexFloat :: M String+lexFloat = oneof+ [ lexSeq [ lexDecimal, lexDot, lexDecimal, lexExponent ]+ , lexSeq [ lexDecimal, lexDot, lexDecimal ]+ , lexSeq [ lexDecimal, lexExponent ]+ ]++lexDecimal :: M String+lexDecimal = liftM delLeadingZeros $ many1 $ lexCharPred isDigit++lex0x :: String -> (Char -> Bool) -> M String+lex0x s f = do+ cs <- sequence [lexCharPred ((==) c) | c <- s]+ ds <- many1 $ lexCharPred f+ return $ map toLower (cs ++ delLeadingZeros ds)++delLeadingZeros :: String -> String+delLeadingZeros x = case dropWhile ((==) '0') x of+ [] -> "0"+ a -> a+ +isHexit :: Char -> Bool+isHexit c = isDigit c || toLower c `elem` ['a' .. 'f']++isOctit :: Char -> Bool+isOctit c = c `elem` ['0' .. '7']++isBinit :: Char -> Bool+isBinit c = c `elem` ['0','1']++lexEscChar :: M String+lexEscChar = do+ _ <- lexCharPred ((==) escChar)+ s <- oneof+ [ liftM (show . (read :: String -> Int)) lexDecimal+ , liftM singleton lexAnyChar+ ]+ return $ escChar : s
+ Grm/Prims.hs view
@@ -0,0 +1,199 @@+{-# OPTIONS -Wall #-}+{-# LANGUAGE DeriveDataTypeable #-} ++-- The grm grammar generator+-- Copyright 2011-2012, Brett Letner++module Grm.Prims+ ( module Grm.Prims+ , module Debug.Trace+ )+where++import Control.Monad+import Data.Char+import Data.Generics hiding (empty)+import Data.List+import Data.Unique+import Debug.Trace+import System.Directory+import System.Exit (ExitCode(..))+import System.FilePath+import System.IO+import System.IO.Unsafe+import System.Process (system)+import Text.PrettyPrint.Leijen++data Terminator = Terminator | Separator deriving (Show, Eq)++data Horiz = Vert | Horiz deriving (Show, Eq)++data Empty = Empty | NonEmpty deriving (Show, Eq)++data Point = Point { beginLoc :: Loc, endLoc :: Loc }+ deriving (Show, Ord, Eq, Data, Typeable)++data Loc = Loc+ { locFilePath :: FilePath+ , locLine :: Int+ , locColumn :: Int+ } deriving (Show, Eq, Ord, Data, Typeable)++class HasMeta t where+ meta :: t a -> a+ -- mapMetaM :: Monad m => (a -> m b) -> t a -> m (t b)++ppString :: String -> Doc+ppString = text . show++ppInteger :: Integer -> Doc+ppInteger = text . show++type Number = String++ppNumber :: Number -> Doc+ppNumber = text++readNumber :: (Num a, Read a) => Number -> a+readNumber ('-':cs) = negate $ readNumber cs+readNumber cs | isBinary cs = fromIntegral $ readBinary cs+readNumber s = read s++readBinary :: String -> Integer+readBinary = foldl' (\b a -> 2*b + f a) 0 . drop 2+ where+ f '0' = 0+ f '1' = 1+ f c = error $ "readBinary:" ++ show c+ +ppChar :: Char -> Doc+ppChar c = text s+ where+ s = case show c of+ '\'':'\\':x:_ | isUpper x -> "'\\" ++ show (ord c) ++ "'" -- control chars+ str -> str++isFloat :: String -> Bool+isFloat s = '.' `elem` s || 'e' `elem` (map toLower s)++isBinary :: String -> Bool+isBinary s = 'b' `elem` map toLower s++isOctal :: String -> Bool+isOctal s = 'o' `elem` map toLower s++ppDouble :: Double -> Doc+ppDouble = text . show++ppList :: (a -> Doc) -> Terminator -> String -> Horiz -> [a] -> Doc+ppList f a s b cs = case b of+ Horiz -> hsep ds+ Vert -> empty <$> indent 2 (vsep ds) <$> empty+ where+ ds = case cs of+ [] -> []+ _ -> if a == Terminator then map g cs else map g (init cs) ++ [f $ last cs]+ g x = if null s then f x else f x <> text s++nubSort :: Ord a => [a] -> [a]+nubSort = nub . sort++ppLident :: String -> Doc+ppLident = text++ppMlcode :: String -> Doc+ppMlcode = text++ppUident :: String -> Doc+ppUident = text++ppUsym :: String -> Doc+ppUsym = text++singleton :: a -> [a]+singleton a = [a]++ppErr :: Loc -> String -> String+ppErr loc s = ppLoc loc ++ ": " ++ s++ppLoc :: Loc -> String+ppLoc loc = locFilePath loc ++ ":" ++ show (locLine loc) ++ ":" ++ show (1 + locColumn loc)++startLoc :: HasMeta m => m Point -> Loc+startLoc = beginLoc . point++stopLoc :: HasMeta m => m Point -> Loc+stopLoc = endLoc . point++noLoc :: Loc+noLoc = Loc "" 0 0++noPoint :: Point+noPoint = Point noLoc noLoc++initLoc :: FilePath -> Loc+initLoc fn = Loc fn 1 0++point :: HasMeta m => m Point -> Point+point = meta++lrPoint :: [Point] -> Point+lrPoint xs = case filter ((/=) noPoint) xs of+ [] -> noPoint+ _ -> Point (minimum $ map beginLoc xs) (maximum $ map endLoc xs)+ +lrPointList :: HasMeta m => [m Point] -> Point+lrPointList = lrPoint . map point++type Uident = String+type Lident = String+type Mlcode = String+type Usym = String++ppShow :: Pretty a => a -> String+ppShow = show . pretty++unreachable :: a+unreachable = panic "unreachable"++unused :: a+unused = panic "unused"++panic :: String -> a+panic s = error $ "internal:" ++ s++lowercase :: String -> String+lowercase "" = ""+lowercase (c:cs) = toLower c : cs++freshNm :: IO String+freshNm = liftM (show . hashUnique) newUnique++commaSep :: [String] -> String+commaSep = concat . intersperse ", "++mySystem :: String -> IO ()+mySystem s = do+ putStrLn s+ ec <- system s+ case ec of+ ExitSuccess -> return ()+ ExitFailure i -> error $ "unable to execute(" ++ show i ++ "):" ++ s++bitsToEncode :: Integer -> Integer+bitsToEncode 0 = 0+bitsToEncode i = ceiling $ logBase 2 (fromIntegral i :: Double)++uId :: a -> String -> String+{-# NOINLINE uId #-}+uId a s = seq a $ unsafePerformIO $ liftM ((++) s . show . hashUnique) newUnique++writeFileBinary :: FilePath -> String -> IO ()+writeFileBinary fn s = withBinaryFile fn WriteMode $ \h -> hPutStrLn h s++findFile :: [FilePath] -> FilePath -> IO (Maybe FilePath)+findFile [] _ = return Nothing+findFile (d:ds) n = do+ let fn = combine d n+ r <- doesFileExist fn+ if r then return (Just fn) else findFile ds n
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011-2012, Brett Letner++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Brett Letner nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README view
@@ -0,0 +1,31 @@+Introducing the grm compiler construction tool. Grm takes a grammar+specification and generates Haskell bindings. Grm is essentially a+simplified bnfc which only generates Haskell (bnfc+http://www.cse.chalmers.se/research/group/Language-technology/BNFC/).++Given a grammar the tool produces:+ - an abstract syntax implementation+ - a Happy parser generator file+ - a pretty-printer++Grm also has some library code for lexing and misc. language+development tasks (e.g. unique identifiers).++I use grm heavily in my pec language+compiler. (git@github.com:stevezhee/pec.git and on hackage). You can+check out pec for example grm usage.++Building+ - type 'make'+ - resolve all hackage dependencies+ - type 'make' again++You can download and install grm via cabal or access the git+repository on github (git@github.com:stevezhee/grm.git).++Any feedback on the design and/or implementation of grm would be+greatly appreciated :)++Thanks,+Brett+brettletner at gmail dot com
+ Setup.hs view
@@ -0,0 +1,5 @@+-- The grm grammar generator+-- Copyright 2011-2012, Brett Letner++import Distribution.Simple+main = defaultMain
+ THANKS view
@@ -0,0 +1,14 @@+The developement of grm would not have been possible without the following free software:+ - ghc http://haskell.org/ghc/+ - bnfc http://www.cse.chalmers.se/research/group/Language-technology/BNFC/+ - happy http://haskell.org/happy/+ - hackage modules http://hackage.haskell.org/packages/hackage.html++In addition I'd like to thank the following people...++Sigbjorn Finne, Andy Gill, Iavor Diatchki, Lee Pike, Isaac+Potoczny-Jones, Brian Adams, Travis Rhoades, Mike Letner, Brevin+Letner, and my family++Thanks guys!+
+ grm.cabal view
@@ -0,0 +1,39 @@+Name: grm++Version: 0.1.0++Synopsis: grm grammar converter++Description: Grm takes a grammar specification and generates Haskell bindings. Given a grammar the tool produces an abstract syntax implementation, a Happy parser generator file, and a pretty-printer.++License: BSD3++License-file: LICENSE++Author: Brett Letner <brettletner@gmail.com>++Maintainer: Brett Letner <brettletner@gmail.com>++Copyright: Brett Letner 2011-2012++Category: Development++Build-type: Simple++Extra-source-files: README, THANKS++Cabal-version: >= 1.8++source-repository head+ type: git+ location: git://github.com/stevezhee/grm.git++Library+ Exposed-modules: Grm.Prims, Grm.Layout, Grm.Lex+ Build-depends: base < 5, wl-pprint, process, syb, filepath, directory++Executable grm+ Main-is: grm.hs+ Build-depends: base < 5, parsec, wl-pprint, filepath, cmdargs, directory, Cabal, process, syb+ Build-tools: happy+
+ grm.hs view
@@ -0,0 +1,491 @@+{-# 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