{-# LANGUAGE TupleSections #-} module Puppet.Parser.Internal where import XPrelude.Extra hiding (many, option, some, try) 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 Text.Megaparsec import Text.Megaparsec.Char import qualified Text.Megaparsec.Char.Lexer as Lexer import qualified Text.Regex.PCRE.ByteString.Utils as Regex import Puppet.Language import Puppet.Parser.Types -- | 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" -- 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 '-' *> pure Negate ) ] , [ Prefix ( symbolic '!' *> pure Not ) ] , [ InfixL ( symbolic '.' *> pure FunctionApplication ) ] , [ InfixL ( reserved "in" *> pure Contains ) ] , [ InfixL ( symbolic '/' *> pure Division ) , InfixL ( symbolic '*' *> pure Multiplication ) ] , [ InfixL ( symbolic '+' *> pure Addition ) , InfixL ( symbolic '-' *> pure Substraction ) ] , [ InfixL ( symbol "<<" *> pure LeftShift ) , InfixL ( symbol ">>" *> pure RightShift ) ] , [ InfixL ( symbol "==" *> pure Equal ) , InfixL ( symbol "!=" *> pure Different ) ] , [ InfixL ( symbol "=~" *> pure RegexMatch ) , InfixL ( symbol "!~" *> pure NotRegexMatch ) ] , [ InfixL ( symbol ">=" *> pure MoreEqualThan ) , InfixL ( symbol "<=" *> pure LessEqualThan ) , InfixL ( symbol ">" *> pure MoreThan ) , InfixL ( symbol "<" *> pure LessThan ) ] , [ InfixL ( reserved "and" *> pure And ) , InfixL ( reserved "or" *> pure 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 = if V.null elsecond then [] else [Terminal (UBoolean True) :!: 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 = if V.null elsecond then [] else [Terminal (UBoolean True) :!: 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" *> pure AndSearch ) , InfixL ( reserved "or" *> pure 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))) -- 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 ] 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