55
66use std:: time:: Instant ;
77
8- use ahash:: AHashMap ;
8+ use ahash:: { AHashMap , AHashSet } ;
99use num_bigint:: BigInt ;
1010
1111use crate :: {
@@ -181,8 +181,6 @@ pub struct ReplSession {
181181 name_map : AHashMap < String , NamespaceId > ,
182182 /// Current size of the global namespace.
183183 namespace_size : usize ,
184- /// Number of external function slots at the start of the namespace.
185- external_function_count : usize ,
186184 /// Script name used in parse/runtime error reporting.
187185 script_name : String ,
188186 /// VM snapshot waiting to be resumed after an interactive yield.
@@ -205,6 +203,8 @@ pub struct ReplSession {
205203 /// `ResolveFutures` is returned to the host, and stored in
206204 /// `PendingFuturesState` for incremental resolution.
207205 future_metadata : AHashMap < u32 , PendingFutureInfo > ,
206+ /// O(1) membership set mirroring `external_functions` for name checks.
207+ external_function_set : AHashSet < String > ,
208208}
209209
210210impl ReplSession {
@@ -238,6 +238,7 @@ impl ReplSession {
238238 }
239239
240240 let namespace_size = namespace_values. len ( ) ;
241+ let external_function_set: AHashSet < String > = external_functions. iter ( ) . cloned ( ) . collect ( ) ;
241242
242243 Self {
243244 interner : InternerBuilder :: new ( "" ) ,
@@ -247,13 +248,13 @@ impl ReplSession {
247248 namespaces : Namespaces :: new ( namespace_values) ,
248249 name_map,
249250 namespace_size,
250- external_function_count : external_function_ids. len ( ) ,
251251 script_name : script_name. to_string ( ) ,
252252 pending_snapshot : None ,
253253 pending_resume_state : None ,
254254 pending_futures_state : None ,
255255 capabilities : None ,
256256 future_metadata : AHashMap :: new ( ) ,
257+ external_function_set,
257258 }
258259 }
259260
@@ -268,6 +269,64 @@ impl ReplSession {
268269 self . capabilities = capabilities;
269270 }
270271
272+ /// Returns `true` if `name` is a registered external function.
273+ fn is_external_function_name ( & self , name : & str ) -> bool {
274+ self . external_function_set . contains ( name)
275+ }
276+
277+ /// Returns the list of registered external function names.
278+ #[ must_use]
279+ pub fn external_function_names ( & self ) -> & [ String ] {
280+ & self . external_functions
281+ }
282+
283+ /// Registers additional external functions on an existing session without
284+ /// clearing state.
285+ ///
286+ /// Functions that are already registered as external functions are silently
287+ /// skipped. Names that collide with existing user variables are also skipped
288+ /// to avoid destroying session state.
289+ ///
290+ /// New functions are added at the end of the namespace and are immediately
291+ /// callable from subsequent `execute()` calls.
292+ ///
293+ /// Returns the names that were skipped due to collision with user variables.
294+ pub fn register_external_functions ( & mut self , new_functions : Vec < String > ) -> Result < Vec < String > , ReplError > {
295+ self . ensure_not_waiting_for_resume ( ) ?;
296+ let mut collisions = Vec :: new ( ) ;
297+ for function_name in new_functions {
298+ if self . external_function_set . contains ( & function_name) {
299+ continue ;
300+ }
301+
302+ // Check if a name_map entry exists.
303+ if let Some ( & existing_slot) = self . name_map . get ( & function_name) {
304+ let value = self . namespaces . get ( GLOBAL_NS_IDX ) . get ( existing_slot) ;
305+ if !matches ! ( value, Value :: Undefined ) {
306+ // Live user variable -- reject to preserve state.
307+ collisions. push ( function_name) ;
308+ continue ;
309+ }
310+ // Tombstoned slot (Value::Undefined) -- reuse it.
311+ let ext_func_id = ExtFunctionId :: new ( self . external_functions . len ( ) ) ;
312+ self . external_functions . push ( function_name. clone ( ) ) ;
313+ self . external_function_set . insert ( function_name) ;
314+ * self . namespaces . get_mut ( GLOBAL_NS_IDX ) . get_mut ( existing_slot) = Value :: ExtFunction ( ext_func_id) ;
315+ } else {
316+ let ext_func_id = ExtFunctionId :: new ( self . external_functions . len ( ) ) ;
317+ self . external_functions . push ( function_name. clone ( ) ) ;
318+ self . external_function_set . insert ( function_name. clone ( ) ) ;
319+
320+ let slot = NamespaceId :: new ( self . namespace_size ) ;
321+ self . namespace_size += 1 ;
322+ self . namespaces . grow_global ( self . namespace_size ) ;
323+ self . name_map . insert ( function_name, slot) ;
324+ * self . namespaces . get_mut ( GLOBAL_NS_IDX ) . get_mut ( slot) = Value :: ExtFunction ( ext_func_id) ;
325+ }
326+ }
327+ Ok ( collisions)
328+ }
329+
271330 /// Returns the current capability set, if any.
272331 #[ must_use]
273332 pub fn capabilities ( & self ) -> Option < & CapabilitySet > {
@@ -303,13 +362,13 @@ impl ReplSession {
303362 namespaces : self . namespaces . deep_clone ( ) ,
304363 name_map : self . name_map . clone ( ) ,
305364 namespace_size : self . namespace_size ,
306- external_function_count : self . external_function_count ,
307365 script_name : self . script_name . clone ( ) ,
308366 pending_snapshot : None ,
309367 pending_resume_state : None ,
310368 pending_futures_state : None ,
311369 capabilities : self . capabilities . clone ( ) ,
312370 future_metadata : AHashMap :: new ( ) ,
371+ external_function_set : self . external_function_set . clone ( ) ,
313372 }
314373 }
315374
@@ -346,7 +405,7 @@ impl ReplSession {
346405 namespaces_bytes,
347406 name_map : self . name_map . iter ( ) . map ( |( k, v) | ( k. clone ( ) , * v) ) . collect ( ) ,
348407 namespace_size : self . namespace_size ,
349- external_function_count : self . external_function_count ,
408+ external_function_count : self . external_functions . len ( ) ,
350409 script_name : self . script_name . clone ( ) ,
351410 } ;
352411
@@ -383,6 +442,7 @@ impl ReplSession {
383442 ) ;
384443
385444 let name_map: AHashMap < String , NamespaceId > = snapshot. name_map . into_iter ( ) . collect ( ) ;
445+ let external_function_set: AHashSet < String > = snapshot. external_functions . iter ( ) . cloned ( ) . collect ( ) ;
386446
387447 Ok ( Self {
388448 interner,
@@ -392,13 +452,13 @@ impl ReplSession {
392452 namespaces,
393453 name_map,
394454 namespace_size : snapshot. namespace_size ,
395- external_function_count : snapshot. external_function_count ,
396455 script_name : snapshot. script_name ,
397456 pending_snapshot : None ,
398457 pending_resume_state : None ,
399458 pending_futures_state : None ,
400459 capabilities : None ,
401460 future_metadata : AHashMap :: new ( ) ,
461+ external_function_set,
402462 } )
403463 }
404464
@@ -671,7 +731,7 @@ impl ReplSession {
671731 let mut vars = Vec :: new ( ) ;
672732
673733 for ( name, & slot) in & self . name_map {
674- if slot . index ( ) < self . external_function_count {
734+ if self . is_external_function_name ( name ) {
675735 continue ;
676736 }
677737 let value = global. get ( slot) ;
@@ -691,7 +751,7 @@ impl ReplSession {
691751 #[ must_use]
692752 pub fn get_variable ( & self , name : & str ) -> Option < Object > {
693753 let & slot = self . name_map . get ( name) ?;
694- if slot . index ( ) < self . external_function_count {
754+ if self . is_external_function_name ( name ) {
695755 return None ;
696756 }
697757
@@ -719,7 +779,7 @@ impl ReplSession {
719779
720780 // First check if the variable exists
721781 let & slot = self . name_map . get ( name) ?;
722- if slot . index ( ) < self . external_function_count {
782+ if self . is_external_function_name ( name ) {
723783 return None ;
724784 }
725785
@@ -791,7 +851,7 @@ impl ReplSession {
791851 let new_value = value. to_value ( & mut self . heap , & interns) ?;
792852
793853 if let Some ( & existing_slot) = self . name_map . get ( name) {
794- if existing_slot . index ( ) < self . external_function_count {
854+ if self . is_external_function_name ( name ) {
795855 new_value. drop_with_heap ( & mut self . heap ) ;
796856 return Err ( InvalidInputError :: invalid_type ( "cannot overwrite external function" ) ) ;
797857 }
@@ -834,7 +894,7 @@ impl ReplSession {
834894 return Ok ( false ) ;
835895 } ;
836896
837- if slot . index ( ) < self . external_function_count {
897+ if self . is_external_function_name ( name ) {
838898 return Err ( InvalidInputError :: invalid_type ( "cannot delete external function" ) ) ;
839899 }
840900
0 commit comments