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
|
2018-04-10 06:35:46 +02:00
|
|
|
import Data.Char (isAlpha, isDigit, isSpace)
|
2018-04-07 21:02:50 +02:00
|
|
|
import Data.Functor
|
2018-04-10 01:11:31 +02:00
|
|
|
import qualified Data.List.NonEmpty as NE
|
2018-04-17 06:39:41 +02:00
|
|
|
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.StringOperations
|
2018-04-10 01:11:31 +02:00
|
|
|
import Text.Megaparsec.Expr
|
2018-04-07 21:02:50 +02:00
|
|
|
|
2018-04-17 06:39:41 +02:00
|
|
|
infixl 3 <+>
|
|
|
|
(<+>) :: MonadPlus m => m a -> m a -> m a
|
|
|
|
(<+>) = mplus
|
|
|
|
|
2018-04-07 21:02:50 +02:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
nixExprLoc :: Parser NExprLoc
|
2018-04-10 19:11:55 +02:00
|
|
|
nixExprLoc = makeExprParser nixTerm $ map (map snd) (nixOperators nixSelector)
|
2018-04-07 21:02:50 +02:00
|
|
|
|
2018-04-09 19:16:25 +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)
|
2018-04-10 06:35:46 +02:00
|
|
|
nixAntiquoted p =
|
2018-04-10 19:11:55 +02:00
|
|
|
Antiquoted <$> (antiStart *> nixToplevelForm <* symbol "}")
|
2018-04-17 06:39:41 +02:00
|
|
|
<+> Plain <$> p
|
2018-04-10 08:34:21 +02:00
|
|
|
<?> "anti-quotation"
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
selDot :: Parser ()
|
2018-04-10 08:34:21 +02:00
|
|
|
selDot = try (symbol "." *> notFollowedBy nixPath) <?> "."
|
|
|
|
|
|
|
|
nixSelect :: Parser NExprLoc -> Parser NExprLoc
|
2018-04-11 03:02:31 +02:00
|
|
|
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)
|
2018-04-10 08:34:21 +02:00
|
|
|
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 $ keyName `sepBy1` selDot
|
|
|
|
|
2018-04-10 08:34:21 +02:00
|
|
|
nixTerm :: Parser NExprLoc
|
2018-04-11 03:02:31 +02:00
|
|
|
nixTerm = do
|
2018-04-10 19:11:55 +02:00
|
|
|
c <- try $ lookAhead $ satisfy $ \x ->
|
|
|
|
pathChar x ||
|
|
|
|
x == '(' ||
|
|
|
|
x == '{' ||
|
|
|
|
x == '[' ||
|
|
|
|
x == '<' ||
|
|
|
|
x == '/' ||
|
|
|
|
x == '"' ||
|
|
|
|
x == '\''
|
|
|
|
case c of
|
2018-04-11 03:02:31 +02:00
|
|
|
'(' -> nixSelect nixParens
|
|
|
|
'{' -> nixSelect nixSet
|
2018-04-10 19:11:55 +02:00
|
|
|
'[' -> nixList
|
|
|
|
'<' -> nixSPath
|
|
|
|
'/' -> nixPath
|
|
|
|
'"' -> nixStringExpr
|
|
|
|
'\'' -> nixStringExpr
|
2018-04-17 06:39:41 +02:00
|
|
|
_ -> msum $
|
2018-04-11 03:02:31 +02:00
|
|
|
[ nixSelect nixSet | c == 'r' ] ++
|
2018-04-10 19:11:55 +02:00
|
|
|
[ 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
|
2018-04-17 06:39:41 +02:00
|
|
|
nixToplevelForm = keywords <+> nixLambda <+> nixExprLoc
|
2018-04-10 19:11:55 +02:00
|
|
|
where
|
2018-04-17 06:39:41 +02:00
|
|
|
keywords = nixLet <+> nixIf <+> nixAssert <+> nixWith
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixSym :: Parser NExprLoc
|
|
|
|
nixSym = annotateLocation1 $ mkSymF <$> identifier
|
|
|
|
|
|
|
|
nixInt :: Parser NExprLoc
|
2018-04-10 01:11:31 +02:00
|
|
|
nixInt = annotateLocation1 (mkIntF <$> integer <?> "integer")
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixFloat :: Parser NExprLoc
|
2018-04-10 01:11:31 +02:00
|
|
|
nixFloat = annotateLocation1 (try (mkFloatF . realToFrac <$> float) <?> "float")
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixBool :: Parser NExprLoc
|
2018-04-17 06:39:41 +02:00
|
|
|
nixBool = annotateLocation1 (bool "true" True <+>
|
2018-04-11 08:40:32 +02:00
|
|
|
bool "false" False) <?> "bool" where
|
2018-04-11 03:02:31 +02:00
|
|
|
bool str b = mkBoolF b <$ reserved str
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixNull :: Parser NExprLoc
|
2018-04-11 03:02:31 +02:00
|
|
|
nixNull = annotateLocation1 (mkNullF <$ reserved "null" <?> "null")
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixParens :: Parser NExprLoc
|
2018-04-10 19:11:55 +02:00
|
|
|
nixParens = parens nixToplevelForm <?> "parens"
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixList :: Parser NExprLoc
|
2018-04-10 01:11:31 +02:00
|
|
|
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
|
|
|
|
pathChar x = isAlpha x || isDigit x || x == '.' || x == '_' || x == '-' || x == '+'
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
slash :: Parser Char
|
2018-04-10 06:35:46 +02:00
|
|
|
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
|
2018-04-10 06:35:46 +02:00
|
|
|
nixSPath = annotateLocation1
|
2018-04-17 06:39:41 +02:00
|
|
|
(mkPathF True <$> try (char '<' *> many (satisfy pathChar <+> slash) <* symbol ">")
|
2018-04-10 06:35:46 +02:00
|
|
|
<?> "spath")
|
2018-04-07 21:02:50 +02:00
|
|
|
|
2018-04-10 08:34:21 +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-10 08:34:21 +02:00
|
|
|
|
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
|
2018-04-10 01:11:31 +02:00
|
|
|
nixLet = annotateLocation1 (reserved "let"
|
2018-04-17 06:39:41 +02:00
|
|
|
*> (letBody <+> letBinders)
|
2018-04-09 19:16:25 +02:00
|
|
|
<?> "let block")
|
2018-04-07 21:02:50 +02:00
|
|
|
where
|
|
|
|
letBinders = NLet
|
|
|
|
<$> nixBinders
|
2018-04-10 19:11:55 +02:00
|
|
|
<*> (reserved "in" *> nixToplevelForm)
|
2018-04-07 21:02:50 +02:00
|
|
|
-- Let expressions `let {..., body = ...}' are just desugared
|
|
|
|
-- into `(rec {..., body = ...}).body'.
|
2018-04-10 02:12:16 +02:00
|
|
|
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
|
2018-04-10 01:11:31 +02:00
|
|
|
nixIf = annotateLocation1 (NIf
|
2018-04-07 21:02:50 +02:00
|
|
|
<$> (reserved "if" *> nixExprLoc)
|
2018-04-10 19:11:55 +02:00
|
|
|
<*> (reserved "then" *> nixToplevelForm)
|
|
|
|
<*> (reserved "else" *> nixToplevelForm)
|
2018-04-09 19:16:25 +02:00
|
|
|
<?> "if")
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixAssert :: Parser NExprLoc
|
2018-04-10 01:11:31 +02:00
|
|
|
nixAssert = annotateLocation1 (NAssert
|
2018-04-07 21:02:50 +02:00
|
|
|
<$> (reserved "assert" *> nixExprLoc)
|
2018-04-10 19:11:55 +02:00
|
|
|
<*> (semi *> nixToplevelForm)
|
2018-04-09 19:16:25 +02:00
|
|
|
<?> "assert")
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixWith :: Parser NExprLoc
|
2018-04-10 01:11:31 +02:00
|
|
|
nixWith = annotateLocation1 (NWith
|
2018-04-10 19:11:55 +02:00
|
|
|
<$> (reserved "with" *> nixToplevelForm)
|
|
|
|
<*> (semi *> nixToplevelForm)
|
2018-04-09 19:16:25 +02:00
|
|
|
<?> "with")
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixLambda :: Parser NExprLoc
|
2018-04-10 19:11:55 +02:00
|
|
|
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)
|
2018-04-17 06:39:41 +02:00
|
|
|
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)
|
2018-04-09 19:16:25 +02:00
|
|
|
<* doubleQ)
|
2018-04-07 21:02:50 +02:00
|
|
|
<?> "double quoted string"
|
|
|
|
|
2018-04-10 08:34:21 +02:00
|
|
|
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)
|
2018-04-09 19:16:25 +02:00
|
|
|
<* 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
|
2018-04-17 06:39:41 +02:00
|
|
|
(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 '}')
|
2018-04-17 06:39:41 +02:00
|
|
|
<+> Plain . singleton <$> char '$'
|
|
|
|
<+> esc
|
|
|
|
<+> Plain . pack <$> some plainChar
|
2018-04-10 01:11:31 +02:00
|
|
|
where
|
|
|
|
plainChar =
|
2018-04-17 06:39:41 +02:00
|
|
|
notFollowedBy (end <+> void (char '$') <+> escStart) *> anyChar
|
2018-04-07 21:02:50 +02:00
|
|
|
|
2018-04-17 06:39:41 +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)
|
2018-04-17 06:39:41 +02:00
|
|
|
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.
|
2018-04-17 06:39:41 +02:00
|
|
|
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
|
2018-04-09 19:16:25 +02:00
|
|
|
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
|
2018-04-09 19:16:25 +02:00
|
|
|
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
|
2018-04-10 20:58:08 +02:00
|
|
|
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.
|
2018-04-17 06:39:41 +02:00
|
|
|
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.
|
2018-04-17 06:39:41 +02:00
|
|
|
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.
|
2018-04-09 19:16:25 +02:00
|
|
|
option (acc ++ [pair], False) $ comma >> go (acc ++ [pair])
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
nixBinders :: Parser [Binding NExprLoc]
|
2018-04-17 06:39:41 +02:00
|
|
|
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)
|
2018-04-10 19:11:55 +02:00
|
|
|
<*> (equals *> nixToplevelForm)
|
2018-04-07 21:02:50 +02:00
|
|
|
<?> "variable binding"
|
2018-04-10 19:11:55 +02:00
|
|
|
scope = parens nixToplevelForm <?> "inherit scope"
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
keyName :: Parser (NKeyName NExprLoc)
|
2018-04-17 06:39:41 +02:00
|
|
|
keyName = dynamicKey <+> staticKey where
|
2018-04-07 21:02:50 +02:00
|
|
|
staticKey = do
|
2018-04-10 01:11:31 +02:00
|
|
|
beg <- getPosition
|
2018-04-07 21:02:50 +02:00
|
|
|
StaticKey <$> identifier <*> pure (Just beg)
|
|
|
|
dynamicKey = DynamicKey <$> nixAntiquoted nixString
|
|
|
|
|
|
|
|
nixSet :: Parser NExprLoc
|
2018-04-10 01:11:31 +02:00
|
|
|
nixSet = annotateLocation1 ((isRec <*> braces nixBinders) <?> "set") where
|
2018-04-11 08:40:32 +02:00
|
|
|
isRec = (reserved "rec" $> NRecSet <?> "recursive set")
|
2018-04-17 06:39:41 +02:00
|
|
|
<+> pure NSet
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
parseNixFile :: MonadIO m => FilePath -> m (Result NExpr)
|
2018-04-10 19:11:55 +02:00
|
|
|
parseNixFile =
|
|
|
|
parseFromFileEx $ stripAnnotation <$> (whiteSpace *> nixToplevelForm <* eof)
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
parseNixFileLoc :: MonadIO m => FilePath -> m (Result NExprLoc)
|
2018-04-10 19:11:55 +02:00
|
|
|
parseNixFileLoc = parseFromFileEx (whiteSpace *> nixToplevelForm <* eof)
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
parseNixText :: Text -> Result NExpr
|
2018-04-10 19:11:55 +02:00
|
|
|
parseNixText =
|
|
|
|
parseFromText $ stripAnnotation <$> (whiteSpace *> nixToplevelForm <* eof)
|
2018-04-07 21:02:50 +02:00
|
|
|
|
|
|
|
parseNixTextLoc :: Text -> Result NExprLoc
|
2018-04-10 19:11:55 +02:00
|
|
|
parseNixTextLoc = parseFromText (whiteSpace *> nixToplevelForm <* eof)
|