3a08227c0c
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.
220 lines
7.5 KiB
Haskell
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
|