Earley-0.8.1: examples/Mixfix.hs
{-# LANGUAGE RecursiveDo #-}
import Control.Applicative
import Control.Arrow(first)
import Data.Foldable(asum, foldrM)
import System.Environment
import Text.Earley
import qualified Data.Set as S
type Ident = String
data IdentPart = Ident Ident | Hole
deriving Show
type HoleyIdent = [IdentPart]
holey :: Ident -> HoleyIdent
holey "" = []
holey ('_':xs) = Hole : holey xs
holey xs = Ident i : holey rest
where (i, rest) = span (/= '_') xs
data Assoc = LeftAssoc | RightAssoc | NonAssoc
deriving (Eq, Show)
data Expr = V HoleyIdent | App Expr [Expr]
deriving Show
grammar :: [[(HoleyIdent, Assoc)]] -> Grammar r String (Prod r String String Expr)
grammar table = mdo
let ident = (V . (:[]) . Ident) <$> satisfy (`S.notMember` mixfixParts)
expr <- foldrM ($) ident (normalApp expr : levels expr)
return expr
where
mixfixParts = S.fromList [s | xs <- table, (ys, _) <- xs, Ident s <- ys]
normalApp expr next = rule $ App <$> expr <*> some next
<|> next
levels expr = map (level expr) table
level expr idents next = mdo
same <- rule $ asum $ next : map (mixfixIdent same) idents
return same
where
mixfixIdent same (ps, a) = App (V ps) <$> go ps
where
go ps' = case ps' of
[Ident s] -> [] <$ namedSymbol s
Hole:rest -> (:) <$> (if a == RightAssoc then next else same) <*> go rest
[Ident s, Hole] -> (:[]) <$ namedSymbol s <*> (if a == LeftAssoc then next else same)
Ident s:Hole:rest -> (:) <$ namedSymbol s <*> expr <*> go rest
_ -> error "invalid identifier"
identTable :: [[(HoleyIdent, Assoc)]]
identTable = (map . map) (first holey)
[ [("_->_", RightAssoc)]
, [("_,_", NonAssoc)]
, [("if_then_else_", RightAssoc)]
, [("_|-_:_", NonAssoc)]
, [("_+_", LeftAssoc)]
, [("_*_", LeftAssoc)]
, [("(_)", NonAssoc)
,("[_]", NonAssoc)
,("<_>", NonAssoc)
]
]
pretty :: Expr -> String
pretty (V ps) = concatMap go ps
where
go Hole = "_"
go (Ident s) = s
pretty (App e es) = "(" ++ pretty e ++ " " ++ unwords (map pretty es) ++ ")"
tokenize :: String -> [Ident]
tokenize "" = []
tokenize (' ':xs) = tokenize xs
tokenize ('\n':xs) = tokenize xs
tokenize (x:xs)
| x `S.member` special = [x] : tokenize xs
| otherwise = (x:as) : tokenize bs
where
(as, bs) = span (`S.notMember` special) xs
special = S.fromList "()[], \n"
main :: IO ()
main = do
x:_ <- getArgs
print $ first (map pretty) $ fullParses $ parser (grammar identTable) (tokenize x)