language-nix 1.0 → 2
raw patch · 13 files changed
+250/−833 lines, 13 filesdep +deepseqdep +lensdep +prettydep −HUnitdep −containersdep −hspecdep ~basesetup-changedPVP ok
version bump matches the API change (PVP)
Dependencies added: deepseq, lens, pretty, regex-posix
Dependencies removed: HUnit, containers, hspec, language-nix, mtl, parsec, transformers
Dependency ranges changed: base
API changes (from Hackage documentation)
- Language.Nix: And :: Expr -> Expr -> Expr
- Language.Nix: Append :: Expr -> Expr -> Expr
- Language.Nix: Apply :: Expr -> Expr -> Expr
- Language.Nix: Assert :: Expr -> Expr
- Language.Nix: Assign :: ScopedIdent -> Expr -> Attr
- Language.Nix: AttrSet :: Bool -> [Attr] -> Expr
- Language.Nix: AttrSetP :: (Maybe String) -> [(String, Maybe Expr)] -> Expr
- Language.Nix: Boolean :: Bool -> Expr
- Language.Nix: Concat :: Expr -> Expr -> Expr
- Language.Nix: DefAttr :: Expr -> Expr -> Expr
- Language.Nix: Deref :: Expr -> Expr -> Expr
- Language.Nix: Equal :: Expr -> Expr -> Expr
- Language.Nix: Fun :: Expr -> Expr -> Expr
- Language.Nix: HasAttr :: Expr -> Expr -> Expr
- Language.Nix: Ident :: String -> Expr
- Language.Nix: IfThenElse :: Expr -> Expr -> Expr -> Expr
- Language.Nix: Implies :: Expr -> Expr -> Expr
- Language.Nix: Import :: Expr -> Expr
- Language.Nix: Inequal :: Expr -> Expr -> Expr
- Language.Nix: Inherit :: ScopedIdent -> [String] -> Attr
- Language.Nix: Let :: [Attr] -> Expr -> Expr
- Language.Nix: List :: [Expr] -> Expr
- Language.Nix: Lit :: String -> Expr
- Language.Nix: Not :: Expr -> Expr
- Language.Nix: Null :: Expr
- Language.Nix: Or :: Expr -> Expr -> Expr
- Language.Nix: SIdent :: [String] -> ScopedIdent
- Language.Nix: Union :: Expr -> Expr -> Expr
- Language.Nix: With :: Expr -> Expr
- Language.Nix: assign :: NixParser String
- Language.Nix: attrSet :: NixParser Expr
- Language.Nix: attrSetPattern :: NixParser Expr
- Language.Nix: attribute :: NixParser Attr
- Language.Nix: braces :: NixParser a -> NixParser a
- Language.Nix: brackets :: NixParser a -> NixParser a
- Language.Nix: builtins :: Env
- Language.Nix: commaSep1 :: NixParser a -> NixParser [a]
- Language.Nix: data Attr
- Language.Nix: data Expr
- Language.Nix: data ParseError :: *
- Language.Nix: dot :: NixParser String
- Language.Nix: eval :: Expr -> Eval Expr
- Language.Nix: expr :: NixParser Expr
- Language.Nix: genIdentifier :: Gen String
- Language.Nix: identifier :: NixParser Expr
- Language.Nix: instance Arbitrary ScopedIdent
- Language.Nix: instance Eq Attr
- Language.Nix: instance Eq Expr
- Language.Nix: instance Eq ScopedIdent
- Language.Nix: instance Error Error
- Language.Nix: instance Read Attr
- Language.Nix: instance Read Expr
- Language.Nix: instance Read ScopedIdent
- Language.Nix: instance Show Attr
- Language.Nix: instance Show Error
- Language.Nix: instance Show Expr
- Language.Nix: instance Show ScopedIdent
- Language.Nix: letExpr :: NixParser Expr
- Language.Nix: lexeme :: NixParser a -> NixParser a
- Language.Nix: list :: NixParser Expr
- Language.Nix: listExpr :: NixParser Expr
- Language.Nix: listOperatorTable :: [[NixOperator]]
- Language.Nix: literal :: NixParser Expr
- Language.Nix: literalURI :: NixParser String
- Language.Nix: natural :: NixParser String
- Language.Nix: newtype ScopedIdent
- Language.Nix: nixLanguage :: LanguageDef
- Language.Nix: nixLexer :: TokenParser
- Language.Nix: nixString :: NixParser String
- Language.Nix: operatorTable :: [[NixOperator]]
- Language.Nix: parens :: NixParser a -> NixParser a
- Language.Nix: parse :: NixParser a -> String -> Either ParseError a
- Language.Nix: parse' :: NixParser a -> SourceName -> String -> Either ParseError a
- Language.Nix: parseNix :: String -> Either ParseError Expr
- Language.Nix: parseNixFile :: FilePath -> IO (Either ParseError Expr)
- Language.Nix: reserved :: String -> NixParser ()
- Language.Nix: reservedOp :: String -> NixParser ()
- Language.Nix: run :: String -> Either Error Expr
- Language.Nix: runEval :: Eval a -> Env -> Either Error a
- Language.Nix: scopedIdentifier :: NixParser ScopedIdent
- Language.Nix: semi :: NixParser String
- Language.Nix: symbol :: String -> NixParser String
- Language.Nix: term :: NixParser Expr
- Language.Nix: type LanguageDef = GenLanguageDef String () Identity
- Language.Nix: type NixOperator = Operator String () Identity Expr
- Language.Nix: type NixParser a = ParsecT String () Identity a
- Language.Nix: type TokenParser = GenTokenParser String () Identity
- Language.Nix: whitespace :: NixParser ()
+ Language.Nix.Binding: binding :: Iso' Binding (Identifier, Path)
+ Language.Nix.Binding: data Binding
+ Language.Nix.Binding: instance Control.DeepSeq.NFData Language.Nix.Binding.Binding
+ Language.Nix.Binding: instance GHC.Classes.Eq Language.Nix.Binding.Binding
+ Language.Nix.Binding: instance GHC.Classes.Ord Language.Nix.Binding.Binding
+ Language.Nix.Binding: instance GHC.Show.Show Language.Nix.Binding.Binding
+ Language.Nix.Binding: instance Text.PrettyPrint.HughesPJClass.Pretty Language.Nix.Binding.Binding
+ Language.Nix.Binding: localName :: Lens' Binding Identifier
+ Language.Nix.Binding: reference :: Lens' Binding Path
+ Language.Nix.Identifier: data Identifier
+ Language.Nix.Identifier: ident :: Iso' Identifier String
+ Language.Nix.Identifier: instance Control.DeepSeq.NFData Language.Nix.Identifier.Identifier
+ Language.Nix.Identifier: instance Data.String.IsString Language.Nix.Identifier.Identifier
+ Language.Nix.Identifier: instance GHC.Classes.Eq Language.Nix.Identifier.Identifier
+ Language.Nix.Identifier: instance GHC.Classes.Ord Language.Nix.Identifier.Identifier
+ Language.Nix.Identifier: instance GHC.Show.Show Language.Nix.Identifier.Identifier
+ Language.Nix.Identifier: instance Text.PrettyPrint.HughesPJClass.Pretty Language.Nix.Identifier.Identifier
+ Language.Nix.Identifier: needsQuoting :: String -> Bool
+ Language.Nix.Identifier: quote :: String -> String
+ Language.Nix.Path: data Path
+ Language.Nix.Path: instance Control.DeepSeq.NFData Language.Nix.Path.Path
+ Language.Nix.Path: instance GHC.Classes.Eq Language.Nix.Path.Path
+ Language.Nix.Path: instance GHC.Classes.Ord Language.Nix.Path.Path
+ Language.Nix.Path: instance GHC.Show.Show Language.Nix.Path.Path
+ Language.Nix.Path: instance Text.PrettyPrint.HughesPJClass.Pretty Language.Nix.Path.Path
+ Language.Nix.Path: path :: Iso' Path [Identifier]
Files
- LICENSE +26/−26
- Language/Nix.hs +0/−549
- Setup.hs +0/−2
- Setup.lhs +8/−0
- language-nix.cabal +50/−37
- parse-nix.hs +0/−16
- run-nix.hs +0/−16
- self-test.hs +0/−187
- src/Language/Nix.hs +11/−0
- src/Language/Nix/Binding.hs +34/−0
- src/Language/Nix/Identifier.hs +64/−0
- src/Language/Nix/Path.hs +51/−0
- test/Main.hs +6/−0
LICENSE view
@@ -1,30 +1,30 @@-Copyright (c) 2013, Peter Simons <simons@cryp.to>--All rights reserved.--Redistribution and use in source and binary forms, with or without-modification, are permitted provided that the following conditions are met:+Redistribution and use in source and binary forms, with or+without modification, are permitted provided that the+following conditions are met: - * Redistributions of source code must retain the above copyright- notice, this list of conditions and the following disclaimer.+ * Redistributions of source code must retain the above+ copyright notice, this list of conditions and the+ following disclaimer. - * Redistributions in binary form must reproduce the above- copyright notice, this list of conditions and the following- disclaimer in the documentation and/or other materials provided- with the distribution.+ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the+ following disclaimer in the documentation and/or other+ materials provided with the distribution. - * Neither the name of Peter Simons nor the names of other- contributors may be used to endorse or promote products derived- from this software without specific prior written permission.+ * The names of its contributors may not be used to endorse+ or promote products derived from this software without+ specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND+CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
− Language/Nix.hs
@@ -1,549 +0,0 @@-{- |- Module : Language.Nix- Copyright : (c) 2013 Peter Simons- License : BSD3- Maintainer : simons@cryp.to- -}--module Language.Nix- (- -- * Evaluating the Nix Language- run, runEval, eval, builtins,-- -- * Running the Parser- parseNixFile, parseNix, parse, parse', ParseError,-- -- * Nix Language AST- Expr(..), ScopedIdent(..), Attr(..), genIdentifier,-- -- * Nix Language Parsers- expr, listExpr, term, operatorTable, listOperatorTable, identifier, literal,- nixString, literalURI, attrSet, scopedIdentifier, attribute, list, letExpr,- attrSetPattern,-- -- * Parsec Language Specification- TokenParser, LanguageDef, NixParser, NixOperator, nixLanguage, nixLexer,- symbol, reserved, reservedOp, lexeme, parens, braces, brackets, natural,- assign, semi, dot, commaSep1, whitespace,- )- where--import Prelude hiding ( lookup )-import Data.Functor.Identity-import Control.Applicative ( (<$>), (<*>), (<$), (<*), (*>) )-import Text.Parsec hiding ( parse )-import qualified Text.Parsec as Parsec-import qualified Text.Parsec.Language as Parsec-import qualified Text.Parsec.Token as Parsec-import Text.Parsec.Expr-import Test.QuickCheck--import Text.Show.Functions ( )-import Control.Monad.Reader-import qualified Control.Monad.Error as ErrT-import Control.Monad.Error hiding ( Error )-import qualified Data.Map as Map ( )-import Data.Map hiding ( map, foldr )---- import Debug.Trace-trace :: a -> b -> b-trace _ b = b------- Nix Language Definition for Parsec ----------------------------------------type TokenParser = Parsec.GenTokenParser String () Identity-type LanguageDef = Parsec.GenLanguageDef String () Identity-type NixParser a = ParsecT String () Identity a-type NixOperator = Operator String () Identity Expr--nixLanguage :: LanguageDef-nixLanguage = Parsec.emptyDef- { Parsec.commentStart = "/*"- , Parsec.commentEnd = "*/"- , Parsec.commentLine = "#"- , Parsec.nestedComments = False- , Parsec.identStart = letter <|> oneOf "_"- , Parsec.identLetter = alphaNum <|> oneOf "-_"- , Parsec.opStart = Parsec.opLetter nixLanguage- , Parsec.opLetter = oneOf ".!{}[]+=?&|/:"- , Parsec.reservedOpNames = [".","!","+","++","&&","||","?","=","//","==","!=",":"]- , Parsec.reservedNames = ["rec","let","in","import","with","inherit","assert","or","if","then","else"]- , Parsec.caseSensitive = True- }--nixLexer :: TokenParser-nixLexer = Parsec.makeTokenParser nixLanguage--symbol :: String -> NixParser String-symbol = Parsec.symbol nixLexer--reserved :: String -> NixParser ()-reserved = Parsec.reserved nixLexer--reservedOp :: String -> NixParser ()-reservedOp = Parsec.reservedOp nixLexer--lexeme :: NixParser a -> NixParser a-lexeme = Parsec.lexeme nixLexer--parens :: NixParser a -> NixParser a-parens = Parsec.parens nixLexer--braces :: NixParser a -> NixParser a-braces = Parsec.braces nixLexer--brackets :: NixParser a -> NixParser a-brackets = Parsec.brackets nixLexer--natural :: NixParser String-natural = show <$> Parsec.natural nixLexer--assign :: NixParser String-assign = symbol "="--semi :: NixParser String-semi = Parsec.semi nixLexer--dot :: NixParser String-dot = Parsec.dot nixLexer--commaSep1 :: NixParser a -> NixParser [a]-commaSep1 = Parsec.commaSep1 nixLexer--whitespace :: NixParser ()-whitespace = Parsec.whiteSpace nixLexer------- Nix Expressions -----------------------------------------------------------newtype ScopedIdent = SIdent [String]- deriving (Read, Show, Eq)--data Attr = Assign ScopedIdent Expr- | Inherit ScopedIdent [String]- deriving (Read, Show, Eq)--genIdentifier :: Gen String-genIdentifier = ((:) <$> elements firstChar <*> listOf (elements identChar)) `suchThat` (`notElem` Parsec.reservedNames nixLanguage)- where firstChar = ['a'..'z'] ++ ['A'..'Z'] ++ "_"- identChar = firstChar ++ ['0'..'9'] ++ "-"--instance Arbitrary ScopedIdent where- arbitrary = SIdent <$> listOf1 genIdentifier--data Expr = Null- | Lit String- | Ident String- | Boolean Bool- | AttrSet Bool [Attr]- | AttrSetP (Maybe String) [(String, Maybe Expr)]- | List [Expr]- | Deref Expr Expr- | HasAttr Expr Expr- | DefAttr Expr Expr- | Concat Expr Expr- | Append Expr Expr- | Not Expr- | Union Expr Expr- | Equal Expr Expr- | Inequal Expr Expr- | And Expr Expr- | Or Expr Expr- | Implies Expr Expr- | Fun Expr Expr- | Let [Attr] Expr- | Apply Expr Expr- | Import Expr- | With Expr- | Assert Expr- | IfThenElse Expr Expr Expr- deriving (Read, Show, Eq)--expr :: NixParser Expr-expr = whitespace >> buildExpressionParser operatorTable term--listExpr :: NixParser Expr-listExpr = buildExpressionParser listOperatorTable term--term :: NixParser Expr-term = choice [ parens expr- , list- , try attrSetPattern- , attrSet- , letExpr- , reserved "import" >> Import <$> expr- , reserved "with" >> With <$> expr <* semi- , reserved "assert" >> Assert <$> expr <* semi- , IfThenElse <$> (reserved "if" *> expr) <*> (reserved "then" *> expr) <*> (reserved "else" *> expr)- , try literal- , identifier- ]--operatorTable :: [[NixOperator]]-operatorTable = x1 : x2 : [ Infix (Apply <$ whitespace) AssocLeft ] : xs- where (x1:x2:xs) = listOperatorTable--listOperatorTable :: [[NixOperator]]-listOperatorTable = [ [ binary "." Deref AssocLeft ]- , [ binary "or" DefAttr AssocNone ]- {- , [ Infix (Apply <$ whitespace) AssocRight ] -}- , [ binary "?" HasAttr AssocNone ]- , [ binary "++" Concat AssocRight ]- , [ binary "+" Append AssocLeft ]- , [ prefix "!" Not ]- , [ binary "//" Union AssocRight ]- , [ binary "==" Equal AssocNone ]- , [ binary "!=" Inequal AssocNone ]- , [ binary "&&" And AssocLeft ]- , [ binary "||" Or AssocLeft ]- , [ binary "->" Implies AssocNone ]- , [ binary ":" Fun AssocRight ]- ]- where- binary :: String -> (Expr -> Expr -> Expr) -> Assoc -> NixOperator- binary op fun = Infix (fun <$ reservedOp op)-- prefix :: String -> (Expr -> Expr) -> NixOperator- prefix op fun = Prefix (fun <$ reservedOp op)--identifier :: NixParser Expr-identifier = Ident <$> Parsec.identifier nixLexer--literal :: NixParser Expr-literal = Lit <$> (stringLiteral <|> nixString <|> natural <|> literalURI)--stringLiteral :: NixParser String-stringLiteral = lexeme $ between (string "\"") (string "\"") (concat <$> many stringChar)- where- stringChar :: NixParser String- stringChar = choice [ many1 (noneOf "$\\\"")- , try $ char '$' >> braces expr >> return ""- , return <$> char '$'- , char '\\' >> anyChar >>= \c -> return ['\\',c]- ]--nixString :: NixParser String-nixString = lexeme $ between (string "''") (string "''") (concat <$> many stringChar)- where- stringChar :: NixParser String- stringChar = choice [ many1 (noneOf "$'")- , try $ char '$' >> braces expr >> return ""- , return <$> char '$'- , try $ (return <$> char '\'') <* notFollowedBy (char '\'')- , try $ string "''" >> string "${"- ]--literalURI :: NixParser String-literalURI = lexeme $ try absoluteURI <|> relativeURI--absoluteURI :: NixParser String-absoluteURI = (++) <$> scheme <*> ((:) <$> char ':' <*> (hierPart <|> opaquePart))--relativeURI :: NixParser String-relativeURI = (++) <$> (absPath <|> relPath) <*> option "" (char '?' >> query)--absPath :: NixParser String-absPath = (:) <$> char '/' <*> pathSegments--authority :: NixParser String-authority = server <|> regName--domainlabel :: NixParser String-domainlabel = (:) <$> alphaNum <*> option "" ((++) <$> many (char '-') <*> domainlabel)--escapedChars :: NixParser Char-escapedChars = char '%' >> hexDigit >> hexDigit--hierPart :: NixParser String-hierPart = (++) <$> (try netPath <|> absPath) <*> option "" (char '?' >> query)--host :: NixParser String-host = hostname <|> ipv4address--hostname :: NixParser String-hostname = many (domainlabel >> char '.') >> toplabel >> option "" (string ".")--hostport :: NixParser String-hostport = (++) <$> host <*> option "" ((:) <$> char ':' <*> port)--ipv4address :: NixParser String-ipv4address = many1 digit >> char '.' >> many1 digit >> char '.' >> many1 digit >> char '.' >> many1 digit--markChars :: NixParser Char-markChars = oneOf "-_.!~*'" -- Note that "()" have been removed here!--netPath :: NixParser String-netPath = (++) <$> ((++) <$> string "//" <*> authority) <*> option "" absPath--opaquePart :: NixParser String-opaquePart = uricNoSlash >> many uric--pathSegments :: NixParser String-pathSegments = (++) <$> segment <*> (concat <$> many ((:) <$> char '/' <*> segment))--pchar :: NixParser Char-pchar = unreservedChars <|> escapedChars <|> oneOf ":@&=+$,"--port :: NixParser String-port = many1 digit--query :: NixParser String-query = many uric--regName :: NixParser String-regName = many1 (unreservedChars <|> escapedChars <|> oneOf "$,:@&=+") -- Note that ';' has been removed here!--relPath :: NixParser String-relPath = (++) <$> relSegment <*> absPath--relSegment :: NixParser String-relSegment = many1 (unreservedChars <|> escapedChars <|> oneOf "@&=+$,") -- Note that ';' has been removed here!--reservedChars :: NixParser Char-reservedChars = oneOf "/?:@&=+$," -- Note that ';' has been removed here!--scheme :: NixParser String-scheme = (:) <$> letter <*> many (alphaNum <|> oneOf "+-.")--segment :: NixParser String-segment = {- (++) <$> -} many pchar {- <*> (concat <$> many ((:) <$> char ';' <*> param)) -}--server :: NixParser String-server = option "" (option "" ((++) <$> userinfo <*> string "@") >> hostport)--toplabel :: NixParser Char-toplabel = letter <|> (letter >> many (alphaNum <|> char '-') >> alphaNum)--unreservedChars :: NixParser Char-unreservedChars = alphaNum <|> markChars--uric :: NixParser Char-uric = reservedChars <|> unreservedChars <|> escapedChars--uricNoSlash :: NixParser Char-uricNoSlash = unreservedChars <|> escapedChars <|> oneOf ";?:@&=+$,"--userinfo :: NixParser String-userinfo = many (unreservedChars <|> escapedChars <|> oneOf ";:&=+$,")--attrSet :: NixParser Expr-attrSet = AttrSet <$> option False (True <$ reserved "rec") <*> braces (attribute `endBy` semi)--scopedIdentifier :: NixParser ScopedIdent-scopedIdentifier = SIdent <$> sepBy1 (Parsec.identifier nixLexer) dot--attribute :: NixParser Attr-attribute = (Assign <$> (SIdent . return <$> stringLiteral <|> scopedIdentifier) <* assign <*> expr)- <|> (Inherit <$> (symbol "inherit" *> option (SIdent []) (parens scopedIdentifier)) <*> many1 (Parsec.identifier nixLexer))--list :: NixParser Expr-list = List <$> brackets (many listExpr)--attrSetPattern :: NixParser Expr-attrSetPattern = AttrSetP <$> optionMaybe atPattern <*> setPattern- where- atPattern = Parsec.identifier nixLexer <* reserved "@"- setPattern = braces $ commaSep1 $ (,) <$> Parsec.identifier nixLexer <*> optionMaybe (reservedOp "?" >> expr) <|> ellipsis- ellipsis = ("...",Nothing) <$ reserved "..."--letExpr :: NixParser Expr-letExpr = choice [ try $ Let <$> (reserved "let" *> try attribute `endBy1` semi) <*> (reserved "in" *> expr)- , (`Let` Ident "body") <$> (reserved "let" *> braces (try attribute `endBy1` semi))- ]--parseNixFile :: FilePath -> IO (Either ParseError Expr)-parseNixFile path = parse' (expr <* eof) path <$> readFile path--parseNix :: String -> Either ParseError Expr-parseNix = parse expr--parse' :: NixParser a -> SourceName -> String -> Either ParseError a-parse' = Parsec.parse--parse :: NixParser a -> String -> Either ParseError a-parse p = parse' (p <* eof) "<string>"------- Nix Evaluation ------------------------------------------------------------type VarName = String-type Env = Map VarName Expr--data Error = CannotCoerceToString Expr- | CannotCoerceToBool Expr- | TypeMismatch Expr- | UndefinedVariable VarName- | Unsupported Expr- | Unstructured String- | InvalidSyntax ParseError- deriving (Show)--instance ErrT.Error Error where- strMsg = Unstructured- noMsg = Unstructured "no error message available"--type Eval a = ErrorT Error (Reader Env) a--getEnv :: VarName -> Eval Expr-getEnv v = ask >>= maybe (throwError (UndefinedVariable v)) return . lookup v--onError :: Eval a -> (Error -> Bool, Eval a) -> Eval a-onError f (p,g) = catchError f (\e -> if p e then g else throwError e)--isUndefinedVariable :: Error -> Bool-isUndefinedVariable (UndefinedVariable _) = True-isUndefinedVariable _ = False--isCoerceToString :: Error -> Bool-isCoerceToString (CannotCoerceToString _) = True-isCoerceToString _ = False--isCoerceToBool :: Error -> Bool-isCoerceToBool (CannotCoerceToBool _) = True-isCoerceToBool _ = False--evalBool :: Expr -> Eval Bool-evalBool e | trace ("evalBool " ++ show e) False = undefined-evalBool (Boolean x) = return x-evalBool (Ident v) = getEnv v >>= evalBool-evalBool (And x y) = (&&) <$> evalBool x <*> evalBool y-evalBool (Or x y) = (||) <$> evalBool x <*> evalBool y-evalBool (Not x) = not <$> evalBool x-evalBool e@(Equal x y) = ((==) <$> evalString x <*> evalString y)- `onError` (isCoerceToString, (==) <$> evalBool x <*> evalBool y)- `onError` (isCoerceToBool, throwError (TypeMismatch e))-evalBool e = throwError (CannotCoerceToBool e)--evalString :: Expr -> Eval String-evalString e | trace ("evalString " ++ show e) False = undefined-evalString (Lit x) = return x-evalString (Append x y) = (++) <$> evalString x <*> evalString y-evalString (Ident v) = getEnv v >>= evalString-evalString e = throwError (CannotCoerceToString e)--evalAttribute :: Attr -> Eval [(VarName,Expr)]-evalAttribute (Assign (SIdent [k]) v) = (return . (,) k) <$> eval v-evalAttribute (Inherit (SIdent []) vs) = sequence [ (,) v <$> getEnv v | v <- vs ]-evalAttribute e = throwError (Unsupported (AttrSet False [e]))--attrSetToEnv :: Attr -> Eval [(VarName,Expr)]-attrSetToEnv (Assign (SIdent [k]) v) = return [(k,v)]-attrSetToEnv (Inherit (SIdent []) vs) = sequence [ (,) v <$> getEnv v | v <- vs ]-attrSetToEnv e = throwError (Unsupported (AttrSet True [e]))--eval :: Expr -> Eval Expr-eval e | trace ("eval " ++ show e) False = undefined-eval Null = return Null-eval e@(Lit _) = return e-eval e@(Boolean _) = return e-eval (Ident v) = getEnv v >>= eval-eval e@(Append _ _) = Lit <$> evalString e-eval e@(And _ _) = Boolean <$> evalBool e-eval e@(Or _ _) = Boolean <$> evalBool e-eval e@(Not _) = Boolean <$> evalBool e-eval e@(Equal _ _) = Boolean <$> evalBool e-eval e@(Inequal _ _) = Boolean <$> evalBool e-eval (IfThenElse b x y) = evalBool b >>= \b' -> eval (if b' then x else y)-eval (DefAttr x y) = eval x `onError` (isUndefinedVariable, eval y)-eval (Let as e) = concat <$> mapM attrSetToEnv as >>= \env -> trace ("add to env: " ++ show env) $ local (union (fromList env)) (eval e)-eval (Apply (Fun (Ident v) x) y) = trace "foo" $ eval y >>= \y' -> local (insert v y') (eval x)-eval (Apply (Ident v) y) = trace "yo" $ getEnv v >>= \x' -> eval (Apply x' y)-eval (Apply x@(Apply _ _) y) = trace "yo" $ eval x >>= \x' -> eval (Apply x' y)-eval (AttrSet False as) = (AttrSet False . map (\(k,v) -> Assign (SIdent [k]) v) . concat) <$> mapM evalAttribute as-eval (AttrSet True as) = concat <$> mapM attrSetToEnv as >>= \as' -> trace ("add to env: " ++ show as') $ local (union (fromList as')) (eval (AttrSet False as))-eval (Deref (Ident v) y) = getEnv v >>= \v' -> eval (Deref v' y)-eval (Deref (AttrSet False as) y@(Ident _)) = concat <$> mapM evalAttribute as >>= \as' -> trace ("add to env: " ++ show as') $ local (\env -> foldr (uncurry insert) env as') (eval y)-eval (Deref (AttrSet True as) y@(Ident _)) = concat <$> mapM attrSetToEnv as >>= \as' -> trace ("add to env: " ++ show as') $ local (\env -> foldr (uncurry insert) env as') (eval y)-eval e@(Deref _ _) = throwError (TypeMismatch e)-eval e = throwError (Unsupported e)------- eval (Apply (Lambda v x) y) = eval y >>= \y' -> trace ("add to env: " ++ show (v,y')) $ local ((v,y'):) (eval x)--- eval (Apply x@(V _) y) = eval x >>= \x' -> eval (Apply x' y)--- eval (Apply x@(Apply _ _) y) = eval x >>= \x' -> eval (Apply x' y)--- eval (Let env e) = trace ("add to env: " ++ show env) $ local (env++) (eval e)--- eval e@(Lambda _ _) = return e--- eval e = throwError (Unsupported e)----- coerceDict :: Value -> Dict--- coerceDict (AttrSetV e) = e--- coerceDict e = error ("cannot coerce expression to attribute set: " ++ show e)------ coerceFun :: Value -> (Value -> Value)--- coerceFun (FunV f) = f--- coerceFun e = error ("cannot coerce expression to function: " ++ show e)------ coerceStr :: Value -> String--- coerceStr (StrV x) = x--- coerceStr e = error ("cannot coerce expression to string: " ++ show e)------ -- getScopedVar :: [String] -> Eval Value--- -- getScopedVar [] = fail "invalid empty scoped variable"--- -- getScopedVar (k:[]) = getEnv k--- -- getScopedVar (k:ks) = getEnv k >>= \e -> local (union (coerceDict e)) (getScopedVar ks)------ -- evalAttr :: Attr -> Eval Dict--- -- evalAttr (Inherit (SIdent k) is) = fromList <$> forM is (\i -> (,) i <$> getScopedVar (k++[i]))--- -- evalAttr (Assign (SIdent []) _) = fail "invalid empty scoped identifier in assignment"--- -- evalAttr (Assign (SIdent (k:[])) e) = singleton k <$> eval e--- -- evalAttr (Assign (SIdent (k:ks)) e) = (singleton k . AttrSetV) <$> evalAttr (Assign (SIdent ks) e)------ simplifyAttr :: Attr -> Map String Expr--- simplifyAttr (Inherit (SIdent _) []) = error "invalid empty inherit statement"--- simplifyAttr (Inherit (SIdent k) is) = unions [ singleton i (foldl1 Deref (map Ident (k++[i]))) | i <- is]--- simplifyAttr (Assign (SIdent []) _) = error "invalid empty scoped identifier in assignment"--- simplifyAttr (Assign (SIdent (k:[])) e) = singleton k e--- simplifyAttr (Assign (SIdent (k:ks)) e) = singleton k (AttrSet False [Assign (SIdent ks) e])------ evalAttr' :: (String, Expr) -> Eval Dict--- evalAttr' (k, e) = singleton k <$> eval e------ evalDict :: Map String Expr -> Eval Dict--- evalDict as = unionsWith mergeDicts <$> mapM evalAttr' (assocs as)------ -- -- (Inherit (SIdent k) is) = fromList <$> forM is (\i -> (,) i <$> getScopedVar (k++[i]))--- -- evalAttr' (Assign (SIdent []) _) = fail "invalid empty scoped identifier in assignment"--- -- evalAttr' (Assign (SIdent (k:[])) e) = singleton k <$> eval e--- -- evalAttr' (Assign (SIdent (k:ks)) e) = (singleton k . AttrSetV) <$> evalAttr (Assign (SIdent ks) e)------ eval :: Expr -> Eval Value--- eval e | trace ("eval: " ++ show e) False = undefined--- eval (Lit v) = return (StrV v)--- eval (Ident v) = getEnv v--- eval (AttrSet False as) = AttrSetV . unionsWith mergeDicts <$> mapM (evalDict . simplifyAttr) as------ eval (AttrSet True as) = do--- env <- ask--- let e :: Map String Expr--- e = unionsWith mergeAttrSets (map simplifyAttr as)--- return (AttrSetV (resolve env e))------ -- mdo { r@(AttrSetV d) <- local (`union` d) (eval (AttrSet False as)); return r }------ -- eval (AttrSet False as) = AttrSetV . unionsWith mergeDicts <$> mapM evalAttr as--- -- eval (AttrSet True as) = mdo { r@(AttrSetV d) <- local (`union` d) (eval (AttrSet False as)); return r }--- eval (Fun (Ident x) y) = do { env <- ask; return (FunV (\v -> runEval' (eval y) (insert x v env))) }--- eval (Apply x y) = coerceFun <$> eval x <*> eval y--- eval (Append x y) = StrV <$> ((++) <$> (coerceStr <$> eval x) <*> (coerceStr <$> eval y))--- eval (Deref x (Ident y)) = coerceDict <$> eval x >>= \x' -> local (const x') (getEnv y)--- -- default catch-all to report the un-expected expression--- eval e = fail ("unsupported: " ++ show e)------ mergeDicts :: Value -> Value -> Value--- mergeDicts x y = AttrSetV (unionWith mergeDicts (coerceDict x) (coerceDict y))------ mergeAttrSets :: Expr -> Expr -> Expr--- mergeAttrSets (AttrSet False x) (AttrSet False y) = AttrSet False (x++y)--- mergeAttrSets x y = error ("mergeAttrSets: cannot merge expressions " ++ show x ++ " and " ++ show y)--run :: String -> Either Error Expr-run = either (Left . InvalidSyntax) (\e -> runEval (eval e) builtins) . parseNix--runEval :: Eval a -> Env -> Either Error a-runEval = runReader . runErrorT--builtins :: Env-builtins = fromList- [ ("true", Boolean True)- , ("false", Boolean False)- , ("null", Null)- ]
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
+ Setup.lhs view
@@ -0,0 +1,8 @@+#!/usr/bin/env runhaskell++> module Main (main) where+>+> import Distribution.Simple+>+> main :: IO ()+> main = defaultMain
language-nix.cabal view
@@ -1,41 +1,54 @@-name: language-nix-version: 1.0-synopsis: Haskell AST and Parsers for the Nix language-description: This library provides parsec combinators that parse Nix into an AST.-homepage: https://github.com/peti/language-nix-license: BSD3-license-file: LICENSE-author: Peter Simons <simons@cryp.to>-maintainer: Peter Simons <simons@cryp.to>-category: Language-build-type: Simple-cabal-version: >= 1.8--Source-Repository head- Type: git- Location: git://github.com/peti/language-nix.git+-- This file has been generated from package.yaml by hpack version 0.7.0.+--+-- see: https://github.com/sol/hpack -Library- Exposed-Modules: Language.Nix- Build-Depends: base >= 3 && < 5, parsec, transformers, QuickCheck,- containers >= 0.4.2, mtl- Ghc-Options: -Wall+name: language-nix+version: 2+synopsis: Data types and useful functions to represent and manipulate the Nix language.+description: Data types and useful functions to represent and manipulate the Nix language.+category: Distribution, Language+homepage: https://github.com/nixos/cabal2nix#readme+bug-reports: https://github.com/nixos/cabal2nix/issues+author: Peter Simons+maintainer: simons@cryp.to+license: BSD3+license-file: LICENSE+build-type: Simple+cabal-version: >= 1.10 -Executable parse-nix- main-is: parse-nix.hs- Build-Depends: base, language-nix, parsec, transformers, QuickCheck,- containers >= 0.4.2, mtl- Ghc-Options: -Wall+source-repository head+ type: git+ location: https://github.com/nixos/cabal2nix -Executable run-nix- main-is: run-nix.hs- Build-Depends: base, language-nix, parsec, transformers, QuickCheck,- containers >= 0.4.2, mtl- Ghc-Options: -Wall+library+ hs-source-dirs:+ src+ ghc-options: -Wall+ build-depends:+ base < 5+ , deepseq+ , lens+ , pretty >= 1.1.2+ , regex-posix+ exposed-modules:+ Language.Nix+ Language.Nix.Binding+ Language.Nix.Identifier+ Language.Nix.Path+ default-language: Haskell2010 -Test-Suite self-test- type: exitcode-stdio-1.0- main-is: self-test.hs- build-depends: base, language-nix, parsec, transformers,- containers >= 0.4.2, mtl, doctest, QuickCheck, hspec, HUnit- Ghc-Options: -Wall+test-suite spec+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs:+ test+ ghc-options: -Wall+ build-depends:+ base < 5+ , deepseq+ , lens+ , pretty >= 1.1.2+ , regex-posix+ , doctest+ , QuickCheck+ default-language: Haskell2010
− parse-nix.hs
@@ -1,16 +0,0 @@--- parse-nix.hs--module Main ( main ) where--import Prelude ( fmap, either )-import Language.Nix ( parseNixFile, parseNix )-import System.Environment ( getArgs )-import System.IO ( IO, getContents, hPrint, print, stderr )-import Control.Monad ( mapM_, (>>=), (>=>) )--main :: IO ()-main = do- args <- getArgs- case args of- [] -> fmap parseNix getContents >>= either (hPrint stderr) print- _ -> mapM_ (parseNixFile >=> either (hPrint stderr) print) args
− run-nix.hs
@@ -1,16 +0,0 @@--- run-nix.hs--module Main ( main ) where--import Prelude ( fmap, either, (.) )-import Language.Nix ( run )-import System.Environment ( getArgs )-import System.IO ( IO, getContents, hPrint, print, stderr, readFile )-import Control.Monad ( mapM_, (>>=), (>=>) )--main :: IO ()-main = do- args <- getArgs- case args of- [] -> fmap run getContents >>= either (hPrint stderr) print- _ -> mapM_ (readFile >=> (either (hPrint stderr) print) . run) args
− self-test.hs
@@ -1,187 +0,0 @@--- self-test.hs--module Main ( main ) where--import Language.Nix-import qualified Text.Parsec.Token as Parsec ( reservedNames )-import Test.DocTest-import Test.QuickCheck-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit.Base ( assertFailure, assertEqual )--gives :: (Show err, Eq a, Show a) => Either err a -> a -> Expectation-gives x y = either (assertFailure . msg) (assertEqual "" y) x- where msg z = "expected: " ++ show y ++ "\nbut got parser error: " ++ show z--parseFail :: Show a => NixParser a -> String -> Expectation-parseFail p input = parse p input `shouldSatisfy` either (const True) (const False)--main :: IO ()-main = do- doctest [ "Language/Nix.hs" ]- hspec $ do- describe "identifier" $ do- it "parses hand-picked sample inputs" $ do- parse identifier "abc" `gives` Ident "abc"- parse identifier "abc " `gives` Ident "abc"- parse identifier "__a-b-c-__ " `gives` Ident "__a-b-c-__"- prop "parses all randomly generated samples" $- forAll genIdentifier $ \input -> either (const False) (Ident input ==) (parse identifier input)- it "does not swallow leading whitespace" $- parseFail identifier " abc"- it "does not parse a de-referencing expression" $- parseFail identifier "abc.def"- it "does not accept reserved words" $- mapM_ (parseFail identifier) (Parsec.reservedNames nixLanguage)- it "accepts identifiers that are a prefix of a reserved word" $- parse identifier "lett" `gives` Ident "lett"-- describe "scopedIdentifier" $ do- it "parses hand-picked sample inputs" $ do- parse scopedIdentifier "abc" `gives` SIdent ["abc"]- parse scopedIdentifier "abc " `gives` SIdent ["abc"]- parse scopedIdentifier "abc.def" `gives` SIdent ["abc","def"]- -- Need to implement pretty-printing again before this test can be used.- -- prop "parses all randomly generated samples" $- -- \sident -> either (const False) (sident ==) (parse scopedIdentifier (show (pretty sident)))- it "does not swallow leading whitespace" $- parseFail scopedIdentifier " abc"- it "does not accept reserved words" $- mapM_ (parseFail scopedIdentifier) (Parsec.reservedNames nixLanguage)- it "accepts identifiers that are a prefix of a reserved word" $- parse scopedIdentifier "lett" `gives` SIdent ["lett"]-- describe "literal" $ do- -- This is not really true: "\n" gives "\\n", not a LF.- -- prop "parses all randomly generated literal strings" $- -- \str -> either (const False) (Lit str ==) (parse literal (show str))- prop "parses all randomly generated integers" $- \n -> either (const False) (Lit (show (abs (n::Int))) ==) (parse literal (show (abs n)))- it "parses paths" $- parse literal "claus/ist/der/beste" `gives` Lit "claus/ist/der/beste"- it "parses URIs" $- parse literal "http://example.org" `gives` Lit "http://example.org"- it "parses antiquotation" $ do- -- The literal parser is more or less useless for actually processing- -- the strings.- parse literal "''abc''${''" `gives` Lit "abc${"- parse literal "\"a${b}c\"" `gives` Lit "ac"- parse literal "\"a${if !x then \"b\" else \"c\"}d\"" `gives` Lit "ad"- parse literal "''a${if b then \"c\" else ''d''}e''" `gives` Lit "ae"-- describe "attrSet" $ do- it "parses an empty attribute set" $ do- parse attrSet "{}" `gives` AttrSet False []- parse attrSet "rec {}" `gives` AttrSet True []- it "parses hand-picked sample inputs" $ do- parse attrSet "{ a = b; }" `gives` AttrSet False [Assign (SIdent ["a"]) (Ident "b")]- parse attrSet "{ a = b.c; }" `gives` AttrSet False [Assign (SIdent ["a"]) (Deref (Ident "b") (Ident "c"))]- parse attrSet "{ a = \"b\"; }" `gives` AttrSet False [Assign (SIdent ["a"]) (Lit "b")]- it "parses attribute sets as values of attribute sets" $- parse attrSet "{ a = { b = c; }; }" `gives` AttrSet False [Assign (SIdent ["a"]) (AttrSet False [Assign (SIdent ["b"]) (Ident "c")])]- it "expects assignments to terminated by a semicolon" $- parseFail attrSet "{ a = b }"- it "ignores comments" $- parse attrSet "{ /* foo */ a = /* bar */ b; # foobar\n }" `gives` AttrSet False [Assign (SIdent ["a"]) (Ident "b")]- it "parses recursive attribute sets" $- parse attrSet "rec { a = b; b = a; }" `gives` AttrSet True [Assign (SIdent ["a"]) (Ident "b"), Assign (SIdent ["b"]) (Ident "a")]- it "parses inherit statements" $ do- parse attrSet "{ inherit a; }" `gives` AttrSet False [Inherit (SIdent []) ["a"]]- parse attrSet "{ inherit a; inherit b; }" `gives` AttrSet False [Inherit (SIdent []) ["a"],Inherit (SIdent []) ["b"]]- parse attrSet "{ inherit a b; }" `gives` AttrSet False [Inherit (SIdent []) ["a","b"]]- parse attrSet "{ inherit (a) b c d; }" `gives` AttrSet False [Inherit (SIdent ["a"]) ["b","c","d"]];- -- The parser cannot handle this expample yet.- -- parse attrSet "{ inherit (import ./foo.nix) a b c; }" `gives` Lit "a"-- describe "list" $ do- it "parses an empty list" $- parse list "[]" `gives` List []- it "parses hand-picked sample inputs" $ do- parse list "[ a b c ]" `gives` List [Ident "a",Ident "b",Ident "c"]- parse list "[ \"b\" { a = [\"c\"]; } d ]" `gives` List [Lit "b",AttrSet False [Assign (SIdent ["a"]) (List [Lit "c"])],Ident "d"]- parse list "[ (a b) c ]" `gives` List [Apply (Ident "a") (Ident "b"),Ident "c"]- parse list "[ 12 8 a 0 ]" `gives` List [Lit "12", Lit "8", Ident "a", Lit "0"]-- describe "reserved" $ do- it "parses a specific reserved name" $ do- parse (reserved "let") "let" `gives` ()- parse (reserved "in") "in" `gives` ()- parse (reserved "rec") "rec" `gives` ()- parseFail (reserved "rec") "let"- it "recognizes if the keyword is actually just a prefix of the input string" $- parseFail (reserved "in") "input"-- describe "expr" $ do- it "parses an empty attribute set" $ do- parse expr "{}" `gives` AttrSet False []- parse expr "rec {}" `gives` AttrSet True []- it "parses an empty list" $- parse expr "[]" `gives` List []- it "parses a de-referencing expression" $ do- parse expr "abc.def" `gives` Deref (Ident "abc") (Ident "def")- parse expr "a.b.c" `gives` Deref (Deref (Ident "a") (Ident "b")) (Ident "c")- it "parses recursive attribute sets" $- parse expr "rec { id = x: x; }" `gives` AttrSet True [Assign (SIdent ["id"]) (Fun (Ident "x") (Ident "x"))]- it "parses boolean expressions" $ do- parse expr "true" `gives` Ident "true"- parse expr "false" `gives` Ident "false"- parse expr "system == \"linux\"" `gives` Equal (Ident "system") (Lit "linux")- parse expr "system != \"linux\"" `gives` Inequal (Ident "system") (Lit "linux")- parse expr "true && true" `gives` And (Ident "true") (Ident "true")- parse expr "false || false" `gives` Or (Ident "false") (Ident "false")- parse expr "isLinux || isDarwin" `gives` Or (Ident "isLinux") (Ident "isDarwin")- parse expr "(isLinux || isDarwin)" `gives` Or (Ident "isLinux") (Ident "isDarwin")- parse expr "(isLinux) || (isDarwin)" `gives` Or (Ident "isLinux") (Ident "isDarwin")- parse expr "!(!isLinux) || (isDarwin)" `gives` Or (Not (Not (Ident "isLinux"))) (Ident "isDarwin")- parse expr "!a && b || c" `gives` Or (And (Not (Ident "a")) (Ident "b")) (Ident "c")- parse expr "a && b || c" `gives` Or (And (Ident "a") (Ident "b")) (Ident "c")- parse expr "a || b && c" `gives` Or (Ident "a") (And (Ident "b") (Ident "c"))- parse expr "(a -> b) -> c" `gives` Implies (Implies (Ident "a") (Ident "b")) (Ident "c")- it "parses simple lambda expressions" $ do- parse expr "x: {}" `gives` Fun (Ident "x") (AttrSet False [])- parse expr "x: y: rec{}" `gives` Fun (Ident "x") (Fun (Ident "y") (AttrSet True []))- it "parses attribute set patterns" $ do- parse expr "{}: {}" `gives` Fun (AttrSet False []) (AttrSet False [])- parse expr "{a ? b, c}: {}" `gives` Fun (AttrSetP Nothing [("a",Just (Ident "b")),("c",Nothing)]) (AttrSet False [])- parse expr "{ id = x: x; }" `gives` AttrSet False [Assign (SIdent ["id"]) (Fun (Ident "x") (Ident "x"))]- parse expr "{ a?null, b }: rec {}" `gives` Fun (AttrSetP Nothing [("a",Just (Ident "null")),("b",Nothing)]) (AttrSet True [])- parse expr "{ a?c.d }: {}" `gives` Fun (AttrSetP Nothing [("a",Just (Deref (Ident "c") (Ident "d")))]) (AttrSet False [])- parse expr "{ a?c.d, ... }: {}" `gives` Fun (AttrSetP Nothing [("a",Just (Deref (Ident "c") (Ident "d"))),("...",Nothing)]) (AttrSet False [])- parse expr "e@{ a?c.d, ... }: {}" `gives` Fun (AttrSetP (Just "e") [("a",Just (Deref (Ident "c") (Ident "d"))),("...",Nothing)]) (AttrSet False [])- it "ignores leading/trailing whitespace" $ do- parse expr " {}" `gives` AttrSet False []- parse expr "{} " `gives` AttrSet False []- parse expr " { } " `gives` AttrSet False []- it "ignores comments" $- parse expr "# foo\n/* bar \n */ { /* bla */ }" `gives` AttrSet False []- it "parses function application" $ do- parse expr "a b c" `gives` Apply (Apply (Ident "a") (Ident "b")) (Ident "c")- parse expr "a.b c" `gives` Apply (Deref (Ident "a") (Ident "b")) (Ident "c")- parse expr "a{b=c.d;}" `gives` Apply (Ident "a") (AttrSet False [Assign (SIdent ["b"]) (Deref (Ident "c") (Ident "d"))])- it "parses import statements" $ do- parse expr "(import ../some/function.nix) c" `gives` Apply (Import (Lit "../some/function.nix")) (Ident "c")- parse expr "let x = import ../some/function.nix; in x" `gives` Let [Assign (SIdent ["x"]) (Import (Lit "../some/function.nix"))] (Ident "x")- it "parses if-then-else statements" $- parse expr "if a b then c { inherit d; } else e" `gives` IfThenElse (Apply (Ident "a") (Ident "b")) (Apply (Ident "c") (AttrSet False [Inherit (SIdent []) ["d"]])) (Ident "e")- it "parses with statements" $- parse expr "with a; a" `gives` Apply (With (Ident "a")) (Ident "a")-- describe "run" $ do- it "can evaluate simple data types" $ do- run "null" `gives` Null- run "true" `gives` Boolean True- run "false" `gives` Boolean False- run "\"null\"" `gives` Lit "null"- run "123" `gives` Lit "123"- run "http://example.org" `gives` Lit "http://example.org"- it "can evaluate hand-picked Nix expressions" $ do- run "rec { y = \"bar\"; f = x: \"foo\" + x; v = f y; }.v" `gives` Lit "foobar"- -- run "{ a.a.a=1; a.a.b=2; a.b=3; }" `gives` Lit "foo"- -- run "{ a.a.a=1; a.a.b=2; a.b=3; }.a.a.a" `gives` Lit "1"- run "bla or false" `gives` Boolean False- run "let a = \"foo\"; b = a; f = x: x+\"bar\"; in f b" `gives` Lit "foobar"- run "let a = { b = \"claus\"; }; b = \"bar\"; in { a = \"foo\"; inherit b; c = a.b; }" `gives` AttrSet False [Assign (SIdent ["a"]) (Lit "foo"),Assign (SIdent ["b"]) (Lit "bar"),Assign (SIdent ["c"]) (Lit "claus")]- run "let a = { b = \"claus\"; }; b = \"bar\"; in { a = \"foo\"; inherit b; c = a.b; }.c" `gives` Lit "claus"- run "let b = \"bar\"; in rec { a = \"foo\"; inherit b; c = a + b; }" `gives` AttrSet False [Assign (SIdent ["a"]) (Lit "foo"),Assign (SIdent ["b"]) (Lit "bar"),Assign (SIdent ["c"]) (Lit "foobar")]- run "let { a = \"foo\"; b = a; f = x: x+\"bar\"; body = f b; }" `gives` Lit "foobar"
+ src/Language/Nix.hs view
@@ -0,0 +1,11 @@+module Language.Nix+ ( module Language.Nix.Identifier+ , module Language.Nix.Path+ , module Language.Nix.Binding+ ) where++import Language.Nix.Identifier+import Language.Nix.Path+import Language.Nix.Binding++{-# ANN module "HLint: ignore Use import/export shortcut" #-}
+ src/Language/Nix/Binding.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TemplateHaskell #-}++module Language.Nix.Binding ( Binding, binding, localName, reference ) where++import Control.DeepSeq+import Control.Lens+import Text.PrettyPrint.HughesPJClass+import Language.Nix.Identifier+import Language.Nix.Path++-- | A 'Binding' represents an identifier that refers to some other 'Path'.++declareLenses [d| data Binding = Bind { localName :: Identifier, reference :: Path }+ deriving (Show, Eq, Ord)+ |]++binding :: Iso' Binding (Identifier,Path)+binding = iso (\(Bind l r) -> (l,r)) (uncurry Bind)++instance NFData Binding where rnf (Bind l r) = l `deepseq` rnf r++instance Pretty Binding where+ pPrint b = case (init ps, last ps) of+ ([], i') -> if i == i'+ then text "inherit" <+> pPrint i' <> semi+ else pPrint i <+> equals <+> pPrint p <> semi+ (p', i') -> if i == i'+ then text "inherit" <+> parens (pPrint (path # p')) <+> pPrint i' <> semi+ else pPrint i <+> equals <+> pPrint p <> semi++ where+ (i, p) = view binding b+ ps = view path p
+ src/Language/Nix/Identifier.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TemplateHaskell #-}++module Language.Nix.Identifier+ (+ -- | Identifiers in Nix are essentially strings. They can be constructed+ -- (and viewed) with the 'ident' isomorphism. For the sake of convenience,+ -- @Identifier@s are an instance of the 'IsString' class.+ --+ -- Reasonable people restrict themselves to identifiers of the form+ -- @[a-zA-Z\_][a-zA-Z0-9\_\'\-]*@, because these don't need quoting. The+ -- methods of the 'Pretty' class can be used to pretty-print an identifier+ -- with proper quoting:+ --+ -- >>> pPrint (ident # "test")+ -- test+ -- >>> pPrint (ident # "foo.bar")+ -- "foo.bar"+ Identifier++ , -- | An isomorphism that allows conversion of 'Identifier' from/to the+ -- standard 'String' type via 'review'.+ --+ -- prop> \str -> fromString str == ident # str+ -- prop> \str -> set ident str undefined == ident # str+ -- prop> \str -> view ident (review ident str) == str+ ident++ , -- | Helper function to quote a given identifier string if necessary.+ --+ -- >>> putStrLn (quote "abc")+ -- abc+ -- >>> putStrLn (quote "abc.def")+ -- "abc.def"+ quote++ , -- | Checks whether a given string needs quoting when interpreted as an+ -- 'Identifier'.+ needsQuoting++ ) where++import Control.DeepSeq+import Control.Lens+import Data.String+import Text.PrettyPrint.HughesPJClass+import Text.Regex.Posix++declareLenses [d| newtype Identifier = Identifier { ident :: String }+ deriving (Show, Eq, Ord, IsString)+ |]++instance Pretty Identifier where+ pPrint i = text (i ^. ident . to quote)++instance NFData Identifier where rnf (Identifier str) = rnf str++needsQuoting :: String -> Bool+needsQuoting str = not (str =~ grammar)+ where grammar :: String -- TODO: should be a compiled regular expression+ grammar = "^[a-zA-Z\\_][a-zA-Z0-9\\_\\'\\-]*$"++quote :: String -> String+quote s = if needsQuoting s then show s else s
+ src/Language/Nix/Path.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE TemplateHaskell #-}++module Language.Nix.Path ( Path, path ) where++import Control.DeepSeq+import Control.Lens+import Language.Nix.Identifier+import Text.PrettyPrint.HughesPJClass ( Pretty (..), char, hcat, punctuate )++-- | Paths are non-empty lists of identifiers in Nix.+--+-- >>> path # [ident # "yo"]+-- Path [Identifier "yo"]+--+-- Any attempt to construct the empty path throws an 'error':+--+-- >>> path # []+-- Path *** Exception: Nix paths cannot be empty+--+-- 'Identifier' is an instance of 'IsString':+--+-- >>> :set -XOverloadedStrings+-- >>> pPrint $ path # ["yo","bar"]+-- yo.bar+--+-- Freaky quoted identifiers are fine except in the first segment:+--+-- >>> pPrint $ path # ["yo","b\"ar"]+-- yo."b\"ar"+-- >>> pPrint $ path # ["5ident"]+-- *** Exception: invalid Nix path: [Identifier "5ident"]+-- >>> pPrint $ path # ["5ident","foo","bar"]+-- *** Exception: invalid Nix path: [Identifier "5ident",Identifier "foo",Identifier "bar"]++declareLenses [d| newtype Path = Path [Identifier]+ deriving (Show, Eq, Ord)+ |]++instance NFData Path where rnf (Path p) = rnf p++instance Pretty Path where+ pPrint p = hcat $ punctuate (char '.') $ pPrint <$> p^.path++path :: Iso' Path [Identifier]+path = iso (\(Path p) -> p) mkPath+ where+ mkPath :: [Identifier] -> Path+ mkPath [] = error "Nix paths cannot be empty"+ mkPath p@(s0:_)+ | s0^.ident.to needsQuoting = error ("invalid Nix path: " ++ show p)+ | otherwise = Path p
+ test/Main.hs view
@@ -0,0 +1,6 @@+module Main ( main ) where++import Test.DocTest++main :: IO ()+main = doctest [ "-isrc", "src" ]