1use super::types::{ExecutionMetrics, ExecutionResult, ExecutionType};
4use crate::type_schema::with_async_scope;
5use shape_ast::error::Result;
6use shape_ast::parser;
7
8impl super::ShapeEngine {
9 pub fn execute(
14 &mut self,
15 executor: &mut impl super::ProgramExecutor,
16 source: &str,
17 ) -> Result<ExecutionResult> {
18 if let Some(ctx) = self.runtime.persistent_context_mut() {
20 ctx.set_data_load_mode(crate::context::DataLoadMode::Sync);
21 }
22
23 self.execute_with_options(executor, source, false)
24 }
25
26 pub async fn execute_async(
43 &mut self,
44 executor: &mut impl super::ProgramExecutor,
45 source: &str,
46 ) -> Result<ExecutionResult> {
47 if let Some(ctx) = self.runtime.persistent_context_mut() {
49 ctx.set_data_load_mode(crate::context::DataLoadMode::Async);
50 }
51
52 let schema_registry = self.runtime.schema_registry_arc();
55 with_async_scope(schema_registry, self.execute_async_inner(executor, source)).await
56 }
57
58 async fn execute_async_inner(
59 &mut self,
60 executor: &mut impl super::ProgramExecutor,
61 source: &str,
62 ) -> Result<ExecutionResult> {
63 let start_time = std::time::Instant::now();
64
65 let parse_start = std::time::Instant::now();
67 let mut program = parser::parse_program(source)?;
68 let parse_time_ms = parse_start.elapsed().as_millis() as u64;
69
70 shape_ast::transform::desugar_program(&mut program);
72
73 let analysis_start = std::time::Instant::now();
74 let analysis_time_ms = analysis_start.elapsed().as_millis() as u64;
75
76 let has_cache = self
78 .runtime
79 .persistent_context()
80 .map(|ctx| ctx.has_data_cache())
81 .unwrap_or(false);
82
83 if has_cache {
84 let queries = self.extract_data_queries(&program)?;
86
87 if let Some(ctx) = self.runtime.persistent_context_mut() {
89 ctx.prefetch_data(queries).await?;
90 }
91 }
92
93 self.set_source(source);
95
96 let runtime_start = std::time::Instant::now();
98 let result = executor.execute_program(self, &program)?;
99 let runtime_time_ms = runtime_start.elapsed().as_millis() as u64;
100
101 let total_time_ms = start_time.elapsed().as_millis() as u64;
102 let memory_used_bytes = self.estimate_memory_usage();
103 let rows_processed = Some(self.default_data.row_count());
104 let messages = self.collect_messages();
105
106 Ok(ExecutionResult {
107 value: result.wire_value,
108 type_info: result.type_info,
109 execution_type: result.execution_type,
110 metrics: ExecutionMetrics {
111 execution_time_ms: total_time_ms,
112 parse_time_ms,
113 analysis_time_ms,
114 runtime_time_ms,
115 memory_used_bytes,
116 rows_processed,
117 },
118 messages,
119 content_json: result.content_json,
120 content_html: result.content_html,
121 content_terminal: result.content_terminal,
122 })
123 }
124
125 pub async fn execute_repl(
131 &mut self,
132 executor: &mut impl super::ProgramExecutor,
133 source: &str,
134 ) -> Result<ExecutionResult> {
135 if let Some(ctx) = self.runtime.persistent_context_mut() {
137 ctx.set_data_load_mode(crate::context::DataLoadMode::Async);
138 }
139
140 let schema_registry = self.runtime.schema_registry_arc();
143 with_async_scope(schema_registry, self.execute_repl_inner(executor, source)).await
144 }
145
146 async fn execute_repl_inner(
147 &mut self,
148 executor: &mut impl super::ProgramExecutor,
149 source: &str,
150 ) -> Result<ExecutionResult> {
151 let start_time = std::time::Instant::now();
152
153 let parse_start = std::time::Instant::now();
155 let mut program = parser::parse_program(source)?;
156 let parse_time_ms = parse_start.elapsed().as_millis() as u64;
157
158 shape_ast::transform::desugar_program(&mut program);
160
161 let analysis_start = std::time::Instant::now();
162 let analysis_time_ms = analysis_start.elapsed().as_millis() as u64;
163
164 let has_cache = self
166 .runtime
167 .persistent_context()
168 .map(|ctx| ctx.has_data_cache())
169 .unwrap_or(false);
170
171 if has_cache {
172 let queries = self.extract_data_queries(&program)?;
173 if let Some(ctx) = self.runtime.persistent_context_mut() {
174 ctx.prefetch_data(queries).await?;
175 }
176 }
177
178 self.runtime.load_program(&program, &self.default_data)?;
188
189 self.set_source(source);
191
192 let runtime_start = std::time::Instant::now();
194 let result = executor.execute_program(self, &program)?;
195 let runtime_time_ms = runtime_start.elapsed().as_millis() as u64;
196
197 let total_time_ms = start_time.elapsed().as_millis() as u64;
198 let memory_used_bytes = self.estimate_memory_usage();
199 let rows_processed = Some(self.default_data.row_count());
200 let messages = self.collect_messages();
201
202 Ok(ExecutionResult {
203 value: result.wire_value,
204 type_info: result.type_info,
205 execution_type: ExecutionType::Repl,
206 metrics: ExecutionMetrics {
207 execution_time_ms: total_time_ms,
208 parse_time_ms,
209 analysis_time_ms,
210 runtime_time_ms,
211 memory_used_bytes,
212 rows_processed,
213 },
214 messages,
215 content_json: result.content_json,
216 content_html: result.content_html,
217 content_terminal: result.content_terminal,
218 })
219 }
220
221 pub fn parse_and_analyze(&mut self, source: &str) -> Result<shape_ast::Program> {
226 let _scope = self.runtime.enter_schema_scope();
229
230 if let Some(ctx) = self.runtime.persistent_context_mut() {
231 ctx.reset_for_new_execution();
232 }
233 let mut program = parser::parse_program(source)?;
234 shape_ast::transform::desugar_program(&mut program);
235 self.set_source(source);
236 Ok(program)
237 }
238
239 pub(super) fn execute_with_options(
241 &mut self,
242 executor: &mut impl super::ProgramExecutor,
243 source: &str,
244 _is_stdlib: bool,
245 ) -> Result<ExecutionResult> {
246 let _scope = self.runtime.enter_schema_scope();
249
250 let start_time = std::time::Instant::now();
251
252 if let Some(ctx) = self.runtime.persistent_context_mut() {
254 ctx.reset_for_new_execution();
255 }
256
257 Self::check_deprecated_apis(source)?;
259
260 let parse_start = std::time::Instant::now();
262 let mut program = parser::parse_program(source)?;
263 let parse_time_ms = parse_start.elapsed().as_millis() as u64;
264
265 shape_ast::transform::desugar_program(&mut program);
267
268 let analysis_start = std::time::Instant::now();
269 let analysis_time_ms = analysis_start.elapsed().as_millis() as u64;
270
271 self.set_source(source);
273
274 let runtime_start = std::time::Instant::now();
276 let result = executor.execute_program(self, &program)?;
277 let runtime_time_ms = runtime_start.elapsed().as_millis() as u64;
278
279 let total_time_ms = start_time.elapsed().as_millis() as u64;
280
281 let memory_used_bytes = self.estimate_memory_usage();
283
284 let rows_processed = Some(self.default_data.row_count());
286
287 let messages = self.collect_messages();
289
290 Ok(ExecutionResult {
291 value: result.wire_value,
292 type_info: result.type_info,
293 execution_type: result.execution_type,
294 metrics: ExecutionMetrics {
295 execution_time_ms: total_time_ms,
296 parse_time_ms,
297 analysis_time_ms,
298 runtime_time_ms,
299 memory_used_bytes,
300 rows_processed,
301 },
302 messages,
303 content_json: result.content_json,
304 content_html: result.content_html,
305 content_terminal: result.content_terminal,
306 })
307 }
308
309 pub fn execute_repl_command(
311 &mut self,
312 executor: &mut impl super::ProgramExecutor,
313 command: &str,
314 ) -> Result<ExecutionResult> {
315 let mut result = self.execute(executor, command)?;
316 result.execution_type = ExecutionType::Repl;
317 Ok(result)
318 }
319
320 fn check_deprecated_apis(source: &str) -> Result<()> {
324 let trimmed = source.trim();
325 if trimmed.starts_with("csv.load") || trimmed.contains("csv.load(") {
326 return Err(shape_ast::error::ShapeError::SemanticError {
327 message: "csv.load has been removed. Use the csv package instead: import { read } from \"csv\""
328 .to_string(),
329 location: None,
330 });
331 }
332 if trimmed.starts_with("load(") || trimmed.starts_with("load (") {
334 return Err(shape_ast::error::ShapeError::SemanticError {
335 message: "load(provider, params) has been removed. Use typed data access instead: data(\"source\", { ... })"
336 .to_string(),
337 location: None,
338 });
339 }
340 Ok(())
341 }
342
343 pub(super) fn estimate_memory_usage(&self) -> Option<usize> {
345 let mut total = 0usize;
347
348 total += self.default_data.row_count() * 48;
350
351 total += 1024; Some(total)
356 }
357
358 pub(super) fn collect_messages(&self) -> Vec<super::types::Message> {
360 vec![]
363 }
364
365 pub fn repl_definitions(&self) -> &[shape_ast::ast::Item] {
373 &self.repl_definitions
374 }
375
376 pub fn has_repl_definitions(&self) -> bool {
379 self.repl_persistence && !self.repl_definitions.is_empty()
380 }
381
382 pub fn absorb_repl_cell_definitions(&mut self, program: &shape_ast::Program) {
389 if !self.repl_persistence {
390 return;
391 }
392 let cell = Self::collect_definition_items(program);
393 if !cell.is_empty() {
394 self.absorb_repl_definitions(cell);
395 }
396 }
397
398 pub fn repl_user_schemas(
403 &self,
404 ) -> &std::collections::HashMap<String, crate::type_schema::TypeSchema> {
405 &self.repl_user_schemas
406 }
407
408 pub fn remember_repl_user_schema(&mut self, schema: crate::type_schema::TypeSchema) {
414 if !self.repl_persistence {
415 return;
416 }
417 self.repl_user_schemas
418 .entry(schema.name.clone())
419 .or_insert(schema);
420 }
421
422 pub fn repl_user_type_names(program: &shape_ast::Program) -> Vec<String> {
426 use shape_ast::ast::Item;
427 let mut names = Vec::new();
428 for item in &program.items {
429 match item {
430 Item::StructType(s, _) => names.push(s.name.clone()),
431 Item::Enum(e, _) => names.push(e.name.clone()),
432 _ => {}
433 }
434 }
435 names
436 }
437
438 fn collect_definition_items(program: &shape_ast::Program) -> Vec<shape_ast::ast::Item> {
449 use shape_ast::ast::Item;
450 program
451 .items
452 .iter()
453 .filter(|item| {
454 matches!(
455 item,
456 Item::Function(..)
457 | Item::StructType(..)
458 | Item::Enum(..)
459 | Item::Trait(..)
460 | Item::Impl(..)
461 | Item::Extend(..)
462 | Item::TypeAlias(..)
463 | Item::AnnotationDef(..)
464 | Item::ForeignFunction(..)
465 | Item::BuiltinTypeDecl(..)
466 | Item::BuiltinFunctionDecl(..)
467 )
468 })
469 .cloned()
470 .collect()
471 }
472
473 fn absorb_repl_definitions(&mut self, cell: Vec<shape_ast::ast::Item>) {
484 for item in cell {
485 let key = Self::definition_identity(&item);
486 if let Some(pos) = self
487 .repl_definitions
488 .iter()
489 .position(|existing| Self::definition_identity(existing) == key)
490 {
491 self.repl_definitions[pos] = item;
492 } else {
493 self.repl_definitions.push(item);
494 }
495 }
496 }
497
498 fn definition_identity(item: &shape_ast::ast::Item) -> String {
502 use shape_ast::ast::Item;
503 match item {
504 Item::Function(f, _) => format!("fn:{}", f.name),
505 Item::StructType(s, _) => format!("type:{}", s.name),
506 Item::Enum(e, _) => format!("enum:{}", e.name),
507 Item::Trait(t, _) => format!("trait:{}", t.name),
508 Item::TypeAlias(a, _) => format!("alias:{}", a.name),
509 Item::AnnotationDef(a, _) => format!("ann:{}", a.name),
510 Item::ForeignFunction(f, _) => format!("fn:{}", f.name),
511 Item::BuiltinTypeDecl(d, _) => format!("type:{}", d.name),
512 Item::BuiltinFunctionDecl(d, _) => format!("fn:{}", d.name),
513 Item::Impl(i, _) => format!(
514 "impl:{}:{}:{}",
515 Self::type_name_key(&i.trait_name),
516 Self::type_name_key(&i.target_type),
517 i.impl_name.as_deref().unwrap_or(""),
518 ),
519 Item::Extend(e, _) => {
520 format!("extend:{}", Self::type_name_key(&e.type_name))
521 }
522 other => format!("other:{:p}", other as *const _),
525 }
526 }
527
528 fn type_name_key(tn: &shape_ast::ast::TypeName) -> &str {
531 match tn {
532 shape_ast::ast::TypeName::Simple(path) => path.name(),
533 shape_ast::ast::TypeName::Generic { name, .. } => name.name(),
534 }
535 }
536}