hnix-0.17.0: src/Nix/Parser.hs
{-# language CPP #-}
{-# language DeriveAnyClass #-}
{-# options_ghc -fno-warn-name-shadowing #-}
-- | Main module for parsing Nix expressions.
module Nix.Parser
( parseNixFile
, parseNixFileLoc
, parseNixText
, parseNixTextLoc
, parseExpr
, parseFromFileEx
, Parser
, parseFromText
, Result
, reservedNames
, NAssoc(..)
, NOpPrecedence(..)
, NOpName(..)
, NSpecialOp(..)
, NOperatorDef(..)
, nixExpr
, nixExprAlgebra
, nixSet
, nixBinders
, nixSelector
, nixSym
, nixPath
, nixString
, nixUri
, nixSearchPath
, nixFloat
, nixInt
, nixBool
, nixNull
, whiteSpace
-- 2022-01-26: NOTE: Try to hide it after OperatorInfo is removed
, NOp(..)
, appOpDef
)
where
import Nix.Prelude hiding ( (<|>)
, some
, many
)
import Data.Foldable ( foldr1 )
import Control.Monad ( msum )
import Control.Monad.Combinators.Expr ( makeExprParser
, Operator( Postfix
, InfixN
, InfixR
, Prefix
, InfixL
)
)
import Data.Char ( isAlpha
, isDigit
, isSpace
)
import Data.Data ( Data(..) )
import Data.List.Extra ( groupSort )
import Data.Fix ( Fix(..) )
import qualified Data.HashSet as HashSet
import qualified Data.Text as Text
import Nix.Expr.Types
import Nix.Expr.Shorthands hiding ( ($>) )
import Nix.Expr.Types.Annotated
import Nix.Expr.Strings ( escapeCodes
, stripIndent
, mergePlain
, removeEmptyPlains
)
import Nix.Render ( MonadFile() )
import Prettyprinter ( Doc
, pretty
)
-- `parser-combinators` ships performance enhanced & MonadPlus-aware combinators.
-- For example `some` and `many` impoted here.
import Text.Megaparsec hiding ( (<|>)
, State
)
import Text.Megaparsec.Char ( space1
, letterChar
, char
)
import qualified Text.Megaparsec.Char.Lexer as Lexer
type Parser = ParsecT Void Text (State SourcePos)
-- * Utils
-- | Different to @isAlphaNum@
isAlphanumeric :: Char -> Bool
isAlphanumeric x = isAlpha x || isDigit x
{-# inline isAlphanumeric #-}
-- | Alternative "<|>" with additional preservation of 'MonadPlus' constraint.
infixl 3 <|>
(<|>) :: MonadPlus m => m a -> m a -> m a
(<|>) = mplus
-- ** Annotated
annotateLocation1 :: Parser a -> Parser (AnnUnit SrcSpan a)
annotateLocation1 p =
do
begin <- getSourcePos
res <- p
end <- get -- The state set before the last whitespace
pure $ AnnUnit (SrcSpan (toNSourcePos begin) (toNSourcePos end)) res
annotateLocation :: Parser (NExprF NExprLoc) -> Parser NExprLoc
annotateLocation = (annUnitToAnn <$>) . annotateLocation1
annotateNamedLocation :: String -> Parser (NExprF NExprLoc) -> Parser NExprLoc
annotateNamedLocation name = annotateLocation . label name
-- ** Grammar
reservedNames :: HashSet VarName
reservedNames =
HashSet.fromList
["let", "in", "if", "then", "else", "assert", "with", "rec", "inherit"]
reservedEnd :: Char -> Bool
reservedEnd x =
isSpace x || (`elem` ("{([})];:.\"'," :: String)) x
{-# inline reservedEnd #-}
reserved :: Text -> Parser ()
reserved n =
lexeme $ try $ chunk n *> lookAhead (void (satisfy reservedEnd) <|> eof)
exprAfterSymbol :: Char -> Parser NExprLoc
exprAfterSymbol p = symbol p *> nixExpr
exprAfterReservedWord :: Text -> Parser NExprLoc
exprAfterReservedWord word = reserved word *> nixExpr
-- | A literal copy of @megaparsec@ one but with addition of the @\r@ for Windows EOL case (@\r\n@).
-- Overall, parser should simply @\r\n -> \n@.
skipLineComment' :: Tokens Text -> Parser ()
skipLineComment' prefix =
chunk prefix *> void (takeWhileP (pure "character") $ \x -> x /= '\n' && x /= '\r')
whiteSpace :: Parser ()
whiteSpace =
do
put =<< getSourcePos
Lexer.space space1 lineCmnt blockCmnt
where
lineCmnt = skipLineComment' "#"
blockCmnt = Lexer.skipBlockComment "/*" "*/"
-- | Lexeme is a unit of the language.
-- Convention is that after lexeme an arbitrary amount of empty entities (space, comments, line breaks) are allowed.
-- This lexeme definition just skips over superflous @megaparsec: lexeme@ abstraction.
lexeme :: Parser a -> Parser a
lexeme p = p <* whiteSpace
symbol :: Char -> Parser Char
symbol = lexeme . char
symbols :: Text -> Parser Text
symbols = lexeme . chunk
-- We restrict the type of 'parens' and 'brackets' here because if they were to
-- take a 'Parser NExprLoc' argument they would parse additional text which
-- wouldn't be captured in the source location annotation.
--
-- Braces and angles in hnix don't enclose a single expression so this type
-- restriction would not be useful.
parens :: Parser (NExprF f) -> Parser (NExprF f)
parens = on between symbol '(' ')'
braces :: Parser a -> Parser a
braces = on between symbol '{' '}'
brackets :: Parser (NExprF f) -> Parser (NExprF f)
brackets = on between symbol '[' ']'
antiquotedIsHungryForTrailingSpaces :: Bool -> Parser (Antiquoted v NExprLoc)
antiquotedIsHungryForTrailingSpaces hungry = Antiquoted <$> (antiStart *> nixExpr <* antiEnd)
where
antiStart :: Parser Text
antiStart = label "${" $ symbols "${"
antiEnd :: Parser Char
antiEnd = label "}" $
bool
id
lexeme
hungry
(char '}')
antiquotedLexeme :: Parser (Antiquoted v NExprLoc)
antiquotedLexeme = antiquotedIsHungryForTrailingSpaces True
antiquoted :: Parser (Antiquoted v NExprLoc)
antiquoted = antiquotedIsHungryForTrailingSpaces False
---------------------------------------------------------------------------------
-- * Parser parts
-- ** Constrants
nixNull :: Parser NExprLoc
nixNull =
annotateNamedLocation "null" $
mkNullF <$ reserved "null"
nixBool :: Parser NExprLoc
nixBool =
annotateNamedLocation "bool" $
on (<|>) lmkBool (True, "true") (False, "false")
where
lmkBool (b, txt) = mkBoolF b <$ reserved txt
integer :: Parser Integer
integer = lexeme Lexer.decimal
nixInt :: Parser NExprLoc
nixInt =
annotateNamedLocation "integer" $
mkIntF <$> integer
float :: Parser Double
float = lexeme Lexer.float
nixFloat :: Parser NExprLoc
nixFloat =
annotateNamedLocation "float" $
try $
mkFloatF . realToFrac <$> float
nixUri :: Parser NExprLoc
nixUri =
lexeme $
annotateLocation $
try $
do
start <- letterChar
protocol <-
takeWhileP mempty $
\ x ->
isAlphanumeric x
|| (`elem` ("+-." :: String)) x
_ <- single ':'
address <-
takeWhile1P mempty $
\ x ->
isAlphanumeric x
|| (`elem` ("%/?:@&=+$,-_.!~*'" :: String)) x
pure . NStr . DoubleQuoted . one . Plain $ start `Text.cons` protocol <> ":" <> address
-- ** Strings
nixAntiquoted :: Parser a -> Parser (Antiquoted a NExprLoc)
nixAntiquoted p =
label "anti-quotation" $
antiquotedLexeme
<|> Plain <$> p
escapeCode :: Parser Char
escapeCode =
msum
[ c <$ char e | (c, e) <- escapeCodes ]
<|> anySingle
stringChar
:: Parser ()
-> Parser ()
-> Parser (Antiquoted Text NExprLoc)
-> Parser (Antiquoted Text NExprLoc)
stringChar end escStart esc =
antiquoted
<|> Plain . one <$> char '$'
<|> esc
<|> Plain . fromString <$> some plainChar
where
plainChar :: Parser Char
plainChar =
notFollowedBy (end <|> void (char '$') <|> escStart) *> anySingle
doubleQuoted :: Parser (NString NExprLoc)
doubleQuoted =
label "double quoted string" $
DoubleQuoted . removeEmptyPlains . mergePlain <$>
inQuotationMarks (many $ stringChar quotationMark (void $ char '\\') doubleEscape)
where
inQuotationMarks :: Parser a -> Parser a
inQuotationMarks expr = quotationMark *> expr <* quotationMark
quotationMark :: Parser ()
quotationMark = void $ char '"'
doubleEscape :: Parser (Antiquoted Text r)
doubleEscape = Plain . one <$> (char '\\' *> escapeCode)
indented :: Parser (NString NExprLoc)
indented =
label "indented string" $
stripIndent <$>
inIndentedQuotation (many $ join stringChar indentedQuotationMark indentedEscape)
where
-- | Read escaping inside of the "'' <expr> ''"
indentedEscape :: Parser (Antiquoted Text r)
indentedEscape =
try $
do
indentedQuotationMark
Plain <$> ("''" <$ char '\'' <|> "$" <$ char '$')
<|>
do
c <- char '\\' *> escapeCode
pure $
bool
EscapedNewline
(Plain $ one c)
('\n' /= c)
-- | Enclosed into indented quatation "'' <expr> ''"
inIndentedQuotation :: Parser a -> Parser a
inIndentedQuotation expr = indentedQuotationMark *> expr <* indentedQuotationMark
-- | Symbol "''"
indentedQuotationMark :: Parser ()
indentedQuotationMark = label "\"''\"" . void $ chunk "''"
nixString' :: Parser (NString NExprLoc)
nixString' = label "string" $ lexeme $ doubleQuoted <|> indented
nixString :: Parser NExprLoc
nixString = annNStr <$> annotateLocation1 nixString'
-- ** Names (variables aka symbols)
identifier :: Parser VarName
identifier =
lexeme $
try $
do
(coerce -> iD) <-
liftA2 Text.cons
(satisfy (\x -> isAlpha x || x == '_'))
(takeWhileP mempty identLetter)
guard $ not $ iD `HashSet.member` reservedNames
pure iD
where
identLetter x = isAlphanumeric x || x == '_' || x == '\'' || x == '-'
nixSym :: Parser NExprLoc
nixSym = annotateLocation $ mkSymF <$> coerce identifier
-- ** ( ) parens
-- | 'nixExpr' returns an expression annotated with a source position,
-- however this position doesn't include the parsed parentheses, so remove the
-- "inner" location annotateion and annotate again, including the parentheses.
nixParens :: Parser NExprLoc
nixParens =
annotateNamedLocation "parens" $
parens $ stripAnnF . unFix <$> nixExpr
-- ** [ ] list
nixList :: Parser NExprLoc
nixList =
annotateNamedLocation "list" $
brackets $ NList <$> many nixTerm
-- ** { } set
nixBinders :: Parser [Binding NExprLoc]
nixBinders = (inherit <|> namedVar) `endBy` symbol ';' where
inherit =
do
-- We can't use 'reserved' here because it would consume the whitespace
-- after the keyword, which is not exactly the semantics of C++ Nix.
try $ chunk "inherit" *> lookAhead (void $ satisfy reservedEnd)
p <- getSourcePos
x <- whiteSpace *> optional scope
label "inherited binding" $
liftA2 (Inherit x)
(many identifier)
(pure (toNSourcePos p))
namedVar =
do
p <- getSourcePos
label "variable binding" $
liftA3 NamedVar
(annotated <$> nixSelector)
(exprAfterSymbol '=')
(pure (toNSourcePos p))
scope = label "inherit scope" nixParens
nixSet :: Parser NExprLoc
nixSet =
annotateNamedLocation "set" $
isRec <*> braces nixBinders
where
isRec =
label "recursive set" (reserved "rec" $> NSet Recursive)
<|> pure (NSet mempty)
-- ** /x/y/z literal Path
pathChar :: Char -> Bool
pathChar x =
isAlphanumeric x || (`elem` ("._-+~" :: String)) x
slash :: Parser Char
slash =
label "slash " $
try $
char '/' <* notFollowedBy (satisfy $ \x -> x == '/' || x == '*' || isSpace x)
pathStr :: Parser Path
pathStr =
lexeme $ coerce . toString <$>
liftA2 (<>)
(takeWhileP mempty pathChar)
(Text.concat <$>
some
(liftA2 Text.cons
slash
(takeWhile1P mempty pathChar)
)
)
nixPath :: Parser NExprLoc
nixPath =
annotateNamedLocation "path" $
try $ mkPathF False <$> coerce pathStr
-- ** <<x>> environment path
-- | A path surrounded by angle brackets, indicating that it should be
-- looked up in the NIX_PATH environment variable at evaluation.
nixSearchPath :: Parser NExprLoc
nixSearchPath =
annotateNamedLocation "spath" $
mkPathF True <$> try (lexeme $ char '<' *> many (satisfy pathChar <|> slash) <* char '>')
-- ** Operators
-- 2022-01-26: NOTE: Rename to 'literal'
newtype NOpName = NOpName Text
deriving
(Eq, Ord, Generic, Typeable, Data, Show, NFData)
instance IsString NOpName where
fromString = coerce . fromString @Text
instance ToString NOpName where
toString = toString @Text . coerce
operator :: NOpName -> Parser Text
operator (coerce -> op) =
case op of
c@"-" -> c `without` '>'
c@"/" -> c `without` '/'
c@"<" -> c `without` '='
c@">" -> c `without` '='
n -> symbols n
where
without :: Text -> Char -> Parser Text
without opChar noNextChar =
lexeme . try $ chunk opChar <* notFollowedBy (char noNextChar)
opWithLoc :: (AnnUnit SrcSpan o -> a) -> o -> NOpName -> Parser a
opWithLoc f op name = f . (op <$) <$> annotateLocation1 (operator name)
-- 2022-01-26: NOTE: Make presedence free and type safe by moving it into type level:
-- https://youtu.be/qaPdg0mZavM?t=1757
-- https://wiki.haskell.org/The_Monad.Reader/Issue5/Number_Param_Types
newtype NOpPrecedence = NOpPrecedence Int
deriving (Eq, Ord, Generic, Bounded, Typeable, Data, Show, NFData)
instance Enum NOpPrecedence where
toEnum = coerce
fromEnum = coerce
instance Num NOpPrecedence where
(+) = coerce ((+) @Int)
(*) = coerce ((*) @Int)
abs = coerce (abs @Int)
signum = coerce (signum @Int)
fromInteger = coerce (fromInteger @Int)
negate = coerce (negate @Int)
-- 2022-01-26: NOTE: This type belongs into 'Type.Expr' & be used in NExprF.
data NAppOp = NAppOp
deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
-- 2022-01-26: NOTE: This type belongs into 'Type.Expr' & be used in NExprF.
data NSpecialOp
= NHasAttrOp
| NSelectOp
| NTerm -- ^ For special handling of internal special cases.
deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
data NAssoc
= NAssocLeft
-- Nota bene: @parser-combinators@ named "associative property" as 'InfixN' stating it as "non-associative property".
-- Binary operators having some associativity is a basis property in mathematical algebras in use (for example, in Category theory). Having no associativity in operators makes theory mostly impossible in use and so non-associativity is not encountered in notations, therefore under 'InfixN' @parser-combinators@ meant "associative".
-- | Bidirectional associativity, or simply: associative property.
| NAssoc
| NAssocRight
deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
-- 2022-01-31: NOTE: This type and related typeclasses & their design, probably need a refinement.
--
-- In the "Nix.Pretty", the code probably should be well-typed to the type of operations its processes.
-- Therefor splitting operation types into separate types there is probably needed.
--
-- After that:
--
-- > { NAssoc, NOpPrecedence, NOpName }
--
-- Can be formed into a type.
--
-- Also 'NAppDef' really has only 1 implementation, @{ NAssoc, NOpPrecedence, NOpName }@
-- were added there only to make type uniformal.
-- All impossible cases ideally should be unrepresentable.
-- | Single operator grammar entries.
data NOperatorDef
= NAppDef NAppOp NAssoc NOpPrecedence NOpName
| NUnaryDef NUnaryOp NAssoc NOpPrecedence NOpName
| NBinaryDef NBinaryOp NAssoc NOpPrecedence NOpName
| NSpecialDef NSpecialOp NAssoc NOpPrecedence NOpName
-- 2022-01-26: NOTE: Ord can be the order of evaluation of precedence (which 'Pretty' printing also accounts for).
deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
-- Supplied since its definition gets called/used frequently.
-- | Functional application operator definition, left associative, high precedence.
appOpDef :: NOperatorDef
appOpDef = NAppDef NAppOp NAssocLeft 1 " " -- This defined as "2" in Nix lang spec.
-- 2022-01-26: NOTE: When total - make sure to hide & inline all these instances to get free solution.
-- | Class to get a private free construction to abstract away the gap between the Nix operation types
-- 'NUnaryOp', 'NBinaryOp', 'NSpecialOp'.
-- And in doing remove 'OperatorInfo' from existance.
class NOp a where
{-# minimal getOpDef, getOpAssoc, getOpPrecedence, getOpName #-}
getOpDef :: a -> NOperatorDef
getOpAssoc :: a -> NAssoc
getOpPrecedence :: a -> NOpPrecedence
getOpName :: a -> NOpName
instance NOp NAppOp where
getOpDef NAppOp = appOpDef
getOpAssoc _op = fun appOpDef
where
fun (NAppDef _op assoc _prec _name) = assoc
fun _ = error "Impossible happened, funapp operation should been matched."
getOpPrecedence _op = fun appOpDef
where
fun (NAppDef _op _assoc prec _name) = prec
fun _ = error "Impossible happened, funapp operation should been matched."
getOpName _ = fun appOpDef
where
fun (NAppDef _op _assoc _prec name) = name
fun _ = error "Impossible happened, funapp operation should been matched."
instance NOp NUnaryOp where
getOpDef =
\case
NNeg -> NUnaryDef NNeg NAssocRight 3 "-"
NNot -> NUnaryDef NNot NAssocRight 8 "!"
getOpAssoc = fun . getOpDef
where
fun (NUnaryDef _op assoc _prec _name) = assoc
fun _ = error "Impossible happened, unary operation should been matched."
getOpPrecedence = fun . getOpDef
where
fun (NUnaryDef _op _assoc prec _name) = prec
fun _ = error "Impossible happened, unary operation should been matched."
getOpName = fun . getOpDef
where
fun (NUnaryDef _op _assoc _prec name) = name
fun _ = error "Impossible happened, unary operation should been matched."
instance NOp NBinaryOp where
getOpDef =
\case
NConcat -> NBinaryDef NConcat NAssocRight 5 "++"
NMult -> NBinaryDef NMult NAssocLeft 6 "*"
NDiv -> NBinaryDef NDiv NAssocLeft 6 "/"
NPlus -> NBinaryDef NPlus NAssocLeft 7 "+"
NMinus -> NBinaryDef NMinus NAssocLeft 7 "-"
NUpdate -> NBinaryDef NUpdate NAssocRight 9 "//"
NLt -> NBinaryDef NLt NAssocLeft 10 "<"
NLte -> NBinaryDef NLte NAssocLeft 10 "<="
NGt -> NBinaryDef NGt NAssocLeft 10 ">"
NGte -> NBinaryDef NGte NAssocLeft 10 ">="
NEq -> NBinaryDef NEq NAssoc 11 "=="
NNEq -> NBinaryDef NNEq NAssoc 11 "!="
NAnd -> NBinaryDef NAnd NAssocLeft 12 "&&"
NOr -> NBinaryDef NOr NAssocLeft 13 "||"
NImpl -> NBinaryDef NImpl NAssocRight 14 "->"
getOpAssoc = fun . getOpDef
where
fun (NBinaryDef _op assoc _prec _name) = assoc
fun _ = error "Impossible happened, binary operation should been matched."
getOpPrecedence = fun . getOpDef
where
fun (NBinaryDef _op _assoc prec _name) = prec
fun _ = error "Impossible happened, binary operation should been matched."
getOpName = fun . getOpDef
where
fun (NBinaryDef _op _assoc _prec name) = name
fun _ = error "Impossible happened, binary operation should been matched."
instance NOp NSpecialOp where
getOpDef =
\case
NSelectOp -> NSpecialDef NSelectOp NAssocLeft 1 "."
NHasAttrOp -> NSpecialDef NHasAttrOp NAssocLeft 4 "?"
NTerm -> NSpecialDef NTerm NAssocLeft 1 "???"
getOpAssoc = fun . getOpDef
where
fun (NSpecialDef _op assoc _prec _name) = assoc
fun _ = error "Impossible happened, special operation should been matched."
getOpPrecedence = fun . getOpDef
where
fun (NSpecialDef _op _assoc prec _name) = prec
fun _ = error "Impossible happened, special operation should been matched."
getOpName = fun . getOpDef
where
fun (NSpecialDef _op _assoc _prec name) = name
fun _ = error "Impossible happened, special operation should been matched."
instance NOp NOperatorDef where
getOpDef op = op
getOpAssoc = \case
(NAppDef _op assoc _prec _name) -> assoc
(NUnaryDef _op assoc _prec _name) -> assoc
(NBinaryDef _op assoc _prec _name) -> assoc
(NSpecialDef _op assoc _prec _name) -> assoc
getOpPrecedence = fun . getOpDef
where
fun (NAppDef _op _assoc prec _name) = prec
fun (NUnaryDef _op _assoc prec _name) = prec
fun (NBinaryDef _op _assoc prec _name) = prec
fun (NSpecialDef _op _assoc prec _name) = prec
getOpName = fun . getOpDef
where
fun (NAppDef _op _assoc _prec name) = name
fun (NUnaryDef _op _assoc _prec name) = name
fun (NBinaryDef _op _assoc _prec name) = name
fun (NSpecialDef _op _assoc _prec name) = name
prefix :: NUnaryOp -> Operator Parser NExprLoc
prefix op =
Prefix $ manyUnaryOp $ opWithLoc annNUnary op $ getOpName op
-- postfix name op = (NUnaryDef name op,
-- Postfix (opWithLoc annNUnary op name))
manyUnaryOp :: MonadPlus f => f (a -> a) -> f (a -> a)
manyUnaryOp f = foldr1 (.) <$> some f
binary
:: NBinaryOp
-> Operator Parser NExprLoc
binary op =
mapAssocToInfix (getOpAssoc op) $ opWithLoc annNBinary op (getOpName op)
mapAssocToInfix :: NAssoc -> m (a -> a -> a) -> Operator m a
mapAssocToInfix NAssocLeft = InfixL
mapAssocToInfix NAssoc = InfixN
mapAssocToInfix NAssocRight = InfixR
-- ** x: y lambda function
-- | Gets all of the arguments for a function.
argExpr :: Parser (Params NExprLoc)
argExpr =
msum
[ atLeft
, onlyname
, atRight
]
<* symbol ':'
where
-- 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.
onlyname =
msum
[ nixUri *> unexpected (Label $ fromList "valid uri" )
, Param <$> identifier
]
-- Parameters named by an identifier on the left (`args @ {x, y}`)
atLeft =
try $
do
name <- identifier <* symbol '@'
(variadic, pset) <- params
pure $ ParamSet (pure name) variadic pset
-- Parameters named by an identifier on the right, or none (`{x, y} @ args`)
atRight =
do
(variadic, pset) <- params
name <- optional $ symbol '@' *> identifier
pure $ ParamSet name variadic pset
-- Return the parameters set.
params = braces getParams
-- Collects the parameters within curly braces. Returns the parameters and
-- an flag indication if the parameters are variadic.
getParams :: Parser (Variadic, [(VarName, Maybe NExprLoc)])
getParams = go mempty
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.
go :: [(VarName, Maybe NExprLoc)] -> Parser (Variadic, [(VarName, Maybe NExprLoc)])
go acc = ((Variadic, acc) <$ symbols "...") <|> getMore
where
getMore :: Parser (Variadic, [(VarName, Maybe NExprLoc)])
getMore =
-- Could be nothing, in which just return what we have so far.
option (mempty, acc) $
do
-- Get an argument name and an optional default.
pair <-
liftA2 (,)
identifier
(optional $ exprAfterSymbol '?')
let args = acc <> one pair
-- Either return this, or attempt to get a comma and restart.
option (mempty, args) $ symbol ',' *> go args
nixLambda :: Parser NExprLoc
nixLambda =
liftA2 annNAbs
(annotateLocation1 $ try argExpr)
nixExpr
-- ** let expression
nixLet :: Parser NExprLoc
nixLet =
annotateNamedLocation "let block" $
reserved "let" *> (letBody <|> letBinders)
where
-- | Expressions `let {..., body = ...}' are just desugared
-- into `(rec {..., body = ...}).body'.
letBody = (\ expr -> NSelect Nothing expr (one $ StaticKey "body")) <$> attrset
where
attrset = annotateLocation $ NSet Recursive <$> braces nixBinders
-- | Regular `let`
letBinders =
liftA2 NLet
nixBinders
(exprAfterReservedWord "in")
-- ** if then else
nixIf :: Parser NExprLoc
nixIf =
annotateNamedLocation "if" $
liftA3 NIf
(reserved "if" *> nixExpr)
(exprAfterReservedWord "then")
(exprAfterReservedWord "else")
-- ** with
nixWith :: Parser NExprLoc
nixWith =
annotateNamedLocation "with" $
liftA2 NWith
(exprAfterReservedWord "with")
(exprAfterSymbol ';' )
-- ** assert
nixAssert :: Parser NExprLoc
nixAssert =
annotateNamedLocation "assert" $
liftA2 NAssert
(exprAfterReservedWord "assert")
(exprAfterSymbol ';' )
-- ** . - reference (selector) into attr
selectorDot :: Parser ()
selectorDot = label "." $ try (symbol '.' *> notFollowedBy nixPath)
keyName :: Parser (NKeyName NExprLoc)
keyName = dynamicKey <|> staticKey
where
staticKey = StaticKey <$> identifier
dynamicKey = DynamicKey <$> nixAntiquoted nixString'
nixSelector :: Parser (AnnUnit SrcSpan (NAttrPath NExprLoc))
nixSelector =
annotateLocation1 $ fromList <$> keyName `sepBy1` selectorDot
nixSelect :: Parser NExprLoc -> Parser NExprLoc
nixSelect term =
do
res <-
liftA2 builder
term
(optional $
liftA2 (flip (,))
(selectorDot *> nixSelector)
(optional $ reserved "or" *> nixTerm)
)
continues <- optional $ lookAhead selectorDot
maybe
id
(const nixSelect)
continues
(pure res)
where
builder
:: NExprLoc
-> Maybe
( Maybe NExprLoc
, AnnUnit SrcSpan (NAttrPath NExprLoc)
)
-> NExprLoc
builder t =
maybe
t
(uncurry (`annNSelect` t))
-- ** _ - syntax hole
nixSynHole :: Parser NExprLoc
nixSynHole =
annotateLocation $ mkSynHoleF <$> coerce (char '^' *> identifier)
-- List of Nix operation parsers with their precedence.
opParsers :: [(NOpPrecedence, Operator Parser NExprLoc)]
opParsers =
-- This is not parsed here, even though technically it's part of the
-- expression table. The problem is that in some cases, such as list
-- membership, it's also a term. And since terms are effectively the
-- highest precedence entities parsed by the expression parser, it ends up
-- working out that we parse them as a kind of "meta-term".
-- {- 1 -}
-- [ ( NSpecialDef "." NSelectOp NAssocLeft
-- , Postfix $
-- do
-- sel <- seldot *> selector
-- mor <- optional (reserved "or" *> term)
-- pure $ \x -> annNSelect x sel mor)
-- ]
-- NApp is left associative
-- 2018-05-07: jwiegley: Thanks to Brent Yorgey for showing me this trick!
specialBuilder NAppOp (InfixL $ annNApp <$ symbols mempty) <>
specialBuilder NHasAttrOp (Postfix $ symbol '?' *> (flip annNHasAttr <$> nixSelector)) <>
builder prefix <>
builder binary
where
specialBuilder :: NOp t => t -> b -> [(NOpPrecedence, b)]
specialBuilder op parser = one (entry op (const parser))
builder :: (Enum t, Bounded t, NOp t) => (t -> b) -> [(NOpPrecedence, b)]
builder tp = fmap (`entry` tp) universe
entry :: NOp t => t -> (t -> b) -> (NOpPrecedence, b)
entry op parser = (getOpPrecedence op, parser op)
-- ** Expr & its constituents (Language term, expr algebra)
nixTerm :: Parser NExprLoc
nixTerm =
do
c <- try . lookAhead . satisfy $
\x -> (`elem` ("({[</\"'^" :: String)) x || pathChar x
case c of
'(' -> nixSelect nixParens
'{' -> nixSelect nixSet
'[' -> nixList
'<' -> nixSearchPath
'/' -> nixPath
'"' -> nixString
'\'' -> nixString
'^' -> nixSynHole
_ ->
msum
$ [ nixSelect nixSet | c == 'r' ]
<> [ nixPath | pathChar c ]
<> if isDigit c
then [ nixFloat, nixInt ]
else
[ nixUri | isAlpha c ]
<> [ nixBool | c == 't' || c == 'f' ]
<> [ nixNull | c == 'n' ]
<> one (nixSelect nixSym)
-- | Bundles parsers into @[[]]@ based on precedence (form is required for `megaparsec`).
nixOperators :: [[ Operator Parser NExprLoc ]]
nixOperators =
snd <$>
groupSort opParsers
-- | Nix expression algebra parser.
-- "Expression algebra" is to explain @megaparsec@ use of the term "Expression" (parser for language algebraic coperators without any statements (without @let@ etc.)), which is essentially an algebra inside the language.
nixExprAlgebra :: Parser NExprLoc
nixExprAlgebra =
makeExprParser
nixTerm
nixOperators
nixExpr :: Parser NExprLoc
nixExpr = keywords <|> nixLambda <|> nixExprAlgebra
where
keywords = nixLet <|> nixIf <|> nixAssert <|> nixWith
-- * Parse
type Result a = Either (Doc Void) a
parseWith
:: Parser a
-> Path
-> Text
-> Either (Doc Void) a
parseWith parser file input =
either
(Left . pretty . errorBundlePretty)
pure
$ (`evalState` initialPos (coerce file)) $ (`runParserT` coerce file) parser input
parseFromFileEx :: MonadFile m => Parser a -> Path -> m (Result a)
parseFromFileEx parser file = parseWith parser file <$> readFile file
parseFromText :: Parser a -> Text -> Result a
parseFromText = (`parseWith` "<string>")
fullContent :: Parser NExprLoc
fullContent = whiteSpace *> nixExpr <* eof
parseNixFile' :: MonadFile m => (Parser NExprLoc -> Parser a) -> Path -> m (Result a)
parseNixFile' f =
parseFromFileEx $ f fullContent
parseNixFile :: MonadFile m => Path -> m (Result NExpr)
parseNixFile =
parseNixFile' (stripAnnotation <$>)
parseNixFileLoc :: MonadFile m => Path -> m (Result NExprLoc)
parseNixFileLoc =
parseNixFile' id
parseNixText' :: (Parser NExprLoc -> Parser a) -> Text -> Result a
parseNixText' f =
parseFromText $ f fullContent
parseNixText :: Text -> Result NExpr
parseNixText =
parseNixText' (stripAnnotation <$>)
parseNixTextLoc :: Text -> Result NExprLoc
parseNixTextLoc =
parseNixText' id
parseExpr :: MonadFail m => Text -> m NExpr
parseExpr =
either
(fail . show)
pure
. parseNixText