packages feed

peggy-0.1.0: Text/Peggy/Parser.hs

module Text.Peggy.Parser (
  syntax,
  ) where

import Control.Applicative
import Data.Char
import Numeric
import Text.Parsec hiding ((<|>), many)
import Text.Parsec.String

import Text.Peggy.Syntax

syntax :: Parser Syntax
syntax = many definition <* skips <* eof

definition :: Parser Definition
definition =
  try (Definition <$> identifier <* symbol ":::" <*> haskellType <* symbol "=" <*> (Token <$> expr)) <|>
  (Definition <$> identifier <* symbol "::" <*> haskellType <* symbol "=" <*> expr)
  <?> "definition"

expr :: Parser Expr
expr = choiceExpr
  
choiceExpr :: Parser Expr
choiceExpr = sepBy1 semanticExpr (symbol "/")  >>= \es -> case es of
  [e] -> pure e
  _ -> pure $ Choice es
  <?> "choice expr"

semanticExpr :: Parser Expr
semanticExpr = sequenceExpr >>= \e ->
  option e $
    Semantic e <$> (symbol "{" *> codeFragment <* symbol "}")

sequenceExpr :: Parser Expr
sequenceExpr = some (try (namedExpr <* notFollowedBy (symbol "::" <|> symbol "="))) >>= \es -> case es of
  [e] -> pure e
  _ -> pure $ Sequence es

namedExpr :: Parser Expr
namedExpr =
  try (Named <$> identifier <* symbol ":" <*> suffixExpr) <|>
  suffixExpr

suffixExpr :: Parser Expr
suffixExpr = prefixExpr >>= go where
  go e = option e (symbol "*" *> go (Many e) <|>
                   symbol "+" *> go (Some e) <|>
                   symbol "?" *> go (Optional e))

prefixExpr :: Parser Expr
prefixExpr =
  (And <$ symbol "?" <*> primExpr) <|>
  (Not <$ symbol "!" <*> primExpr) <|>
  primExpr

primExpr :: Parser Expr
primExpr =
  terminals <|>
  (TerminalCmp <$> set "[^") <|>
  (TerminalSet <$> set "[") <|>
  (TerminalAny <$ symbol ".") <|>
  (NonTerminal <$> identifier) <|>
  try (SepBy  <$ symbol "(" <*> expr <* symbol "," <*> expr <* symbol ")") <|>
  try (SepBy1 <$ symbol "(" <*> expr <* symbol ";" <*> expr <* symbol ")") <|>
  symbol "(" *> expr <* symbol ")"
  <?> "primitive expression"

terminals :: Parser Expr
terminals = lexeme (do
  b <- oneOf "\"\'"
  s <- many charLit
  e <- oneOf "\"\'"
  return $ Terminals (b=='\"') (e=='\"') s)
  <?> "terminals"

charLit :: Parser Char
charLit = escaped <|> noneOf "\"\'" where
  escaped = char '\\' >> escChar

escChar :: Parser Char
escChar =
  ('\n' <$ char 'n' ) <|>
  ('\r' <$ char 'r' ) <|>
  ('\t' <$ char 't' ) <|>
  ('\\' <$ char '\\') <|>
  ('\"' <$ char '\"') <|>
  ('\'' <$ char '\'') <|>
  (chr . fst . head . readHex <$ char 'x' <*> count 2 hexDigit)

set :: String -> Parser [CharRange]
set st = lexeme $ string st *> many range <* char ']'

range :: Parser CharRange
range =
  try (CharRange <$> rchar <* char '-' <*> rchar) <|>
  (CharOne <$> rchar)
  where
    rchar = escaped <|> noneOf "]"
    escaped =
      char '\\' >>
      (escChar <|> 
       (']' <$ char ']') <|>
       ('^' <$ char '^') <|>
       ('-' <$ char '-'))

haskellType :: Parser HaskellType
haskellType = some (noneOf "=")
  <?> "type signature"

codeFragment :: Parser CodeFragment
codeFragment = many codePart
  <?> "code fragment"

codePart :: Parser CodePart
codePart =
  try argument <|>
  Snippet <$> some (try (notFollowedBy argument >> noneOf "}"))

argument :: Parser CodePart
argument = try $ Argument <$ char '$' <*> number where
  number = read <$> some digit

--

identifier :: Parser String
identifier =
  lexeme ((:) <$> startChar <*> many subsequentChar)
  <?> "identifier"  
  where
    startChar = char '_' <|> letter
    subsequentChar = startChar <|> digit

symbol :: String -> Parser String
symbol s = lexeme (string s)
  <?> "symbol: " ++ s

lexeme :: Parser a -> Parser a
lexeme p = try $ skips *> p

skips :: Parser ()
skips = () <$ many ((() <$ space) <|> comment)

comment :: Parser ()
comment = lineComment <|> regionComment
  <?> "comment"

lineComment :: Parser ()
lineComment = () <$ try (string "--") <* manyTill anyChar (char '\n')

regionComment :: Parser ()
regionComment = () <$ try (string "{-") <* com <* string "-}" where
  com = () <$ many (regionComment <|> (notFollowedBy (string "-}") <* anyChar))