packages feed

gachi-schema-0.1.0.0: src/Gachi/Schema/Dhall.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Module: Gachi.Schema.Dhall
-- Description: Dhall schema definitions, encoders, decoders, and templates
--
-- Provides turnkey encoders, decoders, and template definitions for evaluating
-- declarative Dhall project specifications into strongly typed 'Project' and 'ProjectRec'
-- structures.
--
-- All decoding operations enforce the totality and safety guarantees established in
-- Note [Dhall Parameterized Skeleton Templating] [ref:note-dhall-skeleton-templating].
--
-- = Architectural Overview & Rationale
--
-- Dhall is a programmable, strongly normalizing configuration language designed to
-- prevent unexpected failures at execution time. In Gachi, Dhall serves as the canonical
-- medium for defining project templates and configuration manifests because of its unique
-- structural characteristics:
--
-- 1. __Totality and Termination Guarantees:__
--    Dhall expressions are mathematically guaranteed to terminate. Unlike general-purpose
--    languages or Turing-complete template engines (e.g., Nix, Python scripts), a Dhall
--    template can never enter an infinite loop or deadlock the scaffolding engine.
--
-- 2. __Hermetic and Side-Effect Free:__
--    During evaluation, Dhall does not have access to ambient environment variables, arbitrary
--    filesystems, network sockets, or external process execution (unless explicit imports
--    are resolved by the host). This ensures that template instantiation is fully hermetic,
--    deterministic, and reproducible across disparate developer environments.
--
-- 3. __Strong Static Typing:__
--    Dhall configurations are statically typechecked against the expected schema before
--    any directory creation or file writing begins. Mismatched field names, missing attributes,
--    or incorrect types produce actionable, human-readable compiler diagnostics rather than
--    silent corruptions or half-initialized repositories.
--
-- == Evaluation & Codec Integration
--
-- This module leverages "Polytropos.Dhall" to bridge Dhall AST expressions ('Dhall.Core.Expr')
-- directly into high-performance Haskell structures:
--
-- * Nominal decoding ('decodeProjectText') utilizes 'Polytropos.Dhall.GenericDhall' to
--   populate 'Project' structures in a single pass.
-- * Open row decoding ('decodeProjectRecText') extracts raw fields directly into Polytropos
--   flat 'Polytropos.Core.RowRep' memory structures with zero intermediate binary tree allocations.
--
-- = When to Use This Module
--
-- * Reading and evaluating @project.dhall@ files from disk or memory.
-- * Formatting 'Project' or 'ProjectRec' records back into canonical Dhall expressions.
-- * Obtaining the canonical Dhall schema definition ('projectSchemaDhall') for tooling or LSP integration.
--
-- = Worked Example
--
-- >>> import Gachi.Schema.Dhall
-- >>> proj <- decodeProjectText exampleProjectDhall
-- >>> projectName proj
-- "gongju"
module Gachi.Schema.Dhall
  ( -- * High-Level Dhall Evaluation
    decodeProjectText
  , decodeProjectFile
  , decodeProjectEither
  , decodeProjectRecText
  , decodeProjectRecEither

    -- * Rendering & Encoding
  , renderProjectDhall
  , renderProjectRecDhall

    -- * Embedded Dhall Schemas & Turnkey Templates
  , projectSchemaDhall
  , exampleProjectDhall
  ) where

import Control.Exception (SomeException, try)
import Data.Bifunctor (first)
import Data.Text (Text)
import qualified Dhall
import qualified Dhall.Core as Core
import Gachi.Schema.Project (Project, ProjectRec)

-- | Decode a 'Project' specification from a raw Dhall text expression.
--
-- Evaluates the Dhall abstract syntax tree, verifies all expected schema fields
-- against the nominal 'Project' definition, and constructs the resulting record.
--
-- Throws a 'Dhall.Core.DhallErrors' exception if the expression fails to parse,
-- typecheck, or match the expected schema.
--
-- Time complexity: Strictly linear in the size of the Dhall input expression.
decodeProjectText :: Text -> IO Project
decodeProjectText = Dhall.input Dhall.auto

-- | Decode a 'Project' specification from a Dhall file located on disk.
--
-- Automatically resolves relative local file imports (e.g., @./Schema.dhall@)
-- relative to the target file's parent directory.
--
-- Throws an 'IOException' if the file does not exist, or a 'Dhall.Core.DhallErrors'
-- exception if evaluation fails.
decodeProjectFile :: FilePath -> IO Project
decodeProjectFile = Dhall.inputFile Dhall.auto

-- | Safely decode a 'Project' from Dhall text without throwing runtime exceptions.
--
-- Catches any 'SomeException' raised during parsing, typechecking, or normalization
-- and wraps it in a 'Left' containing the formatted diagnostic error message.
--
-- Useful for CLI validation flags, interactive wizards, and language server linting passes.
decodeProjectEither :: Text -> IO (Either String Project)
decodeProjectEither txt = first show <$> (try (decodeProjectText txt) :: IO (Either SomeException Project))

