packages feed

formura-1.0: src/Formura/Parser.hs

{-|
Module      : Formura.Parser
Description : parser combinator
Copyright   : (c) Takayuki Muranushi, 2015
License     : MIT
Maintainer  : muranushi@gmail.com
Stability   : experimental

This module contains combinator for writing Formura parser, and also the parsers for Formura syntax.
-}

{-# LANGUAGE FlexibleContexts, GeneralizedNewtypeDeriving, TypeOperators #-}
module Formura.Parser where

import Control.Applicative
import Control.Lens
import Control.Monad
import Data.Char (isSpace, isLetter, isAlphaNum, isPrint)
import Data.Either (partitionEithers)
import Data.Maybe
import Data.Monoid
import qualified Data.Set as S
import Text.Trifecta hiding (ident)
import Text.Trifecta.Delta
import qualified Text.Parser.Expression as X
import qualified Text.PrettyPrint.ANSI.Leijen as Ppr

import Text.Parser.LookAhead

import Formura.Language.Combinator
import Formura.Vec
import Formura.Syntax

-- * The parser comibnator

-- | The parser monad.
newtype P a = P { runP :: Parser a }
            deriving (Alternative, Monad, Functor, MonadPlus, Applicative, CharParsing, LookAheadParsing, Parsing, DeltaParsing, MarkParsing Delta)

instance Errable P where
  raiseErr = P . raiseErr

instance TokenParsing P where
  someSpace =
    let  f '\n' = False
         f '\r' = False
         f x | isSpace x = True
             | otherwise = False

    in "whitespace" ?> some ((satisfy f >> return ())
             <|> comment
             <|> lineContinuation)
       >> return ()

-- | Document the parser.
(?>) :: String -> P a -> P a
s ?> p = p <?> s

infixr 0 ?>

-- | Parse a string as a keyword. Check if the keyword is indeed in a keyword list.
keyword :: IdentName -> P IdentName
keyword k = "keyword " ++ k ?> do
  when (k `S.notMember` keywordSet) $
    raiseErr $ failed $
    "Please report the compiler developer: \"" ++ k ++ "\" is not in a keyword list!"
  symbol k

-- | The set of keywords. The string is not parsed as a identifier if it's in the keyword list.
keywordSet :: S.Set IdentName
keywordSet = S.fromList
             ["begin", "end", "function", "returns", "let", "in", "lambda", "for", "dimension", "axes",
              "+","-","*","/",".","::","=", ","]


comment :: P ()
comment = "comment" ?> do
  char '#'
  manyTill anyChar (lookAhead newline)
  return ()

lineContinuation :: P ()
lineContinuation = "line continuation" ?> do
  char '\\'
  whiteSpace
  newline
  return ()

-- | Run parser, and record the metadata for the parsed syntax component
parseIn :: Functor f => P (Fix f) -> P (Fix f)
parseIn p = do
  r1 <- rend
  (In m x)  <- p
  r2 <- rend
  let m2 = Just $ Metadata r1 (delta r1) (delta r2)
  return $ In (m <|> m2) x


-- * The parser for Formura syntax

isIdentifierAlphabet0 :: Char -> Bool
isIdentifierAlphabet0 = isLetter
isIdentifierAlphabet1 :: Char -> Bool
isIdentifierAlphabet1 c = isAlphaNum c || c == '_'  || c == '\''
isIdentifierSymbol :: Char -> Bool
isIdentifierSymbol c = isPrint c &&
  not (isIdentifierAlphabet1 c || isSpace c ||
      c `elem` "\"#();[\\]{}")

identName :: P IdentName
identName = "identifier" ?> try $ do
  let s :: P String
      s = some $ "symbolic character" ?> satisfy isIdentifierSymbol
      a0 :: P Char
      a0 = "identifier alphabet character" ?> satisfy isIdentifierAlphabet0
      a1 :: P Char
      a1 = "identifier alphabet character" ?> satisfy isIdentifierAlphabet1
      a :: P String
      a = (:) <$> a0 <*> many a1
  str <- s <|> a
  guard $  str `S.notMember` keywordSet
  whiteSpace
  return str



ident :: (IdentF ∈ fs) => P (Lang fs)
ident = "identifier" ?> parseIn $ Ident <$> identName

elemType :: (ElemTypeF ∈ fs) => P (Lang fs)
elemType = "element type" ?> parseIn $ do
  str <- identName
  guard $ str `S.member` elemTypeNames
  return $ ElemType str
    where
      elemTypeNames = S.fromList ["int","rational","float","double","real"]

funType :: (FunTypeF ∈ fs) => P (Lang fs)
funType = "function type" ?> parseIn $ keyword "function" *> pure FunType


tupleOf :: (TupleF ∈ fs) => P (Lang fs) -> P (Lang fs)
tupleOf p = "tuple" ?> {- don't parseIn here ... -} do
  r1 <- rend
  "tuple opening" ?> try $ symbolic '('
  xs <- p `sepBy` symbolic ','
  symbolic ')'
  r2 <- rend
  case xs of
    -- ... because we treat one-element tuple as parenthesized expression.
    [x] -> return x
    _   -> return $ In (Just $ Metadata r1 (delta r1) (delta r2)) $ Tuple xs

gridIndicesOf :: P a -> P (Vec a)
gridIndicesOf parseIdx = "grid index" ?> do
  "grid opening" ?> try $ symbolic '['
  xs <- parseIdx `sepBy` symbolic ','
  symbolic ']'
  return $ Vec xs

nPlusK :: P NPlusK
nPlusK = "n+k pattern" ?> do
  x <-  identName
  mn <- optional $ do
    s <- symbolic '+' <|> symbolic '-'
    n <- constRationalExpr
    if s == '+' then return n else return (negate n)
  return $ NPlusK x (maybe 0 id mn)



imm :: (ImmF ∈ fs) => P (Lang fs)
imm = "rational literal" ?> parseIn $ do
  Imm <$> constRational

exprOf :: (OperatorF ∈ fs, ApplyF  ∈ fs) => P (Lang fs) -> P (Lang fs)
exprOf termParser = X.buildExpressionParser tbl termParser
  where
    tbl = [[binary "." Apply X.AssocRight],
           [binary "**" (Binop "**") X.AssocLeft],
           [binary "*" (Binop "*") X.AssocLeft, binary "/" (Binop "/") X.AssocLeft],
           [unary "+" (Uniop "+") , unary "-" (Uniop "-") ],
           [binary "+" (Binop "+") X.AssocLeft, binary "-" (Binop "-") X.AssocLeft]
          ]
    unary  name fun = X.Prefix (pUni name fun)
    binary name fun assoc = X.Infix (pBin name fun) assoc

    pUni name fun = "unary operator " ++ name ?> do
      r1 <- rend
      f <- fun <$ keyword name
      r2 <- rend
      return $ \a -> f a & metadata .~ (Just $ joinMeta r1 r2 a a)

    pBin name fun = "binary operator " ++ name ?> do
      r1 <- rend
      f <- fun <$ keyword name
      r2 <- rend
      return $ \a b -> f a b & metadata .~ (Just $ joinMeta r1 r2 a b)

    joinMeta r1 r2 a b = let
      da = case a ^. metadata of
        Nothing -> delta r1
        Just ma -> min (ma ^. metadataBegin) (delta r1)
      db = case b ^. metadata of
        Nothing -> delta r2
        Just mb -> max (mb ^. metadataEnd) (delta r2)
      in Metadata r1 da db

expr10 :: P RExpr
expr10 = fexpr

fexpr :: P RExpr
fexpr = "function application chain" ?> do
  f <- aexpr
  findArgument f
  where
    findArgument :: RExpr -> P RExpr
    findArgument f = parseIn $ do
      mx' <- optional $ gridIndicesOf nPlusK
      case mx' of
        Just x -> findArgument $ Grid x f
        Nothing ->do
          mx <- optional $ aexpr
          case mx of
            Just x -> findArgument $ Apply f x
            Nothing ->  return f


aexpr :: P RExpr
aexpr = tupleOf rExpr <|> letExpr <|> lambdaExpr <|> ident <|> imm


letExpr :: P RExpr
letExpr = "let expression" ?> parseIn $ do
  "keyword let" ?> try $ keyword "let"
  xs <- binding
  keyword "in"
  x <- rExpr
  return $ Let xs x

lambdaExpr :: P RExpr
lambdaExpr = "lambda expression" ?> parseIn $ do
  "keyword for" ?> try $ keyword "for"
  x <- tupleOf lExpr
  y <- rExpr
  return $ Lambda x y

binding :: P (BindingF RExpr)
binding = "statements" ?> do
  stmts <- statementCompound `sepEndBy` statementDelimiter
  return $ Binding $ concat stmts

statementDelimiter :: P ()
statementDelimiter = "statement delimiter" ?> some d >> return ()
  where
    d = (symbolic ';' >> return ()) <|> (newline >> whiteSpace)

statementCompound :: P [StatementF RExpr]
statementCompound = functionSyntaxSugar <|> typeValueStatements

functionSyntaxSugar :: P [StatementF RExpr]
functionSyntaxSugar = "function definition" ?> do
  keyword "begin"
  keyword "function"
  (funName, inExpr, outExpr) <-
    ("returns-form" ?> try returnsForm) <|>
    ("equal-form" ?> try equalForm) <|>
    raiseErr (Err (Just $ Ppr.text "Malformed Function Syntax" <> Ppr.line)
              [Ppr.text "Please check if you are using one of the following forms:",
               Ppr.text "・  begin function f(x) returns y",
               Ppr.text "・  begin function y = f(x)"]
              S.empty)
  statementDelimiter
  b <- binding
  keyword "end"
  keyword "function"
  return [Subst funName $ Lambda inExpr $ Let b outExpr]
  where
    returnsForm :: P (LExpr, LExpr, RExpr)
    returnsForm = do
      fn <- ident
      inx <- tupleOf lExpr
      keyword "returns"
      outx <- rExpr
      return (fn, inx, outx)

    equalForm :: P (LExpr, LExpr, RExpr)
    equalForm = do
      outx <- rExpr
      keyword "="
      fn <- ident
      inx <- tupleOf lExpr
      return (fn, inx, outx)


typeValueStatements :: P [StatementF RExpr]
typeValueStatements = "type-decl and/or substitiution statement" ?> do
  maybeType <- optional $ "statement start by type decl" ?> try $ typeExpr <* keyword "::"

  let lhsAndMaybeRhs :: P (LExpr, Maybe RExpr)
      lhsAndMaybeRhs = do
        lhs   <- lExpr
        mRhs  <- optional (keyword "=" >> rExpr)
        return (lhs, mRhs)
  lamrs <- case maybeType of
    -- When there is type, we allow multiple substitutions, and lhs-only terms.
    Just _ -> lhsAndMaybeRhs `sepBy1` symbol ","
    -- When there is no type, we allow only one substitution.
    Nothing -> do
      lhs <- lExpr
      keyword "="
      rhs <- rExpr
      return [(lhs, Just rhs)]

  let typePart = [ TypeDecl typ lhs
                 | typ <- maybeToList maybeType,
                   lhs <- map fst lamrs
                   ]
      substPart = [Subst lhs rhs
                   | (lhs, Just rhs) <- lamrs]
  -- Type definitions always come before the values.
  return $ typePart ++ substPart




lAexpr :: P LExpr
lAexpr = "atomic l-expr" ?> tupleOf lExpr <|> ident

vectorIndexOf :: P a -> P a
vectorIndexOf content = do
  "vector index access" ?> try $ symbolic '('
  r <- content
  symbolic ')'
  return r

lFexpr :: P LExpr
lFexpr = "applied l-expr" ?> do
  f <- lAexpr
  go f
  where
    go :: LExpr -> P LExpr
    go f = parseIn $ do
      mx <- "grid option" ?> optional $ gridIndicesOf nPlusK
      case mx of
        Just x -> go $ Grid x f
        Nothing -> do
          mx' <- "grid option" ?>  optional (vectorIndexOf identName)
          case mx' of
            Just x -> go $ Vector x f
            Nothing -> return f

lExpr :: P LExpr
lExpr = "l-expr" ?> lFexpr

typeExpr :: P TypeExpr
typeExpr = typeFexpr

typeAexpr :: P TypeExpr
typeAexpr = "atomic type-expression" ?> tupleOf typeExpr <|> elemType <|> funType

typeFexpr :: P TypeExpr
typeFexpr = "applied type-expression" ?> do
  f <- typeAexpr
  go f
  where
    go :: TypeExpr -> P TypeExpr
    go f = parseIn $ do
      mx <- optional (gridIndicesOf constRationalExpr)
      case mx of
        Just x -> go $ GridType x f
        Nothing -> do
          mx' <- optional (vectorIndexOf constIntExpr)
          case mx' of
            Just x -> go $ VectorType x f
            Nothing -> return f


rExpr :: P RExpr
rExpr = "r-expr" ?> exprOf expr10

constRationalExpr :: P Rational
constRationalExpr = "const rational expression" ?> do
  cre <- exprOf imm
  mfoldout evalCRE cre

evalCRE :: ConstRationalExprF Rational -> P Rational
evalCRE (Imm x) = return x
evalCRE (Uniop "+" x) = return x
evalCRE (Uniop "-" x) = return $ negate x
evalCRE (Binop "+" a b) = return $ a + b
evalCRE (Binop "-" a b) = return $ a - b
evalCRE (Binop "*" a b) = return $ a * b
evalCRE (Binop "/" a b) = return $ a / b
evalCRE _ = raiseErr $ failed "unsupported operator in const rational expression"

constRational :: P Rational
constRational = "const rational expression" ?> do
  nos <- naturalOrScientific
  return $ either toRational toRational  nos


constIntExpr :: P Int
constIntExpr = fromInteger <$> natural


specialDeclaration :: P SpecialDeclaration
specialDeclaration = dd  <|> ad
  where
    dd = do
      "dimension declaration" ?> try $ keyword "dimension"
      keyword "::"
      n <- natural
      return $ DimensionDeclaration $ fromInteger n
    ad = do
      "axes declaration" ?> try $ keyword "axes"
      keyword "::"
      xs <- identName `sepBy` symbolic ','
      return $ AxesDeclaration xs

program :: P Program
program = do
  ps <- choice [Left <$> specialDeclaration, Right <$> statementCompound]
        `sepEndBy` statementDelimiter
  let (decls, stmts) = partitionEithers ps
  return $ Program decls (BindingF $ concat stmts)