Skip to main content

shape_runtime/
stdlib_metadata.rs

1//! Stdlib metadata extractor for LSP introspection
2//!
3//! This module parses Shape stdlib files to extract function and pattern
4//! metadata for use in LSP completions and hover information.
5
6use 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/// Metadata extracted from Shape stdlib
16#[derive(Debug, Default)]
17pub struct StdlibMetadata {
18    /// Exported functions from stdlib
19    pub functions: Vec<FunctionInfo>,
20    /// Exported patterns from stdlib
21    pub patterns: Vec<PatternInfo>,
22    /// Declaration-only intrinsic functions from std/core
23    pub intrinsic_functions: Vec<FunctionInfo>,
24    /// Declaration-only intrinsic types from std/core
25    pub intrinsic_types: Vec<TypeInfo>,
26}
27
28/// Pattern metadata for LSP
29#[derive(Debug, Clone)]
30pub struct PatternInfo {
31    /// Pattern name
32    pub name: String,
33    /// Pattern signature
34    pub signature: String,
35    /// Description (if available from comments)
36    pub description: String,
37    /// Parameters (pattern variables)
38    pub parameters: Vec<ParameterInfo>,
39}
40
41impl StdlibMetadata {
42    /// Create empty stdlib metadata
43    pub fn empty() -> Self {
44        Self::default()
45    }
46
47    /// Load and parse all stdlib modules from the given path
48    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        // Use the unified module loader for stdlib discovery + parsing.
59        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                    // Skip modules that fail to parse/compile metadata.
80                }
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                    // All top-level functions are considered exports
104                    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                    // Handle explicit exports
112                    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                            // Foreign functions are not stdlib intrinsics
141                        }
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    /// Infer function category from module path (domain-agnostic)
163    ///
164    /// Uses directory structure to determine category:
165    /// - core/math, core/statistics → Math
166    /// - */indicators/*, */backtesting/*, */simulation/* → Simulation
167    /// - */patterns/* → Utility
168    /// - Default → Utility
169    fn infer_category_from_path(module_path: &str) -> FunctionCategory {
170        let path_lower = module_path.to_lowercase().replace("::", "/");
171
172        // Check path components for categorization
173        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        // Determine category based on path structure (domain-agnostic)
236        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
418/// Get the default stdlib path
419pub fn default_stdlib_path() -> PathBuf {
420    // Explicit override for non-workspace environments (packaged installs, custom dev layouts).
421    if let Ok(path) = std::env::var("SHAPE_STDLIB_PATH") {
422        return PathBuf::from(path);
423    }
424
425    // Workspace builds use the canonical stdlib source of truth.
426    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    // Published crates carry a vendored stdlib copy inside shape-runtime itself.
432    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            // Stdlib may or may not have functions depending on development state
449            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            // Note: stdlib functions (like sma, ema) will be added as stdlib is developed
456        } 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        // Test each file individually
474        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}