-- | Decode an open heterogeneous 'ProjectRec' row record from a Dhall text expression.
--
-- Directly unrolls Dhall record literals into Polytropos flat 'Data.Primitive.SmallArray'
-- memory slots in row order via 'Polytropos.Dhall', achieving zero intermediate binary tree
-- constructor allocations.
--
-- Throws a 'Dhall.Core.DhallErrors' exception if the input expression violates the schema.
decodeProjectRecText :: Text -> IO ProjectRec
decodeProjectRecText = Dhall.input Dhall.auto

-- | Safely decode an open heterogeneous 'ProjectRec' from Dhall text without throwing exceptions.
--
-- Wraps any caught evaluation failure in 'Left' with the descriptive error message,
-- or returns 'Right' with the parsed 'ProjectRec'.
decodeProjectRecEither :: Text -> IO (Either String ProjectRec)
decodeProjectRecEither txt = first show <$> (try (decodeProjectRecText txt) :: IO (Either SomeException ProjectRec))

-- | Render a nominal 'Project' into canonical, pretty-printed Dhall source text.
--
-- Emits a formatted Dhall record literal matching 'projectSchemaDhall'.
--
-- Time complexity: Linear in the size of the project record structure.
renderProjectDhall :: Project -> Text
renderProjectDhall = Core.pretty . Dhall.embed Dhall.inject

-- | Render an open heterogeneous 'ProjectRec' into canonical, pretty-printed Dhall source text.
--
-- Directly inspects the contiguous 'Polytropos.Core.RowRep' memory buffer and emits
-- the corresponding Dhall record literal without building intermediate nominal ASTs.
renderProjectRecDhall :: ProjectRec -> Text
renderProjectRecDhall = Core.pretty . Dhall.embed Dhall.inject

-- | The canonical Dhall schema defining 'Author', 'Package', 'Remote', and 'Project' types.
projectSchemaDhall :: Text
projectSchemaDhall =
  "let Author : Type =\n\
  \  { name : Text\n\
  \  , email : Text\n\
  \  }\n\
  \\n\
  \let Package : Type =\n\
  \  { name : Text\n\
  \  , synopsis : Text\n\
  \  , exposes : List Text\n\
  \  }\n\
  \\n\
  \let Remote : Type =\n\
  \  { name : Text\n\
  \  , url : Text\n\
  \  }\n\
  \\n\
  \let Project : Type =\n\
  \  { name : Text\n\
  \  , description : Text\n\
  \  , license : Text\n\
  \  , author : Author\n\
  \  , ghcVersion : Text\n\
  \  , cabalVersion : Text\n\
  \  , packages : List Package\n\
  \  , remotes : List Remote\n\
  \  }\n\
  \\n\
  \in  { Author = Author\n\
  \    , Package = Package\n\
  \    , Remote = Remote\n\
  \    , Project = Project\n\
  \    }\n"

-- | Turnkey sample Dhall specification for the 'gongju' crucible project.
exampleProjectDhall :: Text
exampleProjectDhall =
  "let Schema =\n\
  \  { Author = { name : Text, email : Text }\n\
  \  , Package = { name : Text, synopsis : Text, exposes : List Text }\n\
  \  , Remote = { name : Text, url : Text }\n\
  \  , Project =\n\
  \      { name : Text\n\
  \      , description : Text\n\
  \      , license : Text\n\
  \      , author : { name : Text, email : Text }\n\
  \      , ghcVersion : Text\n\
  \      , cabalVersion : Text\n\
  \      , packages : List { name : Text, synopsis : Text, exposes : List Text }\n\
  \      , remotes : List { name : Text, url : Text }\n\
  \      }\n\
  \  }\n\
  \\n\
  \let project : Schema.Project =\n\
  \  { name = \"gongju\"\n\
  \  , description = \"Type-level utility functions and Peano arithmetic crucible\"\n\
  \  , license = \"EUPL-1.2\"\n\
  \  , author =\n\
  \      { name = \"Nadia Yvette Chambers\"\n\
  \      , email = \"nadia.yvette.chambers@ik.me\"\n\
  \      }\n\
  \  , ghcVersion = \"9.14.1\"\n\
  \  , cabalVersion = \"3.18\"\n\
  \  , packages =\n\
  \      [ { name = \"gongju\"\n\
  \        , synopsis = \"Pure Peano type-level utilities and HList operations\"\n\
  \        , exposes =\n\
  \            [ \"Gongju.Peano\"\n\
  \            , \"Gongju.Composition\"\n\
  \            , \"Gongju.Positional\"\n\
  \            , \"Gongju.Permute\"\n\
  \            , \"Gongju.HList\"\n\
  \            ]\n\
  \        }\n\
  \      ]\n\
  \  , remotes =\n\
  \      [ { name = \"github\"\n\
  \        , url = \"git@github.com:NadiaYvette/gongju.git\"\n\
  \        }\n\
  \      , { name = \"gitlab\"\n\
  \        , url = \"git@gitlab.com:NadiaYvette/gongju.git\"\n\
  \        }\n\
  \      ]\n\
  \  }\n\
  \\n\
  \in  project\n"