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::Interface(_) => {}
138                        shape_ast::ast::ExportItem::Trait(_) => {}
139                        shape_ast::ast::ExportItem::Annotation(_) => {}
140                        shape_ast::ast::ExportItem::ForeignFunction(_) => {
141                            // Foreign functions are not stdlib intrinsics
142                        }
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    /// Infer function category from module path (domain-agnostic)
164    ///
165    /// Uses directory structure to determine category:
166    /// - core/math, core/statistics → Math
167    /// - */indicators/*, */backtesting/*, */simulation/* → Simulation
168    /// - */patterns/* → Utility
169    /// - Default → Utility
170    fn infer_category_from_path(module_path: &str) -> FunctionCategory {
171        let path_lower = module_path.to_lowercase().replace("::", "/");
172
173        // Check path components for categorization
174        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        // Determine category based on path structure (domain-agnostic)
237        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
419/// Get the default stdlib path
420pub fn default_stdlib_path() -> PathBuf {
421    // Explicit override for non-workspace environments (packaged installs, custom dev layouts).
422    if let Ok(path) = std::env::var("SHAPE_STDLIB_PATH") {
423        return PathBuf::from(path);
424    }
425
426    // Workspace builds use the canonical stdlib source of truth.
427    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    // Published crates carry a vendored stdlib copy inside shape-runtime itself.
433    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            // Stdlib may or may not have functions depending on development state
450            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            // Note: stdlib functions (like sma, ema) will be added as stdlib is developed
457        } 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        // Test each file individually
475        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}