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::Trait(_) => {}
138 shape_ast::ast::ExportItem::Annotation(_) => {}
139 shape_ast::ast::ExportItem::ForeignFunction(_) => {
140 }
142 }
143 }
144 Item::BuiltinTypeDecl(type_decl, span) => {
145 intrinsic_types.push(Self::builtin_type_to_info(
146 type_decl,
147 program.docs.comment_for_span(*span),
148 ));
149 }
150 Item::BuiltinFunctionDecl(func_decl, span) => {
151 intrinsic_functions.push(Self::builtin_function_to_info(
152 func_decl,
153 module_path,
154 program.docs.comment_for_span(*span),
155 ));
156 }
157 _ => {}
158 }
159 }
160 }
161
162 fn infer_category_from_path(module_path: &str) -> FunctionCategory {
170 let path_lower = module_path.to_lowercase().replace("::", "/");
171
172 if path_lower.contains("/math") || path_lower.contains("/statistics") {
174 FunctionCategory::Math
175 } else if path_lower.contains("/indicators")
176 || path_lower.contains("/backtesting")
177 || path_lower.contains("/simulation")
178 {
179 FunctionCategory::Simulation
180 } else if path_lower.contains("/patterns") {
181 FunctionCategory::Utility
182 } else {
183 FunctionCategory::Utility
184 }
185 }
186
187 fn function_to_info(
188 func: &FunctionDef,
189 module_path: &str,
190 doc: Option<&DocComment>,
191 ) -> FunctionInfo {
192 let params: Vec<ParameterInfo> = func
193 .params
194 .iter()
195 .map(|p| ParameterInfo {
196 name: p.simple_name().unwrap_or("_").to_string(),
197 param_type: p
198 .type_annotation
199 .as_ref()
200 .map(Self::format_type_annotation)
201 .unwrap_or_else(|| "any".to_string()),
202 optional: p.default_value.is_some(),
203 description: doc
204 .and_then(|comment| comment.param_doc(p.simple_name().unwrap_or("_")))
205 .unwrap_or_default()
206 .to_string(),
207 constraints: None,
208 })
209 .collect();
210
211 let return_type = func
212 .return_type
213 .as_ref()
214 .map(Self::format_type_annotation)
215 .unwrap_or_else(|| "any".to_string());
216
217 let param_strs: Vec<String> = params
218 .iter()
219 .map(|p| {
220 if p.optional {
221 format!("{}?: {}", p.name, p.param_type)
222 } else {
223 format!("{}: {}", p.name, p.param_type)
224 }
225 })
226 .collect();
227
228 let signature = format!(
229 "{}({}) -> {}",
230 func.name,
231 param_strs.join(", "),
232 return_type
233 );
234
235 let category = Self::infer_category_from_path(module_path);
237
238 FunctionInfo {
239 name: func.name.clone(),
240 signature,
241 description: doc.map(Self::doc_text).unwrap_or_default(),
242 category,
243 parameters: params,
244 return_type,
245 example: doc
246 .and_then(|comment| comment.example_doc())
247 .map(str::to_string),
248 implemented: true,
249 comptime_only: false,
250 }
251 }
252
253 fn builtin_type_to_info(type_decl: &BuiltinTypeDecl, doc: Option<&DocComment>) -> TypeInfo {
254 TypeInfo {
255 name: type_decl.name.clone(),
256 description: doc.map(Self::doc_text).unwrap_or_default(),
257 }
258 }
259
260 fn builtin_function_to_info(
261 func: &BuiltinFunctionDecl,
262 module_path: &str,
263 doc: Option<&DocComment>,
264 ) -> FunctionInfo {
265 let params: Vec<ParameterInfo> = func
266 .params
267 .iter()
268 .map(|p| ParameterInfo {
269 name: p.simple_name().unwrap_or("_").to_string(),
270 param_type: p
271 .type_annotation
272 .as_ref()
273 .map(Self::format_type_annotation)
274 .unwrap_or_else(|| "any".to_string()),
275 optional: p.default_value.is_some(),
276 description: doc
277 .and_then(|comment| comment.param_doc(p.simple_name().unwrap_or("_")))
278 .unwrap_or_default()
279 .to_string(),
280 constraints: None,
281 })
282 .collect();
283 let return_type = Self::format_type_annotation(&func.return_type);
284 let type_params_str = func
285 .type_params
286 .as_ref()
287 .filter(|params| !params.is_empty())
288 .map(|params| {
289 format!(
290 "<{}>",
291 params
292 .iter()
293 .map(|p| p.name())
294 .collect::<Vec<_>>()
295 .join(", ")
296 )
297 })
298 .unwrap_or_default();
299 let signature = format!(
300 "{}{}({}) -> {}",
301 func.name,
302 type_params_str,
303 params
304 .iter()
305 .map(|p| format!("{}: {}", p.name, p.param_type))
306 .collect::<Vec<_>>()
307 .join(", "),
308 return_type
309 );
310 FunctionInfo {
311 name: func.name.clone(),
312 signature,
313 description: doc.map(Self::doc_text).unwrap_or_default(),
314 category: Self::infer_category_from_path(module_path),
315 parameters: params,
316 return_type,
317 example: doc
318 .and_then(|comment| comment.example_doc())
319 .map(str::to_string),
320 implemented: true,
321 comptime_only: crate::builtin_metadata::is_comptime_builtin_function(&func.name),
322 }
323 }
324
325 fn doc_text(comment: &DocComment) -> String {
326 if !comment.body.is_empty() {
327 comment.body.clone()
328 } else {
329 comment.summary.clone()
330 }
331 }
332
333 fn format_type_annotation(ty: &TypeAnnotation) -> String {
334 match ty {
335 TypeAnnotation::Basic(name) => name.clone(),
336 TypeAnnotation::Reference(path) => path.to_string(),
337 TypeAnnotation::Array(inner) => format!("{}[]", Self::format_type_annotation(inner)),
338 TypeAnnotation::Tuple(items) => format!(
339 "[{}]",
340 items
341 .iter()
342 .map(Self::format_type_annotation)
343 .collect::<Vec<_>>()
344 .join(", ")
345 ),
346 TypeAnnotation::Object(fields) => {
347 let inner = fields
348 .iter()
349 .map(|f| {
350 if f.optional {
351 format!(
352 "{}?: {}",
353 f.name,
354 Self::format_type_annotation(&f.type_annotation)
355 )
356 } else {
357 format!(
358 "{}: {}",
359 f.name,
360 Self::format_type_annotation(&f.type_annotation)
361 )
362 }
363 })
364 .collect::<Vec<_>>()
365 .join(", ");
366 format!("{{ {} }}", inner)
367 }
368 TypeAnnotation::Function { params, returns } => {
369 let param_list = params
370 .iter()
371 .map(|p| {
372 let ty = Self::format_type_annotation(&p.type_annotation);
373 if let Some(name) = &p.name {
374 if p.optional {
375 format!("{}?: {}", name, ty)
376 } else {
377 format!("{}: {}", name, ty)
378 }
379 } else {
380 ty
381 }
382 })
383 .collect::<Vec<_>>()
384 .join(", ");
385 format!(
386 "({}) -> {}",
387 param_list,
388 Self::format_type_annotation(returns)
389 )
390 }
391 TypeAnnotation::Union(types) => types
392 .iter()
393 .map(Self::format_type_annotation)
394 .collect::<Vec<_>>()
395 .join(" | "),
396 TypeAnnotation::Intersection(types) => types
397 .iter()
398 .map(Self::format_type_annotation)
399 .collect::<Vec<_>>()
400 .join(" + "),
401 TypeAnnotation::Generic { name, args } => format!(
402 "{}<{}>",
403 name,
404 args.iter()
405 .map(Self::format_type_annotation)
406 .collect::<Vec<_>>()
407 .join(", ")
408 ),
409 TypeAnnotation::Void => "void".to_string(),
410 TypeAnnotation::Never => "never".to_string(),
411 TypeAnnotation::Null => "null".to_string(),
412 TypeAnnotation::Undefined => "undefined".to_string(),
413 TypeAnnotation::Dyn(bounds) => format!("dyn {}", bounds.join(" + ")),
414 }
415 }
416}
417
418pub fn default_stdlib_path() -> PathBuf {
420 if let Ok(path) = std::env::var("SHAPE_STDLIB_PATH") {
422 return PathBuf::from(path);
423 }
424
425 let workspace_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../shape-core/stdlib");
427 if workspace_path.is_dir() {
428 return workspace_path;
429 }
430
431 let packaged_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("stdlib-src");
433 if packaged_path.is_dir() {
434 return packaged_path;
435 }
436
437 packaged_path
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 #[test]
444 fn test_load_stdlib() {
445 let stdlib_path = default_stdlib_path();
446 if stdlib_path.exists() {
447 let metadata = StdlibMetadata::load(&stdlib_path).unwrap();
448 println!("Stdlib path: {:?}", stdlib_path);
450 println!("Found {} stdlib functions", metadata.functions.len());
451 for func in &metadata.functions {
452 println!(" - {}: {}", func.name, func.signature);
453 }
454 println!("Found {} stdlib patterns", metadata.patterns.len());
455 } else {
457 println!("Stdlib path does not exist: {:?}", stdlib_path);
458 }
459 }
460
461 #[test]
462 fn test_empty_stdlib() {
463 let metadata = StdlibMetadata::empty();
464 assert!(metadata.functions.is_empty());
465 assert!(metadata.patterns.is_empty());
466 }
467
468 #[test]
469 fn test_parse_all_stdlib_files() {
470 let stdlib_path = default_stdlib_path();
471 println!("Stdlib path: {:?}", stdlib_path);
472
473 let files = [
475 "core/snapshot.shape",
476 "core/math.shape",
477 "finance/indicators/moving_averages.shape",
478 ];
479
480 for file in &files {
481 let path = stdlib_path.join(file);
482 if path.exists() {
483 let content = std::fs::read_to_string(&path).unwrap();
484 match parse_program(&content) {
485 Ok(program) => {
486 let func_count = program
487 .items
488 .iter()
489 .filter(|i| {
490 matches!(
491 i,
492 shape_ast::ast::Item::Function(_, _)
493 | shape_ast::ast::Item::Export(_, _)
494 )
495 })
496 .count();
497 println!("✓ {} parsed: {} items", file, func_count);
498 }
499 Err(e) => {
500 panic!("✗ {} FAILED to parse: {:?}", file, e);
501 }
502 }
503 } else {
504 println!("⚠ {} not found", file);
505 }
506 }
507 }
508
509 #[test]
510 fn test_intrinsic_declarations_loaded_from_std_core() {
511 let stdlib_path = default_stdlib_path();
512 if !stdlib_path.exists() {
513 return;
514 }
515
516 let metadata = StdlibMetadata::load(&stdlib_path).unwrap();
517 assert!(
518 metadata
519 .intrinsic_types
520 .iter()
521 .any(|t| t.name == "AnyError"),
522 "expected AnyError intrinsic type from std::core declarations"
523 );
524 let abs = metadata
525 .intrinsic_functions
526 .iter()
527 .find(|f| f.name == "abs")
528 .expect("abs intrinsic declaration should exist");
529 assert_eq!(abs.signature, "abs(value: number) -> number");
530 assert!(
531 abs.description.contains("absolute value"),
532 "abs description should come from doc comments"
533 );
534 }
535
536}