packages feed

purenix-1.0: src/PureNix/Expr.hs

{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}

-- | Nix expression types and auxiliary functions.
-- Since 'Expr' is actually a fixpoint over the 'ExprF' base functor, this
-- module also exposes auxiliary functions that serve as constructors.
module PureNix.Expr where

import Data.List.NonEmpty (NonEmpty (..))
import PureNix.Identifiers
import PureNix.Prelude

-- | The fixpoint over 'ExprF', see haddocks there for more information.
newtype Expr = Expr {unExpr :: ExprF Expr}
  -- Explicitly using the @newtype@ strategy here makes the 'Show' instance
  -- much nicer, since it makes it so you don't get all the @Expr@
  -- constructors.
  deriving newtype (Show)

-- | Base functor for a Nix expression.
-- We don't aim to be able to represent every valid Nix expression, just the
-- ones that are relevant for PureNix.
--
-- 'ExprF' is the base functor for the 'Expr' fixpoint.
-- This allows us to easily annotate and consume it during pretty-printing.
--
-- Note that 'String', unlike 'Key' and 'Var', is a raw representation of the intended string, completely unquoted and unescaped.
-- That means that it might consist of, for example, a single '"'.
-- It is the job of the printer to figure out how to correctly escape those.
data ExprF f
  = Var Var
  | Lam Var f
  | App f f
  | Attrs [Var] [(f, [Key])] [(Key, f)]
  | Cond f f f
  | List [f]
  | Bin Op f f
  | Not f
  | Sel f Key
  | Let (NonEmpty (Var, f)) f
  | Int Integer
  | Double Double
  | String Text
  | Path Text
  deriving stock (Functor, Foldable, Traversable, Show)

data Op = Update | Equals | And
  deriving (Eq, Show)

foldExpr :: (ExprF r -> r) -> Expr -> r
foldExpr f = go where go = f . fmap go . unExpr

var :: Var -> Expr
var = Expr . Var

lam :: Var -> Expr -> Expr
lam arg body = Expr $ Lam arg body

app :: Expr -> Expr -> Expr
app f x = Expr $ App f x

cond :: Expr -> Expr -> Expr -> Expr
cond c true false = Expr $ Cond c true false

attrs ::
  [Var] ->
  [(Expr, [Key])] ->
  [(Key, Expr)] ->
  Expr
attrs inherits inheritFroms binds = Expr $ Attrs inherits inheritFroms binds

sel :: Expr -> Key -> Expr
sel e s = Expr $ Sel e s

let' :: [(Var, Expr)] -> Expr -> Expr
let' [] body = body
let' (h : t) body = Expr $ Let (h :| t) body

int :: Integer -> Expr
int = Expr . Int

double :: Double -> Expr
double = Expr . Double

string :: Text -> Expr
string = Expr . String

list :: [Expr] -> Expr
list = Expr . List

bin :: Op -> Expr -> Expr -> Expr
bin op a b = Expr $ Bin op a b

path :: Text -> Expr
path = Expr . Path

constructorFieldNames :: [Var]
constructorFieldNames = numberedVars "__field"

not' :: Expr -> Expr
not' = Expr . Not

-- | Convenience constructor for builtins.
-- Takes a Key, and gives you @builtins.key@
builtin :: Key -> Expr
builtin = sel (var "builtins")

--   Just
-- becomes
--   (a: { __tag = "Just"; __field0 = a; })
constructor :: Text -> [Var] -> Expr
constructor conName fields =
  foldr
    lam
    ( attrs
        []
        []
        (("__tag", string conName) : zipWith (\arg (UnsafeVar name) -> (UnsafeKey name, var arg)) fields constructorFieldNames)
    )
    fields