shape_runtime/engine/
execution.rs1use super::types::{ExecutionMetrics, ExecutionResult, ExecutionType};
4use shape_ast::error::Result;
5use shape_ast::parser;
6
7impl super::ShapeEngine {
8 pub fn execute(
13 &mut self,
14 executor: &impl super::ProgramExecutor,
15 source: &str,
16 ) -> Result<ExecutionResult> {
17 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 pub async fn execute_async(
42 &mut self,
43 executor: &impl super::ProgramExecutor,
44 source: &str,
45 ) -> Result<ExecutionResult> {
46 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 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 shape_ast::transform::desugar_program(&mut program);
60
61 let analysis_start = std::time::Instant::now();
63 self.analyzer.set_source(source);
64 self.analyzer.analyze(&program)?;
65 let analysis_time_ms = analysis_start.elapsed().as_millis() as u64;
66
67 let has_cache = self
69 .runtime
70 .persistent_context()
71 .map(|ctx| ctx.has_data_cache())
72 .unwrap_or(false);
73
74 if has_cache {
75 let queries = self.extract_data_queries(&program)?;
77
78 if let Some(ctx) = self.runtime.persistent_context_mut() {
80 ctx.prefetch_data(queries).await?;
81 }
82 }
83
84 self.set_source(source);
86
87 let runtime_start = std::time::Instant::now();
89 let result = executor.execute_program(self, &program)?;
90 let runtime_time_ms = runtime_start.elapsed().as_millis() as u64;
91
92 let total_time_ms = start_time.elapsed().as_millis() as u64;
93 let memory_used_bytes = self.estimate_memory_usage();
94 let rows_processed = Some(self.default_data.row_count());
95 let messages = self.collect_messages();
96
97 Ok(ExecutionResult {
98 value: result.wire_value,
99 type_info: result.type_info,
100 execution_type: result.execution_type,
101 metrics: ExecutionMetrics {
102 execution_time_ms: total_time_ms,
103 parse_time_ms,
104 analysis_time_ms,
105 runtime_time_ms,
106 memory_used_bytes,
107 rows_processed,
108 },
109 messages,
110 content_json: result.content_json,
111 content_html: result.content_html,
112 content_terminal: result.content_terminal,
113 })
114 }
115
116 pub async fn execute_repl(
122 &mut self,
123 executor: &impl super::ProgramExecutor,
124 source: &str,
125 ) -> Result<ExecutionResult> {
126 if let Some(ctx) = self.runtime.persistent_context_mut() {
128 ctx.set_data_load_mode(crate::context::DataLoadMode::Async);
129 }
130
131 let start_time = std::time::Instant::now();
132
133 let parse_start = std::time::Instant::now();
135 let mut program = parser::parse_program(source)?;
136 let parse_time_ms = parse_start.elapsed().as_millis() as u64;
137
138 shape_ast::transform::desugar_program(&mut program);
140
141 let analysis_start = std::time::Instant::now();
143 self.analyzer.set_source(source);
144 self.analyzer.analyze_incremental(&program)?;
145 let analysis_time_ms = analysis_start.elapsed().as_millis() as u64;
146
147 let has_cache = self
149 .runtime
150 .persistent_context()
151 .map(|ctx| ctx.has_data_cache())
152 .unwrap_or(false);
153
154 if has_cache {
155 let queries = self.extract_data_queries(&program)?;
156 if let Some(ctx) = self.runtime.persistent_context_mut() {
157 ctx.prefetch_data(queries).await?;
158 }
159 }
160
161 self.runtime.load_program(&program, &self.default_data)?;
163
164 self.set_source(source);
166
167 let runtime_start = std::time::Instant::now();
169 let result = executor.execute_program(self, &program)?;
170 let runtime_time_ms = runtime_start.elapsed().as_millis() as u64;
171
172 let total_time_ms = start_time.elapsed().as_millis() as u64;
173 let memory_used_bytes = self.estimate_memory_usage();
174 let rows_processed = Some(self.default_data.row_count());
175 let messages = self.collect_messages();
176
177 Ok(ExecutionResult {
178 value: result.wire_value,
179 type_info: result.type_info,
180 execution_type: ExecutionType::Repl,
181 metrics: ExecutionMetrics {
182 execution_time_ms: total_time_ms,
183 parse_time_ms,
184 analysis_time_ms,
185 runtime_time_ms,
186 memory_used_bytes,
187 rows_processed,
188 },
189 messages,
190 content_json: result.content_json,
191 content_html: result.content_html,
192 content_terminal: result.content_terminal,
193 })
194 }
195
196 pub fn parse_and_analyze(&mut self, source: &str) -> Result<shape_ast::Program> {
201 if let Some(ctx) = self.runtime.persistent_context_mut() {
202 ctx.reset_for_new_execution();
203 }
204 let mut program = parser::parse_program(source)?;
205 shape_ast::transform::desugar_program(&mut program);
206 self.analyzer.set_source(source);
207 self.analyzer.analyze(&program)?;
208 self.set_source(source);
209 Ok(program)
210 }
211
212 pub(super) fn execute_with_options(
214 &mut self,
215 executor: &impl super::ProgramExecutor,
216 source: &str,
217 _is_stdlib: bool,
218 ) -> Result<ExecutionResult> {
219 let start_time = std::time::Instant::now();
220
221 if let Some(ctx) = self.runtime.persistent_context_mut() {
223 ctx.reset_for_new_execution();
224 }
225
226 Self::check_deprecated_apis(source)?;
228
229 let parse_start = std::time::Instant::now();
231 let mut program = parser::parse_program(source)?;
232 let parse_time_ms = parse_start.elapsed().as_millis() as u64;
233
234 shape_ast::transform::desugar_program(&mut program);
236
237 let analysis_start = std::time::Instant::now();
239 self.analyzer.set_source(source);
241 self.analyzer.analyze(&program)?;
242 let analysis_time_ms = analysis_start.elapsed().as_millis() as u64;
243
244 self.set_source(source);
246
247 let runtime_start = std::time::Instant::now();
249 let result = executor.execute_program(self, &program)?;
250 let runtime_time_ms = runtime_start.elapsed().as_millis() as u64;
251
252 let total_time_ms = start_time.elapsed().as_millis() as u64;
253
254 let memory_used_bytes = self.estimate_memory_usage();
256
257 let rows_processed = Some(self.default_data.row_count());
259
260 let messages = self.collect_messages();
262
263 Ok(ExecutionResult {
264 value: result.wire_value,
265 type_info: result.type_info,
266 execution_type: result.execution_type,
267 metrics: ExecutionMetrics {
268 execution_time_ms: total_time_ms,
269 parse_time_ms,
270 analysis_time_ms,
271 runtime_time_ms,
272 memory_used_bytes,
273 rows_processed,
274 },
275 messages,
276 content_json: result.content_json,
277 content_html: result.content_html,
278 content_terminal: result.content_terminal,
279 })
280 }
281
282 pub fn execute_repl_command(
284 &mut self,
285 executor: &impl super::ProgramExecutor,
286 command: &str,
287 ) -> Result<ExecutionResult> {
288 let mut result = self.execute(executor, command)?;
289 result.execution_type = ExecutionType::Repl;
290 Ok(result)
291 }
292
293 fn check_deprecated_apis(source: &str) -> Result<()> {
297 let trimmed = source.trim();
298 if trimmed.starts_with("csv.load") || trimmed.contains("csv.load(") {
299 return Err(shape_ast::error::ShapeError::SemanticError {
300 message: "csv.load has been removed. Use the csv package instead: import { read } from \"csv\""
301 .to_string(),
302 location: None,
303 });
304 }
305 if trimmed.starts_with("load(") || trimmed.starts_with("load (") {
307 return Err(shape_ast::error::ShapeError::SemanticError {
308 message: "load(provider, params) has been removed. Use typed data access instead: data(\"source\", { ... })"
309 .to_string(),
310 location: None,
311 });
312 }
313 Ok(())
314 }
315
316 pub(super) fn estimate_memory_usage(&self) -> Option<usize> {
318 let mut total = 0usize;
320
321 total += self.default_data.row_count() * 48;
323
324 total += 1024; Some(total)
329 }
330
331 pub(super) fn collect_messages(&self) -> Vec<super::types::Message> {
333 vec![]
336 }
337}