From 99b52fe48e39849086d7b16e6c2e48da62623bf2 Mon Sep 17 00:00:00 2001 From: Allen Nelson Date: Mon, 25 Jan 2016 01:00:27 -0600 Subject: [PATCH] separate expr into types and shorthands --- Nix/Atoms.hs | 48 ++++++ Nix/Eval.hs | 1 + Nix/Expr.hs | 321 +---------------------------------------- Nix/Expr/Shorthands.hs | 180 +++++++++++++++++++++++ Nix/Expr/Types.hs | 193 +++++++++++++++++++++++++ Nix/Pretty.hs | 1 + hnix.cabal | 3 + tests/ParserTests.hs | 1 + 8 files changed, 434 insertions(+), 314 deletions(-) create mode 100644 Nix/Atoms.hs create mode 100644 Nix/Expr/Shorthands.hs create mode 100644 Nix/Expr/Types.hs diff --git a/Nix/Atoms.hs b/Nix/Atoms.hs new file mode 100644 index 0000000..f30c409 --- /dev/null +++ b/Nix/Atoms.hs @@ -0,0 +1,48 @@ +module Nix.Atoms where + +import Prelude +import Data.Text (Text, pack) +import GHC.Generics +import Data.Data +import Data.HashMap.Strict (HashMap) + +-- | Atoms are values that evaluate to themselves. This means that +-- they appear in both the parsed AST (in the form of literals) and +-- the evaluated form. +data NAtom + -- | An integer. The c nix implementation currently only supports + -- integers that fit in the range of 'Int64'. + = NInt !Integer + -- | Booleans. + | NBool !Bool + -- | Null values. There's only one of this variant. + | NNull + -- | URIs, which are just string literals, but do not need quotes. + | NUri !Text + deriving (Eq, Ord, Generic, Typeable, Data, Show) + +class ToAtom t where + toAtom :: t -> NAtom + +instance ToAtom Bool where toAtom = NBool +instance ToAtom Int where toAtom = NInt . fromIntegral +instance ToAtom Integer where toAtom = NInt + +class FromAtom t where + fromAtom :: NAtom -> t + fromAtoms :: [NAtom] -> t + fromAtomSet :: HashMap Text NAtom -> t + +-- | Convert a primitive into something which can be made from a +-- constant. So for example `convert 1 :: Expression` +convert :: (ToAtom prim, FromAtom t) => prim -> t +convert = fromAtom . toAtom + +-- | Conversion to environment variables for constants. +atomToEnvString :: NAtom -> Text +atomToEnvString = \case + NInt i -> pack $ show i + NBool True -> "1" + NBool False -> "" + NNull -> "" + NUri uri -> uri diff --git a/Nix/Eval.hs b/Nix/Eval.hs index d01bfb7..087229c 100644 --- a/Nix/Eval.hs +++ b/Nix/Eval.hs @@ -17,6 +17,7 @@ import Data.Typeable (Typeable) import GHC.Generics import Nix.Pretty (atomText) import Nix.StringOperations (runAntiquoted) +import Nix.Atoms import Nix.Expr import Prelude hiding (mapM, sequence) diff --git a/Nix/Expr.hs b/Nix/Expr.hs index f38ece4..d3dd3e7 100644 --- a/Nix/Expr.hs +++ b/Nix/Expr.hs @@ -1,315 +1,8 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE DeriveTraversable #-} -{-# LANGUAGE DeriveFoldable #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DeriveDataTypeable #-} -{-# LANGUAGE DeriveFunctor #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE LambdaCase #-} --- | The nix expression type and supporting types. -module Nix.Expr where - -import Control.Monad hiding (forM_, mapM, sequence) -import Data.Data -import Data.Fix -import Data.Foldable -import Data.Map (Map) -import qualified Data.Map as Map -import Data.Monoid -import Data.Text (Text, pack) -import Data.Traversable -import GHC.Exts -import GHC.Generics -import Prelude hiding (readFile, concat, concatMap, elem, mapM, - sequence, minimum, foldr) - --- | The main nix expression type. This is polymorphic so that it can be made --- a functor, which allows us to traverse expressions and map functions over --- them. The actual 'NExpr' type is a fixed point of this functor, defined --- below. -data NExprF r - = NConstant !NAtom - -- ^ Constants: ints, bools, URIs, and null. - | NStr !(NString r) - -- ^ A string, with interpolated expressions. - | NSym !Text - -- ^ A variable. For example, in the expression @f a@, @f@ is represented - -- as @NSym "f"@ and @a@ as @NSym "a"@. - | NList ![r] - -- ^ A list literal. - | NSet ![Binding r] - -- ^ An attribute set literal, not recursive. - | NRecSet ![Binding r] - -- ^ An attribute set literal, recursive. - | NLiteralPath !FilePath - -- ^ A path expression, which is evaluated to a store path. The path here - -- can be relative, in which case it's evaluated relative to the file in - -- which it appears. - | NEnvPath !FilePath - -- ^ A path which refers to something in the Nix search path (the NIX_PATH - -- environment variable. For example, @@. - | NUnary !NUnaryOp !r - -- ^ Application of a unary operator to an expression. - | NBinary !NBinaryOp !r !r - -- ^ Application of a binary operator to two expressions. - | NSelect !r !(NAttrPath r) !(Maybe r) - -- ^ Dot-reference into an attribute set, optionally providing an - -- alternative if the key doesn't exist. - | NHasAttr !r !(NAttrPath r) - -- ^ Ask if a set contains a given attribute path. - | NAbs !(Params r) !r - -- ^ A function literal (lambda abstraction). - | NApp !r !r - -- ^ Apply a function to an argument. - | NLet ![Binding r] !r - -- ^ Evaluate the second argument after introducing the bindings. - | NIf !r !r !r - -- ^ If-then-else statement. - | NWith !r !r - -- ^ Evaluate an attribute set, bring its bindings into scope, and - -- evaluate the second argument. - | NAssert !r !r - -- ^ Assert that the first returns true before evaluating the second. - deriving (Ord, Eq, Generic, Typeable, Data, Functor, Show) - --- | The monomorphic expression type is a fixed point of the polymorphic one. -type NExpr = Fix NExprF - --- | Atoms are values that evaluate to themselves. This means that --- they appear in both the parsed AST (in the form of literals) and --- the evaluated form. -data NAtom - -- | An integer. The c nix implementation currently only supports - -- integers that fit in the range of 'Int64'. - = NInt !Integer - -- | Booleans. - | NBool !Bool - -- | Null values. There's only one of this variant. - | NNull - -- | URIs, which are just string literals, but do not need quotes. - | NUri !Text - deriving (Eq, Ord, Generic, Typeable, Data, Show) - --- | A single line of the bindings section of a let expression or of a set. -data Binding r - = NamedVar !(NAttrPath r) !r - -- ^ An explicit naming, such as @x = y@ or @x.y = z@. - | Inherit !(Maybe r) ![NKeyName r] - -- ^ Using a name already in scope, such as @inherit x;@ which is shorthand - -- for @x = x;@ or @inherit (x) y;@ which means @y = x.y;@. - deriving (Typeable, Data, Ord, Eq, Functor, Show) - --- | @Params@ represents all the ways the formal parameters to a --- function can be represented. -data Params r - = Param !Text - -- ^ For functions with a single named argument, such as @x: x + 1@. - | ParamSet !(ParamSet r) !(Maybe Text) - -- ^ Explicit parameters (argument must be a set). Might specify a name - -- to bind to the set in the function body. - deriving (Ord, Eq, Generic, Typeable, Data, Functor, Show, - Foldable, Traversable) - --- | An explicit parameter set; provides a shorthand for unpacking arguments. -data ParamSet r - = FixedParamSet !(Map Text (Maybe r)) - -- ^ A fixed set, where no arguments beyond what is specified in the map - -- may be given. The map might contain defaults for arguments not passed. - | VariadicParamSet !(Map Text (Maybe r)) - -- ^ Same as the 'FixedParamSet', but extra arguments are allowed. - deriving (Ord, Eq, Generic, Typeable, Data, Functor, Show, - Foldable, Traversable) - --- | 'Antiquoted' represents an expression that is either --- antiquoted (surrounded by ${...}) or plain (not antiquoted). -data Antiquoted v r = Plain !v | Antiquoted !r - deriving (Ord, Eq, Generic, Typeable, Data, Functor, Show) - --- | An 'NString' is a list of things that are either a plain string --- or an antiquoted expression. After the antiquotes have been evaluated, --- the final string is constructed by concating all the parts. -data NString r - = DoubleQuoted ![Antiquoted Text r] - -- ^ Strings wrapped with double-quotes (") are not allowed to contain - -- literal newline characters. - | Indented ![Antiquoted Text r] - -- ^ Strings wrapped with two single quotes ('') can contain newlines, - -- and their indentation will be stripped. - deriving (Eq, Ord, Generic, Typeable, Data, Functor, Show) - --- | For the the 'IsString' instance, we use a plain doublequoted string. -instance IsString (NString r) where - fromString "" = DoubleQuoted [] - fromString string = DoubleQuoted [Plain $ pack string] - --- | A 'KeyName' is something that can appear at the right side of an --- equals sign. For example, @a@ is a 'KeyName' in @{ a = 3; }@, @let a = 3; --- in ...@, @{}.a@ or @{} ? a@. --- --- Nix supports both static keynames (just an identifier) and dynamic --- identifiers. Dynamic identifiers can be either a string (e.g.: --- @{ "a" = 3; }@) or an antiquotation (e.g.: @let a = "example"; --- in { ${a} = 3; }.example@). --- --- Note: There are some places where a dynamic keyname is not allowed. --- In particular, those include: --- --- * The RHS of a @binding@ inside @let@: @let ${"a"} = 3; in ...@ --- produces a syntax error. --- * The attribute names of an 'inherit': @inherit ${"a"};@ is forbidden. --- --- Note: In Nix, a simple string without antiquotes such as @"foo"@ is --- allowed even if the context requires a static keyname, but the --- parser still considers it a 'DynamicKey' for simplicity. -data NKeyName r - = DynamicKey !(Antiquoted (NString r) r) - | StaticKey !Text - deriving (Eq, Ord, Generic, Typeable, Data, Show) - --- | Deriving this instance automatically is not possible because @r@ --- occurs not only as last argument in @Antiquoted (NString r) r@ -instance Functor NKeyName where - fmap f (DynamicKey (Plain str)) = DynamicKey . Plain $ fmap f str - fmap f (DynamicKey (Antiquoted e)) = DynamicKey . Antiquoted $ f e - fmap _ (StaticKey key) = StaticKey key - --- | A selector (for example in a @let@ or an attribute set) is made up --- of strung-together key names. -type NAttrPath r = [NKeyName r] - --- | There are two unary operations: logical not and integer negation. -data NUnaryOp = NNeg | NNot - deriving (Eq, Ord, Generic, Typeable, Data, Show) - --- | Binary operators expressible in the nix language. -data NBinaryOp - = NEq -- ^ Equality (==) - | NNEq -- ^ Inequality (!=) - | NLt -- ^ Less than (<) - | NLte -- ^ Less than or equal (<=) - | NGt -- ^ Greater than (>) - | NGte -- ^ Greater than or equal (>=) - | NAnd -- ^ Logical and (&&) - | NOr -- ^ Logical or (||) - | NImpl -- ^ Logical implication (->) - | NUpdate -- ^ Joining two attribut sets (//) - | NPlus -- ^ Addition (+) - | NMinus -- ^ Subtraction (-) - | NMult -- ^ Multiplication (*) - | NDiv -- ^ Division (/) - | NConcat -- ^ List concatenation (++) - deriving (Eq, Ord, Generic, Typeable, Data, Show) - --- | Get the name out of the parameter (there might be none). -paramName :: Params r -> Maybe Text -paramName (Param n) = Just n -paramName (ParamSet _ n) = n - --- | Make an integer literal expression. -mkInt :: Integer -> NExpr -mkInt = Fix . NConstant . NInt - --- | Make a regular (double-quoted) string. -mkStr :: Text -> NExpr -mkStr = Fix . NStr . DoubleQuoted . \case - "" -> [] - x -> [Plain x] - -mkIndentedStr :: Text -> NExpr -mkIndentedStr = Fix . NStr . Indented . \case - "" -> [] - x -> [Plain x] - --- | Make a literal URI expression. -mkUri :: Text -> NExpr -mkUri = Fix . NConstant . NUri - --- | Make a path. Use 'True' if the path should be read from the --- environment, else 'False'. -mkPath :: Bool -> FilePath -> NExpr -mkPath False = Fix . NLiteralPath -mkPath True = Fix . NEnvPath - --- | Make a path expression which pulls from the NIX_PATH env variable. -mkEnvPath :: FilePath -> NExpr -mkEnvPath = mkPath True - --- | Make a path expression which references a relative path. -mkRelPath :: FilePath -> NExpr -mkRelPath = mkPath False - --- | Make a variable (symbol) -mkSym :: Text -> NExpr -mkSym = Fix . NSym - -mkSelector :: Text -> NAttrPath NExpr -mkSelector = (:[]) . StaticKey - -mkBool :: Bool -> NExpr -mkBool = Fix . NConstant . NBool - -mkNull :: NExpr -mkNull = Fix (NConstant NNull) - -mkOper :: NUnaryOp -> NExpr -> NExpr -mkOper op = Fix . NUnary op - -mkOper2 :: NBinaryOp -> NExpr -> NExpr -> NExpr -mkOper2 op a = Fix . NBinary op a - -mkParamset :: [(Text, Maybe NExpr)] -> Params NExpr -mkParamset params = ParamSet (mkFixedParamSet params) Nothing - -mkFixedParamSet :: [(Text, Maybe NExpr)] -> ParamSet NExpr -mkFixedParamSet ps = FixedParamSet (Map.fromList ps) - -mkVariadicParamSet :: [(Text, Maybe NExpr)] -> ParamSet NExpr -mkVariadicParamSet ps = VariadicParamSet (Map.fromList ps) - -mkApp :: NExpr -> NExpr -> NExpr -mkApp e = Fix . NApp e - -mkRecSet :: [Binding NExpr] -> NExpr -mkRecSet = Fix . NRecSet - -mkNonRecSet :: [Binding NExpr] -> NExpr -mkNonRecSet = Fix . NSet - -mkLet :: [Binding NExpr] -> NExpr -> NExpr -mkLet bs = Fix . NLet bs - -mkList :: [NExpr] -> NExpr -mkList = Fix . NList - -mkWith :: NExpr -> NExpr -> NExpr -mkWith e = Fix . NWith e - -mkAssert :: NExpr -> NExpr -> NExpr -mkAssert e = Fix . NWith e - -mkIf :: NExpr -> NExpr -> NExpr -> NExpr -mkIf e1 e2 = Fix . NIf e1 e2 - -mkFunction :: Params NExpr -> NExpr -> NExpr -mkFunction params = Fix . NAbs params - --- | Shorthand for producing a binding of a name to an expression. -bindTo :: Text -> NExpr -> Binding NExpr -bindTo name val = NamedVar (mkSelector name) val - --- | Append a list of bindings to a set or let expression. --- For example, adding `[a = 1, b = 2]` to `let c = 3; in 4` produces --- `let a = 1; b = 2; c = 3; in 4`. -appendBindings :: [Binding NExpr] -> NExpr -> NExpr -appendBindings newBindings (Fix e) = case e of - NLet bindings e' -> Fix $ NLet (bindings <> newBindings) e' - NSet bindings -> Fix $ NSet (bindings <> newBindings) - NRecSet bindings -> Fix $ NRecSet (bindings <> newBindings) - _ -> error "Can only append bindings to a set or a let" - --- | Applies a transformation to the body of a nix function. -modifyFunctionBody :: (NExpr -> NExpr) -> NExpr -> NExpr -modifyFunctionBody f (Fix e) = case e of - NAbs params body -> Fix $ NAbs params (f body) - _ -> error "Not a function" +-- | Wraps the expression submodules. +module Nix.Expr ( + module Nix.Expr.Types, + module Nix.Expr.Shorthands + ) where +import Nix.Expr.Types +import Nix.Expr.Shorthands diff --git a/Nix/Expr/Shorthands.hs b/Nix/Expr/Shorthands.hs new file mode 100644 index 0000000..74da7ad --- /dev/null +++ b/Nix/Expr/Shorthands.hs @@ -0,0 +1,180 @@ +-- | A bunch of shorthands for making nix expressions. +module Nix.Expr.Shorthands where + +import Prelude +import Data.Monoid +import Data.Text (Text) +import Data.Fix +import qualified Data.Map as Map +import Nix.Atoms +import Nix.Expr.Types + +-- | Make an integer literal expression. +mkInt :: Integer -> NExpr +mkInt = Fix . NConstant . NInt + +-- | Make a regular (double-quoted) string. +mkStr :: Text -> NExpr +mkStr = Fix . NStr . DoubleQuoted . \case + "" -> [] + x -> [Plain x] + +mkIndentedStr :: Text -> NExpr +mkIndentedStr = Fix . NStr . Indented . \case + "" -> [] + x -> [Plain x] + +-- | Make a literal URI expression. +mkUri :: Text -> NExpr +mkUri = Fix . NConstant . NUri + +-- | Make a path. Use 'True' if the path should be read from the +-- environment, else 'False'. +mkPath :: Bool -> FilePath -> NExpr +mkPath False = Fix . NLiteralPath +mkPath True = Fix . NEnvPath + +-- | Make a path expression which pulls from the NIX_PATH env variable. +mkEnvPath :: FilePath -> NExpr +mkEnvPath = mkPath True + +-- | Make a path expression which references a relative path. +mkRelPath :: FilePath -> NExpr +mkRelPath = mkPath False + +-- | Make a variable (symbol) +mkSym :: Text -> NExpr +mkSym = Fix . NSym + +mkSelector :: Text -> NAttrPath NExpr +mkSelector = (:[]) . StaticKey + +mkBool :: Bool -> NExpr +mkBool = Fix . NConstant . NBool + +mkNull :: NExpr +mkNull = Fix (NConstant NNull) + +mkOper :: NUnaryOp -> NExpr -> NExpr +mkOper op = Fix . NUnary op + +mkOper2 :: NBinaryOp -> NExpr -> NExpr -> NExpr +mkOper2 op a = Fix . NBinary op a + +mkParamset :: [(Text, Maybe NExpr)] -> Params NExpr +mkParamset params = ParamSet (mkFixedParamSet params) Nothing + +mkFixedParamSet :: [(Text, Maybe NExpr)] -> ParamSet NExpr +mkFixedParamSet ps = FixedParamSet (Map.fromList ps) + +mkVariadicParamSet :: [(Text, Maybe NExpr)] -> ParamSet NExpr +mkVariadicParamSet ps = VariadicParamSet (Map.fromList ps) + +mkApp :: NExpr -> NExpr -> NExpr +mkApp e = Fix . NApp e + +mkRecSet :: [Binding NExpr] -> NExpr +mkRecSet = Fix . NRecSet + +mkNonRecSet :: [Binding NExpr] -> NExpr +mkNonRecSet = Fix . NSet + +mkLets :: [Binding NExpr] -> NExpr -> NExpr +mkLets bindings = Fix . NLet bindings + +mkList :: [NExpr] -> NExpr +mkList = Fix . NList + +mkWith :: NExpr -> NExpr -> NExpr +mkWith e = Fix . NWith e + +mkAssert :: NExpr -> NExpr -> NExpr +mkAssert e = Fix . NWith e + +mkIf :: NExpr -> NExpr -> NExpr -> NExpr +mkIf e1 e2 = Fix . NIf e1 e2 + +mkFunction :: Params NExpr -> NExpr -> NExpr +mkFunction params = Fix . NAbs params + +mkDot :: NExpr -> Text -> NExpr +mkDot e key = Fix $ NSelect e [StaticKey key] Nothing + +-- | Shorthand for producing a binding of a name to an expression. +bindTo :: Text -> NExpr -> Binding NExpr +bindTo name val = NamedVar (mkSelector name) val + +-- | Append a list of bindings to a set or let expression. +-- For example, adding `[a = 1, b = 2]` to `let c = 3; in 4` produces +-- `let a = 1; b = 2; c = 3; in 4`. +appendBindings :: [Binding NExpr] -> NExpr -> NExpr +appendBindings newBindings (Fix e) = case e of + NLet bindings e' -> Fix $ NLet (bindings <> newBindings) e' + NSet bindings -> Fix $ NSet (bindings <> newBindings) + NRecSet bindings -> Fix $ NRecSet (bindings <> newBindings) + _ -> error "Can only append bindings to a set or a let" + +-- | Applies a transformation to the body of a nix function. +modifyFunctionBody :: (NExpr -> NExpr) -> NExpr -> NExpr +modifyFunctionBody f (Fix e) = case e of + NAbs params body -> Fix $ NAbs params (f body) + _ -> error "Not a function" + +-- | A let statement with multiple assignments. +letsE :: [(Text, NExpr)] -> NExpr -> NExpr +letsE pairs = Fix . NLet (map (uncurry bindTo) pairs) + +-- | Wrapper for a single-variable @let@. +letE :: Text -> NExpr -> NExpr -> NExpr +letE varName varExpr = letsE [(varName, varExpr)] + +-- | Make an attribute set (non-recursive). +attrsE :: [(Text, NExpr)] -> NExpr +attrsE pairs = Fix $ NSet (map (uncurry bindTo) pairs) + +-- | Make an attribute set (recursive). +recAttrsE :: [(Text, NExpr)] -> NExpr +recAttrsE pairs = Fix $ NRecSet (map (uncurry bindTo) pairs) + +-- | Logical negation. +mkNot :: NExpr -> NExpr +mkNot = Fix . NUnary NNot + +-- | Dot-reference into an attribute set. +(!.) :: NExpr -> Text -> NExpr +(!.) = mkDot +infixl 8 !. + +mkBinop :: NBinaryOp -> NExpr -> NExpr -> NExpr +mkBinop op e1 e2 = Fix (NBinary op e1 e2) + +-- | Various nix binary operators +($==), ($!=), ($<), ($<=), ($>), ($>=), ($&&), ($||), ($->), + ($//), ($+), ($-), ($*), ($/), ($++) + :: NExpr -> NExpr -> NExpr +e1 $== e2 = mkBinop NEq e1 e2 +e1 $!= e2 = mkBinop NNEq e1 e2 +e1 $< e2 = mkBinop NLt e1 e2 +e1 $<= e2 = mkBinop NLte e1 e2 +e1 $> e2 = mkBinop NGt e1 e2 +e1 $>= e2 = mkBinop NGte e1 e2 +e1 $&& e2 = mkBinop NAnd e1 e2 +e1 $|| e2 = mkBinop NOr e1 e2 +e1 $-> e2 = mkBinop NImpl e1 e2 +e1 $// e2 = mkBinop NUpdate e1 e2 +e1 $+ e2 = mkBinop NPlus e1 e2 +e1 $- e2 = mkBinop NMinus e1 e2 +e1 $* e2 = mkBinop NMult e1 e2 +e1 $/ e2 = mkBinop NDiv e1 e2 +e1 $++ e2 = mkBinop NConcat e1 e2 + +-- | Function application expression. +(@@) :: NExpr -> NExpr -> NExpr +(@@) = mkApp +infixl 1 @@ + +-- | Lambda shorthand. +(==>) :: Params NExpr -> NExpr -> NExpr +(==>) = mkFunction + +infixr 1 ==> diff --git a/Nix/Expr/Types.hs b/Nix/Expr/Types.hs new file mode 100644 index 0000000..032e6ba --- /dev/null +++ b/Nix/Expr/Types.hs @@ -0,0 +1,193 @@ +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE DeriveFoldable #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE LambdaCase #-} +-- | The nix expression type and supporting types. +module Nix.Expr.Types where + +import Control.Monad hiding (forM_, mapM, sequence) +import Data.Data +import Data.Fix +import Data.Foldable +import Data.Map (Map) +import Data.Text (Text, pack) +import Data.Traversable +import GHC.Exts +import GHC.Generics +import Nix.Atoms +import Prelude hiding (readFile, concat, concatMap, elem, mapM, + sequence, minimum, foldr) + +-- | The main nix expression type. This is polymorphic so that it can be made +-- a functor, which allows us to traverse expressions and map functions over +-- them. The actual 'NExpr' type is a fixed point of this functor, defined +-- below. +data NExprF r + = NConstant !NAtom + -- ^ Constants: ints, bools, URIs, and null. + | NStr !(NString r) + -- ^ A string, with interpolated expressions. + | NSym !Text + -- ^ A variable. For example, in the expression @f a@, @f@ is represented + -- as @NSym "f"@ and @a@ as @NSym "a"@. + | NList ![r] + -- ^ A list literal. + | NSet ![Binding r] + -- ^ An attribute set literal, not recursive. + | NRecSet ![Binding r] + -- ^ An attribute set literal, recursive. + | NLiteralPath !FilePath + -- ^ A path expression, which is evaluated to a store path. The path here + -- can be relative, in which case it's evaluated relative to the file in + -- which it appears. + | NEnvPath !FilePath + -- ^ A path which refers to something in the Nix search path (the NIX_PATH + -- environment variable. For example, @@. + | NUnary !NUnaryOp !r + -- ^ Application of a unary operator to an expression. + | NBinary !NBinaryOp !r !r + -- ^ Application of a binary operator to two expressions. + | NSelect !r !(NAttrPath r) !(Maybe r) + -- ^ Dot-reference into an attribute set, optionally providing an + -- alternative if the key doesn't exist. + | NHasAttr !r !(NAttrPath r) + -- ^ Ask if a set contains a given attribute path. + | NAbs !(Params r) !r + -- ^ A function literal (lambda abstraction). + | NApp !r !r + -- ^ Apply a function to an argument. + | NLet ![Binding r] !r + -- ^ Evaluate the second argument after introducing the bindings. + | NIf !r !r !r + -- ^ If-then-else statement. + | NWith !r !r + -- ^ Evaluate an attribute set, bring its bindings into scope, and + -- evaluate the second argument. + | NAssert !r !r + -- ^ Assert that the first returns true before evaluating the second. + deriving (Ord, Eq, Generic, Typeable, Data, Functor, Show) + +-- | The monomorphic expression type is a fixed point of the polymorphic one. +type NExpr = Fix NExprF + +-- | A single line of the bindings section of a let expression or of a set. +data Binding r + = NamedVar !(NAttrPath r) !r + -- ^ An explicit naming, such as @x = y@ or @x.y = z@. + | Inherit !(Maybe r) ![NKeyName r] + -- ^ Using a name already in scope, such as @inherit x;@ which is shorthand + -- for @x = x;@ or @inherit (x) y;@ which means @y = x.y;@. + deriving (Typeable, Data, Ord, Eq, Functor, Show) + +-- | @Params@ represents all the ways the formal parameters to a +-- function can be represented. +data Params r + = Param !Text + -- ^ For functions with a single named argument, such as @x: x + 1@. + | ParamSet !(ParamSet r) !(Maybe Text) + -- ^ Explicit parameters (argument must be a set). Might specify a name + -- to bind to the set in the function body. + deriving (Ord, Eq, Generic, Typeable, Data, Functor, Show, + Foldable, Traversable) + +instance IsString (Params r) where + fromString = Param . fromString + +-- | An explicit parameter set; provides a shorthand for unpacking arguments. +data ParamSet r + = FixedParamSet !(Map Text (Maybe r)) + -- ^ A fixed set, where no arguments beyond what is specified in the map + -- may be given. The map might contain defaults for arguments not passed. + | VariadicParamSet !(Map Text (Maybe r)) + -- ^ Same as the 'FixedParamSet', but extra arguments are allowed. + deriving (Ord, Eq, Generic, Typeable, Data, Functor, Show, + Foldable, Traversable) + +-- | 'Antiquoted' represents an expression that is either +-- antiquoted (surrounded by ${...}) or plain (not antiquoted). +data Antiquoted v r = Plain !v | Antiquoted !r + deriving (Ord, Eq, Generic, Typeable, Data, Functor, Show) + +-- | An 'NString' is a list of things that are either a plain string +-- or an antiquoted expression. After the antiquotes have been evaluated, +-- the final string is constructed by concating all the parts. +data NString r + = DoubleQuoted ![Antiquoted Text r] + -- ^ Strings wrapped with double-quotes (") are not allowed to contain + -- literal newline characters. + | Indented ![Antiquoted Text r] + -- ^ Strings wrapped with two single quotes ('') can contain newlines, + -- and their indentation will be stripped. + deriving (Eq, Ord, Generic, Typeable, Data, Functor, Show) + +-- | For the the 'IsString' instance, we use a plain doublequoted string. +instance IsString (NString r) where + fromString "" = DoubleQuoted [] + fromString string = DoubleQuoted [Plain $ pack string] + +-- | A 'KeyName' is something that can appear at the right side of an +-- equals sign. For example, @a@ is a 'KeyName' in @{ a = 3; }@, @let a = 3; +-- in ...@, @{}.a@ or @{} ? a@. +-- +-- Nix supports both static keynames (just an identifier) and dynamic +-- identifiers. Dynamic identifiers can be either a string (e.g.: +-- @{ "a" = 3; }@) or an antiquotation (e.g.: @let a = "example"; +-- in { ${a} = 3; }.example@). +-- +-- Note: There are some places where a dynamic keyname is not allowed. +-- In particular, those include: +-- +-- * The RHS of a @binding@ inside @let@: @let ${"a"} = 3; in ...@ +-- produces a syntax error. +-- * The attribute names of an 'inherit': @inherit ${"a"};@ is forbidden. +-- +-- Note: In Nix, a simple string without antiquotes such as @"foo"@ is +-- allowed even if the context requires a static keyname, but the +-- parser still considers it a 'DynamicKey' for simplicity. +data NKeyName r + = DynamicKey !(Antiquoted (NString r) r) + | StaticKey !Text + deriving (Eq, Ord, Generic, Typeable, Data, Show) + +-- | Deriving this instance automatically is not possible because @r@ +-- occurs not only as last argument in @Antiquoted (NString r) r@ +instance Functor NKeyName where + fmap f (DynamicKey (Plain str)) = DynamicKey . Plain $ fmap f str + fmap f (DynamicKey (Antiquoted e)) = DynamicKey . Antiquoted $ f e + fmap _ (StaticKey key) = StaticKey key + +-- | A selector (for example in a @let@ or an attribute set) is made up +-- of strung-together key names. +type NAttrPath r = [NKeyName r] + +-- | There are two unary operations: logical not and integer negation. +data NUnaryOp = NNeg | NNot + deriving (Eq, Ord, Generic, Typeable, Data, Show) + +-- | Binary operators expressible in the nix language. +data NBinaryOp + = NEq -- ^ Equality (==) + | NNEq -- ^ Inequality (!=) + | NLt -- ^ Less than (<) + | NLte -- ^ Less than or equal (<=) + | NGt -- ^ Greater than (>) + | NGte -- ^ Greater than or equal (>=) + | NAnd -- ^ Logical and (&&) + | NOr -- ^ Logical or (||) + | NImpl -- ^ Logical implication (->) + | NUpdate -- ^ Joining two attribut sets (//) + | NPlus -- ^ Addition (+) + | NMinus -- ^ Subtraction (-) + | NMult -- ^ Multiplication (*) + | NDiv -- ^ Division (/) + | NConcat -- ^ List concatenation (++) + deriving (Eq, Ord, Generic, Typeable, Data, Show) + +-- | Get the name out of the parameter (there might be none). +paramName :: Params r -> Maybe Text +paramName (Param n) = Just n +paramName (ParamSet _ n) = n diff --git a/Nix/Pretty.hs b/Nix/Pretty.hs index a88d7ac..a7a9a5f 100644 --- a/Nix/Pretty.hs +++ b/Nix/Pretty.hs @@ -7,6 +7,7 @@ import Data.Map (toList) import Data.Maybe (isJust) import Data.Text (Text, pack, unpack, replace, strip) import Data.List (isPrefixOf) +import Nix.Atoms import Nix.Expr import Nix.Parser.Library (reservedNames) import Nix.Parser.Operators diff --git a/hnix.cabal b/hnix.cabal index d7ace8b..3ab9919 100644 --- a/hnix.cabal +++ b/hnix.cabal @@ -22,6 +22,7 @@ Flag Parsec Library Default-language: Haskell2010 Exposed-modules: + Nix.Atoms Nix.Eval Nix.Parser Nix.Expr @@ -30,6 +31,8 @@ Library Nix.StringOperations Other-modules: Nix.Parser.Library + Nix.Expr.Types + Nix.Expr.Shorthands Default-extensions: DataKinds DeriveDataTypeable diff --git a/tests/ParserTests.hs b/tests/ParserTests.hs index 6a9a289..2f80b87 100644 --- a/tests/ParserTests.hs +++ b/tests/ParserTests.hs @@ -10,6 +10,7 @@ import Test.Tasty.TH import qualified Data.Map as Map +import Nix.Atoms import Nix.Expr import Nix.Parser import Nix.StringOperations