hnix/Nix/Parser.hs
Benno Fünfstück e8e129a759 factor out "inherit" keyword in binding parser
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 '('.
2014-08-13 22:35:51 +02:00

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