@@ -36,6 +36,12 @@ use crate::core_relations::{
3636 BaseValue , BaseValues , ContainerValue , ContainerValues , ExecutionState , ExternalFunctionId ,
3737 TableId , Value ,
3838} ;
39+ use crate :: {
40+ ast:: { FunctionSubtype , Literal , ResolvedExpr } ,
41+ core:: ResolvedCall ,
42+ sort:: { F , S } ,
43+ typechecking:: FuncType ,
44+ } ;
3945use egglog_bridge:: { ActionRegistry , TableAction } ;
4046
4147/// The four contexts a primitive may run in, named after the
@@ -101,6 +107,26 @@ pub(crate) trait Internal<'a, 'db: 'a>: 'a {
101107 fn raw_exec_state ( & mut self ) -> & mut ExecutionState < ' db > {
102108 self . es_mut ( )
103109 }
110+ fn apply_resolved_function ( & mut self , _func : & FuncType , _args : & [ Value ] ) -> Option < Value > {
111+ None
112+ }
113+
114+ fn apply_table_function (
115+ & mut self ,
116+ subtype : FunctionSubtype ,
117+ action : & TableAction ,
118+ args : & [ Value ] ,
119+ ) -> Option < Value > {
120+ match ( subtype, self . ctx ( ) ) {
121+ ( FunctionSubtype :: Constructor , Context :: Write | Context :: Full ) => {
122+ action. lookup_or_insert ( self . es_mut ( ) , args)
123+ }
124+ ( FunctionSubtype :: Custom , Context :: Read | Context :: Full ) => {
125+ action. lookup ( self . es ( ) , args)
126+ }
127+ _ => None ,
128+ }
129+ }
104130}
105131
106132/// Sealed accessor for the [`ActionRegistry`]. Implemented by every
@@ -197,6 +223,74 @@ pub trait Core<'a, 'db: 'a>: Internal<'a, 'db> {
197223 let mut pure = PureState :: wrap ( self . raw_exec_state ( ) , ctx) ;
198224 fc. apply ( & mut pure, args)
199225 }
226+
227+ /// Dispatch an already type-specialized primitive in the current
228+ /// call-site context.
229+ ///
230+ /// This is a trusted evaluator hook, not an authorization boundary:
231+ /// callers must only pass primitives that were resolved for this same
232+ /// call-site context and whose surrounding expression has already been
233+ /// checked to require no more capability than this state wrapper provides.
234+ /// For example, a primitive body evaluator should typecheck the body under
235+ /// the runtime context it will register for, infer the body's required
236+ /// context, and register the primitive using the matching state wrapper.
237+ fn apply_primitive (
238+ & mut self ,
239+ primitive : & crate :: core:: SpecializedPrimitive ,
240+ args : & [ Value ] ,
241+ ) -> Option < Value > {
242+ let id = primitive. external_id ( self . ctx ( ) ) ;
243+ self . es_mut ( ) . call_external_func ( id, args)
244+ }
245+
246+ /// Evaluate an already typechecked expression in this primitive call
247+ /// context, using `bindings` for local variables.
248+ ///
249+ /// Primitive calls dispatch through the current call-site context.
250+ /// Table-backed function calls follow the same capability split as
251+ /// `unstable-app`: custom functions require a read-capable state,
252+ /// and constructors require a write-capable state that can mint on miss.
253+ ///
254+ /// This method assumes `expr` came from a trusted preparation pipeline that
255+ /// typechecked it for this exact runtime context and rejected expressions
256+ /// whose required capabilities exceed the receiver's state wrapper. It
257+ /// should not be used to evaluate arbitrary resolved expressions from a
258+ /// wider context inside a less-capable wrapper.
259+ fn eval_resolved_expr (
260+ & mut self ,
261+ expr : & ResolvedExpr ,
262+ bindings : & [ ( & str , Value ) ] ,
263+ ) -> Option < Value > {
264+ match expr {
265+ ResolvedExpr :: Lit ( _, literal) => Some ( match literal {
266+ Literal :: Int ( x) => self . base_to_value ( * x) ,
267+ Literal :: Float ( x) => self . base_to_value ( F :: from ( * x) ) ,
268+ Literal :: String ( x) => self . base_to_value ( S :: new ( x. clone ( ) ) ) ,
269+ Literal :: Bool ( x) => self . base_to_value ( * x) ,
270+ Literal :: Unit => self . base_to_value ( ( ) ) ,
271+ } ) ,
272+ ResolvedExpr :: Var ( _, resolved_var) => {
273+ assert ! (
274+ !resolved_var. is_global_ref,
275+ "global variable {:?} reached direct expression evaluation before remove_globals" ,
276+ resolved_var. name
277+ ) ;
278+ bindings
279+ . iter ( )
280+ . find_map ( |( name, value) | ( * name == resolved_var. name ) . then_some ( * value) )
281+ }
282+ ResolvedExpr :: Call ( _, resolved_call, children) => {
283+ let mut values = Vec :: with_capacity ( children. len ( ) ) ;
284+ for child in children {
285+ values. push ( self . eval_resolved_expr ( child, bindings) ?) ;
286+ }
287+ match resolved_call {
288+ ResolvedCall :: Primitive ( primitive) => self . apply_primitive ( primitive, & values) ,
289+ ResolvedCall :: Func ( func) => self . apply_resolved_function ( func, & values) ,
290+ }
291+ }
292+ }
293+ }
200294}
201295
202296/// Read-side methods — name-indexed table lookup. Implemented for
@@ -270,6 +364,15 @@ fn lookup_action<'r>(registry: &'r ActionRegistry, name: &str) -> &'r TableActio
270364 . unwrap_or_else ( || panic ! ( "missing table action for table: {name}" ) )
271365}
272366
367+ fn apply_registered_function < ' a , ' db : ' a > (
368+ state : & mut impl RegistrySealed < ' a , ' db > ,
369+ func : & FuncType ,
370+ args : & [ Value ] ,
371+ ) -> Option < Value > {
372+ let action = lookup_action ( state. registry ( ) , & func. name ) . clone ( ) ;
373+ state. apply_table_function ( func. subtype , & action, args)
374+ }
375+
273376// =====================================================================
274377// The four wrapper types — plain structs, methods come from traits.
275378// =====================================================================
@@ -422,6 +525,9 @@ impl<'a, 'db: 'a> Internal<'a, 'db> for ReadState<'a, 'db> {
422525 fn ctx ( & self ) -> Context {
423526 self . ctx
424527 }
528+ fn apply_resolved_function ( & mut self , func : & FuncType , args : & [ Value ] ) -> Option < Value > {
529+ apply_registered_function ( self , func, args)
530+ }
425531}
426532impl < ' a , ' db : ' a > RegistrySealed < ' a , ' db > for ReadState < ' a , ' db > {
427533 fn registry ( & self ) -> & ActionRegistry {
@@ -441,6 +547,9 @@ impl<'a, 'db: 'a> Internal<'a, 'db> for WriteState<'a, 'db> {
441547 fn ctx ( & self ) -> Context {
442548 self . ctx
443549 }
550+ fn apply_resolved_function ( & mut self , func : & FuncType , args : & [ Value ] ) -> Option < Value > {
551+ apply_registered_function ( self , func, args)
552+ }
444553}
445554impl < ' a , ' db : ' a > RegistrySealed < ' a , ' db > for WriteState < ' a , ' db > {
446555 fn registry ( & self ) -> & ActionRegistry {
@@ -460,6 +569,9 @@ impl<'a, 'db: 'a> Internal<'a, 'db> for FullState<'a, 'db> {
460569 fn ctx ( & self ) -> Context {
461570 self . ctx
462571 }
572+ fn apply_resolved_function ( & mut self , func : & FuncType , args : & [ Value ] ) -> Option < Value > {
573+ apply_registered_function ( self , func, args)
574+ }
463575}
464576impl < ' a , ' db : ' a > RegistrySealed < ' a , ' db > for FullState < ' a , ' db > {
465577 fn registry ( & self ) -> & ActionRegistry {
0 commit comments