anatomy-0.3: src/Anatomy/Parser.hs
module Anatomy.Parser (parseFile, parseDefinition) where
import Control.Monad.Error
import Data.Char (isSpace)
import Data.List (intercalate)
import Text.Parsec hiding (parse)
import Atomo.Parser (continue)
import Atomo.Parser.Base (Parser)
import Atomo.Parser.Expand
import Atomo.Parser.Expr hiding (parser)
import qualified Atomo.Types as AT
import qualified Atomo.Parser.Base as AB
import Anatomy.Debug
import Anatomy.Types
special :: Char
special = '#'
nested :: Parser [Segment]
nested = do
ps <- getState
pos <- getPosition
block <- balancedBetween '{' '}'
res <- lift $ runParserT parser ps (show pos) (cleanup block)
case res of
Left e -> fail ("nested: " ++ show e)
Right ok -> return ok
where
cleanup s = intercalate "\n" . map (drop indentLevel) $ ls
where
ls = dropWhile null . lines $ s
indentLevel = length . takeWhile isSpace $ head ls
chunk :: Parser Segment
chunk = do
c <- satisfy (/= special)
cs <- anyToken `manyTill` (eof <|> lookAhead (char special >> return ()))
dump ("got chunk", c:cs)
return (Chunk (c:cs))
keyword :: Parser Segment
keyword = do
char special
ks <- many1 . try $ do
name <- AB.anyIdent
char ':'
val <- choice
[ fmap Nested nested
, try $ fmap Atomo . choice $
-- literal value
[ unlexeme pLiteral
-- arbitrary expr
, try . between (char '(') (char ')') $
pExpr
]
-- operator reference
, try $ fmap (\x -> Atomo $ AT.Dispatch Nothing (AT.keyword [x] [])) $
between (char '(') (char ')')
(many1 (satisfy AB.isOpLetter))
-- keyword reference
, try $ fmap (\ks -> Atomo $ AT.Dispatch Nothing (AT.keyword ks [])) $
between (char '(') (char ')')
(many1 (AB.identifier >>= \n -> char ':' >> return n))
-- single reference; trailing punctuation is ignored
, do
ident <- AB.anyIdent
let punct = reverse . takeWhile AB.isOpLetter . reverse $ ident
sane = reverse . dropWhile AB.isOpLetter . reverse $ ident
getInput >>= setInput . (punct ++)
return . Atomo . AT.Dispatch Nothing $ AT.single sane (AT.ETop Nothing)
]
dump ("got value", val)
return (name, val)
let (ns, vs) = unzip ks
return (KeywordDispatch ns vs)
single :: Parser Segment
single = fmap (debug "single") $ do
char special
name <- AB.identifier
notFollowedBy (char ':')
dump ("got single identifier", name)
return (SingleDispatch name)
atomo :: Parser Segment
atomo = fmap (debug "atomo") $ do
char special
fmap Atomo (between (char '(') (char ')') pExpr)
parser :: Parser [Segment]
parser = do
ss <- many $ choice
[ try keyword
, try single
, try atomo
, chunk
]
eof
return ss
parseFile :: String -> AT.VM [Segment]
parseFile fn = liftIO (readFile fn) >>= continue parser fn >>= mapM expandSegment
expandSegment :: Segment -> AT.VM Segment
expandSegment (Atomo e) = liftM Atomo (macroExpand e)
expandSegment (Nested ss) =
liftM Nested (mapM expandSegment ss)
expandSegment (InlineDefinition d (Just s)) =
liftM (InlineDefinition d . Just) (expandSegment s)
expandSegment s = return s
defParser :: Parser Definition
defParser = do
thumb <- pDispatch
AB.whiteSpace
cs <- many . try $ do
AB.symbol "|"
d <- pDispatch
AB.whiteSpace
return d
AB.whiteSpace
ret <- AB.symbol ">" >> pDispatch
return Definition
{ defThumb = thumb
, defContracts = cs
, defReturn = ret
}
-- restore the whitespace that a lexeme parser nom'd up
unlexeme :: Parser a -> Parser a
unlexeme p = do
before <- getInput
r <- p
after <- getInput
backtrack before after
return r
where
backtrack b a = setInput (trailing ++ a)
where
trailing
= reverse
. takeWhile isSpace
. reverse
. take (length b - length a)
$ b
-- grab text between characters, balanced
balancedBetween :: Char -> Char -> Parser String
balancedBetween o c = try $ do
char o
raw <- many . choice $
[ many1 $ noneOf [o, c]
, do
res <- balancedBetween o c
return $ o : res ++ [c]
]
char c
return $ concat raw
parseDefinition :: String -> AT.VM Definition
parseDefinition = continue
(do { r <- defParser; eof; return r })
"<definition>"