leo_parser/
lib.rs

1// Copyright (C) 2019-2025 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
27use itertools::Itertools as _;
28
29use leo_ast::{NetworkName, NodeBuilder};
30use leo_errors::{Handler, ParserError, Result};
31use leo_span::{
32    Symbol,
33    source_map::{FileName, SourceFile},
34    sym,
35};
36
37mod conversions;
38
39#[cfg(test)]
40mod test;
41
42pub fn parse_expression(
43    handler: Handler,
44    node_builder: &NodeBuilder,
45    source: &str,
46    start_pos: u32,
47    _network: NetworkName,
48) -> Result<leo_ast::Expression> {
49    let node = leo_parser_lossless::parse_expression(handler.clone(), source, start_pos)?;
50    conversions::to_expression(&node, node_builder, &handler)
51}
52
53pub fn parse_statement(
54    handler: Handler,
55    node_builder: &NodeBuilder,
56    source: &str,
57    start_pos: u32,
58    _network: NetworkName,
59) -> Result<leo_ast::Statement> {
60    let node = leo_parser_lossless::parse_statement(handler.clone(), source, start_pos)?;
61    conversions::to_statement(&node, node_builder, &handler)
62}
63
64pub fn parse_module(
65    handler: Handler,
66    node_builder: &NodeBuilder,
67    source: &str,
68    start_pos: u32,
69    program_name: Symbol,
70    path: Vec<Symbol>,
71    _network: NetworkName,
72) -> Result<leo_ast::Module> {
73    let node_module = leo_parser_lossless::parse_module(handler.clone(), source, start_pos)?;
74    conversions::to_module(&node_module, node_builder, program_name, path, &handler)
75}
76
77pub fn parse(
78    handler: Handler,
79    node_builder: &NodeBuilder,
80    source: &SourceFile,
81    modules: &[std::rc::Rc<SourceFile>],
82    _network: NetworkName,
83) -> Result<leo_ast::Program> {
84    let program_node = leo_parser_lossless::parse_main(handler.clone(), &source.src, source.absolute_start)?;
85    let mut program = conversions::to_main(&program_node, node_builder, &handler)?;
86    let program_name = *program.program_scopes.first().unwrap().0;
87
88    // Determine the root directory of the main file (for module resolution)
89    let root_dir = match &source.name {
90        FileName::Real(path) => path.parent().map(|p| p.to_path_buf()),
91        _ => None,
92    };
93
94    for module in modules {
95        let node_module = leo_parser_lossless::parse_module(handler.clone(), &module.src, module.absolute_start)?;
96        if let Some(key) = compute_module_key(&module.name, root_dir.as_deref()) {
97            // Ensure no module uses a keyword in its name
98            for segment in &key {
99                if symbol_is_keyword(*segment) {
100                    return Err(ParserError::keyword_used_as_module_name(key.iter().format("::"), segment).into());
101                }
102            }
103
104            let module_ast = conversions::to_module(&node_module, node_builder, program_name, key.clone(), &handler)?;
105            program.modules.insert(key, module_ast);
106        }
107    }
108
109    Ok(program)
110}
111
112/// Creates a new AST from a given file path and source code text.
113pub fn parse_ast(
114    handler: Handler,
115    node_builder: &NodeBuilder,
116    source: &SourceFile,
117    modules: &[std::rc::Rc<SourceFile>],
118    network: NetworkName,
119) -> Result<leo_ast::Ast> {
120    Ok(leo_ast::Ast::new(parse(handler, node_builder, source, modules, network)?))
121}
122
123fn symbol_is_keyword(symbol: Symbol) -> bool {
124    matches!(
125        symbol,
126        sym::address |
127        sym::aleo |
128        sym::As |
129        sym::assert |
130        sym::assert_eq |
131        sym::assert_neq |
132        sym::Async |   // if you need it
133        sym::block |
134        sym::bool |
135        sym::Const |
136        sym::constant |
137        sym::constructor |
138        sym::Else |
139        sym::False |
140        sym::field |
141        sym::Fn |
142        sym::For |
143        sym::function |
144        sym::Future |
145        sym::group |
146        sym::i8 |
147        sym::i16 |
148        sym::i32 |
149        sym::i64 |
150        sym::i128 |
151        sym::If |
152        sym::import |
153        sym::In |
154        sym::inline |
155        sym::Let |
156        sym::leo |
157        sym::mapping |
158        sym::storage |
159        sym::network |
160        sym::private |
161        sym::program |
162        sym::public |
163        sym::record |
164        sym::Return |
165        sym::scalar |
166        sym::script |
167        sym::SelfLower |
168        sym::signature |
169        sym::string |
170        sym::Struct |
171        sym::transition |
172        sym::True |
173        sym::u8 |
174        sym::u16 |
175        sym::u32 |
176        sym::u64 |
177        sym::u128
178    )
179}
180
181/// Computes a module key from a `FileName`, optionally relative to a root directory.
182///
183/// This function converts a file path like `src/foo/bar.leo` into a `Vec<Symbol>` key
184/// like `["foo", "bar"]`, suitable for inserting into the program's module map.
185///
186/// # Arguments
187/// * `name` - The filename of the module, either real (from disk) or synthetic (custom).
188/// * `root_dir` - The root directory to strip from the path, if any.
189///
190/// # Returns
191/// * `Some(Vec<Symbol>)` - The computed module key.
192/// * `None` - If the path can't be stripped or processed.
193fn compute_module_key(name: &FileName, root_dir: Option<&std::path::Path>) -> Option<Vec<Symbol>> {
194    // Normalize the path depending on whether it's a custom or real file
195    let path = match name {
196        FileName::Custom(name) => std::path::Path::new(name).to_path_buf(),
197        FileName::Real(path) => {
198            let root = root_dir?;
199            path.strip_prefix(root).ok()?.to_path_buf()
200        }
201    };
202
203    // Convert path components (e.g., "foo/bar") into symbols: ["foo", "bar"]
204    let mut key: Vec<Symbol> =
205        path.components().map(|comp| Symbol::intern(&comp.as_os_str().to_string_lossy())).collect();
206
207    // Strip the file extension from the last component (e.g., "bar.leo" → "bar")
208    if let Some(last) = path.file_name()
209        && let Some(stem) = std::path::Path::new(last).file_stem()
210    {
211        key.pop(); // Remove "bar.leo"
212        key.push(Symbol::intern(&stem.to_string_lossy())); // Add "bar"
213    }
214
215    Some(key)
216}