Skip to main content

harn_hostlib/ast/
mod.rs

1//! AST host capability.
2//!
3//! Wraps tree-sitter parsing, symbol extraction, and outline generation —
4//! the Swift `Sources/ASTEngine/` surface ported into Rust. The implementation
5//! is fully wired so AST builtins share one canonical wire format.
6//!
7//! ## Wire format
8//!
9//! - Row/column coordinates are **0-based** across all three builtins,
10//!   matching tree-sitter's native `Point` representation. Swift's
11//!   `ASTEngine` historically returned 1-based coordinates for symbols;
12//!   we normalize on 0-based here so `parse_file`, `symbols`, and
13//!   `outline` share one convention.
14//! - `parse_file` emits a flat node list with `parent_id` rather than
15//!   nested children — keeps the wire JSON-serializable without inflating
16//!   it with object copies.
17//! - `symbols` and `outline` carry a `signature` string (e.g.
18//!   `"fn foo(bar: i32)"`) on every entry to match Swift's
19//!   `TreeSitterSymbol.signature`.
20//!
21//! ## Languages
22//!
23//! [`language::Language`] mirrors Swift's `TreeSitterLanguage` enum
24//! verbatim: TypeScript/TSX, JavaScript/JSX, Python, Go, Rust, Java,
25//! C, C++, C#, Ruby, Kotlin, PHP, Scala, Bash, Swift, Zig, Elixir, Lua,
26//! Haskell, R. Adding/dropping languages requires a coordinated change
27//! in both repos.
28//!
29
30use std::sync::Arc;
31
32use harn_vm::VmValue;
33
34use crate::error::HostlibError;
35use crate::registry::{BuiltinRegistry, HostlibCapability, RegisteredBuiltin, SyncHandler};
36
37mod bracket_balance;
38mod function_body;
39mod fuzzy;
40mod imports;
41mod language;
42mod mutation;
43mod outline;
44mod parse;
45mod parse_errors;
46mod symbols;
47mod symbols_call;
48mod types;
49mod undefined_names;
50
51pub use language::Language;
52pub use types::{OutlineItem, ParseError, ParsedNode, Symbol, SymbolKind, UndefinedName};
53
54/// Programmatic entry point to the AST builtins. Embedders typically go
55/// through the registered builtins, but tests and tools that want
56/// strongly-typed access can use these helpers directly.
57pub mod api {
58    use std::path::Path;
59
60    use crate::error::HostlibError;
61
62    use super::language::Language;
63    use super::outline::build_outline;
64    use super::parse::{parse_source, read_source};
65    use super::symbols::extract;
66    use super::types::{OutlineItem, Symbol};
67
68    /// Parse `path` (with optional language hint) and return its symbols.
69    pub fn symbols(
70        path: &Path,
71        language_hint: Option<&str>,
72    ) -> Result<(Language, Vec<Symbol>), HostlibError> {
73        let language = detect(path, language_hint)?;
74        let source = read_source(&path.to_string_lossy(), 0)?;
75        let tree = parse_source(&source, language)?;
76        Ok((language, extract(&tree, &source, language)))
77    }
78
79    /// Parse `path` and return a hierarchical outline.
80    pub fn outline(
81        path: &Path,
82        language_hint: Option<&str>,
83    ) -> Result<(Language, Vec<OutlineItem>), HostlibError> {
84        let (language, symbols) = symbols(path, language_hint)?;
85        Ok((language, build_outline(symbols)))
86    }
87
88    /// Parse a source `str` for `language` and return its symbols. Useful
89    /// for unit tests where the input lives in-memory rather than on disk.
90    pub fn symbols_from_source(
91        source: &str,
92        language: Language,
93    ) -> Result<Vec<Symbol>, HostlibError> {
94        let tree = parse_source(source, language)?;
95        Ok(extract(&tree, source, language))
96    }
97
98    fn detect(path: &Path, language_hint: Option<&str>) -> Result<Language, HostlibError> {
99        Language::detect(path, language_hint).ok_or_else(|| HostlibError::InvalidParameter {
100            builtin: "ast::api",
101            param: "language",
102            message: format!(
103                "could not infer a tree-sitter grammar for `{}` \
104                 (extension or `language` field unrecognized)",
105                path.display()
106            ),
107        })
108    }
109}
110
111/// AST capability handle. Stateless; tree-sitter parsers are constructed
112/// per-call (cheap relative to grammar lookup) so the capability itself
113/// has nothing to own.
114#[derive(Default)]
115pub struct AstCapability;
116
117impl HostlibCapability for AstCapability {
118    fn module_name(&self) -> &'static str {
119        "ast"
120    }
121
122    fn register_builtins(&self, registry: &mut BuiltinRegistry) {
123        register(registry, "hostlib_ast_parse_file", "parse_file", parse::run);
124        register(
125            registry,
126            "hostlib_ast_symbols",
127            "symbols",
128            symbols_call::run,
129        );
130        register(registry, "hostlib_ast_outline", "outline", outline::run);
131        register(
132            registry,
133            "hostlib_ast_parse_errors",
134            "parse_errors",
135            parse_errors::run,
136        );
137        register(
138            registry,
139            "hostlib_ast_undefined_names",
140            "undefined_names",
141            undefined_names::run,
142        );
143        register(
144            registry,
145            "hostlib_ast_function_body",
146            "function_body",
147            function_body::run_single,
148        );
149        register(
150            registry,
151            "hostlib_ast_function_bodies",
152            "function_bodies",
153            function_body::run_bulk,
154        );
155        register(
156            registry,
157            "hostlib_ast_extract_imports",
158            "extract_imports",
159            imports::run,
160        );
161        register(
162            registry,
163            "hostlib_ast_symbol_extract",
164            "symbol_extract",
165            mutation::run_extract,
166        );
167        register(
168            registry,
169            "hostlib_ast_symbol_delete",
170            "symbol_delete",
171            mutation::run_delete,
172        );
173        register(
174            registry,
175            "hostlib_ast_symbol_replace",
176            "symbol_replace",
177            mutation::run_replace,
178        );
179        register(
180            registry,
181            "hostlib_ast_bracket_balance",
182            "bracket_balance",
183            bracket_balance::run,
184        );
185    }
186}
187
188fn register(
189    registry: &mut BuiltinRegistry,
190    name: &'static str,
191    method: &'static str,
192    runner: fn(&[VmValue]) -> Result<VmValue, HostlibError>,
193) {
194    let handler: SyncHandler = Arc::new(runner);
195    registry.register(RegisteredBuiltin {
196        name,
197        module: "ast",
198        method,
199        handler,
200    });
201}