packages feed

camfort-0.903: src/Camfort/Specification/Stencils/Parser.y

{

module Camfort.Specification.Stencils.Parser
  ( specParser
  , SpecParseError
  ) where

import Control.Monad.Except (throwError)
import Data.Char (isLetter, isNumber, isAlphaNum, toLower, isAlpha, isSpace)
import Data.List (intercalate, isInfixOf)

import           Camfort.Specification.Parser
  (SpecParser, mkParser)
import           Camfort.Specification.Stencils.Model
  (Approximation(..), Multiplicity(..))
import           Camfort.Specification.Stencils.Parser.Types
import qualified Camfort.Specification.Stencils.Syntax as Syn

}

%monad { StencilSpecParser } { >>= } { return }
%name parseSpecification SPEC
%tokentype { Token }
%token
  stencil     { TId _ "stencil" }
  access      { TId _ "access" }
  region      { TId _ "region" }
  readOnce    { TId _ "readonce" }
  pointed     { TId _ "pointed" }
  nonpointed  { TId _ "nonpointed" }
  atMost      { TId _ "atmost" }
  atLeast     { TId _ "atleast" }
  dim         { TId _ "dim" }
  depth       { TId _ "depth" }
  forward     { TId _ "forward" }
  backward    { TId _ "backward" }
  centered    { TId _ "centered" }
  id          { TId _ $$ }
  num         { TNum $$ }
  '+'         { TPlus }
  '*'         { TStar }
  '::'        { TDoubleColon }
  '='         { TEqual }
  '('         { TLParen }
  ')'         { TRParen }

%left '+'
%left '*'

%%

SPEC :: { Specification }
: REGIONDEC                 { RegionDec (fst $1) (snd $1) }
| stencil SPECDEC '::' VARS { SpecDec ($2 True) $4 }
| access  SPECDEC '::' VARS { SpecDec ($2 False) $4 }

REGIONDEC :: { (String, Region) }
: region '::' id '=' REGION { ($3, $5) }

REGION ::                       { Region }
: REGIONCONST                   { RegionConst $1 }
| REGION '+' REGION             { Or $1 $3 }
| REGION '*' REGION             { And $1 $3 }
| '(' REGION ')'                { $2 }
| id                            { Var $1 }

REGIONCONST :: { Syn.Region }
: forward  '(' REGION_ATTRS ')' { applyAttr Syn.Forward  $3 }
| backward '(' REGION_ATTRS ')' { applyAttr Syn.Backward $3 }
| centered '(' REGION_ATTRS ')' { applyAttr Syn.Centered $3 }
| pointed  '(' dim '=' num ')'  { Syn.Centered 0 (read $5) True }

REGION_ATTRS :: { (Depth Int, Dim Int, Bool) }
  : DEPTH DIM_REFL    { ($1, fst $2, snd $2) }
  | DIM   DEPTH_REFL  { (fst $2, $1, snd $2) }
  | REFL  DEPTH DIM   { ($2, $3, $1) }
  | REFL  DIM DEPTH   { ($3, $2, $1) }

DIM_REFL :: { (Dim Int, Bool) }
DIM_REFL
   : REFL DIM { ($2, $1) }
   | DIM REFL { ($1, $2) }
   | DIM      { ($1, True) }

DEPTH_REFL :: { (Depth Int, Bool) }
DEPTH_REFL
   : DEPTH REFL { ($1, $2) }
   | REFL DEPTH { ($2, $1) }
   | DEPTH      { ($1, True) }

DEPTH :: { Depth Int }
DEPTH : depth '=' num { Depth $ read $3 }

DIM :: { Dim Int }
DIM : dim '=' num { Dim $ read $3 }

REFL :: { Bool }
 : nonpointed { False }

SPECDEC :: { Syn.IsStencil -> SpecInner }
: MULTIPLICITY { SpecInner $1 }

MULTIPLICITY ::          { Multiplicity (Approximation Region) }
: readOnce APPROXIMATION { Once $2 }
| APPROXIMATION          { Mult $1 }

