packages feed

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)