language-puppet-1.5.1: src/Puppet/Parser/Internal.hs
{-# LANGUAGE TupleSections #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use id" #-}
{-# HLINT ignore "Use <$>" #-}
module Puppet.Parser.Internal where
import Control.Monad.Combinators.Expr
import qualified Data.Char as Char
import qualified Data.List as List
import qualified Data.List.NonEmpty as NE
import qualified Data.Maybe.Strict as S
import qualified Data.Scientific as Scientific
import qualified Data.Text as Text
import qualified Data.Vector as V
import Puppet.Language
import Puppet.Parser.Types
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as Lexer
import qualified Text.Regex.PCRE.ByteString.Utils as Regex
import XPrelude.Extra hiding (key, many, some, try)
-- | Space consumer
sc :: Parser ()
sc = Lexer.space space1 (Lexer.skipLineComment "#") (Lexer.skipBlockComment "/*" "*/")
-- | Lexeme consumes spaces after the input parser
lexeme :: Parser a -> Parser a
lexeme = Lexer.lexeme sc
-- | Consumes a text then consumes spaces
symbol :: Text -> Parser ()
symbol = void . Lexer.symbol sc
-- | Consumes a character then consumes spaces
symbolic :: Char -> Parser ()
symbolic = lexeme . void . single
braces :: Parser a -> Parser a
braces = between (symbolic '{') (symbolic '}')
parens :: Parser a -> Parser a
parens = between (symbolic '(') (symbolic ')')
brackets :: Parser a -> Parser a
brackets = between (symbolic '[') (symbolic ']')
comma :: Parser ()
comma = symbolic ','
sepComma :: Parser a -> Parser [a]
sepComma p = p `sepEndBy` comma
sepComma1 :: Parser a -> Parser [a]
sepComma1 p = p `sepEndBy1` comma
-- | Parses an 'Expression'.
expression :: Parser Expression
expression = do
expr <- makeExprParser (lexeme terminal) expressionTable
ms <- optional $ do
symbolic '?'
let case_expr = do
c <-
SelectorDefault <$ symbol "default" -- default case
<|> SelectorType <$> try datatype
<|> fmap
SelectorValue
( UVariableReference <$> variableReference
<|> UBoolean <$> puppetBool
<|> UUndef <$ symbol "undef"
<|> literalValue
<|> UInterpolable <$> interpolableString
<|> URegexp <$> termRegexp
)
symbol "=>"
e <- expression
pure (c :!: e)
cases <- braces (sepComma1 case_expr)
pure (ConditionalValue expr (V.fromList cases))
case ms of
Nothing -> pure expr
Just cv -> pure cv
stringLiteral' :: Parser Text
stringLiteral' = between (char '\'') (symbolic '\'') interior
where
interior = Text.pack . concat <$> many (some (noneOf ['\'', '\\']) <|> (char '\\' *> fmap escape anySingle))
escape '\'' = "'"
escape x = ['\\', x]
identifier :: Parser (Tokens Text)
identifier = takeWhile1P Nothing isIdentifierChar
-- | Only Ascii, hyphens (-) are not allowed.
isIdentifierChar :: Char -> Bool
isIdentifierChar x = Char.isAsciiLower x || Char.isAsciiUpper x || Char.isDigit x || (x == '_')
-- | Like 'indentifier' but hyphens (-) are allowed.
bareword :: Parser Text
bareword = Text.cons <$> satisfy Char.isAsciiLower <*> takeWhileP Nothing isBarewordChar
where
isBarewordChar :: Char -> Bool
isBarewordChar x = isIdentifierChar x || (x == '-')
reserved :: Text -> Parser ()
reserved s =
try $ do
void (chunk s)
notFollowedBy (satisfy isIdentifierChar)
sc
qualif :: Parser Text -> Parser Text
qualif p = do
header <- option "" (chunk "::")
(header <>) . Text.intercalate "::" <$> p `sepBy1` chunk "::"
qualif1 :: Parser Text -> Parser Text
qualif1 p = do
r <- qualif p
unless ("::" `Text.isInfixOf` r) (fail "This parser is not qualified")
pure r
-- | Consumes a var $foo and then spaces
variableReference :: Parser Text
variableReference = do
v <- char '$' *> lexeme variableName
when (Text.all Char.isDigit v) (fail "Can't assign fully numeric variables")
pure v
variableName :: Parser Text
variableName = qualif identifier
-- yay with reserved words
typeName :: Parser Text
typeName = className
className :: Parser Text
className = lexeme $ qualif $ genericModuleName False
funcName :: Parser Text
funcName = lexeme $ qualif $ genericModuleName False
moduleName :: Parser Text
moduleName = lexeme $ genericModuleName False
parameterName :: Parser Text
parameterName = moduleName
resourceNameRef :: Parser Text
resourceNameRef = lexeme $ qualif (genericModuleName True)
genericModuleName :: Bool -> Parser Text
genericModuleName isReference = do
let acceptable x = Char.isAsciiLower x || Char.isDigit x || (x == '_')
firstletter =
if isReference
then fmap Char.toLower (satisfy Char.isAsciiUpper)
else satisfy Char.isAsciiLower
Text.cons <$> firstletter <*> takeWhileP Nothing acceptable
-- | Variable expression
varExpression :: Parser Expression
varExpression = Terminal . UVariableReference <$> variableReference
-- | String interpolation
interpolableString :: Parser (Vector Expression)
interpolableString =
V.fromList
<$> between
(char '"')
(symbolic '"')
(many (interpolableVariableReference <|> doubleQuotedStringContent <|> fmap (Terminal . UString . Text.singleton) (char '$')))
where
doubleQuotedStringContent =
Terminal . UString . Text.pack . concat
<$> some ((char '\\' *> fmap escaper anySingle) <|> some (noneOf ['"', '\\', '$']))
escaper :: Char -> String
escaper 'n' = "\n"
escaper 't' = "\t"
escaper 'r' = "\r"
escaper '"' = "\""
escaper '\\' = "\\"
escaper '$' = "$"
escaper x = ['\\', x]
-- this is specialized because we can't be "tokenized" here
varname = Text.concat <$> some (chunk "::" <|> identifier)
varexpr = Terminal . UVariableReference <$> varname
indexchain = makeExprParser varexpr [[Postfix indexLookupChain]] -- e.g: os['release']['major']
interpolableVariableReference = do
void (char '$')
let fenced =
try (indexchain <* char '}')
<|> try (varexpr <* char '}')
<|> (expression <* char '}')
(symbolic '{' *> fenced) <|> try varexpr <|> pure (Terminal (UString (Text.singleton '$')))
integerOrDouble :: Parser (Either Integer Double)
integerOrDouble = Left <$> hex <|> (either Right Left . Scientific.floatingOrInteger <$> Lexer.scientific)
where
hex = chunk "0x" *> Lexer.hexadecimal
puppetArray :: Parser UnresolvedValue
puppetArray = fmap (UArray . V.fromList) (brackets (sepComma expression)) <?> "Array"
puppetHash :: Parser UnresolvedValue
puppetHash = fmap (UHash . V.fromList) (braces (sepComma hashPart)) <?> "Hash"
where
hashPart = (:!:) <$> (expression <* symbol "=>") <*> expression
puppetBool :: Parser Bool
puppetBool =
(reserved "true" >> pure True)
<|> (reserved "false" >> pure False)
<?> "Boolean"
resourceReferenceRaw :: Parser (Text, [Expression])
resourceReferenceRaw = do
let restype_parser = qualif (genericModuleName True)
resnames_parser = brackets (expression `sepBy1` comma)
(,) <$> restype_parser <*> resnames_parser <?> "Resource reference"
resourceReference :: Parser UnresolvedValue
resourceReference = do
(restype, resnames) <- resourceReferenceRaw
pure $ UResourceReference restype $ case resnames of
[x] -> x
_ -> Terminal $ UArray (V.fromList resnames)
-- | Functions that have named that are not valid ...
specialFunctions :: Parser Text
specialFunctions =
chunk "Integer"
<|> chunk "Numeric"
<|> chunk "Sensitive"
-- The first argument defines if non-parenthesized arguments are acceptable
genFunctionCall :: Bool -> Parser (Text, Vector Expression)
genFunctionCall nonparens = do
fname <- (specialFunctions <|> funcName) <?> "Function name"
let -- first check if the function arg is not a qualified name (ex.: include foo::bar)
-- if it is not, then we expect an expression
qualif_param = Terminal . UString <$> qualif1 moduleName <* notFollowedBy (single '(') -- <* lookAhead (anySingleBut '(')
func_arg expr = try qualif_param <|> expr <?> "Function argument"
terminalF = terminalG FunctionWithoutParens
expressionF = makeExprParser (lexeme terminalF) expressionTable <?> "Function expression"
withparens = parens (func_arg expression `sepEndBy` comma)
withoutparens =
if nonparens
then func_arg expressionF `sepEndBy1` comma
else fail "Not an argument list allowed with function without parentheses"
args <- withparens <|> withoutparens
pure (fname, V.fromList args)
literalValue :: Parser UnresolvedValue
literalValue = lexeme (fmap UString stringLiteral' <|> fmap UString bareword <|> fmap UNumber numericalvalue <?> "Literal Value")
where
signed :: (Num n) => Parser (n -> n)
signed = (negate <$ char '-') <|> pure (\x -> x)
numericalvalue =
((,) <$> signed <*> integerOrDouble) >>= \case
(s, Left x) -> pure (s (fromIntegral x))
(s, Right y) -> pure (s (Scientific.fromFloatDigits y))
data TerminalMode
= FunctionWithoutParens
| StandardMode
-- this is a hack for functions :(
terminalG :: TerminalMode -> Parser Expression
terminalG mode =
parens expression
<|> fmap (Terminal . UInterpolable) interpolableString
<|> (Terminal UUndef <$ reserved "undef")
<|> fmap (Terminal . URegexp) termRegexp
<|> varExpression
<|> fmap Terminal puppetArray
<|> fmap Terminal puppetHash
<|> fmap (Terminal . UBoolean) puppetBool
<|> case mode of
FunctionWithoutParens -> remaining
StandardMode -> lambda <|> remaining
where
lambda = fmap Terminal (fmap UHOLambdaCall (try lambdaCall) <|> try funcCall)
remaining =
fmap (Terminal . UDataType) datatype
<|> fmap Terminal resourceReference
<|> fmap Terminal literalValue
funcCall :: Parser UnresolvedValue
funcCall = uncurry UFunctionCall <$> genFunctionCall False
regexp :: Parser Text
regexp = do
void (single '/')
Text.pack . concat
<$> many (do { void (char '\\'); x <- anySingle; return ['\\', x] } <|> some (noneOf ['/', '\\']))
<* symbolic '/'
compileRegexp :: Text -> Parser CompRegex
compileRegexp p = case Regex.compile' Regex.compBlank Regex.execBlank (encodeUtf8 p) of
Right r -> pure $ CompRegex p r
Left ms -> fail ("Can't parse regexp /" <> Text.unpack p <> "/ : " ++ show ms)
termRegexp :: Parser CompRegex
termRegexp = regexp >>= compileRegexp
terminal :: Parser Expression
terminal = terminalG StandardMode
expressionTable :: [[Operator Parser Expression]]
expressionTable =
[ [Postfix indexLookupChain], -- http://stackoverflow.com/questions/10475337/parsec-expr-repeated-prefix-postfix-operator-not-supported
[Prefix (symbolic '-' $> Negate)],
[Prefix (symbolic '!' $> Not)],
[InfixL (symbolic '.' $> FunctionApplication)],
[InfixL (reserved "in" $> Contains)],
[ InfixL (symbolic '/' $> Division),
InfixL (symbolic '*' $> Multiplication)
],
[ InfixL (symbolic '+' $> Addition),
InfixL (symbolic '-' $> Substraction)
],
[ InfixL (symbol "<<" $> LeftShift),
InfixL (symbol ">>" $> RightShift)
],
[ InfixL (symbol "==" $> Equal),
InfixL (symbol "!=" $> Different)
],
[ InfixL (symbol "=~" $> RegexMatch),
InfixL (symbol "!~" $> NotRegexMatch)
],
[ InfixL (symbol ">=" $> MoreEqualThan),
InfixL (symbol "<=" $> LessEqualThan),
InfixL (symbol ">" $> MoreThan),
InfixL (symbol "<" $> LessThan)
],
[ InfixL (reserved "and" $> And),
InfixL (reserved "or" $> Or)
]
]
-- | Postfix of a chain of lookup indexes such as "['release']['major']"
indexLookupChain :: Parser (Expression -> Expression)
indexLookupChain = List.foldr1 (flip (.)) <$> some checkLookup
where
checkLookup = flip Lookup <$> brackets expression
stringExpression :: Parser Expression
stringExpression =
Terminal . UInterpolable <$> interpolableString
<|> (reserved "undef" $> Terminal UUndef)
<|> Terminal . UBoolean <$> puppetBool
<|> varExpression
<|> Terminal <$> literalValue
-- | a = b = 0
chainedVariableReferences :: Parser [Text]
chainedVariableReferences = do
h <- variableReference
t <- many (try next)
pure (h : t)
where
next = symbolic '=' *> variableReference <* lookAhead (single '=' *> space1)
varAssign :: Parser VarAssignDecl
varAssign = do
p <- getSourcePos
mt <- optional datatype
vs <- chainedVariableReferences
void $ symbolic '='
expr <- expression
pe <- getSourcePos
pure (VarAssignDecl mt vs expr (p :!: pe))
nodeDecl :: Parser [NodeDecl]
nodeDecl = do
p <- getSourcePos
reserved "node"
let toString (UString s) = s
toString (UNumber n) = scientific2text n
toString _ = panic "Can't happen at nodeDecl"
nodename = (reserved "default" >> pure NodeDefault) <|> fmap (NodeName . toString) literalValue
ns <- (fmap NodeMatch termRegexp <|> nodename) `sepBy1` comma
inheritance <- option S.Nothing (fmap S.Just (reserved "inherits" *> nodename))
st <- braces statementList
pe <- getSourcePos
pure [NodeDecl n st inheritance (p :!: pe) | n <- ns]
defineDecl :: Parser DefineDecl
defineDecl = do
p <- getSourcePos
reserved "define"
name <- typeName
-- TODO check native type
params <- option V.empty puppetClassParameters
st <- braces statementList
pe <- getSourcePos
pure (DefineDecl name params st (p :!: pe))
puppetClassParameters :: Parser Parameters
puppetClassParameters = V.fromList <$> parens (sepComma var)
where
toStrictMaybe (Just x) = S.Just x
toStrictMaybe Nothing = S.Nothing
var :: Parser (Pair (Pair Text (S.Maybe UDataType)) (S.Maybe Expression))
var = do
tp <- toStrictMaybe <$> optional datatype
n <- variableReference
df <- toStrictMaybe <$> optional (symbolic '=' *> expression)
pure ((n :!: tp) :!: df)
puppetIfStyleCondition :: Parser (Pair Expression (Vector Statement))
puppetIfStyleCondition = (:!:) <$> expression <*> braces statementList
unlessCondition :: Parser ConditionalDecl
unlessCondition = do
p <- getSourcePos
reserved "unless"
(cond :!: stmts) <- puppetIfStyleCondition
elsecond <- option V.empty (reserved "else" *> braces statementList)
let ec = [Terminal (UBoolean True) :!: elsecond | not (V.null elsecond)]
pe <- getSourcePos
pure (ConditionalDecl (V.fromList ((Not cond :!: stmts) : ec)) (p :!: pe))
ifCondition :: Parser ConditionalDecl
ifCondition = do
p <- getSourcePos
reserved "if"
maincond <- puppetIfStyleCondition
others <- many (reserved "elsif" *> puppetIfStyleCondition)
elsecond <- option V.empty (reserved "else" *> braces statementList)
let ec = [Terminal (UBoolean True) :!: elsecond | not (V.null elsecond)]
pe <- getSourcePos
pure (ConditionalDecl (V.fromList (maincond : others ++ ec)) (p :!: pe))
caseCondition :: Parser ConditionalDecl
caseCondition = do
let puppetRegexpCase = Terminal . URegexp <$> termRegexp
defaultCase = Terminal (UBoolean True) <$ reserved "default"
matchesToExpression e (x, stmts) = f x :!: stmts
where
f = case x of
(Terminal (UBoolean _)) -> identity
(Terminal (URegexp _)) -> RegexMatch e
_ -> Equal e
cases = do
matches <- (puppetRegexpCase <|> defaultCase <|> expression) `sepBy1` comma
void $ symbolic ':'
stmts <- braces statementList
pure $ map (,stmts) matches
p <- getSourcePos
reserved "case"
expr1 <- expression
condlist <- concat <$> braces (some cases)
pe <- getSourcePos
pure (ConditionalDecl (V.fromList (map (matchesToExpression expr1) condlist)) (p :!: pe))
data OperatorChain a
= OperatorChain a LinkType (OperatorChain a)
| EndOfChain a
instance Foldable OperatorChain where
foldMap f (EndOfChain x) = f x
foldMap f (OperatorChain a _ nx) = f a <> foldMap f nx
operatorChainStatement :: OperatorChain a -> a
operatorChainStatement (OperatorChain a _ _) = a
operatorChainStatement (EndOfChain x) = x
zipChain :: OperatorChain a -> [(a, a, LinkType)]
zipChain (OperatorChain a d nx) = (a, operatorChainStatement nx, d) : zipChain nx
zipChain (EndOfChain _) = []
depOperator :: Parser LinkType
depOperator =
(RBefore <$ symbol "->")
<|> (RNotify <$ symbol "~>")
assignment :: Parser AttributeDecl
assignment =
(AttributeDecl <$> lexeme key <*> arrowOp <*> expression)
<|> (AttributeWildcard <$> (symbolic '*' *> symbol "=>" *> expression))
where
key = bareword <?> "Assignment key"
arrowOp =
(AssignArrow <$ symbol "=>")
<|> (AppendArrow <$ symbol "+>")
-- | Resource Collector
resCollDecl :: Position -> Text -> Parser ResCollDecl
resCollDecl p restype = do
openchev <- some (char '<')
when (length openchev > 2) (fail "Too many brackets")
void $ symbolic '|'
e <- option AlwaysTrue searchExpression
void (char '|')
void (count (length openchev) (char '>'))
sc
overrides <- option [] $ braces (sepComma assignment)
let collectortype =
if length openchev == 1
then Collector
else ExportedCollector
pe <- getSourcePos
pure (ResCollDecl collectortype restype e (V.fromList overrides) (p :!: pe))
where
searchExpression :: Parser SearchExpression
searchExpression =
let searchTable :: [[Operator Parser SearchExpression]]
searchTable =
[ [ InfixL (reserved "and" $> AndSearch),
InfixL (reserved "or" $> OrSearch)
]
]
searchterm = parens searchExpression <|> check
check = do
attrib <- parameterName
opr <-
(EqualitySearch <$ symbol "==")
<|> (NonEqualitySearch <$ symbol "!=")
term <- stringExpression
pure (opr attrib term)
in makeExprParser (lexeme searchterm) searchTable
classDecl :: Parser ClassDecl
classDecl = do
p <- getSourcePos
reserved "class"
ClassDecl
<$> className
<*> option V.empty puppetClassParameters
<*> option S.Nothing (fmap S.Just (reserved "inherits" *> className))
<*> braces statementList
<*> ((p :!:) <$> getSourcePos)
mainFuncDecl :: Parser MainFuncDecl
mainFuncDecl = do
p <- getSourcePos
(fname, args) <- genFunctionCall True
pe <- getSourcePos
pure (MainFuncDecl fname args (p :!: pe))
hoLambdaDecl :: Parser HigherOrderLambdaDecl
hoLambdaDecl = do
p <- getSourcePos
fc <- lambdaCall
pe <- getSourcePos
pure (HigherOrderLambdaDecl fc (p :!: pe))
dotLambdaDecl :: Parser HigherOrderLambdaDecl
dotLambdaDecl = do
p <- getSourcePos
ex <- expression
pe <- getSourcePos
hf <- case ex of
FunctionApplication e (Terminal (UHOLambdaCall hf)) -> do
unless (null (hf ^. hoLambdaExpr)) (fail "Can't call a function with . and ()")
pure (hf & hoLambdaExpr .~ V.singleton e)
Terminal (UHOLambdaCall hf) -> do
when (null (hf ^. hoLambdaExpr)) (fail "This function needs data to operate on")
pure hf
_ -> fail "A method chained by dots."
pure (HigherOrderLambdaDecl hf (p :!: pe))
resDefaultDecl :: Parser ResDefaultDecl
resDefaultDecl = do
p <- getSourcePos
rnd <- resourceNameRef
let assignmentList = V.fromList <$> sepComma1 assignment
asl <- braces assignmentList
pe <- getSourcePos
pure (ResDefaultDecl rnd asl (p :!: pe))
resOverrideDecl :: Parser [ResOverrideDecl]
resOverrideDecl = do
p <- getSourcePos
restype <- resourceNameRef
names <- brackets (expression `sepBy1` comma) <?> "Resource reference values"
assignments <- V.fromList <$> braces (sepComma assignment)
pe <- getSourcePos
pure [ResOverrideDecl restype n assignments (p :!: pe) | n <- names]
arrayof :: Parser p -> Parser [p]
arrayof p = symbolic '[' *> sepBy p comma <* symbolic ']'
-- | Heterogeneous chain (interleaving resource declarations with
-- resource references) needs to be supported:
--
-- class { 'docker::service': } ->
-- Class['docker']
chainableResources :: Parser [Statement]
chainableResources = do
let withresname = do
p <- getSourcePos
restype <- resourceNameRef
lookAhead anySingle >>= \case
'[' -> do
resnames <- brackets (expression `sepBy1` comma)
pe <- getSourcePos
pure (ChainResRefr restype resnames (p :!: pe))
_ -> ChainResColl <$> resCollDecl p restype
let oneresource = pure <$> try withresname <|> map ChainResDecl <$> resDeclGroup
chain <- parseRelationships (oneresource <|> concat <$> arrayof oneresource)
let relations = do
(g1, g2, lt) <- zipChain chain
(rt1, rn1, _ :!: pe1) <- concatMap extractResRef g1
(rt2, rn2, ps2 :!: _) <- concatMap extractResRef g2
pure (DepDecl (rt1 :!: rn1) (rt2 :!: rn2) lt (pe1 :!: ps2))
pure $ map DependencyDeclaration relations <> (chain ^.. folded . folded . to extractChainStatement . folded)
where
extractResRef :: ChainableRes -> [(Text, Expression, PPosition)]
extractResRef (ChainResColl _) = []
extractResRef (ChainResDecl (ResDecl rt rn _ _ pp)) = [(rt, rn, pp)]
extractResRef (ChainResRefr rt rns pp) = [(rt, rn, pp) | rn <- rns]
extractChainStatement :: ChainableRes -> [Statement]
extractChainStatement (ChainResColl r) = [ResourceCollectionDeclaration r]
extractChainStatement (ChainResDecl d) = [ResourceDeclaration d]
extractChainStatement ChainResRefr {} = []
parseRelationships :: Parser a -> Parser (OperatorChain a)
parseRelationships p = do
g <- p
o <- optional depOperator
case o of
Just o' -> OperatorChain g o' <$> parseRelationships p
Nothing -> pure (EndOfChain g)
resDeclGroup :: Parser [ResDecl]
resDeclGroup = do
let resourceName = expression
resourceDeclaration = do
p <- getSourcePos
names <- brackets (sepComma1 resourceName) <|> fmap pure resourceName
void $ symbolic ':'
vals <- fmap V.fromList (sepComma assignment)
pe <- getSourcePos
pure [(n, vals, p :!: pe) | n <- names]
groupDeclaration = (,) <$> many (char '@') <*> typeName <* symbolic '{'
(virts, rtype) <- try groupDeclaration -- for matching reasons, this gets a try until the opening brace
let sep = symbolic ';' <|> comma
x <- resourceDeclaration `sepEndBy1` sep
void $ symbolic '}'
virtuality <- case virts of
"" -> pure Normal
"@" -> pure Virtual
"@@" -> pure Exported
_ -> fail "Invalid virtuality"
return [ResDecl rtype rname conts virtuality pos | (rname, conts, pos) <- concat x]
statement :: Parser [Statement]
statement =
(pure . HigherOrderLambdaDeclaration <$> try dotLambdaDecl)
<|> (pure . VarAssignmentDeclaration <$> varAssign)
<|> (map NodeDeclaration <$> nodeDecl)
<|> (pure . DefineDeclaration <$> defineDecl)
<|> (pure . ConditionalDeclaration <$> unlessCondition)
<|> (pure . ConditionalDeclaration <$> ifCondition)
<|> (pure . ConditionalDeclaration <$> caseCondition)
<|> (pure . ResourceDefaultDeclaration <$> try resDefaultDecl)
<|> (map ResourceOverrideDeclaration <$> try resOverrideDecl)
<|> chainableResources
<|> (pure . ClassDeclaration <$> classDecl)
<|> (pure . HigherOrderLambdaDeclaration <$> try hoLambdaDecl)
<|> (pure . MainFunctionDeclaration <$> mainFuncDecl)
<?> "Statement"
datatype :: Parser UDataType
datatype =
dtString
<|> dtInteger
<|> dtFloat
<|> dtNumeric
<|> (UDTBoolean <$ reserved "Boolean")
<|> (UDTScalar <$ reserved "Scalar")
<|> (UDTData <$ reserved "Data")
<|> (UDTAny <$ reserved "Any")
<|> (UDTCollection <$ reserved "Collection")
<|> dtArray
<|> dtHash
<|> (UDTUndef <$ reserved "Undef")
<|> (reserved "Optional" *> (UDTOptional <$> brackets datatype))
<|> (UNotUndef <$ reserved "NotUndef")
<|> (reserved "Variant" *> (UDTVariant . NE.fromList <$> brackets (datatype `sepBy1` symbolic ',')))
<|> (reserved "Regexp" *> (UDTRegexp <$> optional (brackets termRegexp)))
<|> (UDTDeferred <$ reserved "Deferred")
<|> (reserved "Sensitive" *> (UDTSensitive <$> brackets datatype))
-- while all the other cases are straightforward, it seems that the
-- following syntax is a valid regexp for puppet:
-- '^dqsqsdqs$'
-- instead of:
-- /^dqsqsdqs$/
--
-- That is the reason there is a "quotedRegexp" case
<|> (reserved "Pattern" *> (UDTPattern . NE.fromList <$> brackets ((termRegexp <|> quotedRegexp) `sepBy1` symbolic ',')))
<|> (reserved "Enum" *> (UDTEnum . NE.fromList <$> brackets (expression `sepBy1` symbolic ',')))
<|> dtExternal
<?> "UDataType"
where
quotedRegexp = stringLiteral' >>= compileRegexp
integer = integerOrDouble >>= either (return . fromIntegral) (\d -> fail ("Integer value expected, instead of " ++ show d))
float = either fromIntegral identity <$> integerOrDouble
dtArgs str def parseArgs = do
void $ reserved str
fromMaybe def <$> optional (brackets parseArgs)
dtbounded s constructor parser = dtArgs s (constructor Nothing Nothing) $ do
lst <- parser `sepBy1` symbolic ','
case lst of
[minlen] -> return $ constructor (Just minlen) Nothing
[minlen, maxlen] -> return $ constructor (Just minlen) (Just maxlen)
_ -> fail ("Too many arguments to datatype " ++ Text.unpack s)
dtString = dtbounded "String" UDTString integer
dtInteger = dtbounded "Integer" UDTInteger integer
dtFloat = dtbounded "Float" UDTFloat float
dtNumeric = dtbounded "Numeric" (\ma mb -> UDTVariant (UDTFloat ma mb :| [UDTInteger (truncate <$> ma) (truncate <$> mb)])) float
dtArray = do
reserved "Array"
ml <- optional $ brackets $ do
tp <- datatype
rst <- optional (symbolic ',' *> integer `sepBy1` symbolic ',')
return (tp, rst)
case ml of
Nothing -> return (UDTArray UDTData 0 Nothing)
Just (t, Nothing) -> return (UDTArray t 0 Nothing)
Just (t, Just [mi]) -> return (UDTArray t mi Nothing)
Just (t, Just [mi, mx]) -> return (UDTArray t mi (Just mx))
Just (_, Just _) -> fail "Too many arguments to datatype Array"
dtHash = do
reserved "Hash"
ml <- optional $ brackets $ do
tk <- datatype
symbolic ','
tv <- datatype
rst <- optional (symbolic ',' *> integer `sepBy1` symbolic ',')
return (tk, tv, rst)
case ml of
Nothing -> return (UDTHash UDTScalar UDTData 0 Nothing)
Just (tk, tv, Nothing) -> return (UDTHash tk tv 0 Nothing)
Just (tk, tv, Just [mi]) -> return (UDTHash tk tv mi Nothing)
Just (tk, tv, Just [mi, mx]) -> return (UDTHash tk tv mi (Just mx))
Just (_, _, Just _) -> fail "Too many arguments to datatype Hash"
dtExternal =
choice
[ reserved "Stdlib::Absolutepath" $> UDTData,
reserved "Stdlib::Base32" $> UDTData,
reserved "Stdlib::Base64" $> UDTData,
reserved "Stdlib::Compat::Absolute_path" $> UDTData,
reserved "Stdlib::Compat::Array" $> UDTData,
reserved "Stdlib::Compat::Bool" $> UDTData,
reserved "Stdlib::Compat::Float" $> UDTData,
reserved "Stdlib::Compat::Hash" $> UDTData,
reserved "Stdlib::Compat::Integer" $> UDTData,
reserved "Stdlib::Compat::Ip_address" $> UDTData,
reserved "Stdlib::Compat::Ipv4" $> UDTData,
reserved "Stdlib::Compat::Ipv6" $> UDTData,
reserved "Stdlib::Compat::Numeric" $> UDTData,
reserved "Stdlib::Compat::String" $> UDTData,
reserved "Stdlib::Ensure::Service" $> UDTData,
reserved "Stdlib::Filemode" $> UDTData,
reserved "Stdlib::Filesource" $> UDTData,
reserved "Stdlib::Fqdn" $> UDTData,
reserved "Stdlib::Host" $> UDTData,
reserved "Stdlib::HTTPSUrl" $> UDTData,
reserved "Stdlib::HTTPUrl" $> UDTData,
reserved "Stdlib::IP::Address::Nosubnet" $> UDTData,
reserved "Stdlib::Ip_address" $> UDTData,
reserved "Stdlib::IP::Address" $> UDTData,
reserved "Stdlib::IP::Address::V4::CIDR" $> UDTData,
reserved "Stdlib::IP::Address::V4::Nosubnet" $> UDTData,
reserved "Stdlib::IP::Address::V4" $> UDTData,
reserved "Stdlib::IP::Address::V6::Alternative" $> UDTData,
reserved "Stdlib::IP::Address::V6::Compressed" $> UDTData,
reserved "Stdlib::IP::Address::V6::Full" $> UDTData,
reserved "Stdlib::IP::Address::V6::Nosubnet::Alternative" $> UDTData,
reserved "Stdlib::IP::Address::V6::Nosubnet::Compressed" $> UDTData,
reserved "Stdlib::IP::Address::V6::Nosubnet::Full" $> UDTData,
reserved "Stdlib::IP::Address::V6::Nosubnet" $> UDTData,
reserved "Stdlib::IP::Address::V6" $> UDTData,
reserved "Stdlib::Ipv4" $> UDTData,
reserved "Stdlib::Ipv6" $> UDTData,
reserved "Stdlib::MAC" $> UDTData,
reserved "Stdlib::Port::Privileged" $> UDTData,
reserved "Stdlib::Port" $> UDTData,
reserved "Stdlib::Port::Unprivileged" $> UDTData,
reserved "Stdlib::Unixpath" $> UDTData,
reserved "Stdlib::Windowspath" $> UDTData,
reserved "Nginx::ErrorLogSeverity" $> UDTData,
reserved "Jenkins::Tunnel" $> UDTData,
reserved "Systemd::Unit" $> UDTData,
reserved "Systemd::ServiceLimits" $> UDTData,
reserved "Systemd::Dropin" $> UDTData,
reserved "Systemd::JournaldSettings" $> UDTData,
reserved "Systemd::JournaldSettings::Ensure" $> UDTData,
reserved "Systemd::LogindSettings" $> UDTData,
reserved "Systemd::LogindSettings::Ensure" $> UDTData,
reserved "Systemd::CoredumpSettings" $> UDTData
]
statementList :: Parser (Vector Statement)
statementList = V.fromList . concat <$> many statement
lambdaCall :: Parser HOLambdaCall
lambdaCall = do
let tostrict (Just x) = S.Just x
tostrict Nothing = S.Nothing
HOLambdaCall
<$> lambFunc
<*> parameters
<*> lambParams
<*> (symbolic '{' *> fmap (V.fromList . concat) (many (try statement)))
<*> fmap tostrict (optional expression)
<* symbolic '}'
where
parameters :: Parser (V.Vector Expression)
parameters = maybe V.empty V.fromList <$> optional (parens (expression `sepBy` comma))
lambFunc :: Parser LambdaFunc
lambFunc = LambdaFunc <$> moduleName
lambParams :: Parser LambdaParameters
lambParams = between (symbolic '|') (symbolic '|') hp
where
lambdaParameter :: Parser LambdaParameter
lambdaParameter = LambdaParam <$> optional datatype <*> lexeme (char '$' *> identifier)
hp = V.fromList <$> lambdaParameter `sepBy1` comma