Skip to main content

shape_runtime/engine/
execution.rs

1//! Main execution methods for Shape engine
2
3use super::types::{ExecutionMetrics, ExecutionResult, ExecutionType};
4use shape_ast::error::Result;
5use shape_ast::parser;
6
7impl super::ShapeEngine {
8    /// Execute a Shape program from source code (sync mode)
9    ///
10    /// This method allows blocking data loads (REPL mode).
11    /// For scripts/backtests, use execute_async() instead.
12    pub fn execute(
13        &mut self,
14        executor: &mut impl super::ProgramExecutor,
15        source: &str,
16    ) -> Result<ExecutionResult> {
17        // Set REPL/sync mode - allow blocking loads
18        if let Some(ctx) = self.runtime.persistent_context_mut() {
19            ctx.set_data_load_mode(crate::context::DataLoadMode::Sync);
20        }
21
22        self.execute_with_options(executor, source, false)
23    }
24
25    /// Execute a Shape program with async prefetching (Phase 6/8)
26    ///
27    /// This method:
28    /// 1. Sets Async data mode (runtime data requests use cache, not blocking)
29    /// 2. Parses and analyzes the program
30    /// 3. Determines required data (symbols/timeframes)
31    /// 4. Prefetches data concurrently
32    /// 5. Executes synchronously using cached data
33    ///
34    /// # Example
35    ///
36    /// ```ignore
37    /// let provider = DataFrameAdapter::new(...);
38    /// let mut engine = ShapeEngine::with_async_provider(provider)?;
39    /// let result = engine.execute_async(&interpreter, "let sma = close.sma(20)").await?;
40    /// ```
41    pub async fn execute_async(
42        &mut self,
43        executor: &mut impl super::ProgramExecutor,
44        source: &str,
45    ) -> Result<ExecutionResult> {
46        // Set async mode - runtime data requests must use cache
47        if let Some(ctx) = self.runtime.persistent_context_mut() {
48            ctx.set_data_load_mode(crate::context::DataLoadMode::Async);
49        }
50
51        let start_time = std::time::Instant::now();
52
53        // Parse the source
54        let parse_start = std::time::Instant::now();
55        let mut program = parser::parse_program(source)?;
56        let parse_time_ms = parse_start.elapsed().as_millis() as u64;
57
58        // Desugar high-level syntax (e.g., from-queries to method chains) before analysis
59        shape_ast::transform::desugar_program(&mut program);
60
61        let analysis_start = std::time::Instant::now();
62        let analysis_time_ms = analysis_start.elapsed().as_millis() as u64;
63
64        // Prefetch data if using async provider
65        let has_cache = self
66            .runtime
67            .persistent_context()
68            .map(|ctx| ctx.has_data_cache())
69            .unwrap_or(false);
70
71        if has_cache {
72            // Extract symbols/timeframes from program
73            let queries = self.extract_data_queries(&program)?;
74
75            // Prefetch all required data concurrently
76            if let Some(ctx) = self.runtime.persistent_context_mut() {
77                ctx.prefetch_data(queries).await?;
78            }
79        }
80
81        // Store source text for error messages during execution
82        self.set_source(source);
83
84        // Execute synchronously using cached data
85        let runtime_start = std::time::Instant::now();
86        let result = executor.execute_program(self, &program)?;
87        let runtime_time_ms = runtime_start.elapsed().as_millis() as u64;
88
89        let total_time_ms = start_time.elapsed().as_millis() as u64;
90        let memory_used_bytes = self.estimate_memory_usage();
91        let rows_processed = Some(self.default_data.row_count());
92        let messages = self.collect_messages();
93
94        Ok(ExecutionResult {
95            value: result.wire_value,
96            type_info: result.type_info,
97            execution_type: result.execution_type,
98            metrics: ExecutionMetrics {
99                execution_time_ms: total_time_ms,
100                parse_time_ms,
101                analysis_time_ms,
102                runtime_time_ms,
103                memory_used_bytes,
104                rows_processed,
105            },
106            messages,
107            content_json: result.content_json,
108            content_html: result.content_html,
109            content_terminal: result.content_terminal,
110        })
111    }
112
113    /// Execute a REPL command with persistent state
114    ///
115    /// Unlike `execute_async`, this uses incremental analysis where variables
116    /// and functions persist across commands. Call `init_repl()` once before
117    /// the first call to this method.
118    pub async fn execute_repl(
119        &mut self,
120        executor: &mut impl super::ProgramExecutor,
121        source: &str,
122    ) -> Result<ExecutionResult> {
123        // Set async mode - runtime data requests must use cache
124        if let Some(ctx) = self.runtime.persistent_context_mut() {
125            ctx.set_data_load_mode(crate::context::DataLoadMode::Async);
126        }
127
128        let start_time = std::time::Instant::now();
129
130        // Parse the source
131        let parse_start = std::time::Instant::now();
132        let mut program = parser::parse_program(source)?;
133        let parse_time_ms = parse_start.elapsed().as_millis() as u64;
134
135        // Desugar high-level syntax (e.g., from-queries to method chains) before analysis
136        shape_ast::transform::desugar_program(&mut program);
137
138        let analysis_start = std::time::Instant::now();
139        let analysis_time_ms = analysis_start.elapsed().as_millis() as u64;
140
141        // Prefetch data if using async provider
142        let has_cache = self
143            .runtime
144            .persistent_context()
145            .map(|ctx| ctx.has_data_cache())
146            .unwrap_or(false);
147
148        if has_cache {
149            let queries = self.extract_data_queries(&program)?;
150            if let Some(ctx) = self.runtime.persistent_context_mut() {
151                ctx.prefetch_data(queries).await?;
152            }
153        }
154
155        // Process imports and declarations before execution
156        self.runtime.load_program(&program, &self.default_data)?;
157
158        // Store source text for error messages during execution
159        self.set_source(source);
160
161        // Execute
162        let runtime_start = std::time::Instant::now();
163        let result = executor.execute_program(self, &program)?;
164        let runtime_time_ms = runtime_start.elapsed().as_millis() as u64;
165
166        let total_time_ms = start_time.elapsed().as_millis() as u64;
167        let memory_used_bytes = self.estimate_memory_usage();
168        let rows_processed = Some(self.default_data.row_count());
169        let messages = self.collect_messages();
170
171        Ok(ExecutionResult {
172            value: result.wire_value,
173            type_info: result.type_info,
174            execution_type: ExecutionType::Repl,
175            metrics: ExecutionMetrics {
176                execution_time_ms: total_time_ms,
177                parse_time_ms,
178                analysis_time_ms,
179                runtime_time_ms,
180                memory_used_bytes,
181                rows_processed,
182            },
183            messages,
184            content_json: result.content_json,
185            content_html: result.content_html,
186            content_terminal: result.content_terminal,
187        })
188    }
189
190    /// Parse and analyze source code without executing it.
191    ///
192    /// Returns the analyzed AST `Program`, ready for compilation.
193    /// Used by the recompile-and-resume flow.
194    pub fn parse_and_analyze(&mut self, source: &str) -> Result<shape_ast::Program> {
195        if let Some(ctx) = self.runtime.persistent_context_mut() {
196            ctx.reset_for_new_execution();
197        }
198        let mut program = parser::parse_program(source)?;
199        shape_ast::transform::desugar_program(&mut program);
200        self.set_source(source);
201        Ok(program)
202    }
203
204    /// Execute a Shape program with options
205    pub(super) fn execute_with_options(
206        &mut self,
207        executor: &mut impl super::ProgramExecutor,
208        source: &str,
209        _is_stdlib: bool,
210    ) -> Result<ExecutionResult> {
211        let start_time = std::time::Instant::now();
212
213        // Always reset variable scopes before each execution
214        if let Some(ctx) = self.runtime.persistent_context_mut() {
215            ctx.reset_for_new_execution();
216        }
217
218        // Check for deprecated APIs before parsing (the deprecated syntax may not parse cleanly)
219        Self::check_deprecated_apis(source)?;
220
221        // Parse the source
222        let parse_start = std::time::Instant::now();
223        let mut program = parser::parse_program(source)?;
224        let parse_time_ms = parse_start.elapsed().as_millis() as u64;
225
226        // Desugar high-level syntax (e.g., from-queries to method chains) before analysis
227        shape_ast::transform::desugar_program(&mut program);
228
229        let analysis_start = std::time::Instant::now();
230        let analysis_time_ms = analysis_start.elapsed().as_millis() as u64;
231
232        // Store source text for error messages during execution
233        self.set_source(source);
234
235        // Execute the program
236        let runtime_start = std::time::Instant::now();
237        let result = executor.execute_program(self, &program)?;
238        let runtime_time_ms = runtime_start.elapsed().as_millis() as u64;
239
240        let total_time_ms = start_time.elapsed().as_millis() as u64;
241
242        // Get memory usage estimate (heap allocation approximation)
243        let memory_used_bytes = self.estimate_memory_usage();
244
245        // Get rows processed count from market data
246        let rows_processed = Some(self.default_data.row_count());
247
248        // Collect any messages from the runtime
249        let messages = self.collect_messages();
250
251        Ok(ExecutionResult {
252            value: result.wire_value,
253            type_info: result.type_info,
254            execution_type: result.execution_type,
255            metrics: ExecutionMetrics {
256                execution_time_ms: total_time_ms,
257                parse_time_ms,
258                analysis_time_ms,
259                runtime_time_ms,
260                memory_used_bytes,
261                rows_processed,
262            },
263            messages,
264            content_json: result.content_json,
265            content_html: result.content_html,
266            content_terminal: result.content_terminal,
267        })
268    }
269
270    /// Execute a REPL command
271    pub fn execute_repl_command(
272        &mut self,
273        executor: &mut impl super::ProgramExecutor,
274        command: &str,
275    ) -> Result<ExecutionResult> {
276        let mut result = self.execute(executor, command)?;
277        result.execution_type = ExecutionType::Repl;
278        Ok(result)
279    }
280
281    /// Check for deprecated APIs before parsing. Some deprecated call syntax may
282    /// not parse cleanly (e.g. escaped quotes in raw strings), so we detect them
283    /// via pattern matching on the source text and produce helpful diagnostics.
284    fn check_deprecated_apis(source: &str) -> Result<()> {
285        let trimmed = source.trim();
286        if trimmed.starts_with("csv.load") || trimmed.contains("csv.load(") {
287            return Err(shape_ast::error::ShapeError::SemanticError {
288                message: "csv.load has been removed. Use the csv package instead: import { read } from \"csv\""
289                    .to_string(),
290                location: None,
291            });
292        }
293        // Check for bare load(provider, params) — the global load function was removed
294        if trimmed.starts_with("load(") || trimmed.starts_with("load (") {
295            return Err(shape_ast::error::ShapeError::SemanticError {
296                message: "load(provider, params) has been removed. Use typed data access instead: data(\"source\", { ... })"
297                    .to_string(),
298                location: None,
299            });
300        }
301        Ok(())
302    }
303
304    /// Estimate memory usage based on runtime state
305    pub(super) fn estimate_memory_usage(&self) -> Option<usize> {
306        // Estimate based on known allocations
307        let mut total = 0usize;
308
309        // Market data rows (each row ~48 bytes for 6 f64 values)
310        total += self.default_data.row_count() * 48;
311
312        // Variable storage estimate (rough approximation)
313        // This is a simplified estimate - real tracking would require custom allocator
314        total += 1024; // Base overhead for runtime structures
315
316        Some(total)
317    }
318
319    /// Collect messages from runtime execution
320    pub(super) fn collect_messages(&self) -> Vec<super::types::Message> {
321        // Currently the runtime doesn't track messages, but this provides the interface
322        // for future implementation
323        vec![]
324    }
325}