e8e129a759
When parsing '{ inherit a; }' without this fix, the `scopedInherit` parser would be tried first. Because this parser already consumes the `inherit` keyword, it won't backtrack anymore and the non-scoped `inherit` parser will never be tried, so we get a parse failure which is of course not correct. The solution is to first parse `inherit` (in both cases) and then decide whether it is a scoped import or not by looking for a following '('.
158 lines
5.7 KiB
Haskell
158 lines
5.7 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 Data.List (foldl1')
|
|
import qualified Data.Map as Map
|
|
import Data.Text hiding (head, map, foldl1')
|
|
import Nix.Types
|
|
import Nix.Parser.Library
|
|
import Prelude hiding (elem)
|
|
|
|
-- | The lexer for this parser is defined in 'Nix.Parser.Library'.
|
|
nixApp :: Parser NExpr
|
|
nixApp = go <$> someTill (whiteSpace *> nixExpr True) (try (lookAhead stop))
|
|
where
|
|
go [] = error "some has failed us"
|
|
go [x] = x
|
|
go (f:x:xs) = go (Fix (NApp f x) : xs)
|
|
|
|
stop = () <$ oneOf "=,;])}" <|> stopWords <|> eof
|
|
|
|
nixExpr :: Bool -> Parser NExpr
|
|
nixExpr = buildExpressionParser table . nixTermOrAttr
|
|
where
|
|
table =
|
|
[ [ prefix "-" NNeg ]
|
|
-- , [ prefix "~" NSubpath ] -- deprecated
|
|
, [ binary "?" NHasAttr AssocNone ]
|
|
, [ binary "++" NConcat AssocRight ]
|
|
, [ binary "*" NMult AssocLeft, binary "/" NDiv AssocLeft ]
|
|
, [ binary "+" NPlus AssocLeft, binary "-" NMinus AssocLeft ]
|
|
, [ prefix "!" NNot ]
|
|
, [ binary "//" NUpdate AssocRight ]
|
|
, [ binary "<" NLt AssocLeft, binary ">" NGt AssocLeft
|
|
, binary "<=" NLte AssocLeft, binary ">=" NGte AssocLeft ]
|
|
, [ binary "==" NEq AssocNone, binary "!=" NNEq AssocNone ]
|
|
, [ binary "&&" NAnd AssocLeft ]
|
|
, [ binary "||" NOr AssocLeft ]
|
|
, [ binary "->" NImpl AssocNone ]
|
|
]
|
|
|
|
binary name fun =
|
|
Infix $ (\x y -> Fix (NOper (fun x y))) <$ reservedOp name
|
|
prefix name fun =
|
|
Prefix $ Fix . NOper . fun <$ reservedOp name
|
|
-- postfix name fun = Postfix (Fix . NOper . fun <$ symbol name)
|
|
|
|
nixTermOrAttr :: Bool -> Parser NExpr
|
|
nixTermOrAttr = buildExpressionParser table . nixTerm where
|
|
table = [[Infix ((\x y -> Fix (NOper (NAttr x y))) <$ reservedOp ".") AssocLeft]]
|
|
|
|
nixTerm :: Bool -> Parser NExpr
|
|
nixTerm allowLambdas = choice
|
|
[ nixInt
|
|
, nixParens
|
|
, nixList
|
|
, nixLet
|
|
, nixIf
|
|
, nixBool
|
|
, nixNull
|
|
, nixPath -- can be expensive due to back-tracking
|
|
, setLambdaStringOrSym allowLambdas
|
|
]
|
|
|
|
nixInt :: Parser NExpr
|
|
nixInt = mkInt <$> decimal <?> "integer"
|
|
|
|
nixBool :: Parser NExpr
|
|
nixBool = try (true <|> false) <?> "bool" where
|
|
true = mkBool True <$ string "true"
|
|
false = mkBool False <$ string "false"
|
|
|
|
nixNull :: Parser NExpr
|
|
nixNull = try (mkNull <$ string "null") <?> "null"
|
|
|
|
nixParens :: Parser NExpr
|
|
nixParens = parens nixApp <?> "parens"
|
|
|
|
nixList :: Parser NExpr
|
|
nixList = brackets (Fix . NList <$> many (nixTermOrAttr False <* whiteSpace)) <?> "list"
|
|
|
|
nixPath :: Parser NExpr
|
|
nixPath = try $ fmap mkPath $ mfilter ('/' `elem`) $ some (oneOf "A-Za-z_0-9.:/")
|
|
|
|
nixLet :: Parser NExpr
|
|
nixLet = fmap Fix $ NLet
|
|
<$> (reserved "let" *> nixBinders)
|
|
<*> (whiteSpace *> reserved "in" *> nixApp)
|
|
|
|
nixIf :: Parser NExpr
|
|
nixIf = fmap Fix $ NIf
|
|
<$> (reserved "if" *> nixApp)
|
|
<*> (whiteSpace *> reserved "then" *> nixApp)
|
|
<*> (whiteSpace *> reserved "else" *> nixApp)
|
|
|
|
-- | This is a bit tricky because we don't know whether we're looking at a set
|
|
-- or a lambda until we've looked ahead a bit. And then it may be neither,
|
|
-- in which case we fall back to expected a plain string or identifier.
|
|
setLambdaStringOrSym :: Bool -> Parser NExpr
|
|
setLambdaStringOrSym True = try nixLambda <|> setLambdaStringOrSym False
|
|
setLambdaStringOrSym False = try nixSet <|> keyName
|
|
|
|
nixLambda :: Parser NExpr
|
|
nixLambda = Fix <$> (NAbs <$> (argExpr <?> "arguments") <*> nixApp)
|
|
|
|
stringish :: Parser NExpr
|
|
stringish = (char '"' *> (merge <$> manyTill stringChar (char '"')))
|
|
<|> (char '$' *> braces nixApp)
|
|
where
|
|
merge = foldl1' (\x y -> Fix (NOper (NConcat x y)))
|
|
|
|
stringChar = char '\\' *> (mkStr . singleton <$> anyChar)
|
|
<|> (try (string "${") *> nixApp <* char '}')
|
|
<|> (mkStr . pack <$> many (noneOf "\"\\"))
|
|
|
|
argExpr :: Parser NExpr
|
|
argExpr = (try (Fix . NArgs . FormalSet <$> paramSet)
|
|
<|> try (Fix . NArgs . FormalName <$> identifier <* whiteSpace)
|
|
<|> try (Fix . NArgs <$> (FormalLeftAt <$> identifier <* whiteSpace <*> paramSet))
|
|
<|> try (Fix . NArgs <$> (FormalRightAt <$> paramSet <*> identifier <* whiteSpace))) <* symbolic ':'
|
|
where
|
|
paramSet :: Parser (FormalParamSet NExpr)
|
|
paramSet = (FormalParamSet . Map.fromList <$> argList)
|
|
argList :: Parser [(Text, Maybe NExpr)]
|
|
argList = braces ((argName <* whiteSpace) `sepBy` symbolic ',') <* symbolic ':'
|
|
<?> "arglist"
|
|
argName :: Parser (Text, Maybe NExpr)
|
|
argName = (,) <$> (identifier <* whiteSpace)
|
|
<*> optional (symbolic '?' *> nixExpr False)
|
|
|
|
nixBinders :: Parser [Binding NExpr]
|
|
nixBinders = choice
|
|
[ reserved "inherit" *> whiteSpace *> (scopedInherit <|> inherit) <?> "inherited binding"
|
|
, namedVar
|
|
] `endBy` symbolic ';'
|
|
where
|
|
scopedInherit = try (symbolic '(') *>
|
|
(ScopedInherit <$> nixExpr False <* symbolic ')' <*> many keyName) <?> "scoped inherit binding"
|
|
inherit = Inherit <$> many keyName
|
|
namedVar = NamedVar <$> keyName <*> (symbolic '=' *> nixApp) <?> "variable binding"
|
|
|
|
keyName :: Parser NExpr
|
|
keyName = (stringish <|> (mkSym <$> identifier)) <* whiteSpace
|
|
|
|
nixSet :: Parser NExpr
|
|
nixSet = Fix <$> (NSet <$> isRec <*> (braces nixBinders <?> "set")) where
|
|
isRec = try (reserved "rec" *> pure Rec) <|> pure NonRec
|
|
|
|
parseNixFile :: MonadIO m => FilePath -> m (Result NExpr)
|
|
parseNixFile = parseFromFileEx $ nixApp <* eof
|
|
|
|
parseNixString :: String -> Result NExpr
|
|
parseNixString = parseFromString $ nixApp <* eof
|