1use crate::metadata::{FunctionCategory, FunctionInfo, ParameterInfo, TypeInfo};
7use shape_ast::ast::{
8 BuiltinFunctionDecl, BuiltinTypeDecl, FunctionDef, Item, Program, Span, 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 let source = loader
68 .resolve_module_path(&import_path)
69 .ok()
70 .and_then(|path| std::fs::read_to_string(path).ok())
71 .unwrap_or_default();
72 match loader.load_module(&import_path) {
73 Ok(module) => {
74 Self::extract_from_program(
75 &module.ast,
76 &module_path,
77 &source,
78 &mut functions,
79 &mut patterns,
80 &mut intrinsic_functions,
81 &mut intrinsic_types,
82 );
83 }
84 Err(_) => {
85 }
87 }
88 }
89
90 Ok(Self {
91 functions,
92 patterns,
93 intrinsic_functions,
94 intrinsic_types,
95 })
96 }
97
98 fn extract_from_program(
99 program: &Program,
100 module_path: &str,
101 source: &str,
102 functions: &mut Vec<FunctionInfo>,
103 _patterns: &mut Vec<PatternInfo>,
104 intrinsic_functions: &mut Vec<FunctionInfo>,
105 intrinsic_types: &mut Vec<TypeInfo>,
106 ) {
107 for item in &program.items {
108 match item {
109 Item::Function(func, _) => {
110 functions.push(Self::function_to_info(func, module_path));
112 }
113 Item::Export(export, _) => {
114 match &export.item {
116 shape_ast::ast::ExportItem::Function(func) => {
117 functions.push(Self::function_to_info(func, module_path));
118 }
119 shape_ast::ast::ExportItem::TypeAlias(_) => {}
120 shape_ast::ast::ExportItem::Named(_) => {}
121 shape_ast::ast::ExportItem::Enum(_) => {}
122 shape_ast::ast::ExportItem::Struct(_) => {}
123 shape_ast::ast::ExportItem::Interface(_) => {}
124 shape_ast::ast::ExportItem::Trait(_) => {}
125 shape_ast::ast::ExportItem::ForeignFunction(_) => {
126 }
128 }
129 }
130 Item::BuiltinTypeDecl(type_decl, span) => {
131 intrinsic_types.push(Self::builtin_type_to_info(type_decl, source, *span));
132 }
133 Item::BuiltinFunctionDecl(func_decl, span) => {
134 intrinsic_functions.push(Self::builtin_function_to_info(
135 func_decl,
136 module_path,
137 source,
138 *span,
139 ));
140 }
141 _ => {}
142 }
143 }
144 }
145
146 fn infer_category_from_path(module_path: &str) -> FunctionCategory {
154 let path_lower = module_path.to_lowercase().replace("::", "/");
155
156 if path_lower.contains("/math") || path_lower.contains("/statistics") {
158 FunctionCategory::Math
159 } else if path_lower.contains("/indicators")
160 || path_lower.contains("/backtesting")
161 || path_lower.contains("/simulation")
162 {
163 FunctionCategory::Simulation
164 } else if path_lower.contains("/patterns") {
165 FunctionCategory::Utility
166 } else {
167 FunctionCategory::Utility
168 }
169 }
170
171 fn function_to_info(func: &FunctionDef, module_path: &str) -> FunctionInfo {
172 let params: Vec<ParameterInfo> = func
173 .params
174 .iter()
175 .map(|p| ParameterInfo {
176 name: p.simple_name().unwrap_or("_").to_string(),
177 param_type: p
178 .type_annotation
179 .as_ref()
180 .map(Self::format_type_annotation)
181 .unwrap_or_else(|| "any".to_string()),
182 optional: p.default_value.is_some(),
183 description: String::new(),
184 constraints: None,
185 })
186 .collect();
187
188 let return_type = func
189 .return_type
190 .as_ref()
191 .map(Self::format_type_annotation)
192 .unwrap_or_else(|| "any".to_string());
193
194 let param_strs: Vec<String> = params
195 .iter()
196 .map(|p| {
197 if p.optional {
198 format!("{}?: {}", p.name, p.param_type)
199 } else {
200 format!("{}: {}", p.name, p.param_type)
201 }
202 })
203 .collect();
204
205 let signature = format!(
206 "{}({}) -> {}",
207 func.name,
208 param_strs.join(", "),
209 return_type
210 );
211
212 let category = Self::infer_category_from_path(module_path);
214
215 FunctionInfo {
216 name: func.name.clone(),
217 signature,
218 description: format!("Function from stdlib.{}", module_path),
219 category,
220 parameters: params,
221 return_type,
222 example: None,
223 implemented: true,
224 comptime_only: false,
225 }
226 }
227
228 fn builtin_type_to_info(type_decl: &BuiltinTypeDecl, source: &str, span: Span) -> TypeInfo {
229 let description = extract_doc_comment(source, span)
230 .filter(|s| !s.is_empty())
231 .unwrap_or_else(|| format!("Builtin type `{}`", type_decl.name));
232 TypeInfo {
233 name: type_decl.name.clone(),
234 description,
235 }
236 }
237
238 fn builtin_function_to_info(
239 func: &BuiltinFunctionDecl,
240 module_path: &str,
241 source: &str,
242 span: Span,
243 ) -> FunctionInfo {
244 let params: Vec<ParameterInfo> = func
245 .params
246 .iter()
247 .map(|p| ParameterInfo {
248 name: p.simple_name().unwrap_or("_").to_string(),
249 param_type: p
250 .type_annotation
251 .as_ref()
252 .map(Self::format_type_annotation)
253 .unwrap_or_else(|| "any".to_string()),
254 optional: p.default_value.is_some(),
255 description: String::new(),
256 constraints: None,
257 })
258 .collect();
259 let return_type = Self::format_type_annotation(&func.return_type);
260 let type_params_str = func
261 .type_params
262 .as_ref()
263 .filter(|params| !params.is_empty())
264 .map(|params| {
265 format!(
266 "<{}>",
267 params
268 .iter()
269 .map(|p| p.name.as_str())
270 .collect::<Vec<_>>()
271 .join(", ")
272 )
273 })
274 .unwrap_or_default();
275 let signature = format!(
276 "{}{}({}) -> {}",
277 func.name,
278 type_params_str,
279 params
280 .iter()
281 .map(|p| format!("{}: {}", p.name, p.param_type))
282 .collect::<Vec<_>>()
283 .join(", "),
284 return_type
285 );
286 let description = extract_doc_comment(source, span)
287 .filter(|s| !s.is_empty())
288 .unwrap_or_else(|| format!("Builtin function `{}`", func.name));
289
290 FunctionInfo {
291 name: func.name.clone(),
292 signature,
293 description,
294 category: Self::infer_category_from_path(module_path),
295 parameters: params,
296 return_type,
297 example: None,
298 implemented: true,
299 comptime_only: crate::builtin_metadata::is_comptime_builtin_function(&func.name),
300 }
301 }
302
303 fn format_type_annotation(ty: &TypeAnnotation) -> String {
304 match ty {
305 TypeAnnotation::Basic(name) | TypeAnnotation::Reference(name) => name.clone(),
306 TypeAnnotation::Array(inner) => format!("{}[]", Self::format_type_annotation(inner)),
307 TypeAnnotation::Tuple(items) => format!(
308 "[{}]",
309 items
310 .iter()
311 .map(Self::format_type_annotation)
312 .collect::<Vec<_>>()
313 .join(", ")
314 ),
315 TypeAnnotation::Object(fields) => {
316 let inner = fields
317 .iter()
318 .map(|f| {
319 if f.optional {
320 format!(
321 "{}?: {}",
322 f.name,
323 Self::format_type_annotation(&f.type_annotation)
324 )
325 } else {
326 format!(
327 "{}: {}",
328 f.name,
329 Self::format_type_annotation(&f.type_annotation)
330 )
331 }
332 })
333 .collect::<Vec<_>>()
334 .join(", ");
335 format!("{{ {} }}", inner)
336 }
337 TypeAnnotation::Function { params, returns } => {
338 let param_list = params
339 .iter()
340 .map(|p| {
341 let ty = Self::format_type_annotation(&p.type_annotation);
342 if let Some(name) = &p.name {
343 if p.optional {
344 format!("{}?: {}", name, ty)
345 } else {
346 format!("{}: {}", name, ty)
347 }
348 } else {
349 ty
350 }
351 })
352 .collect::<Vec<_>>()
353 .join(", ");
354 format!(
355 "({}) -> {}",
356 param_list,
357 Self::format_type_annotation(returns)
358 )
359 }
360 TypeAnnotation::Union(types) => types
361 .iter()
362 .map(Self::format_type_annotation)
363 .collect::<Vec<_>>()
364 .join(" | "),
365 TypeAnnotation::Intersection(types) => types
366 .iter()
367 .map(Self::format_type_annotation)
368 .collect::<Vec<_>>()
369 .join(" + "),
370 TypeAnnotation::Optional(inner) => format!("{}?", Self::format_type_annotation(inner)),
371 TypeAnnotation::Generic { name, args } => format!(
372 "{}<{}>",
373 name,
374 args.iter()
375 .map(Self::format_type_annotation)
376 .collect::<Vec<_>>()
377 .join(", ")
378 ),
379 TypeAnnotation::Void => "void".to_string(),
380 TypeAnnotation::Any => "any".to_string(),
381 TypeAnnotation::Never => "never".to_string(),
382 TypeAnnotation::Null => "null".to_string(),
383 TypeAnnotation::Undefined => "undefined".to_string(),
384 TypeAnnotation::Dyn(bounds) => format!("dyn {}", bounds.join(" + ")),
385 }
386 }
387}
388
389fn extract_doc_comment(source: &str, span: Span) -> Option<String> {
390 if source.is_empty() {
391 return None;
392 }
393
394 let clamped_start = span.start.min(source.len());
395 let line_index = source[..clamped_start]
396 .bytes()
397 .filter(|b| *b == b'\n')
398 .count();
399 let lines: Vec<&str> = source.lines().collect();
400 if lines.is_empty() || line_index == 0 {
401 return None;
402 }
403
404 let mut docs = Vec::new();
405 let mut idx = line_index.saturating_sub(1) as isize;
406
407 while idx >= 0 {
408 let line = lines[idx as usize].trim_start();
409 if let Some(rest) = line.strip_prefix("///") {
410 docs.push(rest.trim_start().to_string());
411 idx -= 1;
412 continue;
413 }
414 if line.is_empty() && !docs.is_empty() {
415 idx -= 1;
416 continue;
417 }
418 break;
419 }
420
421 if docs.is_empty() {
422 None
423 } else {
424 docs.reverse();
425 Some(docs.join("\n"))
426 }
427}
428
429pub fn default_stdlib_path() -> PathBuf {
431 if let Ok(path) = std::env::var("SHAPE_STDLIB_PATH") {
433 return PathBuf::from(path);
434 }
435
436 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../shape-core/stdlib")
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443
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}