module Nix.Parser (
parseNixFile,
parseNixFileLoc,
parseNixString,
parseNixStringLoc,
parseNixText,
parseNixTextLoc,
Result(..)
) where
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class
import Data.Foldable hiding (concat)
import qualified Data.Map as Map
import Data.Text hiding (head, map, foldl1', foldl', concat)
import Nix.Parser.Library
import Nix.Parser.Operators
import Nix.Expr
import Nix.StringOperations
import Prelude hiding (elem)
annotateLocation :: Parser a -> Parser (Ann SrcSpan a)
annotateLocation p = do
begin <- position
res <- p
end <- position
let span = SrcSpan begin end
pure $ Ann span res
annotateLocation1 :: Parser (NExprF NExprLoc) -> Parser NExprLoc
annotateLocation1 = fmap annToAnnF . annotateLocation
nixExpr :: Parser NExpr
nixExpr = stripAnnotation <$> nixExprLoc
nixExprLoc :: Parser NExprLoc
nixExprLoc = whiteSpace *> (nixToplevelForm <|> foldl' makeParser nixTerm nixOperators)
where
makeParser :: Parser NExprLoc -> Either NSpecialOp NOperatorDef -> Parser NExprLoc
makeParser term (Left NSelectOp) = nixSelect term
makeParser term (Left NAppOp) = chainl1 term $ pure $ \a b -> (nApp a b)
makeParser term (Left NHasAttrOp) = nixHasAttr term
makeParser term (Right (NUnaryDef name op))
= build <$> many (annotateLocation (void $ symbol name)) <*> term
where build :: [Ann SrcSpan ()] -> NExprLoc -> NExprLoc
build = flip $ foldl' (\t' (Ann s ()) -> nUnary (Ann s 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 :: Parser (NExprLoc -> NExprLoc -> NExprLoc)
op = choice . map (\(n,o) -> (\(Ann a ()) -> nBinary (Ann a o)) <$> annotateLocation (reservedOp n)) $ ops
antiStart :: Parser String
antiStart = try (string "${") <?> show ("${" :: String)
nixAntiquoted :: Parser a -> Parser (Antiquoted a NExprLoc)
nixAntiquoted p = Antiquoted <$> (antiStart *> nixExprLoc <* symbolic '}') <|> Plain <$> p
selDot :: Parser ()
selDot = try (char '.' *> notFollowedBy (("path" :: String) <$ nixPath)) *> whiteSpace
<?> "."
nixSelector :: Parser (Ann SrcSpan (NAttrPath NExprLoc))
nixSelector = annotateLocation $ keyName `sepBy1` selDot
nixSelect :: Parser NExprLoc -> Parser NExprLoc
nixSelect term = build
<$> term
<*> optional ((,) <$> (selDot *> nixSelector) <*> optional (reserved "or" *> nixExprLoc))
where
build :: NExprLoc -> Maybe (Ann SrcSpan (NAttrPath NExprLoc), Maybe NExprLoc) -> NExprLoc
build t Nothing = t
build t (Just (s,o)) = nSelectLoc t s o
nixHasAttr :: Parser NExprLoc -> Parser NExprLoc
nixHasAttr term = build <$> term <*> optional (reservedOp "?" *> nixSelector) where
build :: NExprLoc -> Maybe (Ann SrcSpan (NAttrPath NExprLoc)) -> NExprLoc
build t Nothing = t
build t (Just s) = nHasAttr t s
nixTerm :: Parser NExprLoc
nixTerm = nixSelect $ choice
[ nixInt, nixBool, nixNull, nixParens, nixList, nixPath, nixSPath, nixUri
, nixStringExpr, nixSet, nixSym ]
nixToplevelForm :: Parser NExprLoc
nixToplevelForm = choice [nixLambda, nixLet, nixIf, nixAssert, nixWith]
nixSym :: Parser NExprLoc
nixSym = annotateLocation1 $ mkSymF <$> identifier
nixInt :: Parser NExprLoc
nixInt = annotateLocation1 $ mkIntF <$> token decimal <?> "integer"
nixBool :: Parser NExprLoc
nixBool = annotateLocation1 $ try (true <|> false) <?> "bool" where
true = mkBoolF True <$ symbol "true"
false = mkBoolF False <$ symbol "false"
nixNull :: Parser NExprLoc
nixNull = annotateLocation1 $ mkNullF <$ try (symbol "null") <?> "null"
nixParens :: Parser NExprLoc
nixParens = parens nixExprLoc <?> "parens"
nixList :: Parser NExprLoc
nixList = annotateLocation1 $ brackets (NList <$> many nixTerm) <?> "list"
pathChars :: String
pathChars = ['A'..'Z'] ++ ['a'..'z'] ++ "._-+" ++ ['0'..'9']
slash :: Parser Char
slash = try (char '/' <* notFollowedBy (char '/')) <?> "slash"
nixSPath :: Parser NExprLoc
nixSPath = annotateLocation1 $ mkPathF True <$> try (char '<' *> some (oneOf pathChars <|> slash) <* symbolic '>')
<?> "spath"
nixPath :: Parser NExprLoc
nixPath = annotateLocation1 $ token $ fmap (mkPathF False) $ ((++)
<$> (try ((++) <$> many (oneOf pathChars) <*> fmap (:[]) slash) <?> "path")
<*> fmap concat
( some (some (oneOf pathChars)
<|> liftA2 (:) slash (some (oneOf pathChars)))
)
)
<?> "path"
nixLet :: Parser NExprLoc
nixLet = annotateLocation1 $ NLet
<$> (reserved "let" *> nixBinders)
<*> (whiteSpace *> reserved "in" *> nixExprLoc)
<?> "let"
nixIf :: Parser NExprLoc
nixIf = annotateLocation1 $ NIf
<$> (reserved "if" *> nixExprLoc)
<*> (whiteSpace *> reserved "then" *> nixExprLoc)
<*> (whiteSpace *> reserved "else" *> nixExprLoc)
<?> "if"
nixAssert :: Parser NExprLoc
nixAssert = annotateLocation1 $ NAssert
<$> (reserved "assert" *> nixExprLoc)
<*> (semi *> nixExprLoc)
nixWith :: Parser NExprLoc
nixWith = annotateLocation1 $ NWith
<$> (reserved "with" *> nixExprLoc)
<*> (semi *> nixExprLoc)
nixLambda :: Parser NExprLoc
nixLambda = (nAbs <$> annotateLocation (try argExpr <?> "lambda arguments") <*> nixExprLoc) <?> "lambda"
nixStringExpr :: Parser NExprLoc
nixStringExpr = nStr <$> annotateLocation nixString
uriAfterColonC :: Parser Char
uriAfterColonC = alphaNum <|> oneOf "%/?:@&=+$,-_.!~*'"
nixUri :: Parser NExprLoc
nixUri = annotateLocation1 $ token $ fmap (mkUriF . pack) $ (++)
<$> try ((++) <$> (scheme <* char ':') <*> fmap (\x -> [':',x]) uriAfterColonC)
<*> many uriAfterColonC
where
scheme = (:) <$> letter <*> many (alphaNum <|> oneOf "+-.")
nixString :: Parser (NString NExprLoc)
nixString = doubleQuoted <|> indented <?> "string"
where
doubleQuoted :: Parser (NString NExprLoc)
doubleQuoted = DoubleQuoted . removePlainEmpty . mergePlain
<$> (doubleQ *> many (stringChar doubleQ (void $ char '\\') doubleEscape)
<* token doubleQ)
<?> "double quoted string"
doubleQ = void $ char '"'
doubleEscape = Plain . singleton <$> (char '\\' *> escapeCode)
indented :: Parser (NString NExprLoc)
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 *> nixExprLoc <* char '}')
<|> 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 (Params NExprLoc)
argExpr = choice [atLeft, onlyname, atRight] <* symbolic ':' where
onlyname = choice [nixUri >> unexpected "valid uri",
Param <$> identifier]
atLeft = try $ do
name <- identifier <* symbolic '@'
(constructor, params) <- params
return $ ParamSet (constructor params) (Just name)
atRight = do
(constructor, params) <- params
name <- optional $ symbolic '@' *> identifier
return $ ParamSet (constructor params) name
params = do
(args, dotdots) <- braces getParams
let constructor = if dotdots then VariadicParamSet else FixedParamSet
return (constructor, Map.fromList args)
getParams :: Parser ([(Text, Maybe NExprLoc)], Bool)
getParams = go [] where
go acc = (token (string "...") >> return (acc, True)) <|> getMore acc
getMore acc =
option (acc, False) $ do
pair <- liftA2 (,) identifier (optional $ symbolic '?' *> nixExprLoc)
option (acc ++ [pair], False) $ symbolic ',' >> go (acc ++ [pair])
nixBinders :: Parser [Binding NExprLoc]
nixBinders = (inherit <|> namedVar) `endBy` symbolic ';' where
inherit = Inherit <$> (reserved "inherit" *> optional scope)
<*> many keyName
<?> "inherited binding"
namedVar = NamedVar <$> (annotated <$> nixSelector) <*> (symbolic '=' *> nixExprLoc)
<?> "variable binding"
scope = parens nixExprLoc <?> "inherit scope"
keyName :: Parser (NKeyName NExprLoc)
keyName = dynamicKey <|> staticKey where
staticKey = StaticKey <$> identifier
dynamicKey = DynamicKey <$> nixAntiquoted nixString
nixSet :: Parser NExprLoc
nixSet = annotateLocation1 $ (isRec <*> braces nixBinders) <?> "set" where
isRec = (try (reserved "rec" *> pure NRecSet) <?> "recursive set")
<|> pure NSet
parseNixFile :: MonadIO m => FilePath -> m (Result NExpr)
parseNixFile = parseFromFileEx $ nixExpr <* eof
parseNixFileLoc :: MonadIO m => FilePath -> m (Result NExprLoc)
parseNixFileLoc = parseFromFileEx $ nixExprLoc <* eof
parseNixString :: String -> Result NExpr
parseNixString = parseFromString $ nixExpr <* eof
parseNixStringLoc :: String -> Result NExprLoc
parseNixStringLoc = parseFromString $ nixExprLoc <* eof
parseNixText :: Text -> Result NExpr
parseNixText = parseNixString . unpack
parseNixTextLoc :: Text -> Result NExprLoc
parseNixTextLoc = parseNixStringLoc . unpack