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 language;
38mod outline;
39mod parse;
40mod symbols;
41mod symbols_call;
42mod types;
43
44pub use language::Language;
45pub use types::{OutlineItem, ParsedNode, Symbol, SymbolKind};
46
47/// Programmatic entry point to the AST builtins. Embedders typically go
48/// through the registered builtins, but tests and tools that want
49/// strongly-typed access can use these helpers directly.
50pub mod api {
51    use std::path::Path;
52
53    use crate::error::HostlibError;
54
55    use super::language::Language;
56    use super::outline::build_outline;
57    use super::parse::{parse_source, read_source};
58    use super::symbols::extract;
59    use super::types::{OutlineItem, Symbol};
60
61    /// Parse `path` (with optional language hint) and return its symbols.
62    pub fn symbols(
63        path: &Path,
64        language_hint: Option<&str>,
65    ) -> Result<(Language, Vec<Symbol>), HostlibError> {
66        let language = detect(path, language_hint)?;
67        let source = read_source(&path.to_string_lossy(), 0)?;
68        let tree = parse_source(&source, language)?;
69        Ok((language, extract(&tree, &source, language)))
70    }
71
72    /// Parse `path` and return a hierarchical outline.
73    pub fn outline(
74        path: &Path,
75        language_hint: Option<&str>,
76    ) -> Result<(Language, Vec<OutlineItem>), HostlibError> {
77        let (language, symbols) = symbols(path, language_hint)?;
78        Ok((language, build_outline(symbols)))
79    }
80
81    /// Parse a source `str` for `language` and return its symbols. Useful
82    /// for unit tests where the input lives in-memory rather than on disk.
83    pub fn symbols_from_source(
84        source: &str,
85        language: Language,
86    ) -> Result<Vec<Symbol>, HostlibError> {
87        let tree = parse_source(source, language)?;
88        Ok(extract(&tree, source, language))
89    }
90
91    fn detect(path: &Path, language_hint: Option<&str>) -> Result<Language, HostlibError> {
92        Language::detect(path, language_hint).ok_or_else(|| HostlibError::InvalidParameter {
93            builtin: "ast::api",
94            param: "language",
95            message: format!(
96                "could not infer a tree-sitter grammar for `{}` \
97                 (extension or `language` field unrecognized)",
98                path.display()
99            ),
100        })
101    }
102}
103
104/// AST capability handle. Stateless; tree-sitter parsers are constructed
105/// per-call (cheap relative to grammar lookup) so the capability itself
106/// has nothing to own.
107#[derive(Default)]
108pub struct AstCapability;
109
110impl HostlibCapability for AstCapability {
111    fn module_name(&self) -> &'static str {
112        "ast"
113    }
114
115    fn register_builtins(&self, registry: &mut BuiltinRegistry) {
116        register(registry, "hostlib_ast_parse_file", "parse_file", parse::run);
117        register(
118            registry,
119            "hostlib_ast_symbols",
120            "symbols",
121            symbols_call::run,
122        );
123        register(registry, "hostlib_ast_outline", "outline", outline::run);
124    }
125}
126
127fn register(
128    registry: &mut BuiltinRegistry,
129    name: &'static str,
130    method: &'static str,
131    runner: fn(&[VmValue]) -> Result<VmValue, HostlibError>,
132) {
133    let handler: SyncHandler = Arc::new(runner);
134    registry.register(RegisteredBuiltin {
135        name,
136        module: "ast",
137        method,
138        handler,
139    });
140}