APPROXIMATION :: { Approximation Region }
: atLeast REGION { Bound (Just $2) Nothing }
| atMost REGION  { Bound Nothing (Just $2) }
| REGION         { Exact $1 }

VARS :: { [String] }
: id VARS { $1 : $2 }
| id      { [$1] }

{

-- ** Errors

data SpecParseError
  -- | Not a valid identifier character.
  = NotAnIdentifier Char
  -- | Tokens do not represent a syntactically valid specification.
  | CouldNotParseSpecification [Token]
  deriving (Eq)

instance Show SpecParseError where
  show (CouldNotParseSpecification ts) =
    "Could not parse specification at: \"" ++ prettyTokens ts ++ "\"\n"
  show (NotAnIdentifier c) = "Invalid character in identifier: " ++ show c

notAnIdentifier :: Char -> SpecParseError
notAnIdentifier = NotAnIdentifier

couldNotParseSpecification :: [Token] -> SpecParseError
couldNotParseSpecification = CouldNotParseSpecification

type StencilSpecParser a = Either SpecParseError a

newtype Depth a = Depth a
newtype Dim a = Dim a

applyAttr :: (Int -> Int -> Bool -> Syn.Region)
          -> (Depth Int, Dim Int, Bool)
          -> Syn.Region
applyAttr constr (Depth d, Dim dim, irrefl) = constr d dim irrefl

data Token
  = TDoubleColon
  | TStar
  | TPlus
  | TEqual
  | TComma
  | TLParen
  | TRParen
  | TId String String -- first string contains the original text
                      -- second is normalised (e.g., for keywords)
  | TNum String
 deriving (Show, Eq)

addToTokens :: Token -> String -> StencilSpecParser [ Token ]
addToTokens tok rest = do
 tokens <- lexer rest
 return $ tok : tokens

lexer :: String -> StencilSpecParser [ Token ]
lexer []                                              = return []
lexer (' ':xs)                                        = lexer xs
lexer ('\t':xs)                                       = lexer xs
lexer (':':':':xs)                                    = addToTokens TDoubleColon xs
lexer ('*':xs)                                        = addToTokens TStar xs
lexer ('+':xs)                                        = addToTokens TPlus xs
lexer ('=':xs)                                        = addToTokens TEqual xs
-- Comma hack: drop commas that are not separating numbers,
-- in order to avoid need for 2-token lookahead.
lexer (',':xs)
  | x':xs' <- dropWhile isSpace xs, not (isNumber x') = lexer (x':xs')
  | otherwise                                         = addToTokens TComma xs
lexer ('(':xs)                                       = addToTokens TLParen xs
lexer (')':xs)                                       = addToTokens TRParen xs
lexer (x:xs)
  | isLetter x                                        =
        aux (\x -> TId x $ fmap toLower x) $ \ c -> isAlphaNum c || c == '_'
  | isPositiveNumber x                                = aux TNum isNumber
  | otherwise
     = throwError $ notAnIdentifier x
 where
   isPositiveNumber x = isNumber x && x /= '0'
   aux f p = (f target :) <$> lexer rest
     where (target, rest) = span p (x:xs)

specParser :: SpecParser SpecParseError Specification
specParser = mkParser (\src -> do
                          tokens <- lexer src
                          parseSpecification tokens)
             ["stencil", "region", "access"]

happyError :: [ Token ] -> StencilSpecParser a
happyError = throwError . couldNotParseSpecification

-- | Pretty-print the tokens, showing the smallest unique prefix of tokens
prettyTokens :: [ Token ] -> String
prettyTokens =
    (++ "... ") . intercalate " " . map prettyToken . takeUniquePrefix 1
  where
    takeUniquePrefix _ [] = []
    takeUniquePrefix n ts =
      if ((take n ts) `isInfixOf` (drop n ts))
      then takeUniquePrefix (n+1) ts
      else take n ts

prettyToken TDoubleColon = "::"
prettyToken TStar        = "*"
prettyToken TPlus        = "+"
prettyToken TEqual       = "="
prettyToken TComma       = ","
prettyToken TLParen      = "("
prettyToken TRParen      = ")"
prettyToken (TId s _)    = s
prettyToken (TNum n)     = n

}