hnix/Nix/Parser.hs
Benno Fünfstück 3a08227c0c Parse double unary - correctly and improve perf
We now don't use `buildExpressionParser` from parsers because it cannot
deal with double unary `-`, as in `--a`. Because nix doesn't have
operators which have the same precendence but different associativities,
we can greatly simplify the operator parser. This also has the effect of
improving performance and the error messages a bit.
2014-08-27 22:12:12 +02:00

220 lines
7.5 KiB
Haskell

{-# LANGUAGE CPP #-}
module Nix.Parser (parseNixFile, parseNixString, Result(..)) where
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class
import Data.Foldable
import qualified Data.Map as Map
import Data.Text hiding (head, map, foldl1', foldl')
import Nix.Types
import Nix.Parser.Library
import Prelude hiding (elem)
-- | The lexer for this parser is defined in 'Nix.Parser.Library'.
nixExpr :: Parser NExpr
nixExpr = whiteSpace *> (nixToplevelForm <|> foldl' makeParser nixOpArg nixOperators)
where
makeParser term (Left NSelectOp) = nixSelect term
makeParser term (Left NAppOp) = chainl1 term $ pure $ \a b -> Fix (NApp a b)
makeParser term (Left NHasAttrOp) = nixHasAttr term
makeParser term (Right (NUnaryDef name op))
= build <$> many (void $ symbol name) <*> term
where build = flip $ foldl' (\t' () -> mkOper op t')
makeParser term (Right (NBinaryDef assoc ops)) = case assoc of
NAssocLeft -> chainl1 term op
NAssocRight -> chainr1 term op
NAssocNone -> term <**> (flip <$> op <*> term <|> pure id)
where op = choice . map (\(n,o) -> mkOper2 o <$ reservedOp n) $ ops
antiStart :: Parser String
antiStart = try (string "${") <?> show ("${" :: String)
nixAntiquoted :: Parser a -> Parser (Antiquoted a NExpr)
nixAntiquoted p = Antiquoted <$> (antiStart *> nixExpr <* symbolic '}') <|> Plain <$> p
selDot :: Parser ()
selDot = try (char '.' *> notFollowedBy (("path" :: String) <$ nixPath)) *> whiteSpace
<?> "."
nixSelector :: Parser (NSelector NExpr)
nixSelector = keyName `sepBy1` selDot where
nixSelect :: Parser NExpr -> Parser NExpr
nixSelect term = build
<$> term
<*> optional ((,) <$> (selDot *> nixSelector) <*> optional (reserved "or" *> nixExpr))
where
build t Nothing = t
build t (Just (s,o)) = Fix $ NSelect t s o
nixHasAttr :: Parser NExpr -> Parser NExpr
nixHasAttr term = build <$> term <*> optional (reservedOp "?" *> nixSelector) where
build t Nothing = t
build t (Just s) = Fix $ NHasAttr t s
nixOpArg :: Parser NExpr
nixOpArg = nixSelect $ choice
[ nixInt, nixBool, nixNull, nixParens, nixList, nixPath, nixSPath, nixUri
, nixStringExpr, nixSet, nixSym ]
nixToplevelForm :: Parser NExpr
nixToplevelForm = choice [nixLambda, nixLet, nixIf, nixAssert, nixWith]
nixSym :: Parser NExpr
nixSym = mkSym <$> identifier
nixInt :: Parser NExpr
nixInt = mkInt <$> token decimal <?> "integer"
nixBool :: Parser NExpr
nixBool = try (true <|> false) <?> "bool" where
true = mkBool True <$ symbol "true"
false = mkBool False <$ symbol "false"
nixNull :: Parser NExpr
nixNull = mkNull <$ try (symbol "null") <?> "null"
nixParens :: Parser NExpr
nixParens = parens nixExpr <?> "parens"
nixList :: Parser NExpr
nixList = brackets (Fix . NList <$> many nixOpArg) <?> "list"
pathChars :: String
pathChars = ['A'..'Z'] ++ ['a'..'z'] ++ "._-+" ++ ['0'..'9']
slash :: Parser Char
slash = try (char '/' <* notFollowedBy (char '/')) <?> "slash"
nixSPath :: Parser NExpr
nixSPath = mkPath True <$> try (char '<' *> some (oneOf pathChars <|> slash) <* symbolic '>')
<?> "spath"
nixPath :: Parser NExpr
nixPath = token $ fmap (mkPath False) $ ((++)
<$> (try ((++) <$> many (oneOf pathChars) <*> fmap (:[]) slash) <?> "path")
<*> some (oneOf pathChars <|> slash))
<?> "path"
nixLet :: Parser NExpr
nixLet = fmap Fix $ NLet
<$> (reserved "let" *> nixBinders)
<*> (whiteSpace *> reserved "in" *> nixExpr)
<?> "let"
nixIf :: Parser NExpr
nixIf = fmap Fix $ NIf
<$> (reserved "if" *> nixExpr)
<*> (whiteSpace *> reserved "then" *> nixExpr)
<*> (whiteSpace *> reserved "else" *> nixExpr)
<?> "if"
nixAssert :: Parser NExpr
nixAssert = fmap Fix $ NAssert
<$> (reserved "assert" *> nixExpr)
<*> (semi *> nixExpr)
nixWith :: Parser NExpr
nixWith = fmap Fix $ NWith
<$> (reserved "with" *> nixExpr)
<*> (semi *> nixExpr)
nixLambda :: Parser NExpr
nixLambda = Fix <$> (NAbs <$> (try argExpr <?> "lambda arguments") <*> nixExpr) <?> "lambda"
nixStringExpr :: Parser NExpr
nixStringExpr = Fix . NStr <$> nixString
uriAfterColonC :: Parser Char
uriAfterColonC = alphaNum <|> oneOf "%/?:@&=+$,-_.!~*'"
nixUri :: Parser NExpr
nixUri = token $ fmap (mkUri . pack) $ (++)
<$> try ((++) <$> (scheme <* char ':') <*> fmap (\x -> [':',x]) uriAfterColonC)
<*> many uriAfterColonC
where
scheme = (:) <$> letter <*> many (alphaNum <|> oneOf "+-.")
nixString :: Parser (NString NExpr)
nixString = doubleQuoted <|> indented <?> "string"
where
doubleQuoted = NString DoubleQuoted . removePlainEmpty . mergePlain
<$> (doubleQ *> many (stringChar doubleQ (void $ char '\\') doubleEscape)
<* token doubleQ)
<?> "double quoted string"
doubleQ = void $ char '"'
doubleEscape = Plain . singleton <$> (char '\\' *> escapeCode)
indented = stripIndent
<$> (indentedQ *> many (stringChar indentedQ indentedQ indentedEscape)
<* token indentedQ)
<?> "indented string"
indentedQ = void $ try (string "''") <?> "\"''\""
indentedEscape = fmap Plain
$ try (indentedQ *> char '\\') *> fmap singleton escapeCode
<|> try (indentedQ *> ("''" <$ char '\'' <|> "$" <$ char '$'))
stringChar end escStart esc
= esc
<|> Antiquoted <$> (antiStart *> nixExpr <* char '}') -- don't skip trailing space
<|> Plain . singleton <$> char '$'
<|> Plain . pack <$> some plainChar
where plainChar = notFollowedBy (end <|> void (char '$') <|> escStart) *> anyChar
escapeCode = choice [ c <$ char e | (c,e) <- escapeCodes ] <|> anyChar
argExpr :: Parser (Formals NExpr)
argExpr = choice
[ idOrAtPattern <$> identifierNotUri <*> optional (symbolic '@' *> paramSet)
, setOrAtPattern <$> paramSet <*> optional (symbolic '@' *> identifier)
] <* symbolic ':'
where
paramSet :: Parser (FormalParamSet NExpr)
paramSet = FormalParamSet . Map.fromList <$> argList
argList :: Parser [(Text, Maybe NExpr)]
argList = braces (argName `sepBy` symbolic ',') <?> "arglist"
identifierNotUri :: Parser Text
identifierNotUri = notFollowedBy nixUri *> identifier
argName :: Parser (Text, Maybe NExpr)
argName = (,) <$> identifier
<*> optional (symbolic '?' *> nixExpr)
idOrAtPattern :: Text -> Maybe (FormalParamSet NExpr) -> Formals NExpr
idOrAtPattern i Nothing = FormalName i
idOrAtPattern i (Just s) = FormalLeftAt i s
setOrAtPattern :: FormalParamSet NExpr -> Maybe Text -> Formals NExpr
setOrAtPattern s Nothing = FormalSet s
setOrAtPattern s (Just i) = FormalRightAt s i
nixBinders :: Parser [Binding NExpr]
nixBinders = (inherit <|> namedVar) `endBy` symbolic ';' where
inherit = Inherit <$> (reserved "inherit" *> optional scope) <*> many ((:[]) <$> keyName)
<?> "inherited binding"
namedVar = NamedVar <$> nixSelector <*> (symbolic '=' *> nixExpr)
<?> "variable binding"
scope = parens nixExpr <?> "inherit scope"
keyName :: Parser (NKeyName NExpr)
keyName = dynamicKey <|> staticKey where
staticKey = StaticKey <$> identifier
dynamicKey = DynamicKey <$> nixAntiquoted nixString
nixSet :: Parser NExpr
nixSet = Fix <$> (NSet <$> isRec <*> braces nixBinders) <?> "set" where
isRec = (try (reserved "rec" *> pure Rec) <?> "recursive set")
<|> pure NonRec
parseNixFile :: MonadIO m => FilePath -> m (Result NExpr)
parseNixFile = parseFromFileEx $ nixExpr <* eof
parseNixString :: String -> Result NExpr
parseNixString = parseFromString $ nixExpr <* eof