Skip to main content

shape_runtime/
lib.rs

1// ShapeError carries location info for good diagnostics, making it larger than clippy's threshold.
2#![allow(clippy::result_large_err)]
3
4//! Runtime module for Shape execution
5//!
6//! This module contains the pattern matching engine, query executor,
7//! and evaluation logic for the Shape language.
8//!
9//! Shape is a general-purpose scientific computing language for high-speed
10//! time-series analysis. The runtime is domain-agnostic - all domain-specific
11//! logic (finance, IoT, sensors, etc.) belongs in stdlib modules.
12
13// Import from shape-value (moved there to break circular dependency)
14pub use shape_value::AlignedVec;
15
16pub mod alerts;
17pub mod annotation_context;
18pub mod arrow_c;
19pub mod ast_extensions;
20pub mod binary_reader;
21pub mod blob_prefetch;
22pub mod blob_store;
23pub mod blob_wire_format;
24pub mod builtin_metadata;
25pub mod chart_detect;
26pub mod closure;
27pub mod code_search;
28pub mod columnar_aggregations;
29pub mod const_eval;
30pub mod content_builders;
31pub mod content_dispatch;
32pub mod content_methods;
33pub mod content_renderer;
34pub mod context;
35pub mod crypto;
36pub mod data;
37pub mod dependency_resolver;
38pub mod distributed_gc;
39pub mod doc_extract;
40pub mod engine;
41pub mod event_queue;
42pub mod execution_proof;
43pub mod extension_context;
44pub mod extensions;
45pub mod extensions_config;
46pub mod frontmatter;
47pub mod fuzzy;
48pub mod fuzzy_property;
49pub mod hashing;
50pub mod intrinsics;
51pub mod join_executor;
52pub mod leakage;
53pub mod lookahead_guard;
54pub mod metadata;
55pub mod module_bindings;
56pub mod module_exports;
57pub mod module_loader;
58pub mod module_manifest;
59pub mod multi_table;
60pub mod multiple_testing;
61pub mod native_resolution;
62pub mod output_adapter;
63pub mod package_bundle;
64pub mod package_lock;
65pub mod pattern_library;
66pub mod pattern_state_machine;
67pub mod plugins;
68pub mod progress;
69pub mod project;
70#[cfg(all(test, feature = "deep-tests"))]
71mod project_deep_tests;
72pub mod provider_registry;
73pub mod query_builder;
74pub mod query_executor;
75pub mod query_result;
76pub mod renderers;
77pub mod schema_cache;
78pub mod schema_inference;
79pub mod simd_comparisons;
80pub mod simd_forward_fill;
81pub mod simd_i64;
82pub mod simd_rolling;
83pub mod simd_statistics;
84pub mod simulation;
85pub mod snapshot;
86pub mod state_diff;
87pub mod statistics;
88pub mod stdlib;
89pub mod stdlib_io;
90pub mod stdlib_metadata;
91pub mod stdlib_time;
92pub mod stream_executor;
93pub mod sync_bridge;
94pub mod time_window;
95pub mod timeframe_utils;
96pub mod type_mapping;
97pub mod type_methods;
98pub mod type_schema;
99pub mod type_system;
100pub mod visitor;
101pub mod window_executor;
102pub mod window_manager;
103pub mod wire_conversion;
104
105// Export commonly used types
106pub use alerts::{Alert, AlertRouter, AlertSeverity, AlertSink};
107pub use context::{DataLoadMode, ExecutionContext as Context};
108pub use data::DataFrame;
109pub use data::OwnedDataRow as RowValue;
110pub use event_queue::{
111    EventQueue, MemoryEventQueue, QueuedEvent, SharedEventQueue, SuspensionState, WaitCondition,
112    create_event_queue,
113};
114pub use extensions::{
115    ExtensionCapability, ExtensionDataSource, ExtensionLoader, ExtensionOutputSink,
116    LoadedExtension, ParsedOutputField, ParsedOutputSchema, ParsedQueryParam, ParsedQuerySchema,
117};
118pub use extensions_config::{
119    ExtensionEntry as GlobalExtensionEntry, ExtensionsConfig as GlobalExtensionsConfig,
120    load_extensions_config, load_extensions_config_from,
121};
122pub use hashing::{HashDigest, combine_hashes, hash_bytes, hash_file, hash_string};
123pub use intrinsics::{IntrinsicFn, IntrinsicsRegistry};
124pub use join_executor::JoinExecutor;
125pub use leakage::{LeakageDetector, LeakageReport, LeakageSeverity, LeakageType, LeakageWarning};
126pub use module_bindings::ModuleBindingRegistry;
127pub use module_exports::{
128    FrameInfo, ModuleContext, ModuleExportRegistry, ModuleExports, ModuleFn, VmStateAccessor,
129};
130pub use multiple_testing::{MultipleTestingGuard, MultipleTestingStats, WarningLevel};
131pub use progress::{
132    LoadPhase, ProgressEvent, ProgressGranularity, ProgressHandle, ProgressRegistry,
133};
134pub use query_result::{AlertResult, QueryResult, QueryType};
135use shape_value::ValueWord;
136pub use shape_value::ValueWord as Value;
137pub use stream_executor::{StreamEvent, StreamExecutor, StreamState};
138pub use sync_bridge::{
139    SyncDataProvider, block_on_shared, get_runtime_handle, initialize_shared_runtime,
140};
141pub use type_schema::{
142    FieldDef, FieldType, SchemaId, TypeSchema, TypeSchemaBuilder, TypeSchemaRegistry,
143};
144pub use window_executor::WindowExecutor;
145pub use wire_conversion::{
146    nb_extract_typed_value, nb_to_envelope, nb_to_wire, nb_typed_value_to_envelope, wire_to_nb,
147};
148
149use self::type_methods::TypeMethodRegistry;
150pub use error::{Result, ShapeError, SourceLocation};
151use shape_ast::ast::{Program, Query};
152pub use shape_ast::error;
153use shape_wire::WireValue;
154use std::collections::HashMap;
155use std::path::PathBuf;
156use std::sync::Arc;
157use std::sync::RwLock;
158use std::time::Duration;
159
160/// The main runtime engine for Shape
161pub struct Runtime {
162    /// Module loader for .shape files
163    module_loader: module_loader::ModuleLoader,
164    /// Persistent execution context for REPL
165    persistent_context: Option<context::ExecutionContext>,
166    /// Shared type method registry for user-defined methods
167    type_method_registry: Arc<TypeMethodRegistry>,
168    /// Registry for annotation definitions (`annotation warmup() { ... }`, etc.)
169    annotation_registry: Arc<RwLock<annotation_context::AnnotationRegistry>>,
170    /// Module binding registry shared with VM/JIT execution pipelines.
171    module_binding_registry: Arc<RwLock<module_bindings::ModuleBindingRegistry>>,
172    /// Debug mode flag — enables verbose logging/tracing when true
173    debug_mode: bool,
174    /// Execution timeout — the executor can check elapsed time against this
175    execution_timeout: Option<Duration>,
176    /// Memory limit in bytes — allocation tracking can reference this
177    memory_limit: Option<usize>,
178    /// Last structured runtime error payload produced by execution.
179    ///
180    /// Hosts can consume this to render AnyError with target-specific
181    /// renderers (ANSI/HTML/plain) without parsing flat strings.
182    last_runtime_error: Option<WireValue>,
183    /// Optional blob store for content-addressed function blobs.
184    blob_store: Option<Arc<dyn crate::blob_store::BlobStore>>,
185    /// Optional keychain for module signature verification.
186    keychain: Option<crate::crypto::keychain::Keychain>,
187}
188
189impl Default for Runtime {
190    fn default() -> Self {
191        Self::new()
192    }
193}
194
195impl Runtime {
196    /// Create a new runtime instance
197    pub fn new() -> Self {
198        Self::new_internal(true)
199    }
200
201    pub(crate) fn new_without_stdlib() -> Self {
202        Self::new_internal(false)
203    }
204
205    fn new_internal(_load_stdlib: bool) -> Self {
206        let module_loader = module_loader::ModuleLoader::new();
207        let module_binding_registry =
208            Arc::new(RwLock::new(module_bindings::ModuleBindingRegistry::new()));
209
210        Self {
211            module_loader,
212            persistent_context: None,
213            type_method_registry: Arc::new(TypeMethodRegistry::new()),
214            annotation_registry: Arc::new(RwLock::new(
215                annotation_context::AnnotationRegistry::new(),
216            )),
217            module_binding_registry,
218            debug_mode: false,
219            execution_timeout: None,
220            memory_limit: None,
221            last_runtime_error: None,
222            blob_store: None,
223            keychain: None,
224        }
225    }
226
227    pub fn annotation_registry(&self) -> Arc<RwLock<annotation_context::AnnotationRegistry>> {
228        self.annotation_registry.clone()
229    }
230
231    pub fn enable_persistent_context(&mut self, data: &DataFrame) {
232        self.persistent_context = Some(context::ExecutionContext::new_with_registry(
233            data,
234            self.type_method_registry.clone(),
235        ));
236    }
237
238    pub fn enable_persistent_context_without_data(&mut self) {
239        self.persistent_context = Some(context::ExecutionContext::new_empty_with_registry(
240            self.type_method_registry.clone(),
241        ));
242    }
243
244    pub fn set_persistent_context(&mut self, ctx: context::ExecutionContext) {
245        self.persistent_context = Some(ctx);
246    }
247
248    pub fn persistent_context(&self) -> Option<&context::ExecutionContext> {
249        self.persistent_context.as_ref()
250    }
251
252    pub fn persistent_context_mut(&mut self) -> Option<&mut context::ExecutionContext> {
253        self.persistent_context.as_mut()
254    }
255
256    /// Store the last structured runtime error payload.
257    pub fn set_last_runtime_error(&mut self, payload: Option<WireValue>) {
258        self.last_runtime_error = payload;
259    }
260
261    /// Clear any stored structured runtime error payload.
262    pub fn clear_last_runtime_error(&mut self) {
263        self.last_runtime_error = None;
264    }
265
266    /// Borrow the last structured runtime error payload.
267    pub fn last_runtime_error(&self) -> Option<&WireValue> {
268        self.last_runtime_error.as_ref()
269    }
270
271    /// Take the last structured runtime error payload.
272    pub fn take_last_runtime_error(&mut self) -> Option<WireValue> {
273        self.last_runtime_error.take()
274    }
275
276    pub fn type_method_registry(&self) -> &Arc<TypeMethodRegistry> {
277        &self.type_method_registry
278    }
279
280    /// Get the module-binding registry shared with VM/JIT execution.
281    pub fn module_binding_registry(&self) -> Arc<RwLock<module_bindings::ModuleBindingRegistry>> {
282        self.module_binding_registry.clone()
283    }
284
285    /// Add a module search path for imports
286    ///
287    /// This is useful when executing scripts - add the script's directory
288    /// to the module search paths for resolution.
289    pub fn add_module_path(&mut self, path: PathBuf) {
290        self.module_loader.add_module_path(path);
291    }
292
293    /// Set the keychain for module signature verification.
294    ///
295    /// Propagates to the module loader so it can verify module signatures
296    /// at load time.
297    pub fn set_keychain(&mut self, keychain: crate::crypto::keychain::Keychain) {
298        self.keychain = Some(keychain.clone());
299        self.module_loader.set_keychain(keychain);
300    }
301
302    /// Set the blob store for content-addressed function blobs.
303    ///
304    /// Propagates to the module loader so it can lazily fetch blobs
305    /// not found in inline caches.
306    pub fn set_blob_store(&mut self, store: Arc<dyn crate::blob_store::BlobStore>) {
307        self.blob_store = Some(store.clone());
308        self.module_loader.set_blob_store(store);
309    }
310
311    /// Set the project root and prepend its configured module paths
312    pub fn set_project_root(&mut self, root: &std::path::Path, extra_paths: &[PathBuf]) {
313        self.module_loader.set_project_root(root, extra_paths);
314    }
315
316    /// Set resolved dependency paths for the module loader
317    pub fn set_dependency_paths(
318        &mut self,
319        deps: std::collections::HashMap<String, std::path::PathBuf>,
320    ) {
321        self.module_loader.set_dependency_paths(deps);
322    }
323
324    /// Get the resolved dependency paths from the module loader.
325    pub fn get_dependency_paths(&self) -> &std::collections::HashMap<String, std::path::PathBuf> {
326        self.module_loader.get_dependency_paths()
327    }
328
329    /// Register extension-provided module artifacts into the unified module loader.
330    pub fn register_extension_module_artifacts(
331        &mut self,
332        modules: &[crate::extensions::ParsedModuleSchema],
333    ) {
334        for module in modules {
335            for artifact in &module.artifacts {
336                let code = match (&artifact.source, &artifact.compiled) {
337                    (Some(source), Some(compiled)) => module_loader::ModuleCode::Both {
338                        source: Arc::from(source.as_str()),
339                        compiled: Arc::from(compiled.clone()),
340                    },
341                    (Some(source), None) => {
342                        module_loader::ModuleCode::Source(Arc::from(source.as_str()))
343                    }
344                    (None, Some(compiled)) => {
345                        module_loader::ModuleCode::Compiled(Arc::from(compiled.clone()))
346                    }
347                    (None, None) => continue,
348                };
349                self.module_loader
350                    .register_extension_module(artifact.module_path.clone(), code);
351            }
352        }
353    }
354
355    /// Build a fresh module loader with the same search/dependency settings.
356    ///
357    /// This is used by external executors (VM/JIT) so import resolution stays
358    /// aligned with runtime configuration.
359    pub fn configured_module_loader(&self) -> module_loader::ModuleLoader {
360        let mut loader = self.module_loader.clone_without_cache();
361        if let Some(ref store) = self.blob_store {
362            loader.set_blob_store(store.clone());
363        }
364        if let Some(ref kc) = self.keychain {
365            loader.set_keychain(kc.clone());
366        }
367        loader
368    }
369
370    /// Load `std::core::*` modules via the unified module loader and register them in runtime context.
371    ///
372    /// This is the canonical stdlib bootstrap path used by the engine and CLI.
373    pub fn load_core_stdlib_into_context(&mut self, data: &DataFrame) -> Result<()> {
374        let module_paths = self.module_loader.list_core_stdlib_module_imports()?;
375
376        for module_path in module_paths {
377            // Skip the prelude module — it contains re-export imports that reference
378            // non-exported symbols (traits, constants). Prelude injection is handled
379            // separately by the graph-based compilation pipeline in the bytecode executor.
380            if module_path == "std::core::prelude" {
381                continue;
382            }
383
384            let resolved = self.module_loader.resolve_module_path(&module_path).ok();
385            let context_dir = resolved
386                .as_ref()
387                .and_then(|path| path.parent().map(|p| p.to_path_buf()));
388            let module = self.module_loader.load_module(&module_path)?;
389            self.load_program_with_context(&module.ast, data, context_dir.as_ref())?;
390        }
391
392        Ok(())
393    }
394
395    pub fn load_program(&mut self, program: &Program, data: &DataFrame) -> Result<()> {
396        self.load_program_with_context(program, data, None)
397    }
398
399    pub(crate) fn load_program_with_context(
400        &mut self,
401        program: &Program,
402        data: &DataFrame,
403        context_dir: Option<&PathBuf>,
404    ) -> Result<()> {
405        let mut persistent_ctx = self.persistent_context.take();
406
407        let result = if let Some(ref mut ctx) = persistent_ctx {
408            if data.row_count() > 0 {
409                ctx.update_data(data);
410            }
411            self.process_program_items(program, ctx, context_dir)
412        } else {
413            let mut ctx = context::ExecutionContext::new_with_registry(
414                data,
415                self.type_method_registry.clone(),
416            );
417            self.process_program_items(program, &mut ctx, context_dir)
418        };
419
420        self.persistent_context = persistent_ctx;
421        result
422    }
423
424    fn process_program_items(
425        &mut self,
426        program: &Program,
427        ctx: &mut context::ExecutionContext,
428        context_dir: Option<&PathBuf>,
429    ) -> Result<()> {
430        for item in &program.items {
431            match item {
432                shape_ast::ast::Item::Import(import, _) => {
433                    let module = self
434                        .module_loader
435                        .load_module_with_context(&import.from, context_dir)?;
436
437                    match &import.items {
438                        shape_ast::ast::ImportItems::Named(imports) => {
439                            for import_spec in imports {
440                                if let Some(export) = module.exports.get(&import_spec.name) {
441                                    if import_spec.is_annotation {
442                                        continue;
443                                    }
444                                    let var_name =
445                                        import_spec.alias.as_ref().unwrap_or(&import_spec.name);
446                                    match export {
447                                        module_loader::Export::Function(_) => {
448                                            // Function exports are registered by the VM
449                                        }
450                                        module_loader::Export::Value(value) => {
451                                            if ctx.get_variable(var_name)?.is_none() {
452                                                ctx.set_variable(var_name, value.clone())?;
453                                            }
454                                            self.module_binding_registry
455                                                .write()
456                                                .unwrap()
457                                                .register_const(var_name, value.clone())?;
458                                        }
459                                        _ => {}
460                                    }
461                                } else {
462                                    return Err(ShapeError::ModuleError {
463                                        message: format!(
464                                            "Export '{}' not found in module '{}'",
465                                            import_spec.name, import.from
466                                        ),
467                                        module_path: Some(import.from.clone().into()),
468                                    });
469                                }
470                            }
471                        }
472                        shape_ast::ast::ImportItems::Namespace { .. } => {
473                            // Namespace imports for extension modules are handled by the VM
474                        }
475                    }
476                }
477                shape_ast::ast::Item::Export(export, _) => {
478                    match &export.item {
479                        shape_ast::ast::ExportItem::Function(_) => {
480                            // Function exports are registered by the VM
481                        }
482                        shape_ast::ast::ExportItem::BuiltinFunction(_)
483                        | shape_ast::ast::ExportItem::BuiltinType(_)
484                        | shape_ast::ast::ExportItem::Annotation(_) => {}
485                        shape_ast::ast::ExportItem::Named(specs) => {
486                            for spec in specs {
487                                if let Ok(value) = ctx.get_variable(&spec.name) {
488                                    if value.is_none() {
489                                        return Err(ShapeError::RuntimeError {
490                                            message: format!(
491                                                "Cannot export undefined variable '{}'",
492                                                spec.name
493                                            ),
494                                            location: None,
495                                        });
496                                    }
497                                }
498                            }
499                        }
500                        shape_ast::ast::ExportItem::TypeAlias(alias_def) => {
501                            let overrides = HashMap::new();
502                            if let Some(ref overrides_ast) = alias_def.meta_param_overrides {
503                                for (_key, _expr) in overrides_ast {
504                                    // TODO: Use const_eval for simple literal evaluation
505                                }
506                            }
507
508                            let base_type = match &alias_def.type_annotation {
509                                shape_ast::ast::TypeAnnotation::Basic(n) => n.clone(),
510                                shape_ast::ast::TypeAnnotation::Reference(n) => n.to_string(),
511                                _ => "any".to_string(),
512                            };
513
514                            ctx.register_type_alias(&alias_def.name, &base_type, Some(overrides));
515                        }
516                        shape_ast::ast::ExportItem::Enum(enum_def) => {
517                            ctx.register_enum(enum_def.clone());
518                        }
519                        shape_ast::ast::ExportItem::Struct(struct_def) => {
520                            ctx.register_struct_type(struct_def.clone());
521                        }
522                        shape_ast::ast::ExportItem::Interface(_)
523                        | shape_ast::ast::ExportItem::Trait(_) => {
524                            // Type definitions handled at compile time
525                        }
526                        shape_ast::ast::ExportItem::ForeignFunction(_) => {
527                            // Foreign function exports are registered by the VM
528                        }
529                    }
530                }
531                shape_ast::ast::Item::Function(_function, _) => {
532                    // Functions are registered by the VM
533                }
534                shape_ast::ast::Item::TypeAlias(alias_def, _) => {
535                    let overrides = HashMap::new();
536                    if let Some(ref overrides_ast) = alias_def.meta_param_overrides {
537                        for (_key, _expr) in overrides_ast {
538                            // TODO: Use const_eval for simple literal evaluation
539                        }
540                    }
541
542                    let base_type = match &alias_def.type_annotation {
543                        shape_ast::ast::TypeAnnotation::Basic(n) => n.clone(),
544                        shape_ast::ast::TypeAnnotation::Reference(n) => n.to_string(),
545                        _ => "any".to_string(),
546                    };
547
548                    ctx.register_type_alias(&alias_def.name, &base_type, Some(overrides));
549                }
550                shape_ast::ast::Item::Interface(_, _) => {}
551                shape_ast::ast::Item::Trait(_, _) => {}
552                shape_ast::ast::Item::Impl(_, _) => {}
553                shape_ast::ast::Item::Enum(enum_def, _) => {
554                    ctx.register_enum(enum_def.clone());
555                }
556                shape_ast::ast::Item::StructType(struct_def, _) => {
557                    ctx.register_struct_type(struct_def.clone());
558                }
559                shape_ast::ast::Item::Extend(extend_stmt, _) => {
560                    let registry = ctx.type_method_registry();
561                    for method in &extend_stmt.methods {
562                        registry.register_method(&extend_stmt.type_name, method.clone());
563                    }
564                }
565                shape_ast::ast::Item::AnnotationDef(ann_def, _) => {
566                    self.annotation_registry
567                        .write()
568                        .unwrap()
569                        .register(ann_def.clone());
570                }
571                _ => {}
572            }
573        }
574        Ok(())
575    }
576
577    pub fn execute_query(
578        &mut self,
579        query: &shape_ast::ast::Item,
580        data: &DataFrame,
581    ) -> Result<QueryResult> {
582        let mut persistent_ctx = self.persistent_context.take();
583        let result = if let Some(ref mut ctx) = persistent_ctx {
584            ctx.update_data(data);
585            self.execute_query_with_context(query, ctx)
586        } else {
587            let mut ctx = context::ExecutionContext::new_with_registry(
588                data,
589                self.type_method_registry.clone(),
590            );
591            self.execute_query_with_context(query, &mut ctx)
592        };
593        self.persistent_context = persistent_ctx;
594        result
595    }
596
597    fn execute_query_with_context(
598        &mut self,
599        query: &shape_ast::ast::Item,
600        ctx: &mut context::ExecutionContext,
601    ) -> Result<QueryResult> {
602        let id = ctx.get_current_id().unwrap_or_default();
603        let timeframe = ctx
604            .get_current_timeframe()
605            .map(|t| t.to_string())
606            .unwrap_or_default();
607
608        match query {
609            shape_ast::ast::Item::Query(q, _) => match q {
610                Query::Backtest(_) => Err(ShapeError::RuntimeError {
611                    message: "Backtesting not supported in core runtime.".to_string(),
612                    location: None,
613                }),
614                Query::Alert(_alert_query) => {
615                    let alert_id = format!("alert_{}", chrono::Utc::now().timestamp_micros());
616                    Ok(
617                        QueryResult::new(QueryType::Alert, id, timeframe).with_alert(AlertResult {
618                            id: alert_id,
619                            active: false,
620                            message: "Alert triggered".to_string(),
621                            level: "info".to_string(),
622                            timestamp: chrono::Utc::now(),
623                        }),
624                    )
625                }
626                Query::With(with_query) => {
627                    for cte in &with_query.ctes {
628                        let cte_result = self.execute_query_with_context(
629                            &shape_ast::ast::Item::Query(
630                                (*cte.query).clone(),
631                                shape_ast::ast::Span::DUMMY,
632                            ),
633                            ctx,
634                        )?;
635                        let value = cte_result.value.unwrap_or(ValueWord::none());
636                        ctx.set_variable_nb(&cte.name, value)?;
637                    }
638                    self.execute_query_with_context(
639                        &shape_ast::ast::Item::Query(
640                            (*with_query.query).clone(),
641                            shape_ast::ast::Span::DUMMY,
642                        ),
643                        ctx,
644                    )
645                }
646            },
647            shape_ast::ast::Item::Expression(_, _) => {
648                Ok(QueryResult::new(QueryType::Value, id, timeframe).with_value(ValueWord::none()))
649            }
650            shape_ast::ast::Item::VariableDecl(var_decl, _) => {
651                ctx.declare_pattern(&var_decl.pattern, var_decl.kind, ValueWord::none())?;
652                Ok(QueryResult::new(QueryType::Value, id, timeframe).with_value(ValueWord::none()))
653            }
654            shape_ast::ast::Item::Assignment(assignment, _) => {
655                ctx.set_pattern(&assignment.pattern, ValueWord::none())?;
656                Ok(QueryResult::new(QueryType::Value, id, timeframe).with_value(ValueWord::none()))
657            }
658            shape_ast::ast::Item::Statement(_, _) => {
659                Ok(QueryResult::new(QueryType::Value, id, timeframe).with_value(ValueWord::none()))
660            }
661            _ => Err(ShapeError::RuntimeError {
662                message: format!("Unsupported item for query execution: {:?}", query),
663                location: None,
664            }),
665        }
666    }
667
668    pub fn execute_without_data(&mut self, item: &shape_ast::ast::Item) -> Result<QueryResult> {
669        let mut persistent_ctx = self.persistent_context.take();
670
671        let result = if let Some(ref mut ctx) = persistent_ctx {
672            match item {
673                shape_ast::ast::Item::Expression(_, _) => {
674                    Ok(
675                        QueryResult::new(QueryType::Value, "".to_string(), "".to_string())
676                            .with_value(ValueWord::none()),
677                    )
678                }
679                shape_ast::ast::Item::Statement(_, _) => {
680                    Ok(
681                        QueryResult::new(QueryType::Value, "".to_string(), "".to_string())
682                            .with_value(ValueWord::none()),
683                    )
684                }
685                shape_ast::ast::Item::VariableDecl(var_decl, _) => {
686                    ctx.declare_pattern(&var_decl.pattern, var_decl.kind, ValueWord::none())?;
687                    Ok(
688                        QueryResult::new(QueryType::Value, "".to_string(), "".to_string())
689                            .with_value(ValueWord::none()),
690                    )
691                }
692                shape_ast::ast::Item::Assignment(assignment, _) => {
693                    ctx.set_pattern(&assignment.pattern, ValueWord::none())?;
694                    Ok(
695                        QueryResult::new(QueryType::Value, "".to_string(), "".to_string())
696                            .with_value(ValueWord::none()),
697                    )
698                }
699                shape_ast::ast::Item::TypeAlias(_, _) => {
700                    self.process_program_items(
701                        &Program {
702                            items: vec![item.clone()],
703                            docs: shape_ast::ast::ProgramDocs::default(),
704                        },
705                        ctx,
706                        None,
707                    )?;
708                    Ok(
709                        QueryResult::new(QueryType::Value, "".to_string(), "".to_string())
710                            .with_value(ValueWord::none()),
711                    )
712                }
713                _ => Err(ShapeError::RuntimeError {
714                    message: format!("Operation requires context: {:?}", item),
715                    location: None,
716                }),
717            }
718        } else {
719            let mut ctx = context::ExecutionContext::new_empty_with_registry(
720                self.type_method_registry.clone(),
721            );
722            self.process_program_items(
723                &Program {
724                    items: vec![item.clone()],
725                    docs: shape_ast::ast::ProgramDocs::default(),
726                },
727                &mut ctx,
728                None,
729            )?;
730            persistent_ctx = Some(ctx);
731            Ok(
732                QueryResult::new(QueryType::Value, "".to_string(), "".to_string())
733                    .with_value(ValueWord::none()),
734            )
735        };
736
737        self.persistent_context = persistent_ctx;
738        result
739    }
740
741    /// Format a value using Shape format definitions from stdlib
742    ///
743    /// Currently a placeholder until VM-based format execution is implemented.
744    pub fn format_value(
745        &mut self,
746        _value: Value,
747        type_name: &str,
748        format_name: Option<&str>,
749        _param_overrides: std::collections::HashMap<String, Value>,
750    ) -> Result<String> {
751        if let Some(name) = format_name {
752            Ok(format!("<formatted {} as {}>", type_name, name))
753        } else {
754            Ok(format!("<formatted {}>", type_name))
755        }
756    }
757
758    /// Enable or disable debug mode.
759    ///
760    /// When enabled, the runtime produces verbose tracing output via `tracing`
761    /// and enables any debug-only code paths in the executor.
762    pub fn set_debug_mode(&mut self, enabled: bool) {
763        self.debug_mode = enabled;
764        if enabled {
765            tracing::debug!("Shape runtime debug mode enabled");
766        }
767    }
768
769    /// Query whether debug mode is active.
770    pub fn debug_mode(&self) -> bool {
771        self.debug_mode
772    }
773
774    /// Set the maximum wall-clock duration for a single execution.
775    ///
776    /// The executor can periodically check elapsed time against this limit
777    /// and abort with a timeout error if exceeded.
778    pub fn set_execution_timeout(&mut self, timeout: Duration) {
779        self.execution_timeout = Some(timeout);
780    }
781
782    /// Query the configured execution timeout, if any.
783    pub fn execution_timeout(&self) -> Option<Duration> {
784        self.execution_timeout
785    }
786
787    /// Set a memory limit (in bytes) for the runtime.
788    ///
789    /// Allocation tracking can reference this value to decide when to refuse
790    /// new allocations or trigger garbage collection.
791    pub fn set_memory_limit(&mut self, limit: usize) {
792        self.memory_limit = Some(limit);
793    }
794
795    /// Query the configured memory limit, if any.
796    pub fn memory_limit(&self) -> Option<usize> {
797        self.memory_limit
798    }
799}