1use crate::metadata::{FunctionCategory, FunctionInfo, ParameterInfo, TypeInfo};
7use shape_ast::ast::{
8 BuiltinFunctionDecl, BuiltinTypeDecl, DocComment, FunctionDef, Item, Program, TypeAnnotation,
9};
10use shape_ast::error::Result;
11#[cfg(test)]
12use shape_ast::parser::parse_program;
13use std::path::{Path, PathBuf};
14
15#[derive(Debug, Default)]
17pub struct StdlibMetadata {
18 pub functions: Vec<FunctionInfo>,
20 pub patterns: Vec<PatternInfo>,
22 pub intrinsic_functions: Vec<FunctionInfo>,
24 pub intrinsic_types: Vec<TypeInfo>,
26}
27
28#[derive(Debug, Clone)]
30pub struct PatternInfo {
31 pub name: String,
33 pub signature: String,
35 pub description: String,
37 pub parameters: Vec<ParameterInfo>,
39}
40
41impl StdlibMetadata {
42 pub fn empty() -> Self {
44 Self::default()
45 }
46
47 pub fn load(stdlib_path: &Path) -> Result<Self> {
49 let mut functions = Vec::new();
50 let mut patterns = Vec::new();
51 let mut intrinsic_functions = Vec::new();
52 let mut intrinsic_types = Vec::new();
53
54 if !stdlib_path.exists() {
55 return Ok(Self::empty());
56 }
57
58 let mut loader = crate::module_loader::ModuleLoader::new();
60 loader.set_stdlib_path(stdlib_path.to_path_buf());
61
62 for import_path in loader.list_stdlib_module_imports()? {
63 let module_path = import_path
64 .strip_prefix("std::")
65 .unwrap_or(&import_path)
66 .replace("::", "/");
67 match loader.load_module(&import_path) {
68 Ok(module) => {
69 Self::extract_from_program(
70 &module.ast,
71 &module_path,
72 &mut functions,
73 &mut patterns,
74 &mut intrinsic_functions,
75 &mut intrinsic_types,
76 );
77 }
78 Err(_) => {
79 }
81 }
82 }
83
84 Ok(Self {
85 functions,
86 patterns,
87 intrinsic_functions,
88 intrinsic_types,
89 })
90 }
91
92 fn extract_from_program(
93 program: &Program,
94 module_path: &str,
95 functions: &mut Vec<FunctionInfo>,
96 _patterns: &mut Vec<PatternInfo>,
97 intrinsic_functions: &mut Vec<FunctionInfo>,
98 intrinsic_types: &mut Vec<TypeInfo>,
99 ) {
100 for item in &program.items {
101 match item {
102 Item::Function(func, span) => {
103 functions.push(Self::function_to_info(
105 func,
106 module_path,
107 program.docs.comment_for_span(*span),
108 ));
109 }
110 Item::Export(export, span) => {
111 match &export.item {
113 shape_ast::ast::ExportItem::Function(func) => {
114 functions.push(Self::function_to_info(
115 func,
116 module_path,
117 program.docs.comment_for_span(*span),
118 ));
119 }
120 shape_ast::ast::ExportItem::BuiltinFunction(func) => {
121 intrinsic_functions.push(Self::builtin_function_to_info(
122 func,
123 module_path,
124 program.docs.comment_for_span(*span),
125 ));
126 }
127 shape_ast::ast::ExportItem::BuiltinType(type_decl) => {
128 intrinsic_types.push(Self::builtin_type_to_info(
129 type_decl,
130 program.docs.comment_for_span(*span),
131 ));
132 }
133 shape_ast::ast::ExportItem::TypeAlias(_) => {}
134 shape_ast::ast::ExportItem::Named(_) => {}
135 shape_ast::ast::ExportItem::Enum(_) => {}
136 shape_ast::ast::ExportItem::Struct(_) => {}
137 shape_ast::ast::ExportItem::Interface(_) => {}
138 shape_ast::ast::ExportItem::Trait(_) => {}
139 shape_ast::ast::ExportItem::Annotation(_) => {}
140 shape_ast::ast::ExportItem::ForeignFunction(_) => {
141 }
143 }
144 }
145 Item::BuiltinTypeDecl(type_decl, span) => {
146 intrinsic_types.push(Self::builtin_type_to_info(
147 type_decl,
148 program.docs.comment_for_span(*span),
149 ));
150 }
151 Item::BuiltinFunctionDecl(func_decl, span) => {
152 intrinsic_functions.push(Self::builtin_function_to_info(
153 func_decl,
154 module_path,
155 program.docs.comment_for_span(*span),
156 ));
157 }
158 _ => {}
159 }
160 }
161 }
162
163 fn infer_category_from_path(module_path: &str) -> FunctionCategory {
171 let path_lower = module_path.to_lowercase().replace("::", "/");
172
173 if path_lower.contains("/math") || path_lower.contains("/statistics") {
175 FunctionCategory::Math
176 } else if path_lower.contains("/indicators")
177 || path_lower.contains("/backtesting")
178 || path_lower.contains("/simulation")
179 {
180 FunctionCategory::Simulation
181 } else if path_lower.contains("/patterns") {
182 FunctionCategory::Utility
183 } else {
184 FunctionCategory::Utility
185 }
186 }
187
188 fn function_to_info(
189 func: &FunctionDef,
190 module_path: &str,
191 doc: Option<&DocComment>,
192 ) -> FunctionInfo {
193 let params: Vec<ParameterInfo> = func
194 .params
195 .iter()
196 .map(|p| ParameterInfo {
197 name: p.simple_name().unwrap_or("_").to_string(),
198 param_type: p
199 .type_annotation
200 .as_ref()
201 .map(Self::format_type_annotation)
202 .unwrap_or_else(|| "any".to_string()),
203 optional: p.default_value.is_some(),
204 description: doc
205 .and_then(|comment| comment.param_doc(p.simple_name().unwrap_or("_")))
206 .unwrap_or_default()
207 .to_string(),
208 constraints: None,
209 })
210 .collect();
211
212 let return_type = func
213 .return_type
214 .as_ref()
215 .map(Self::format_type_annotation)
216 .unwrap_or_else(|| "any".to_string());
217
218 let param_strs: Vec<String> = params
219 .iter()
220 .map(|p| {
221 if p.optional {
222 format!("{}?: {}", p.name, p.param_type)
223 } else {
224 format!("{}: {}", p.name, p.param_type)
225 }
226 })
227 .collect();
228
229 let signature = format!(
230 "{}({}) -> {}",
231 func.name,
232 param_strs.join(", "),
233 return_type
234 );
235
236 let category = Self::infer_category_from_path(module_path);
238
239 FunctionInfo {
240 name: func.name.clone(),
241 signature,
242 description: doc.map(Self::doc_text).unwrap_or_default(),
243 category,
244 parameters: params,
245 return_type,
246 example: doc
247 .and_then(|comment| comment.example_doc())
248 .map(str::to_string),
249 implemented: true,
250 comptime_only: false,
251 }
252 }
253
254 fn builtin_type_to_info(type_decl: &BuiltinTypeDecl, doc: Option<&DocComment>) -> TypeInfo {
255 TypeInfo {
256 name: type_decl.name.clone(),
257 description: doc.map(Self::doc_text).unwrap_or_default(),
258 }
259 }
260
261 fn builtin_function_to_info(
262 func: &BuiltinFunctionDecl,
263 module_path: &str,
264 doc: Option<&DocComment>,
265 ) -> FunctionInfo {
266 let params: Vec<ParameterInfo> = func
267 .params
268 .iter()
269 .map(|p| ParameterInfo {
270 name: p.simple_name().unwrap_or("_").to_string(),
271 param_type: p
272 .type_annotation
273 .as_ref()
274 .map(Self::format_type_annotation)
275 .unwrap_or_else(|| "any".to_string()),
276 optional: p.default_value.is_some(),
277 description: doc
278 .and_then(|comment| comment.param_doc(p.simple_name().unwrap_or("_")))
279 .unwrap_or_default()
280 .to_string(),
281 constraints: None,
282 })
283 .collect();
284 let return_type = Self::format_type_annotation(&func.return_type);
285 let type_params_str = func
286 .type_params
287 .as_ref()
288 .filter(|params| !params.is_empty())
289 .map(|params| {
290 format!(
291 "<{}>",
292 params
293 .iter()
294 .map(|p| p.name.as_str())
295 .collect::<Vec<_>>()
296 .join(", ")
297 )
298 })
299 .unwrap_or_default();
300 let signature = format!(
301 "{}{}({}) -> {}",
302 func.name,
303 type_params_str,
304 params
305 .iter()
306 .map(|p| format!("{}: {}", p.name, p.param_type))
307 .collect::<Vec<_>>()
308 .join(", "),
309 return_type
310 );
311 FunctionInfo {
312 name: func.name.clone(),
313 signature,
314 description: doc.map(Self::doc_text).unwrap_or_default(),
315 category: Self::infer_category_from_path(module_path),
316 parameters: params,
317 return_type,
318 example: doc
319 .and_then(|comment| comment.example_doc())
320 .map(str::to_string),
321 implemented: true,
322 comptime_only: crate::builtin_metadata::is_comptime_builtin_function(&func.name),
323 }
324 }
325
326 fn doc_text(comment: &DocComment) -> String {
327 if !comment.body.is_empty() {
328 comment.body.clone()
329 } else {
330 comment.summary.clone()
331 }
332 }
333
334 fn format_type_annotation(ty: &TypeAnnotation) -> String {
335 match ty {
336 TypeAnnotation::Basic(name) => name.clone(),
337 TypeAnnotation::Reference(path) => path.to_string(),
338 TypeAnnotation::Array(inner) => format!("{}[]", Self::format_type_annotation(inner)),
339 TypeAnnotation::Tuple(items) => format!(
340 "[{}]",
341 items
342 .iter()
343 .map(Self::format_type_annotation)
344 .collect::<Vec<_>>()
345 .join(", ")
346 ),
347 TypeAnnotation::Object(fields) => {
348 let inner = fields
349 .iter()
350 .map(|f| {
351 if f.optional {
352 format!(
353 "{}?: {}",
354 f.name,
355 Self::format_type_annotation(&f.type_annotation)
356 )
357 } else {
358 format!(
359 "{}: {}",
360 f.name,
361 Self::format_type_annotation(&f.type_annotation)
362 )
363 }
364 })
365 .collect::<Vec<_>>()
366 .join(", ");
367 format!("{{ {} }}", inner)
368 }
369 TypeAnnotation::Function { params, returns } => {
370 let param_list = params
371 .iter()
372 .map(|p| {
373 let ty = Self::format_type_annotation(&p.type_annotation);
374 if let Some(name) = &p.name {
375 if p.optional {
376 format!("{}?: {}", name, ty)
377 } else {
378 format!("{}: {}", name, ty)
379 }
380 } else {
381 ty
382 }
383 })
384 .collect::<Vec<_>>()
385 .join(", ");
386 format!(
387 "({}) -> {}",
388 param_list,
389 Self::format_type_annotation(returns)
390 )
391 }
392 TypeAnnotation::Union(types) => types
393 .iter()
394 .map(Self::format_type_annotation)
395 .collect::<Vec<_>>()
396 .join(" | "),
397 TypeAnnotation::Intersection(types) => types
398 .iter()
399 .map(Self::format_type_annotation)
400 .collect::<Vec<_>>()
401 .join(" + "),
402 TypeAnnotation::Generic { name, args } => format!(
403 "{}<{}>",
404 name,
405 args.iter()
406 .map(Self::format_type_annotation)
407 .collect::<Vec<_>>()
408 .join(", ")
409 ),
410 TypeAnnotation::Void => "void".to_string(),
411 TypeAnnotation::Never => "never".to_string(),
412 TypeAnnotation::Null => "null".to_string(),
413 TypeAnnotation::Undefined => "undefined".to_string(),
414 TypeAnnotation::Dyn(bounds) => format!("dyn {}", bounds.join(" + ")),
415 }
416 }
417}
418
419pub fn default_stdlib_path() -> PathBuf {
421 if let Ok(path) = std::env::var("SHAPE_STDLIB_PATH") {
423 return PathBuf::from(path);
424 }
425
426 let workspace_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../shape-core/stdlib");
428 if workspace_path.is_dir() {
429 return workspace_path;
430 }
431
432 let packaged_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("stdlib-src");
434 if packaged_path.is_dir() {
435 return packaged_path;
436 }
437
438 packaged_path
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444 #[test]
445 fn test_load_stdlib() {
446 let stdlib_path = default_stdlib_path();
447 if stdlib_path.exists() {
448 let metadata = StdlibMetadata::load(&stdlib_path).unwrap();
449 println!("Stdlib path: {:?}", stdlib_path);
451 println!("Found {} stdlib functions", metadata.functions.len());
452 for func in &metadata.functions {
453 println!(" - {}: {}", func.name, func.signature);
454 }
455 println!("Found {} stdlib patterns", metadata.patterns.len());
456 } else {
458 println!("Stdlib path does not exist: {:?}", stdlib_path);
459 }
460 }
461
462 #[test]
463 fn test_empty_stdlib() {
464 let metadata = StdlibMetadata::empty();
465 assert!(metadata.functions.is_empty());
466 assert!(metadata.patterns.is_empty());
467 }
468
469 #[test]
470 fn test_parse_all_stdlib_files() {
471 let stdlib_path = default_stdlib_path();
472 println!("Stdlib path: {:?}", stdlib_path);
473
474 let files = [
476 "core/snapshot.shape",
477 "core/math.shape",
478 "finance/indicators/moving_averages.shape",
479 ];
480
481 for file in &files {
482 let path = stdlib_path.join(file);
483 if path.exists() {
484 let content = std::fs::read_to_string(&path).unwrap();
485 match parse_program(&content) {
486 Ok(program) => {
487 let func_count = program
488 .items
489 .iter()
490 .filter(|i| {
491 matches!(
492 i,
493 shape_ast::ast::Item::Function(_, _)
494 | shape_ast::ast::Item::Export(_, _)
495 )
496 })
497 .count();
498 println!("✓ {} parsed: {} items", file, func_count);
499 }
500 Err(e) => {
501 panic!("✗ {} FAILED to parse: {:?}", file, e);
502 }
503 }
504 } else {
505 println!("⚠ {} not found", file);
506 }
507 }
508 }
509
510 #[test]
511 fn test_intrinsic_declarations_loaded_from_std_core() {
512 let stdlib_path = default_stdlib_path();
513 if !stdlib_path.exists() {
514 return;
515 }
516
517 let metadata = StdlibMetadata::load(&stdlib_path).unwrap();
518 assert!(
519 metadata
520 .intrinsic_types
521 .iter()
522 .any(|t| t.name == "AnyError"),
523 "expected AnyError intrinsic type from std::core declarations"
524 );
525 let abs = metadata
526 .intrinsic_functions
527 .iter()
528 .find(|f| f.name == "abs")
529 .expect("abs intrinsic declaration should exist");
530 assert_eq!(abs.signature, "abs(value: number) -> number");
531 assert!(
532 abs.description.contains("absolute value"),
533 "abs description should come from doc comments"
534 );
535 }
536
537}