nova-nix-0.10.0.0: src/Nix/Expr/Types.hs
-- | Core AST types for the Nix expression language.
--
-- Every Nix source file parses into an 'Expr'. The AST is a direct
-- representation of the language grammar - no desugaring at parse time.
-- The evaluator ("Nix.Eval") reduces expressions to values.
module Nix.Expr.Types
( -- * Expressions
Expr (..),
-- * Atoms
NixAtom (..),
-- * Attribute paths
AttrPath,
AttrKey (..),
-- * Source positions
SourceOrigin (..),
sourceName,
SrcPos (..),
-- * Bindings
Binding (..),
InheritedName (..),
-- * Formals (function parameters)
Formals (..),
Formal (..),
-- * Closure capture info
CaptureInfo (..),
-- * Operators
UnaryOp (..),
BinaryOp (..),
-- * String parts (interpolation)
StringPart (..),
)
where
import Data.ByteString (ByteString)
import Data.Int (Int64)
import Data.Text (Text)
-- | Atomic (literal) values.
data NixAtom
= NixInt !Int64
| NixFloat !Double
| NixBool !Bool
| NixNull
| NixUri !Text
| NixPath !Text
deriving (Eq, Show)
-- | A part of an interpolated string (@"hello ${name}"@).
data StringPart
= -- | Literal bytes, the source's with escapes resolved, which are the
-- bytes the value holds whether or not they are UTF-8.
StrLit !ByteString
| -- | Resolved escape bytes inside an indented string (@'''@, @''$@,
-- @''${@, @''\x@). Kept apart from 'StrLit' because upstream's lexer
-- emits an escape without the hasIndentation mark: the indentation
-- scan skips it (it only ends a line's leading whitespace) while the
-- strip runs over it as over any literal, so an escaped newline
-- starts a line whose spaces are stripped but were never measured.
StrEsc !ByteString
| -- | Interpolated expression (@${expr}@).
StrInterp !Expr
deriving (Eq, Show)
-- | An attribute key: either a static identifier or a dynamic expression.
data AttrKey
= -- | Static key: @{ foo = val; }@
StaticKey !Text
| -- | Dynamic key: @{ ${expr} = val; }@
DynamicKey !Expr
deriving (Eq, Show)
-- | Attribute path: @a.b.c@ is @[StaticKey "a", StaticKey "b", StaticKey "c"]@.
type AttrPath = [AttrKey]
-- | Where parsed source came from, which decides what a position in it
-- reports: upstream's positions make the same split (@Pos::Origin@,
-- position.hh at 2.24.9), and only one in a file names a file.
data SourceOrigin
= -- | A file, by the path it was read from.
SourceFile !Text
| -- | Text with no file behind it (an @--expr@ argument), by the name
-- its parse errors give it.
SourceText !Text
deriving (Eq, Show)
-- | The name a parse error in the source gives it.
sourceName :: SourceOrigin -> Text
sourceName (SourceFile path) = path
sourceName (SourceText name) = name
-- | A point in parsed source: where the source came from, and the 1-based
-- line and column (in characters) the lexer gives the token there.
data SrcPos
= SrcPos
-- | Where the source came from.
!SourceOrigin
-- | The line.
{-# UNPACK #-} !Int
-- | The column.
{-# UNPACK #-} !Int
deriving (Eq, Show)
-- | A binding in an attribute set or let expression, at the position of
-- the attribute path or name that defines it.
data Binding
= -- | @key = expr;@. One key, as upstream's @ExprAttrs@ holds one name
-- per definition (nixexpr.hh at 2.24.9): the parser hoists a dotted
-- @a.b.c = v;@ into @a = { b = { c = v; }; };@ the way upstream's
-- @addAttr@ does (parser-state.hh), so no binding carries a path.
-- Every level of the hoisted path is at the path's first key, as
-- upstream gives each the position of the whole path.
NamedBinding !SrcPos !AttrKey !Expr
| -- | One name of @inherit x y;@: the attribute and the variable it
-- copies, which is bound and evaluated in the scope around the binding
-- set rather than in the set itself, so @rec { inherit x; }@ does not
-- name itself. Upstream's parser builds the same pair: an attribute of
-- kind Inherited holding an ExprVar.
Inherit !SrcPos !Text !Expr
| -- | @inherit (from) x y;@
InheritFrom !Expr ![InheritedName]
deriving (Eq, Show)
-- | One name of an @inherit (from) ...@ clause, at the position it is
-- written.
data InheritedName = InheritedName !SrcPos !Text
deriving (Eq, Show)
-- | A single formal parameter with optional default.
data Formal = Formal
{ fName :: !Text,
fDefault :: !(Maybe Expr)
}
deriving (Eq, Show)
-- | Function parameter pattern.
data Formals
= -- | Single identifier: @x: body@
FormalName !Text
| -- | Attribute set pattern with optional ellipsis: @{ a, b }: body@
FormalSet ![Formal] !Bool
| -- | Named set pattern: @args\@{ a, b }: body@
FormalNamedSet !Text ![Formal] !Bool
deriving (Eq, Show)
-- | Closure capture information for lambda trimming.
--
-- After 'Nix.Expr.ClosureTrim.trimClosures', lambdas that only reference
-- a subset of their outer scope carry a 'Captures' list. At eval time
-- this drives extraction of exactly those thunks into a flat t'Nix.Eval.Types.Env',
-- breaking the retention chain to the full parent scope.
data CaptureInfo
= -- | No capture analysis performed (lambda untrimmed).
NoCaptureInfo
| -- | Sorted list of @(level, index)@ pairs to extract from the parent env.
Captures ![(Int, Int)]
| -- | Like 'Captures', but the trimmed env must preserve its with-scopes
-- ('Nix.Eval.Types.envWithScopesRaw'), with builtins appended, so that
-- 'EWithVar' references can still be resolved.
CapturesWithScopes ![(Int, Int)]
deriving (Eq, Show)
-- | Unary operators.
data UnaryOp
= OpNot
| OpNegate
deriving (Eq, Show)
-- | Binary operators.
data BinaryOp
= OpAdd
| OpSub
| OpMul
| OpDiv
| OpAnd
| OpOr
| OpImpl
| OpEq
| OpNeq
| OpLt
| OpLte
| OpGt
| OpGte
| OpConcat
| OpUpdate
deriving (Eq, Show)
-- | The Nix expression AST.
--
-- This covers the full Nix language as documented in the Nix manual.
-- Every constructor is strict in its children to prevent thunk buildup
-- during parsing.
data Expr
= -- | Literal value.
ELit !NixAtom
| -- | String with possible interpolations.
EStr ![StringPart]
| -- | Indented string (double single-quoted).
EIndStr ![StringPart]
| -- | Path literal with interpolations (@.\/${v}\/x@): the literal
-- head piece, text because a relative one is joined to the base
-- directory ('Nix.Expr.Resolve.resolveRelativePaths'), and the pieces
-- after it. Evaluates to a path: the pieces concatenate with
-- no separator, each interpolation coerced as a path segment (a path
-- contributes its text without a store copy; a string carrying
-- store-path context is refused), and the whole is canonicalized -
-- the same semantics as the @+@ chain @head + s1 + s2@, which
-- upstream desugars it to.
EPathStr !Text ![StringPart]
| -- | Variable reference.
EVar !Text
| -- | Attribute set: @{ bindings }@ or @rec { bindings }@.
--
-- The 'CaptureInfo' field is populated by 'Nix.Expr.ClosureTrim.trimClosures'
-- for recursive attr sets after variable resolution. Non-recursive sets
-- always carry 'NoCaptureInfo'.
EAttrs !Bool ![Binding] !CaptureInfo
| -- | List: @[ e1 e2 e3 ]@.
EList ![Expr]
| -- | Attribute selection: @expr.attrpath@ or @expr.attrpath or default@.
ESelect !Expr !AttrPath !(Maybe Expr)
| -- | Has attribute: @expr ? attrpath@.
EHasAttr !Expr !AttrPath
| -- | Function application: @f x@.
EApp !Expr !Expr
| -- | Lambda: @formals: body@.
--
-- The 'CaptureInfo' field is populated by 'Nix.Expr.ClosureTrim.trimClosures'
-- after variable resolution. 'NoCaptureInfo' means untrimmed (eval
-- captures the full parent env); 'Captures' lists exactly which outer
-- slots to extract into a flat env.
ELambda !Formals !Expr !CaptureInfo
| -- | Let binding: @let bindings in body@.
--
-- The 'CaptureInfo' field is populated by 'Nix.Expr.ClosureTrim.trimClosures'
-- after variable resolution. Positional let blocks that only reference
-- a subset of their parent scope carry a 'Captures' list; at eval time
-- this drives construction of a trimmed parent env.
ELet ![Binding] !Expr !CaptureInfo
| -- | If-then-else: @if cond then t else f@.
EIf !Expr !Expr !Expr
| -- | With expression: @with expr; body@.
EWith !Expr !Expr
| -- | Assert: @assert cond; body@.
EAssert !Expr !Expr
| -- | Unary operator application.
EUnary !UnaryOp !Expr
| -- | Binary operator application.
EBinary !BinaryOp !Expr !Expr
| -- | Search path lookup: @\<nixpkgs\>@ is @ESearchPath "nixpkgs"@.
ESearchPath !Text
| -- | De Bruijn-style resolved variable: @(level, index)@.
-- Produced by 'Nix.Expr.Resolve.resolveVars' for variables
-- bound by lambda formals. @level@ counts parent-chain hops;
-- @index@ is the positional slot within that env's slot array.
EResolvedVar !Int !Int
| -- | Variable resolved via with-scopes (not lexical).
-- Produced by 'Nix.Expr.Resolve.resolveVars' for names inside
-- a @with@ body that are not found in any lexical scope.
-- At runtime, looked up in the env's with-scopes
-- ('Nix.Eval.Types.envWithScopesRaw'), with builtins as the fallback.
EWithVar !Text
deriving (Eq, Show)