Skip to main content

leo_parser/
lib.rs

1// Copyright (C) 2019-2026 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17//! The Leo parser.
18//!
19//! This crate now makes use of `leo-parser-lossless`, and
20//! translates its output to the Leo AST. The functions such
21//! as `to_expression` and `to_statement` in the `conversions`
22//! module directly convert `SyntaxNode`s from the lossless tree into
23//! AST nodes. The publicly exposed functions such as `parse_expression`
24//! and `parse_statement` can be called without reference to the lossless
25//! parser to complete the entire parsing task.
26//!
27//! When the `rowan` feature is enabled, the rowan-based parser is available
28//! as an alternative implementation (currently stubbed).
29
30use itertools::Itertools as _;
31
32// Re-export the rowan-based parser when the feature is enabled.
33// TODO: Integrate this as the primary parser once implementation is complete.
34#[cfg(feature = "rowan")]
35pub use leo_parser_rowan;
36
37use leo_ast::{NetworkName, NodeBuilder};
38use leo_errors::{Handler, ParserError, Result};
39use leo_span::{
40    Symbol,
41    source_map::{FileName, SourceFile},
42    sym,
43};
44
45mod conversions;
46
47#[cfg(test)]
48mod test;
49
50pub fn parse_expression(
51    handler: Handler,
52    node_builder: &NodeBuilder,
53    source: &str,
54    start_pos: u32,
55    _network: NetworkName,
56) -> Result<leo_ast::Expression> {
57    let node = leo_parser_lossless::parse_expression(handler.clone(), source, start_pos)?;
58    let conversion_context = conversions::ConversionContext::new(&handler, node_builder);
59    conversion_context.to_expression(&node)
60}
61
62pub fn parse_statement(
63    handler: Handler,
64    node_builder: &NodeBuilder,
65    source: &str,
66    start_pos: u32,
67    _network: NetworkName,
68) -> Result<leo_ast::Statement> {
69    let node = leo_parser_lossless::parse_statement(handler.clone(), source, start_pos)?;
70    let conversion_context = conversions::ConversionContext::new(&handler, node_builder);
71    conversion_context.to_statement(&node)
72}
73
74pub fn parse_module(
75    handler: Handler,
76    node_builder: &NodeBuilder,
77    source: &str,
78    start_pos: u32,
79    program_name: Symbol,
80    path: Vec<Symbol>,
81    _network: NetworkName,
82) -> Result<leo_ast::Module> {
83    let node_module = leo_parser_lossless::parse_module(handler.clone(), source, start_pos)?;
84    let conversion_context = conversions::ConversionContext::new(&handler, node_builder);
85    conversion_context.to_module(&node_module, program_name, path)
86}
87
88pub fn parse(
89    handler: Handler,
90    node_builder: &NodeBuilder,
91    source: &SourceFile,
92    modules: &[std::rc::Rc<SourceFile>],
93    _network: NetworkName,
94) -> Result<leo_ast::Program> {
95    let conversion_context = conversions::ConversionContext::new(&handler, node_builder);
96
97    let program_node = leo_parser_lossless::parse_main(handler.clone(), &source.src, source.absolute_start)?;
98    let mut program = conversion_context.to_main(&program_node)?;
99    let program_name = *program.program_scopes.first().unwrap().0;
100
101    // Determine the root directory of the main file (for module resolution)
102    let root_dir = match &source.name {
103        FileName::Real(path) => path.parent().map(|p| p.to_path_buf()),
104        _ => None,
105    };
106
107    for module in modules {
108        let node_module = leo_parser_lossless::parse_module(handler.clone(), &module.src, module.absolute_start)?;
109        if let Some(key) = compute_module_key(&module.name, root_dir.as_deref()) {
110            // Ensure no module uses a keyword in its name
111            for segment in &key {
112                if symbol_is_keyword(*segment) {
113                    return Err(ParserError::keyword_used_as_module_name(key.iter().format("::"), segment).into());
114                }
115            }
116
117            let module_ast = conversion_context.to_module(&node_module, program_name, key.clone())?;
118            program.modules.insert(key, module_ast);
119        }
120    }
121
122    Ok(program)
123}
124
125/// Creates a new AST from a given file path and source code text.
126pub fn parse_ast(
127    handler: Handler,
128    node_builder: &NodeBuilder,
129    source: &SourceFile,
130    modules: &[std::rc::Rc<SourceFile>],
131    network: NetworkName,
132) -> Result<leo_ast::Ast> {
133    Ok(leo_ast::Ast::new(parse(handler, node_builder, source, modules, network)?))
134}
135
136fn symbol_is_keyword(symbol: Symbol) -> bool {
137    matches!(
138        symbol,
139        sym::address |
140        sym::aleo |
141        sym::As |
142        sym::assert |
143        sym::assert_eq |
144        sym::assert_neq |
145        sym::Async |   // if you need it
146        sym::block |
147        sym::bool |
148        sym::Const |
149        sym::constant |
150        sym::constructor |
151        sym::Else |
152        sym::False |
153        sym::field |
154        sym::Fn |
155        sym::For |
156        sym::function |
157        sym::Future |
158        sym::group |
159        sym::i8 |
160        sym::i16 |
161        sym::i32 |
162        sym::i64 |
163        sym::i128 |
164        sym::If |
165        sym::import |
166        sym::In |
167        sym::inline |
168        sym::Let |
169        sym::leo |
170        sym::mapping |
171        sym::storage |
172        sym::network |
173        sym::private |
174        sym::program |
175        sym::public |
176        sym::record |
177        sym::Return |
178        sym::scalar |
179        sym::script |
180        sym::SelfLower |
181        sym::signature |
182        sym::string |
183        sym::Struct |
184        sym::transition |
185        sym::True |
186        sym::u8 |
187        sym::u16 |
188        sym::u32 |
189        sym::u64 |
190        sym::u128
191    )
192}
193
194/// Computes a module key from a `FileName`, optionally relative to a root directory.
195///
196/// This function converts a file path like `src/foo/bar.leo` into a `Vec<Symbol>` key
197/// like `["foo", "bar"]`, suitable for inserting into the program's module map.
198///
199/// # Arguments
200/// * `name` - The filename of the module, either real (from disk) or synthetic (custom).
201/// * `root_dir` - The root directory to strip from the path, if any.
202///
203/// # Returns
204/// * `Some(Vec<Symbol>)` - The computed module key.
205/// * `None` - If the path can't be stripped or processed.
206fn compute_module_key(name: &FileName, root_dir: Option<&std::path::Path>) -> Option<Vec<Symbol>> {
207    // Normalize the path depending on whether it's a custom or real file
208    let path = match name {
209        FileName::Custom(name) => std::path::Path::new(name).to_path_buf(),
210        FileName::Real(path) => {
211            let root = root_dir?;
212            path.strip_prefix(root).ok()?.to_path_buf()
213        }
214    };
215
216    // Convert path components (e.g., "foo/bar") into symbols: ["foo", "bar"]
217    let mut key: Vec<Symbol> =
218        path.components().map(|comp| Symbol::intern(&comp.as_os_str().to_string_lossy())).collect();
219
220    // Strip the file extension from the last component (e.g., "bar.leo" → "bar")
221    if let Some(last) = path.file_name()
222        && let Some(stem) = std::path::Path::new(last).file_stem()
223    {
224        key.pop(); // Remove "bar.leo"
225        key.push(Symbol::intern(&stem.to_string_lossy())); // Add "bar"
226    }
227
228    Some(key)
229}