{-| Module : Assertica.Surface.Elaborator Description : Elaborator from surface AST to core AST Copyright : (c) 2026 Ahmad Ali Parr License : BSL-1.0 Converts the surface (intermediate) AST into the core AST. Performs: - Name resolution (variables, qualified names) - Type annotation propagation - Syntactic sugar expansion (infix to application, etc.) - Implicit argument elaboration (minimal) - Error reporting with source locations INVARIANTS: - All names must be resolvable (either built-in or in scope) - All type annotations must be well-formed - No unbound variables in elaborated terms -} module Assertica.Surface.Elaborator ( -- * Main elaboration functions elaborateTerm , elaborateType , elaborateProposition , elaborateAssertion , elaborateProof -- * Elaboration context , ElabContext (..) , emptyContext , addBinding -- * Error types , ElabError (..) ) where import Assertica.Surface.AST import qualified Assertica.Core.AST as Core import Data.Text (Text) import qualified Data.Text as T import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Set (Set) import qualified Data.Set as Set import Data.Maybe (fromMaybe, mapMaybe, catMaybes) -- ============================================================================ -- ELABORATION CONTEXT -- ============================================================================ {-| Elaboration context tracks variable bindings and types. -} data ElabContext = ElabContext { ecBindings :: Map Text Core.Var -- ^ Variable bindings , ecTypes :: Map Text Core.Type -- ^ Type information for variables , ecNextId :: Int -- ^ Next unique identifier , ecModule :: [Text] -- ^ Current module path } deriving (Show) {-| Empty context. -} emptyContext :: ElabContext emptyContext = ElabContext Map.empty Map.empty 0 [] {-| Add a binding to the context. -} addBinding :: Text -> Maybe Core.Type -> ElabContext -> (Core.Var, ElabContext) addBinding name mty ctx = let var = Core.Var name (ecNextId ctx) ctx' = ctx { ecBindings = Map.insert name var (ecBindings ctx) , ecTypes = case mty of Just ty -> Map.insert name ty (ecTypes ctx) Nothing -> ecTypes ctx , ecNextId = ecNextId ctx + 1 } in (var, ctx') {-| Add multiple bindings. -} addBindings :: [Text] -> ElabContext -> ([Core.Var], ElabContext) addBindings [] ctx = ([], ctx) addBindings (n:ns) ctx = let (v, ctx') = addBinding n Nothing ctx (vs, ctx'') = addBindings ns ctx' in (v:vs, ctx'') -- ============================================================================ -- ERROR TYPES -- ============================================================================ {-| Elaboration error with optional source location. -} data ElabError = ElabError { eeMessage :: String , eeLoc :: Maybe SourceLoc } deriving (Eq, Show) -- ============================================================================ -- ELABORATION MONAD -- ============================================================================ {-| Elaboration result: either an error or a value. -} type Elab a = Either ElabError a {-| Fail with an error. -} elab_fail :: String -> Maybe SourceLoc -> Elab a elab_fail msg loc = Left (ElabError msg loc) {-| Resolve a name, looking it up in context or treating as builtin. -} resolveName :: Text -> ElabContext -> Elab Core.Var resolveName name ctx = case Map.lookup name (ecBindings ctx) of Just var -> return var Nothing -> -- Builtin or unresolved - for now treat as global reference return (Core.Var name 0) {-| Resolve a qualified name. -} resolveQName :: SurfaceQName -> ElabContext -> Elab Core.QName resolveQName qn ctx = case qn of [] -> elab_fail "Empty qualified name" Nothing [name] -> -- Simple name: check context first, else assume it's a global case Map.lookup name (ecBindings ctx) of Just (Core.Var vname vid) -> return (Core.QName [T.pack (show vid)] vname) -- Local variable reference Nothing -> return (Core.QName (ecModule ctx) name) names -> -- Qualified name: module.name.name... let modName = init names localName = last names in return (Core.QName modName localName) -- ============================================================================ -- TERM ELABORATION -- ============================================================================ {-| Elaborate a surface term to a core term. -} elaborateTerm :: Located SurfaceTerm -> Either ElabError Core.Term elaborateTerm (Located term loc) = elaborateTermWithCtx term emptyContext {-| Elaborate a term with context. -} elaborateTermWithCtx :: SurfaceTerm -> ElabContext -> Elab Core.Term elaborateTermWithCtx term ctx = case term of STVar name -> do var <- resolveName name ctx return (Core.Var var) STConst c -> do const <- elaborateConstant c return (Core.Const const) STLam vars mty body -> do (typedVars, ctx') <- case mty of Just ty -> do ty' <- elaborateTypeWithCtx ty ctx let binders = [Core.Binder v (Just ty') | v <- replicate (length vars) (Core.Var (T.pack "") 0)] -- Create proper variables (newVars, ctx'') <- foldM (\(acc, c) v -> do (v', c') <- return (addBinding v (Just ty') c) return (acc ++ [v'], c') ) ([], ctx) vars return (zipWith Core.Binder newVars (repeat (Just ty')), ctx'') Nothing -> do (newVars, ctx'') <- addBindings vars ctx return (map (\v -> Core.Binder v Nothing) newVars, ctx'') body' <- elaborateTermWithCtx (locValue body) ctx' -- Build nested lambdas from right to left return $ foldr (\b acc -> Core.Lam b acc) body' typedVars STApp func args -> do func' <- elaborateTermWithCtx (locValue func) ctx args' <- mapM (\arg -> elaborateTermWithCtx (locValue arg) ctx) args return $ foldl Core.App func' args' STInfix l op r -> do -- Convert infix to application: a + b => (+) a b l' <- elaborateTermWithCtx (locValue l) ctx r' <- elaborateTermWithCtx (locValue r) ctx opVar <- resolveName op ctx return (Core.App (Core.App (Core.Var opVar) l') r') STLet name mty e1 e2 -> do e1' <- elaborateTermWithCtx (locValue e1) ctx ty' <- case mty of Just ty -> elaborateTypeWithCtx ty ctx Nothing -> return (Core.TyUniverse Core.Type0) -- Default to Type (var, ctx') <- return (addBinding name (Just ty') ctx) e2' <- elaborateTermWithCtx (locValue e2) ctx' return (Core.Let (Core.Binder var (Just ty')) e1' e2') STCase e clauses mdef -> do e' <- elaborateTermWithCtx (locValue e) ctx clauses' <- mapM (elaborateClause ctx) clauses mdef' <- case mdef of Just def -> elaborateTermWithCtx (locValue def) ctx >>= return . Just Nothing -> return Nothing return (Core.Case e' clauses' mdef') STAnn e ty -> do e' <- elaborateTermWithCtx (locValue e) ctx ty' <- elaborateTypeWithCtx ty ctx return (Core.Ann e' ty') STForall name mty body -> do ty' <- case mty of Just ty -> elaborateTypeWithCtx ty ctx Nothing -> return (Core.TyUniverse Core.Type0) (var, ctx') <- return (addBinding name (Just ty') ctx) body' <- elaborateTermWithCtx (locValue body) ctx' return (Core.Forall (Core.Binder var (Just ty')) body') STTuple es -> do es' <- mapM (\e -> elaborateTermWithCtx (locValue e) ctx) es -- Represent as nested pairs: (a, b, c) ~ Tuple3 a b c -- For now, just represent as tuple constructor let qn = Core.QName [] (T.pack ("Tuple" ++ show (length es))) return (Core.Constr qn es') STList es -> do es' <- mapM (\e -> elaborateTermWithCtx (locValue e) ctx) es -- Build list from cons cells: [a, b, c] ~ Cons a (Cons b (Cons c Nil)) let nil = Core.Constr (Core.QName [] (T.pack "Nil")) [] return $ foldr (\e acc -> Core.Constr (Core.QName [] (T.pack "Cons")) [e, acc]) nil es' STConstr qn args -> do qn' <- resolveQName qn ctx args' <- mapM (\e -> elaborateTermWithCtx (locValue e) ctx) args return (Core.Constr qn' args') {-| Elaborate a clause (pattern -> body). -} elaborateClause :: ElabContext -> SurfaceClause -> Elab Core.Clause elaborateClause ctx (SurfaceClause pat body) = do (pat', ctx') <- elaboratePattern (locValue pat) ctx body' <- elaborateTermWithCtx (locValue body) ctx' return (Core.Clause pat' body') {-| Elaborate a pattern and extend context with bound variables. -} elaboratePattern :: SurfacePattern -> ElabContext -> Elab (Core.Pattern, ElabContext) elaboratePattern pat ctx = case pat of SPVar name -> do (var, ctx') <- return (addBinding name Nothing ctx) return (Core.PatVar var, ctx') SPConstr qn pats -> do qn' <- resolveQName qn ctx (pats', ctx') <- foldM (\(acc, c) p -> do (p', c') <- elaboratePattern p c return (acc ++ [p'], c') ) ([], ctx) pats return (Core.PatConstr qn' pats', ctx') SPWildcard -> return (Core.PatWildcard, ctx) SPLit c -> do c' <- elaborateConstant c return (Core.PatLit (show c'), ctx) SPTuple pats -> do (pats', ctx') <- foldM (\(acc, c) p -> do (p', c') <- elaboratePattern p c return (acc ++ [p'], c') ) ([], ctx) pats let qn = Core.QName [] (T.pack ("Tuple" ++ show (length pats))) return (Core.PatConstr qn pats', ctx') {-| Elaborate a constant. -} elaborateConstant :: SurfaceConstant -> Elab Core.Constant elaborateConstant = \case IntLit n -> return (Core.IntLit n) BoolLit b -> return (Core.BoolLit b) StringLit s -> return (Core.StringLit s) UnitLit -> return Core.UnitConst -- ============================================================================ -- TYPE ELABORATION -- ============================================================================ {-| Elaborate a surface type to a core type. -} elaborateType :: SurfaceType -> Either ElabError Core.Type elaborateType ty = elaborateTypeWithCtx ty emptyContext {-| Elaborate a type with context. -} elaborateTypeWithCtx :: SurfaceType -> ElabContext -> Elab Core.Type elaborateTypeWithCtx ty ctx = case ty of STyVar name -> do var <- resolveName name ctx return (Core.TyVar var) STyConst qn args -> do qn' <- resolveQName qn ctx args' <- mapM (\a -> elaborateTypeWithCtx a ctx) args return (Core.TyConst qn' args') STyFun a b -> do a' <- elaborateTypeWithCtx a ctx b' <- elaborateTypeWithCtx b ctx return (Core.TyFun a' b') STyForall name mty body -> do ty' <- case mty of Just t -> elaborateTypeWithCtx t ctx Nothing -> return (Core.TyUniverse Core.Type0) (var, ctx') <- return (addBinding name (Just ty') ctx) body' <- elaborateTypeWithCtx body ctx' return (Core.TyForall (Core.Binder var (Just ty')) body') STyTuple tys -> do tys' <- mapM (\t -> elaborateTypeWithCtx t ctx) tys let qn = Core.QName [] (T.pack ("Tuple" ++ show (length tys))) return (Core.TyConst qn tys') STyApp a b -> do a' <- elaborateTypeWithCtx a ctx b' <- elaborateTypeWithCtx b ctx return (Core.TyApp a' b') -- ============================================================================ -- PROPOSITION ELABORATION -- ============================================================================ {-| Elaborate a surface proposition to a core proposition. -} elaborateProposition :: Located SurfaceProposition -> Either ElabError Core.Proposition elaborateProposition (Located prop _) = elaboratePropositionWithCtx prop emptyContext {-| Elaborate a proposition with context. -} elaboratePropositionWithCtx :: SurfaceProposition -> ElabContext -> Elab Core.Proposition elaboratePropositionWithCtx prop ctx = case prop of SPEq l r -> do l' <- elaborateTermWithCtx (locValue l) ctx r' <- elaborateTermWithCtx (locValue r) ctx return (Core.Eq l' r') SPForall name mty body -> do ty' <- case mty of Just t -> elaborateTypeWithCtx t ctx Nothing -> return (Core.TyUniverse Core.Type0) (var, ctx') <- return (addBinding name (Just ty') ctx) body' <- elaboratePropositionWithCtx (locValue body) ctx' return (Core.Forall' (Core.Binder var (Just ty')) body') SPExists name mty body -> do ty' <- case mty of Just t -> elaborateTypeWithCtx t ctx Nothing -> return (Core.TyUniverse Core.Type0) (var, ctx') <- return (addBinding name (Just ty') ctx) body' <- elaboratePropositionWithCtx (locValue body) ctx' return (Core.Exists (Core.Binder var (Just ty')) body') SPAnd p1 p2 -> do p1' <- elaboratePropositionWithCtx (locValue p1) ctx p2' <- elaboratePropositionWithCtx (locValue p2) ctx return (Core.And p1' p2') SPOr p1 p2 -> do p1' <- elaboratePropositionWithCtx (locValue p1) ctx p2' <- elaboratePropositionWithCtx (locValue p2) ctx return (Core.Or p1' p2') SPImpl p1 p2 -> do p1' <- elaboratePropositionWithCtx (locValue p1) ctx p2' <- elaboratePropositionWithCtx (locValue p2) ctx return (Core.Impl p1' p2') SPNot p -> do p' <- elaboratePropositionWithCtx (locValue p) ctx return (Core.Not p') SPTrue -> return Core.Top SPFalse -> return Core.Bot -- ============================================================================ -- PROOF ELABORATION -- ============================================================================ {-| Elaborate a surface proof to a core proof. -} elaborateProof :: Located SurfaceProof -> Either ElabError Core.Proof elaborateProof (Located proof _) = elaborateProofWithCtx proof emptyContext {-| Elaborate a proof with context. -} elaborateProofWithCtx :: SurfaceProof -> ElabContext -> Elab Core.Proof elaborateProofWithCtx proof ctx = case proof of SPRefl -> return (Core.Refl (Core.Const Core.UnitConst)) SPSymm p -> do p' <- elaborateProofWithCtx (locValue p) ctx return (Core.Symm p') SPTrans p1 p2 -> do p1' <- elaborateProofWithCtx (locValue p1) ctx p2' <- elaborateProofWithCtx (locValue p2) ctx return (Core.Trans p1' p2') SPIntro vars body -> do (newVars, ctx') <- addBindings vars ctx body' <- elaborateProofWithCtx (locValue body) ctx' return $ foldr (\v acc -> Core.Intro (Core.Binder v Nothing) acc) body' newVars SPVar name -> do var <- resolveName name ctx return (Core.ProofVar var) SPOpaque qn -> do qn' <- resolveQName qn ctx return (Core.Opaque qn' Core.Top) -- Opaque proof with Top proposition SPByName strategy -> -- Strategies: by_refl, by_simp, etc. case T.unpack strategy of "by_refl" -> return (Core.Refl (Core.Const Core.UnitConst)) "by_simp" -> return (Core.Refl (Core.Const Core.UnitConst)) -- Simplified _ -> elab_fail ("Unknown proof strategy: " ++ T.unpack strategy) Nothing -- ============================================================================ -- ASSERTION ELABORATION -- ============================================================================ {-| Elaborate a surface assertion to a core assertion. -} elaborateAssertion :: SurfaceAssertion -> Either ElabError Core.Assertion elaborateAssertion (SurfaceAssertion nameLocated propLocated proofLocatedOpt) = do let name = locValue nameLocated let nameLoc = locLoc nameLocated -- Create qualified name (for now, unqualified) let qn = Core.QName [] name -- Elaborate proposition prop <- elaboratePropositionWithCtx (locValue propLocated) emptyContext -- Elaborate optional proof proof <- case proofLocatedOpt of Just p -> elaborateProofWithCtx (locValue p) emptyContext >>= return . Just Nothing -> return Nothing return (Core.Assertion qn prop proof) -- ============================================================================ -- HELPER FUNCTIONS -- ============================================================================ {-| Monadic fold (for building up elaboration state). -} foldM :: Monad m => (a -> b -> m a) -> a -> [b] -> m a foldM f z xs = foldr (\x acc -> acc >>= \a -> f a x) (return z) xs