hnix/src/Nix/Parser.hs

318 lines
11 KiB
Haskell
Raw Normal View History

2018-04-10 17:34:21 +02:00
{-# LANGUAGE CPP #-}
2018-04-07 21:02:50 +02:00
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
module Nix.Parser (
parseNixFile,
parseNixFileLoc,
parseNixText,
parseNixTextLoc,
Result(..)
) where
import Control.Monad
import Control.Monad.IO.Class
import Data.Char (isAlpha, isDigit, isSpace)
2018-04-07 21:02:50 +02:00
import Data.Functor
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE
import Data.Text hiding (map)
2018-04-07 21:02:50 +02:00
import Nix.Expr hiding (($>))
import Nix.Parser.Library
import Nix.Parser.Operators
import Nix.Strings
import Text.Megaparsec.Expr
2018-04-07 21:02:50 +02:00
infixl 3 <+>
(<+>) :: MonadPlus m => m a -> m a -> m a
(<+>) = mplus
2018-04-07 21:02:50 +02:00
--------------------------------------------------------------------------------
nixExprLoc :: Parser NExprLoc
nixExprLoc = makeExprParser nixTerm $ map (map snd) (nixOperators nixSelector)
2018-04-07 21:02:50 +02:00
antiStart :: Parser Text
2018-04-11 08:40:32 +02:00
antiStart = symbol "${" <?> show ("${" :: String)
2018-04-07 21:02:50 +02:00
nixAntiquoted :: Parser a -> Parser (Antiquoted a NExprLoc)
nixAntiquoted p =
Antiquoted <$> (antiStart *> nixToplevelForm <* symbol "}")
<+> Plain <$> p
<?> "anti-quotation"
2018-04-07 21:02:50 +02:00
selDot :: Parser ()
selDot = try (symbol "." *> notFollowedBy nixPath) <?> "."
nixSelect :: Parser NExprLoc -> Parser NExprLoc
nixSelect term = do
res <- build
<$> term
<*> optional ((,) <$> (selDot *> nixSelector)
<*> optional (reserved "or" *> nixTerm))
continues <- optional $ lookAhead selDot
case continues of
Nothing -> pure res
Just _ -> nixSelect (pure res)
where
build :: NExprLoc
-> Maybe (Ann SrcSpan (NAttrPath NExprLoc), Maybe NExprLoc)
-> NExprLoc
build t Nothing = t
build t (Just (s,o)) = nSelectLoc t s o
2018-04-07 21:02:50 +02:00
nixSelector :: Parser (Ann SrcSpan (NAttrPath NExprLoc))
nixSelector = annotateLocation $ do
(x:xs) <- keyName `sepBy1` selDot
return $ x :| xs
2018-04-07 21:02:50 +02:00
nixTerm :: Parser NExprLoc
nixTerm = do
c <- try $ lookAhead $ satisfy $ \x ->
pathChar x ||
x == '(' ||
x == '{' ||
x == '[' ||
x == '<' ||
x == '/' ||
x == '"' ||
x == '\''
case c of
'(' -> nixSelect nixParens
'{' -> nixSelect nixSet
'[' -> nixList
'<' -> nixSPath
'/' -> nixPath
'"' -> nixStringExpr
'\'' -> nixStringExpr
_ -> msum $
[ nixSelect nixSet | c == 'r' ] ++
[ nixPath | pathChar c ] ++
2018-04-17 03:45:28 +02:00
if isDigit c
then [ nixFloat
, nixInt ]
else [ nixUri | isAlpha c ] ++
[ nixBool | c == 't' || c == 'f' ] ++
[ nixNull | c == 'n' ] ++
[ nixSelect nixSym ]
2018-04-07 21:02:50 +02:00
nixToplevelForm :: Parser NExprLoc
nixToplevelForm = keywords <+> nixLambda <+> nixExprLoc
where
keywords = nixLet <+> nixIf <+> nixAssert <+> nixWith
2018-04-07 21:02:50 +02:00
nixSym :: Parser NExprLoc
nixSym = annotateLocation1 $ mkSymF <$> identifier
nixInt :: Parser NExprLoc
nixInt = annotateLocation1 (mkIntF <$> integer <?> "integer")
2018-04-07 21:02:50 +02:00
nixFloat :: Parser NExprLoc
nixFloat = annotateLocation1 (try (mkFloatF . realToFrac <$> float) <?> "float")
2018-04-07 21:02:50 +02:00
nixBool :: Parser NExprLoc
nixBool = annotateLocation1 (bool "true" True <+>
2018-04-11 08:40:32 +02:00
bool "false" False) <?> "bool" where
bool str b = mkBoolF b <$ reserved str
2018-04-07 21:02:50 +02:00
nixNull :: Parser NExprLoc
nixNull = annotateLocation1 (mkNullF <$ reserved "null" <?> "null")
2018-04-07 21:02:50 +02:00
nixParens :: Parser NExprLoc
nixParens = parens nixToplevelForm <?> "parens"
2018-04-07 21:02:50 +02:00
nixList :: Parser NExprLoc
nixList = annotateLocation1 (brackets (NList <$> many nixTerm) <?> "list")
2018-04-07 21:02:50 +02:00
2018-04-10 18:45:04 +02:00
pathChar :: Char -> Bool
2018-04-21 08:17:57 +02:00
pathChar x = isAlpha x || isDigit x || x == '.' || x == '_' || x == '-' || x == '+' || x == '~'
2018-04-07 21:02:50 +02:00
slash :: Parser Char
slash = try (char '/' <* notFollowedBy (satisfy (\x -> x == '/' || x == '*' || isSpace x)))
2018-04-07 21:02:50 +02:00
<?> "slash"
-- | A path surrounded by angle brackets, indicating that it should be
-- looked up in the NIX_PATH environment variable at evaluation.
nixSPath :: Parser NExprLoc
nixSPath = annotateLocation1
(mkPathF True <$> try (char '<' *> many (satisfy pathChar <+> slash) <* symbol ">")
<?> "spath")
2018-04-07 21:02:50 +02:00
pathStr :: Parser FilePath
2018-04-10 18:45:04 +02:00
pathStr = lexeme $ liftM2 (++) (many (satisfy pathChar))
(Prelude.concat <$> some (liftM2 (:) slash (some (satisfy pathChar))))
2018-04-07 21:02:50 +02:00
nixPath :: Parser NExprLoc
2018-04-10 09:40:11 +02:00
nixPath = annotateLocation1 (try (mkPathF False <$> pathStr) <?> "path")
2018-04-07 21:02:50 +02:00
nixLet :: Parser NExprLoc
nixLet = annotateLocation1 (reserved "let"
*> (letBody <+> letBinders)
<?> "let block")
2018-04-07 21:02:50 +02:00
where
letBinders = NLet
<$> nixBinders
<*> (reserved "in" *> nixToplevelForm)
2018-04-07 21:02:50 +02:00
-- Let expressions `let {..., body = ...}' are just desugared
-- into `(rec {..., body = ...}).body'.
letBody = (\x pos -> NSelect x (StaticKey "body" (Just pos) :| []) Nothing)
<$> aset <*> getPosition
2018-04-07 21:02:50 +02:00
aset = annotateLocation1 $ NRecSet <$> braces nixBinders
nixIf :: Parser NExprLoc
nixIf = annotateLocation1 (NIf
2018-04-07 21:02:50 +02:00
<$> (reserved "if" *> nixExprLoc)
<*> (reserved "then" *> nixToplevelForm)
<*> (reserved "else" *> nixToplevelForm)
<?> "if")
2018-04-07 21:02:50 +02:00
nixAssert :: Parser NExprLoc
nixAssert = annotateLocation1 (NAssert
2018-04-07 21:02:50 +02:00
<$> (reserved "assert" *> nixExprLoc)
<*> (semi *> nixToplevelForm)
<?> "assert")
2018-04-07 21:02:50 +02:00
nixWith :: Parser NExprLoc
nixWith = annotateLocation1 (NWith
<$> (reserved "with" *> nixToplevelForm)
<*> (semi *> nixToplevelForm)
<?> "with")
2018-04-07 21:02:50 +02:00
nixLambda :: Parser NExprLoc
nixLambda = nAbs <$> annotateLocation (try argExpr) <*> nixToplevelForm
2018-04-07 21:02:50 +02:00
nixStringExpr :: Parser NExprLoc
nixStringExpr = nStr <$> annotateLocation nixString
nixUri :: Parser NExprLoc
2018-04-10 17:34:21 +02:00
nixUri = annotateLocation1 $ lexeme $ try $ do
start <- letterChar
protocol <- many $ satisfy $ \x ->
isAlpha x || isDigit x || x `elem` ("+-." :: String)
_ <- string ":"
address <- some $ satisfy $ \x ->
isAlpha x || isDigit x || x `elem` ("%/?:@&=+$,-_.!~*'" :: String)
return $ mkUriF $ pack $ start : protocol ++ ':' : address
2018-04-07 21:02:50 +02:00
nixString :: Parser (NString NExprLoc)
nixString = lexeme (doubleQuoted <+> indented <?> "string")
2018-04-07 21:02:50 +02:00
where
doubleQuoted :: Parser (NString NExprLoc)
doubleQuoted = DoubleQuoted . removePlainEmpty . mergePlain
2018-04-11 08:26:00 +02:00
<$> (doubleQ *> many (stringChar doubleQ (void $ char '\\')
doubleEscape)
<* doubleQ)
2018-04-07 21:02:50 +02:00
<?> "double quoted string"
doubleQ = void (char '"')
2018-04-07 21:02:50 +02:00
doubleEscape = Plain . singleton <$> (char '\\' *> escapeCode)
indented :: Parser (NString NExprLoc)
indented = stripIndent
2018-04-11 08:26:00 +02:00
<$> (indentedQ *> many (stringChar indentedQ indentedQ
indentedEscape)
<* indentedQ)
2018-04-07 21:02:50 +02:00
<?> "indented string"
2018-04-11 08:26:00 +02:00
indentedQ = void (string "''" <?> "\"''\"")
2018-04-11 03:45:57 +02:00
indentedEscape = try $ do
indentedQ
(Plain <$> ("''" <$ char '\'' <+> "$" <$ char '$')) <+> do
2018-04-11 03:45:57 +02:00
_ <- char '\\'
c <- escapeCode
pure $ if c == '\n'
then EscapedNewline
else Plain $ singleton c
2018-04-07 21:02:50 +02:00
2018-04-11 08:26:00 +02:00
stringChar end escStart esc =
Antiquoted <$> (antiStart *> nixToplevelForm <* char '}')
<+> Plain . singleton <$> char '$'
<+> esc
<+> Plain . pack <$> some plainChar
where
plainChar =
notFollowedBy (end <+> void (char '$') <+> escStart) *> anyChar
2018-04-07 21:02:50 +02:00
escapeCode = msum [ c <$ char e | (c,e) <- escapeCodes ] <+> anyChar
2018-04-07 21:02:50 +02:00
-- | Gets all of the arguments for a function.
argExpr :: Parser (Params NExprLoc)
argExpr = msum [atLeft, onlyname, atRight] <* symbol ":" where
2018-04-07 21:02:50 +02:00
-- An argument not in curly braces. There's some potential ambiguity
-- in the case of, for example `x:y`. Is it a lambda function `x: y`, or
-- a URI `x:y`? Nix syntax says it's the latter. So we need to fail if
-- there's a valid URI parse here.
onlyname = msum [nixUri >> unexpected (Label ('v' NE.:| "alid uri")),
2018-04-07 21:02:50 +02:00
Param <$> identifier]
-- Parameters named by an identifier on the left (`args @ {x, y}`)
atLeft = try $ do
name <- identifier <* symbol "@"
2018-04-07 21:02:50 +02:00
(variadic, params) <- params
return $ ParamSet params variadic (Just name)
-- Parameters named by an identifier on the right, or none (`{x, y} @ args`)
atRight = do
(variadic, params) <- params
name <- optional $ symbol "@" *> identifier
2018-04-07 21:02:50 +02:00
return $ ParamSet params variadic name
-- Return the parameters set.
params = do
(args, dotdots) <- braces getParams
return (dotdots, args)
2018-04-07 21:02:50 +02:00
-- Collects the parameters within curly braces. Returns the parameters and
-- a boolean indicating if the parameters are variadic.
getParams :: Parser ([(Text, Maybe NExprLoc)], Bool)
getParams = go [] where
-- Attempt to parse `...`. If this succeeds, stop and return True.
-- Otherwise, attempt to parse an argument, optionally with a
-- default. If this fails, then return what has been accumulated
-- so far.
go acc = ((acc, True) <$ symbol "...") <+> getMore acc
2018-04-07 21:02:50 +02:00
getMore acc =
-- Could be nothing, in which just return what we have so far.
option (acc, False) $ do
-- Get an argument name and an optional default.
pair <- liftM2 (,) identifier (optional $ question *> nixToplevelForm)
2018-04-07 21:02:50 +02:00
-- Either return this, or attempt to get a comma and restart.
option (acc ++ [pair], False) $ comma >> go (acc ++ [pair])
2018-04-07 21:02:50 +02:00
nixBinders :: Parser [Binding NExprLoc]
nixBinders = (inherit <+> namedVar) `endBy` semi where
2018-04-07 21:02:50 +02:00
inherit = Inherit <$> (reserved "inherit" *> optional scope)
<*> many keyName
<?> "inherited binding"
namedVar = NamedVar <$> (annotated <$> nixSelector)
<*> (equals *> nixToplevelForm)
2018-04-07 21:02:50 +02:00
<?> "variable binding"
scope = parens nixToplevelForm <?> "inherit scope"
2018-04-07 21:02:50 +02:00
keyName :: Parser (NKeyName NExprLoc)
keyName = dynamicKey <+> staticKey where
2018-04-07 21:02:50 +02:00
staticKey = do
beg <- getPosition
2018-04-07 21:02:50 +02:00
StaticKey <$> identifier <*> pure (Just beg)
dynamicKey = DynamicKey <$> nixAntiquoted nixString
nixSet :: Parser NExprLoc
nixSet = annotateLocation1 ((isRec <*> braces nixBinders) <?> "set") where
2018-04-11 08:40:32 +02:00
isRec = (reserved "rec" $> NRecSet <?> "recursive set")
<+> pure NSet
2018-04-07 21:02:50 +02:00
parseNixFile :: MonadIO m => FilePath -> m (Result NExpr)
parseNixFile =
parseFromFileEx $ stripAnnotation <$> (whiteSpace *> nixToplevelForm <* eof)
2018-04-07 21:02:50 +02:00
parseNixFileLoc :: MonadIO m => FilePath -> m (Result NExprLoc)
parseNixFileLoc = parseFromFileEx (whiteSpace *> nixToplevelForm <* eof)
2018-04-07 21:02:50 +02:00
parseNixText :: Text -> Result NExpr
parseNixText =
parseFromText $ stripAnnotation <$> (whiteSpace *> nixToplevelForm <* eof)
2018-04-07 21:02:50 +02:00
parseNixTextLoc :: Text -> Result NExprLoc
parseNixTextLoc = parseFromText (whiteSpace *> nixToplevelForm <* eof)