canontra-0.1.0.0: src/Canontra/Canonical/FusedStream.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StrictData #-}
{- |
Module : Canontra.Canonical.FusedStream
Description : Fused single-pass direct-to-hash and direct-to-builder serialization.
Inlines all Normalizer v3 rules (multi-scope docstring stripping, comment elimination,
parameter canonicalization, decorator/symbol ordering, IEEE-754 float normalization,
and Unicode NFC canonicalization) directly into the binary serialization stream.
Eliminates intermediate AST materialization on the GHC nursery heap and connects
IR nodes directly to SHA-256 context folds.
-}
module Canontra.Canonical.FusedStream
( fusedStreamProgram
, fusedStreamDeclarations
, fusedStreamModule
, fusedStreamDeclarationStructural
, fusedStreamDeclaration
, fusedStreamStmt
, fusedStreamExpr
, fusedHashProgram
, fusedHashDeclarations
) where
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as BB
import Data.List (sort, sortBy)
import Data.Ord (comparing)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Data.Word (Word8)
import Canontra.Canonical.Float (encodeCanonicalFloat)
import Canontra.Canonical.StreamingHash (hashBuilderDirect)
import Canontra.Canonical.Unicode (canonicalizeText)
import Canontra.IR.Declaration
import Canontra.IR.Dependency
import Canontra.IR.Expression
import Canontra.IR.Program
import Canontra.Normalize.Normalize (isReflectionDocstring, normalizeModuleDeclarations, preservesDocstrings)
import Canontra.Types (Fingerprint)
-- | Compute F1 Structural fingerprint directly from IR with fused single-pass normalization and hashing.
{-# INLINE fusedHashProgram #-}
fusedHashProgram :: Program -> Fingerprint
fusedHashProgram = hashBuilderDirect . fusedStreamProgram
-- | Compute F2 Declaration fingerprint directly from declarations with fused single-pass normalization and hashing.
{-# INLINE fusedHashDeclarations #-}
fusedHashDeclarations :: [Declaration] -> Fingerprint
fusedHashDeclarations = hashBuilderDirect . fusedStreamDeclarations
-- | Serialize a Program directly to a Builder applying normalization on-the-fly.
{-# INLINE fusedStreamProgram #-}
fusedStreamProgram :: Program -> BB.Builder
fusedStreamProgram (Program modules lang) =
tag 0x01 <>
encodeText lang <>
encodeList fusedStreamModuleB modules
-- | Serialize a list of declarations directly to a Builder applying normalization on-the-fly (F2 format).
{-# INLINE fusedStreamDeclarations #-}
fusedStreamDeclarations :: [Declaration] -> BB.Builder
fusedStreamDeclarations decls =
tag 0x02 <> encodeList fusedStreamDeclarationB (normalizeModuleDeclarations decls)
-- | Serialize a Module directly to a strict ByteString.
fusedStreamModule :: Module -> BB.Builder
fusedStreamModule = fusedStreamModuleB
fusedStreamModuleB :: Module -> BB.Builder
fusedStreamModuleB (Module _ imps decls stmts) =
let normImps = sort (map normalizeImport imps)
normDecls = normalizeModuleDeclarations decls
cleanStmts = fusedCleanStmts stmts
in tag 0x10 <>
encodeText "" <>
encodeList fusedStreamImportB normImps <>
encodeList fusedStreamDeclarationStructuralB normDecls <>
encodeList fusedStreamStmtB cleanStmts
normalizeImport :: ImportDecl -> ImportDecl
normalizeImport = \case
ImportModule modName alias -> ImportModule modName alias
ImportFrom modName (ImportSymbols syms) -> ImportFrom modName (ImportSymbols (sort syms))
ImportFrom modName ImportAll -> ImportFrom modName ImportAll
fusedStreamImportB :: ImportDecl -> BB.Builder
fusedStreamImportB = \case
ImportModule modName maybeAlias ->
tag 0x20 <> encodeText modName <> encodeMaybe encodeText maybeAlias
ImportFrom modName (ImportSymbols syms) ->
tag 0x21 <> encodeText modName <> encodeList (\(s, a) -> encodeText s <> encodeMaybe encodeText a) syms
ImportFrom modName ImportAll ->
tag 0x22 <> encodeText modName
-- | Serialize a Declaration with full structural body (F1 format).
fusedStreamDeclarationStructural :: Declaration -> BB.Builder
fusedStreamDeclarationStructural = fusedStreamDeclarationStructuralB
fusedStreamDeclarationStructuralB :: Declaration -> BB.Builder
fusedStreamDeclarationStructuralB = fusedStreamDeclarationStructuralWithPreserveB False
fusedStreamDeclarationStructuralWithPreserveB :: Bool -> Declaration -> BB.Builder
fusedStreamDeclarationStructuralWithPreserveB classPreserve = \case
DeclFunction (Function name params retType decs body isAsync) ->
let preserve = classPreserve || preservesDocstrings decs
in tag 0x30 <>
encodeText name <>
encodeList fusedStreamParamB params <>
encodeMaybe (encodeText . T.strip) retType <>
encodeList encodeText (sort decs) <>
encodeList fusedStreamStmtB (fusedCleanStmtsWithPreserve preserve body) <>
(if isAsync then tag 0x01 else tag 0x00)
DeclClass (Class name bases methods decs) ->
let isClsPreserved = classPreserve || preservesDocstrings decs
in tag 0x31 <>
encodeText name <>
encodeList encodeText bases <>
encodeList (fusedStreamDeclarationStructuralWithPreserveB isClsPreserved . DeclFunction) methods <>
encodeList encodeText (sort decs)
DeclStruct (Struct name fields methods vis) ->
tag 0x32 <>
encodeText name <>
encodeList (\(f, t) -> encodeText f <> encodeMaybe (encodeText . T.strip) t) fields <>
encodeList (fusedStreamDeclarationStructuralB . DeclFunction) methods <>
encodeText vis
DeclInterface (Interface name methods bases) ->
tag 0x33 <>
encodeText name <>
encodeList (fusedStreamDeclarationStructuralB . DeclFunction) methods <>
encodeList encodeText (sort bases)
DeclReceiver (Receiver var ty ptr) fn ->
tag 0x34 <>
encodeText var <> encodeText ty <> (if ptr then tag 0x01 else tag 0x00) <>
fusedStreamDeclarationStructuralB (DeclFunction fn)
DeclTrait (Trait name methods superTrs) ->
tag 0x35 <>
encodeText name <>
encodeList (fusedStreamDeclarationStructuralB . DeclFunction) methods <>
encodeList encodeText (sort superTrs)
DeclImpl (Impl mTr tgt methods) ->
tag 0x36 <>
encodeMaybe encodeText mTr <>
encodeText tgt <>
encodeList (fusedStreamDeclarationStructuralB . DeclFunction) methods
DeclVariable varName maybeType ->
tag 0x37 <> encodeText varName <> encodeMaybe (encodeText . T.strip) maybeType
DeclTypeAlias aliasName origType ->
tag 0x38 <> encodeText aliasName <> encodeMaybe (encodeText . T.strip) origType
-- | Serialize a Declaration signature without body (F2 format).
fusedStreamDeclaration :: Declaration -> BB.Builder
fusedStreamDeclaration = fusedStreamDeclarationB
fusedStreamDeclarationB :: Declaration -> BB.Builder
fusedStreamDeclarationB = \case
DeclFunction (Function name params retType decs _ isAsync) ->
tag 0x30 <>
encodeText name <>
encodeList fusedStreamParamB params <>
encodeMaybe (encodeText . T.strip) retType <>
encodeList encodeText (sort decs) <>
(if isAsync then tag 0x01 else tag 0x00)
DeclClass (Class name bases methods decs) ->
tag 0x31 <>
encodeText name <>
encodeList encodeText bases <>
encodeList (fusedStreamDeclarationB . DeclFunction) methods <>
encodeList encodeText (sort decs)
DeclStruct (Struct name fields methods vis) ->
tag 0x32 <>
encodeText name <>
encodeList (\(f, t) -> encodeText f <> encodeMaybe (encodeText . T.strip) t) fields <>
encodeList (fusedStreamDeclarationB . DeclFunction) methods <>
encodeText vis
DeclInterface (Interface name methods bases) ->
tag 0x33 <>
encodeText name <>
encodeList (fusedStreamDeclarationB . DeclFunction) methods <>
encodeList encodeText (sort bases)
DeclReceiver (Receiver var ty ptr) fn ->
tag 0x34 <>
encodeText var <> encodeText ty <> (if ptr then tag 0x01 else tag 0x00) <>
fusedStreamDeclarationB (DeclFunction fn)
DeclTrait (Trait name methods superTrs) ->
tag 0x35 <>
encodeText name <>
encodeList (fusedStreamDeclarationB . DeclFunction) methods <>
encodeList encodeText (sort superTrs)
DeclImpl (Impl mTr tgt methods) ->
tag 0x36 <>
encodeMaybe encodeText mTr <>
encodeText tgt <>
encodeList (fusedStreamDeclarationB . DeclFunction) methods
DeclVariable varName maybeType ->
tag 0x37 <> encodeText varName <> encodeMaybe (encodeText . T.strip) maybeType
DeclTypeAlias aliasName origType ->
tag 0x38 <> encodeText aliasName <> encodeMaybe (encodeText . T.strip) origType
fusedStreamParamB :: Parameter -> BB.Builder
fusedStreamParamB (Parameter name kind defVal mType) =
encodeText name <>
tag (paramKindTag kind) <>
encodeMaybe (encodeText . T.strip) defVal <>
encodeMaybe (encodeText . T.strip) mType
paramKindTag :: ParamKind -> Word8
paramKindTag = \case
ParamPositional -> 0x01
ParamKeywordOnly -> 0x02
ParamVarArgs -> 0x03
ParamKwArgs -> 0x04
ParamPositionalOnly -> 0x05
-- | Serialize a Statement directly to a Builder.
fusedStreamStmt :: Stmt -> BB.Builder
fusedStreamStmt = fusedStreamStmtB
fusedStreamStmtB :: Stmt -> BB.Builder
fusedStreamStmtB = \case
StmtAssign targets expr ->
tag 0x40 <> encodeList fusedStreamExprB targets <> fusedStreamExprB expr
StmtAugAssign target op expr ->
tag 0x41 <> fusedStreamExprB target <> tag (opTag op) <> fusedStreamExprB expr
StmtExpr expr ->
tag 0x42 <> fusedStreamExprB expr
StmtReturn maybeExpr ->
tag 0x43 <> encodeMaybe fusedStreamExprB maybeExpr
StmtIf cond body elseSuite ->
tag 0x44 <> fusedStreamExprB cond <> encodeList fusedStreamStmtB (fusedCleanStmts body) <> encodeList fusedStreamStmtB (fusedCleanStmts elseSuite)
StmtWhile cond body elseSuite ->
tag 0x45 <> fusedStreamExprB cond <> encodeList fusedStreamStmtB (fusedCleanStmts body) <> encodeList fusedStreamStmtB (fusedCleanStmts elseSuite)
StmtFor target iter body elseSuite ->
tag 0x46 <> fusedStreamExprB target <> fusedStreamExprB iter <> encodeList fusedStreamStmtB (fusedCleanStmts body) <> encodeList fusedStreamStmtB (fusedCleanStmts elseSuite)
StmtTry body handlers elseSuite finalSuite ->
tag 0x47 <> encodeList fusedStreamStmtB (fusedCleanStmts body) <>
encodeList (\(c, a, b) -> encodeMaybe fusedStreamExprB c <> encodeMaybe encodeText a <> encodeList fusedStreamStmtB (fusedCleanStmts b)) handlers <>
encodeList fusedStreamStmtB (fusedCleanStmts elseSuite) <> encodeList fusedStreamStmtB (fusedCleanStmts finalSuite)
StmtWith items body ->
tag 0x48 <> encodeList (\(e, a) -> fusedStreamExprB e <> encodeMaybe fusedStreamExprB a) items <> encodeList fusedStreamStmtB (fusedCleanStmts body)
StmtAssert expr maybeMsg ->
tag 0x49 <> fusedStreamExprB expr <> encodeMaybe fusedStreamExprB maybeMsg
StmtRaise maybeExpr maybeCause ->
tag 0x4A <> encodeMaybe fusedStreamExprB maybeExpr <> encodeMaybe fusedStreamExprB maybeCause
StmtBreak -> tag 0x4B
StmtContinue -> tag 0x4C
StmtPass -> tag 0x4D
StmtDelete exprs -> tag 0x4E <> encodeList fusedStreamExprB exprs
StmtGlobal vars -> tag 0x4F <> encodeList encodeText (sort vars)
StmtNonlocal vars -> tag 0x50 <> encodeList encodeText (sort vars)
StmtAnnAssign target ty maybeVal ->
tag 0x51 <> fusedStreamExprB target <> fusedStreamExprB ty <> encodeMaybe fusedStreamExprB maybeVal
StmtAsyncFor target iter body elseSuite ->
tag 0x52 <> fusedStreamExprB target <> fusedStreamExprB iter <> encodeList fusedStreamStmtB (fusedCleanStmts body) <> encodeList fusedStreamStmtB (fusedCleanStmts elseSuite)
StmtAsyncWith items body ->
tag 0x53 <> encodeList (\(e, a) -> fusedStreamExprB e <> encodeMaybe fusedStreamExprB a) items <> encodeList fusedStreamStmtB (fusedCleanStmts body)
StmtMatch expr cases ->
tag 0x54 <> fusedStreamExprB expr <> encodeList fusedStreamMatchCaseB cases
StmtGo expr ->
tag 0x55 <> fusedStreamExprB expr
StmtDefer expr ->
tag 0x56 <> fusedStreamExprB expr
StmtChanSend ch val ->
tag 0x57 <> fusedStreamExprB ch <> fusedStreamExprB val
StmtSelect cases ->
tag 0x58 <> encodeList (\(sc, b) -> fusedStreamSelectCaseB sc <> encodeList fusedStreamStmtB (fusedCleanStmts b)) cases
StmtLoop body ->
tag 0x59 <> encodeList fusedStreamStmtB (fusedCleanStmts body)
StmtSwitch expr cases defStmts ->
tag 0x5A <> fusedStreamExprB expr <> encodeList (\(c, b) -> fusedStreamExprB c <> encodeList fusedStreamStmtB (fusedCleanStmts b)) cases <> encodeList fusedStreamStmtB (fusedCleanStmts defStmts)
fusedStreamSelectCaseB :: SelectCase -> BB.Builder
fusedStreamSelectCaseB = \case
SelectSend ch val -> tag 0x01 <> fusedStreamExprB ch <> fusedStreamExprB val
SelectRecv mV ch -> tag 0x02 <> encodeMaybe encodeText mV <> fusedStreamExprB ch
SelectDefault -> tag 0x03
fusedStreamMatchCaseB :: MatchCase -> BB.Builder
fusedStreamMatchCaseB (MatchCase pat guard body) =
fusedStreamExprB pat <> encodeMaybe fusedStreamExprB guard <> encodeList fusedStreamStmtB (fusedCleanStmts body)
-- | Serialize an Expression directly to a Builder.
fusedStreamExpr :: Expr -> BB.Builder
fusedStreamExpr = fusedStreamExprB
fusedStreamExprB :: Expr -> BB.Builder
fusedStreamExprB = \case
ExprId ident -> tag 0x60 <> encodeText ident
ExprLit lit -> tag 0x61 <> canonicalizeLitB lit
ExprBinary op e1 e2 -> tag 0x62 <> tag (opTag op) <> fusedStreamExprB e1 <> fusedStreamExprB e2
ExprUnary op e -> tag 0x63 <> tag (opTag op) <> fusedStreamExprB e
ExprCall target args kwArgs ->
let normKwArgs = sortBy (comparing fst) kwArgs
in tag 0x64 <> fusedStreamExprB target <> encodeList fusedStreamExprB args <> encodeList (\(k, v) -> encodeText k <> fusedStreamExprB v) normKwArgs
ExprAttr target attr -> tag 0x65 <> fusedStreamExprB target <> encodeText attr
ExprSubscript target idx -> tag 0x66 <> fusedStreamExprB target <> fusedStreamExprB idx
ExprList items -> tag 0x67 <> encodeList fusedStreamExprB items
ExprTuple items -> tag 0x68 <> encodeList fusedStreamExprB items
ExprDict items -> tag 0x69 <> encodeList (\(k, v) -> fusedStreamExprB k <> fusedStreamExprB v) items
ExprSet items -> tag 0x6A <> encodeList fusedStreamExprB items
ExprLambda params body -> tag 0x6B <> encodeList fusedStreamParamB params <> fusedStreamExprB body
ExprTernary cond t f -> tag 0x6C <> fusedStreamExprB cond <> fusedStreamExprB t <> fusedStreamExprB f
ExprListComp item comps -> tag 0x6D <> fusedStreamExprB item <> encodeList fusedStreamCompB comps
ExprDictComp k v comps -> tag 0x6E <> fusedStreamExprB k <> fusedStreamExprB v <> encodeList fusedStreamCompB comps
ExprGenerator item comps -> tag 0x6F <> fusedStreamExprB item <> encodeList fusedStreamCompB comps
ExprSetComp item comps -> tag 0x80 <> fusedStreamExprB item <> encodeList fusedStreamCompB comps
ExprWalrus name val -> tag 0x81 <> encodeText name <> fusedStreamExprB val
ExprAwait e -> tag 0x82 <> fusedStreamExprB e
ExprYield me -> tag 0x83 <> encodeMaybe fusedStreamExprB me
ExprYieldFrom e -> tag 0x84 <> fusedStreamExprB e
ExprFormattedString parts -> tag 0x85 <> encodeList fusedStreamFStringPartB parts
ExprStarred e -> tag 0x86 <> fusedStreamExprB e
ExprKwStarred e -> tag 0x87 <> fusedStreamExprB e
ExprSlice ms me mst -> tag 0x88 <> encodeMaybe fusedStreamExprB ms <> encodeMaybe fusedStreamExprB me <> encodeMaybe fusedStreamExprB mst
ExprOptChain e prop -> tag 0x89 <> fusedStreamExprB e <> encodeText prop
ExprNullish e1 e2 -> tag 0x8A <> fusedStreamExprB e1 <> fusedStreamExprB e2
ExprChanRecv ch -> tag 0x8B <> fusedStreamExprB ch
ExprTryOp e -> tag 0x8C <> fusedStreamExprB e
ExprMacroCall name args -> tag 0x8D <> encodeText name <> encodeList fusedStreamExprB args
ExprJSX tagElem attrs children ->
let normAttrs = sortBy (comparing fst) attrs
in tag 0x8E <> encodeText tagElem <>
encodeList (\(k, v) -> encodeText k <> fusedStreamExprB v) normAttrs <>
encodeList fusedStreamExprB children
fusedStreamFStringPartB :: FStringPart -> BB.Builder
fusedStreamFStringPartB = \case
FStringText t -> tag 0x01 <> encodeText t
FStringExpr e conv fmt -> tag 0x02 <> fusedStreamExprB e <> encodeMaybe encodeText conv <> encodeMaybe encodeText fmt
fusedStreamCompB :: CompFor -> BB.Builder
fusedStreamCompB (CompFor target iter ifs) =
fusedStreamExprB target <> fusedStreamExprB iter <> encodeList fusedStreamExprB ifs
canonicalizeLitB :: Lit -> BB.Builder
canonicalizeLitB = \case
LitInt n -> tag 0x70 <> BB.integerDec n
LitFloat f -> tag 0x71 <> BB.byteString (encodeCanonicalFloat f)
LitString s -> tag 0x72 <> encodeText s
LitBytes b -> tag 0x73 <> encodeText b
LitBool True -> tag 0x74
LitBool False -> tag 0x75
LitNone -> tag 0x76
LitEllipsis -> tag 0x77
opTag :: Op -> Word8
opTag = \case
OpAdd -> 0x01; OpSub -> 0x02; OpMul -> 0x03; OpDiv -> 0x04; OpFloorDiv -> 0x05; OpMod -> 0x06; OpPow -> 0x07
OpBitAnd -> 0x08; OpBitOr -> 0x09; OpBitXor -> 0x0A; OpShiftL -> 0x0B; OpShiftR -> 0x0C
OpEq -> 0x0D; OpNotEq -> 0x0E; OpLt -> 0x0F; OpLtE -> 0x10; OpGt -> 0x11; OpGtE -> 0x12
OpAnd -> 0x13; OpOr -> 0x14; OpNot -> 0x15; OpInvert -> 0x16; OpIn -> 0x17; OpNotIn -> 0x18; OpIs -> 0x19; OpIsNot -> 0x1A
OpMatMult -> 0x1B
tag :: Word8 -> BB.Builder
tag = BB.word8
encodeText :: T.Text -> BB.Builder
encodeText t =
let canonicalT = canonicalizeText t
bs = TE.encodeUtf8 canonicalT
in BB.int64BE (fromIntegral (BS.length bs)) <> BB.byteString bs
encodeList :: (a -> BB.Builder) -> [a] -> BB.Builder
encodeList itemSerializer xs =
BB.int64BE (fromIntegral (length xs)) <> mconcat (map itemSerializer xs)
encodeMaybe :: (a -> BB.Builder) -> Maybe a -> BB.Builder
encodeMaybe _ Nothing = tag 0x00
encodeMaybe itemSerializer (Just x) = tag 0x01 <> itemSerializer x
-- | Drops leading docstring and filters redundant passes / docstring expressions in a suite.
fusedCleanStmts :: [Stmt] -> [Stmt]
fusedCleanStmts = fusedCleanStmtsWithPreserve False
fusedCleanStmtsWithPreserve :: Bool -> [Stmt] -> [Stmt]
fusedCleanStmtsWithPreserve preserve rawStmts =
let stripped = if preserve then rawStmts else stripLeadingDocstring rawStmts
nonDoc = if preserve then stripped else filter (not . isDocstringStmt) stripped
elimPass = if length nonDoc > 1 then filter (not . isPass) nonDoc else nonDoc
in if null elimPass then [StmtPass] else elimPass
where
isDocstringStmt (StmtExpr (ExprLit (LitString s))) = not (isReflectionDocstring s)
isDocstringStmt _ = False
isPass StmtPass = True
isPass _ = False
stripLeadingDocstring [] = []
stripLeadingDocstring (StmtExpr (ExprLit (LitString s)) : rest)
| isReflectionDocstring s = StmtExpr (ExprLit (LitString s)) : rest
| otherwise = rest
stripLeadingDocstring xs = xs