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