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
}