rpf-0.2.4: RPF/Parser.hs
module RPF.Parser where
import Control.Applicative hiding (many,optional,(<|>))
import Control.Monad
import qualified Data.ByteString.Char8 as BS
import Data.IP
import Data.List
import Data.Maybe
import Network.DNS.Types (Domain)
import Network.DomainAuth
import RPF.Domain
import RPF.IP
import RPF.Lexer
import RPF.State
import RPF.Types
import Text.Parsec
import Text.ParserCombinators.Parsec.Expr
----------------------------------------------------------------
type Parser a = Lexer ParserState a
----------------------------------------------------------------
parsePolicy :: String -> Policy
parsePolicy cs = case runParser config initialState "" cs of
Left err -> error $ show err
Right rs -> rs
----------------------------------------------------------------
config :: Parser Policy
config = do
whiteSpace
blks <- many1 defBlock
eof
st <- getState
checkUnused (unused st)
return $ Policy blks (iptbls st) (domtbls st)
where
iptbls = map makeIPTable . reverse . iplol
domtbls = map makeDomainTable . reverse . domlol
checkUnused [] = return ()
checkUnused us = unexpected $ ": Not used -- " ++ concat (intersperse ", " us)
----------------------------------------------------------------
defBlock :: Parser Block
defBlock = many definition >> block
definition :: Parser ()
definition = do
cst <- identifier -- $foo
reservedOp "="
dat <- ipList <|> domainList <|> resultList
semi
define cst dat
return ()
where
define cst dat = do
st <- getState
let ent = lookup cst (consttbl st)
when (isJust ent) $ unexpected $ ": Multiple declarations of '" ++ cst ++ "'"
setState st {
consttbl = (cst, dat) : consttbl st
, unused = cst : unused st
}
block :: Parser Block
block = do
blknm <- blockname
checkBlock blknm
cs <- braces (many actionCond)
check blknm cs
nextBlock
return $ Block blknm cs
where
blockname :: Parser BlockName
blockname = sym2enum blockNames [minBound..maxBound]
checkBlock blknm = do
st <- getState
let cblknm = head $ blocks st
when (blknm /= cblknm) $ unexpected $ ": " ++ show cblknm ++ "{} is expected"
nextBlock = do st <- getState
setState st { blocks = tail $ blocks st }
check blknm cs = case last cs of
ActionCond _ Nothing _ -> return ()
_ -> unexpected $ ": action without condition must exist in " ++ show blknm ++ "{}"
----------------------------------------------------------------
actionCond :: Parser ActionCond
actionCond = do
pos <- getPosition
let l = sourceLine pos
act <- action
cnd <- option Nothing condition
semi
return $ ActionCond l cnd act
where
action :: Parser Action
action = sym2enum actionWords [minBound..maxBound]
condition = do
colon
cnd <- cond
return $ Just cnd
----------------------------------------------------------------
cond :: Parser Cond
cond = buildExpressionParser table term
where
table = [[Infix opAnd AssocRight]]
term :: Parser Cond
term = do
char '#'
var@(vtyp,vid) <- variable
checkVar vid
opr <- opMatch <|> opNotMatch
cst@(ctyp,cval) <- constant
when (vtyp /= ctyp) $ unexpected ": Data type mismatch"
when (vtyp == DT_Res) $ checkResult vid cval
return $ var `opr` cst
where
variable :: Parser Variable
variable = var2enum variableNames [minBound..maxBound] variableTypes
checkVar vid = do
st <- getState
let blknm = head $ blocks st
sanity = fromJust $ lookup blknm varSanity
unless (vid `elem` sanity) $ unexpected $ ": #" ++ show vid ++ " cannot be used in " ++ show blknm ++ "{}"
checkResult vid (CV_Result cval) = do
let sanity = fromJust $ lookup vid resultSanity
mapM_ (test vid sanity) cval
checkResult _ _ = return ()
test vid rs r = unless (r `elem` rs) $ unexpected $ ": '" ++ show r ++ "' cannot be used for #" ++ show vid
----------------------------------------------------------------
constant :: Parser Constant
constant = ipList <|> domainList <|> resultList2
<|> definedConstant <|> yesOrNo
definedConstant :: Parser Constant
definedConstant = identifier >>= resolve
where
resolve cst = do
st <- getState
let ent = lookup cst $ consttbl st
when (isNothing ent) $ unexpected $ ": No definition of '" ++ cst ++ "'"
let cused = cst : used st
cunused = delete cst $ unused st
setState st { used = cused, unused = cunused }
let cd@(typ, CV_Index n) = fromJust ent
if typ == DT_Res then return (typ, CV_Result (reslol st !! n))
else return cd
yesOrNo :: Parser Constant
yesOrNo = do
b <- yesno
return (DT_Sig, CV_Sig b)
where
yesno :: Parser Bool
yesno = sym2enum noyes [minBound..maxBound]
----------------------------------------------------------------
op :: String -> a -> Parser a
op str opr = do reservedOp str
return opr
opAnd :: Parser (Cond -> Cond -> Cond)
opAnd = op "&&" (:&&)
opMatch :: Parser (Variable -> Constant -> Cond)
opMatch = op "==" (:==)
opNotMatch :: Parser (Variable -> Constant -> Cond)
opNotMatch = op "!=" (:!=)
----------------------------------------------------------------
ip4range :: Parser IPRange
ip4range = IPv4Range . read <$> many1 (oneOf $ ['0'..'9'] ++ "./")
ip6range :: Parser IPRange
ip6range = IPv6Range . read <$> many1 (oneOf $ ['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F'] ++ ".:/")
ipList :: Parser Constant
ipList = do
ips <- commaSep1 (try(lexeme ip4range) <|> lexeme ip6range)
n <- index ips
return (DT_IP, CV_Index n)
where
index ips = do
st <- getState
let n = ipcnt st
setState st {
iplol = ips : iplol st
, ipcnt = n + 1
}
return n
----------------------------------------------------------------
domain :: Parser Domain
domain = BS.pack <$> (char '"' >> many1 (noneOf "\"")) <* symbol "\""
domainList :: Parser Constant
domainList = do
dms <- commaSep1 (lexeme domain)
n <- index dms
return (DT_Dom, CV_Index n)
where
index dms = do
st <- getState
let n = domcnt st
setState st {
domlol = dms : domlol st
, domcnt = n + 1
}
return n
----------------------------------------------------------------
result :: Parser DAResult
result = (char '\'' >> authResult) <* char '\'' -- backward compatibility
<|> authResult
where
authResult :: Parser DAResult
authResult = sym2enum resultWords [minBound..maxBound]
resultList :: Parser Constant
resultList = do
rs <- commaSep1 (lexeme result)
n <- index rs
return (DT_Res, CV_Index n)
where
index rs = do
st <- getState
let n = rescnt st
setState st {
reslol = rs : reslol st
, rescnt = n + 1
}
return n
resultList2 :: Parser Constant
resultList2 = do
rs <- commaSep1 (lexeme result)
return (DT_Res, CV_Result rs)
----------------------------------------------------------------
sym2enum :: [String] -> [a] -> Parser a
sym2enum ss es = choice ps
where
func res sym = do {reserved res; return sym}
ps = zipWith func ss es
var2enum :: [String] -> [VariableId] -> [DataType] -> Parser Variable
var2enum ss ns ts = choice ps
where
func res nam typ = do {reserved res; return (typ,nam)}
ps = zipWith3 func ss ns ts