File size: 6,030 Bytes
a10b189 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-|
Module : Assertica.Backend.Emit
Description : Emit syntactically valid Haskell source code
Copyright : (c) 2026 Ahmad Ali Parr
License : BSL-1.0
Converts compiled intermediate representations (from CodeGen, TermCompiler, etc.)
into syntactically valid Haskell 2010 source code that can be processed by GHC.
RESPONSIBILITIES:
1. **Module Headers**: Generate correct module declaration with exports
2. **Imports**: Produce valid import statements with proper syntax
3. **Definitions**: Format term and type definitions with correct syntax
4. **Type Signatures**: Generate type declarations with proper indentation
5. **Comments**: Add source location hints and documentation
6. **Pretty-Printing**: Consistent, readable formatting
HASKELL SYNTAX REQUIREMENTS:
- Module header: "module Name (exports) where"
- Imports: "import [qualified] Module [(selective)]"
- Type signature: "name :: type"
- Definition: "name = body" or "name pattern = body"
- Indentation: 2 spaces (configurable)
PRINCIPLES:
1. **Determinism**: Same input produces identical Haskell source
2. **Validity**: All generated code must parse as Haskell 2010 + GHC extensions
3. **Readability**: Human-readable formatting for debugging
4. **Completeness**: All AST nodes properly formatted
-}
module Assertica.Backend.Emit
( -- * Main emission functions
emitHaskellModule
, emitHaskellDefinition
, emitHaskellExpression
-- * Formatting utilities
, prettyPrint
, formatModuleHeader
, formatImports
, formatDefinition
, formatTypeSignature
-- * Configuration
, EmitConfig(..)
, defaultEmitConfig
) where
import Data.Text (Text)
import qualified Data.Text as T
import Data.List (intercalate)
import GHC.Generics (Generic)
import Assertica.Backend.CodeGen
( HaskellModule(..)
, HaskellDefinition(..)
, HaskellExpr(..)
)
-- ============================================================================
-- EMIT CONFIGURATION
-- ============================================================================
{-| Configuration for Haskell code emission -}
data EmitConfig = EmitConfig
{ ecIndentSize :: Int -- ^ Spaces per indentation level (default: 2)
, ecLineLength :: Int -- ^ Target line length (default: 80)
, ecAddComments :: Bool -- ^ Include source comments (default: True)
, ecPreludeImports :: Bool -- ^ Auto-import Prelude (default: True)
}
deriving (Show, Generic)
{-| Default emission configuration -}
defaultEmitConfig :: EmitConfig
defaultEmitConfig = EmitConfig
{ ecIndentSize = 2
, ecLineLength = 80
, ecAddComments = True
, ecPreludeImports = True
}
-- ============================================================================
-- MAIN EMISSION FUNCTIONS
-- ============================================================================
{-| Emit a complete Haskell module as source code.
This is the primary interface for generating output .hs files.
Takes a HaskellModule and produces a complete, syntactically valid Haskell
program that can be compiled with GHC.
-}
emitHaskellModule :: EmitConfig -> HaskellModule -> Text
emitHaskellModule config (HaskellModule modName imports defs comments) =
T.unlines $
-- Comments
map (T.cons '-') comments ++
[""]
-- Module header
++ [formatModuleHeader modName]
-- Imports
++ (if null imports then [] else [""] ++ formatImports config imports)
-- Definitions
++ (if null defs then [] else [""] ++ map (emitHaskellDefinition config) defs)
{-| Emit a single Haskell definition -}
emitHaskellDefinition :: EmitConfig -> HaskellDefinition -> Text
emitHaskellDefinition config (HaskellDefinition name typeSig body sourceLocation) =
let
-- Type signature (if present)
sigLines = case typeSig of
Nothing -> []
Just sig -> [formatTypeSignature name sig]
-- Definition line
defLine = formatDefinition name body
-- Source location comment
commentLines = case sourceLocation of
Nothing -> []
Just loc | ecAddComments config -> ["-- Source: " <> loc]
_ -> []
in
T.unlines $ commentLines ++ sigLines ++ [defLine]
{-| Emit a single Haskell expression -}
emitHaskellExpression :: EmitConfig -> HaskellExpr -> Text
emitHaskellExpression _config (HaskellExpr expr _mtype) =
expr
-- ============================================================================
-- FORMATTING FUNCTIONS
-- ============================================================================
{-| Format module header -}
formatModuleHeader :: [Text] -> Text
formatModuleHeader modName =
"module " <> T.intercalate "." modName <> " where"
{-| Format import statements -}
formatImports :: EmitConfig -> [Text] -> [Text]
formatImports _config imports =
map ("import " <>) imports
{-| Format a type signature -}
formatTypeSignature :: Text -> Text -> Text
formatTypeSignature name typeSig =
name <> " :: " <> typeSig
{-| Format a definition with name and body -}
formatDefinition :: Text -> Text -> Text
formatDefinition name body =
name <> " = " <> body
-- ============================================================================
-- PRETTY-PRINTING
-- ============================================================================
{-| Pretty-print a piece of code with proper indentation and formatting.
This takes raw code (possibly from a compiler) and formats it nicely
for human consumption and debugging.
-}
prettyPrint :: EmitConfig -> Text -> Text
prettyPrint config code =
-- Add proper indentation, line wrapping, etc.
-- For now, return as-is
code
{-| Indent a text block by n levels -}
indent :: Int -> Text -> Text
indent n txt =
let spaces = T.replicate n " "
lines_ = T.lines txt
in T.unlines $ map (spaces <>) lines_
{-| Wrap text to specified line length -}
wrap :: Int -> Text -> Text
wrap _lineLen txt =
-- Simple implementation: just return as-is
-- A full implementation would break at spaces
txt
|