leo_parser/parser/
mod.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 parser to convert Leo code text into a [`Program`] AST type.
18//!
19//! This module contains the [`parse()`] function which calls the underlying [`tokenize()`]
20//! method to create a new program AST.
21
22use crate::{Token, tokenizer::*};
23
24use leo_ast::*;
25use leo_errors::{Handler, ParserError, Result};
26use leo_span::{
27    Span,
28    Symbol,
29    source_map::{FileName, SourceFile},
30};
31
32use indexmap::IndexMap;
33use itertools::Itertools;
34
35mod context;
36pub(super) use context::ParserContext;
37
38mod expression;
39mod file;
40mod statement;
41pub(super) mod type_;
42
43/// Parses the main source file and any associated modules into a `Program` AST.
44///
45/// # Arguments
46/// * `handler` - Used for diagnostics and error reporting.
47/// * `node_builder` - Factory for building AST nodes.
48/// * `source` - The main source file to parse.
49/// * `modules` - A list of module source files (each wrapped in `Rc<SourceFile>`).
50/// * `network` - The Aleo network context (e.g., TestnetV0).
51///
52/// # Returns
53/// * `Ok(Program)` - The parsed program including all modules.
54/// * `Err(CompilerError)` - If any part of parsing fails.
55pub fn parse(
56    handler: Handler,
57    node_builder: &NodeBuilder,
58    source: &SourceFile,
59    modules: &[std::rc::Rc<SourceFile>],
60    network: NetworkName,
61) -> Result<Program> {
62    // === Parse the main source file ===
63    let mut tokens = ParserContext::new(
64        &handler,
65        node_builder,
66        crate::tokenize(&source.src, source.absolute_start)?,
67        None, // no program name yet
68        network,
69    );
70
71    // Build the main program AST
72    let mut program = tokens.parse_program()?;
73    let program_name = tokens.program_name;
74
75    // Determine the root directory of the main file (for module resolution)
76    let root_dir = match &source.name {
77        FileName::Real(path) => path.parent().map(|p| p.to_path_buf()),
78        _ => None,
79    };
80
81    // === Parse each module file ===
82    for module in modules {
83        let mut module_tokens = ParserContext::new(
84            &handler,
85            node_builder,
86            crate::tokenize(&module.src, module.absolute_start)?,
87            program_name,
88            network,
89        );
90
91        // Compute the module key from its filename (e.g., `foo/bar.leo` => ["foo", "bar"])
92        if let Some(key) = compute_module_key(&module.name, root_dir.as_deref()) {
93            // Ensure no module uses a keyword in its name
94            for segment in &key {
95                if let Some(keyword) = Token::symbol_to_keyword(*segment) {
96                    return Err(
97                        ParserError::keyword_used_as_module_name(key.iter().format("::").to_string(), keyword).into()
98                    );
99                }
100            }
101
102            let module_ast = module_tokens.parse_module(&key)?;
103            program.modules.insert(key, module_ast);
104        }
105    }
106
107    Ok(program)
108}
109
110/// Computes a module key from a `FileName`, optionally relative to a root directory.
111///
112/// This function converts a file path like `src/foo/bar.leo` into a `Vec<Symbol>` key
113/// like `["foo", "bar"]`, suitable for inserting into the program's module map.
114///
115/// # Arguments
116/// * `name` - The filename of the module, either real (from disk) or synthetic (custom).
117/// * `root_dir` - The root directory to strip from the path, if any.
118///
119/// # Returns
120/// * `Some(Vec<Symbol>)` - The computed module key.
121/// * `None` - If the path can't be stripped or processed.
122fn compute_module_key(name: &FileName, root_dir: Option<&std::path::Path>) -> Option<Vec<Symbol>> {
123    // Normalize the path depending on whether it's a custom or real file
124    let path = match name {
125        FileName::Custom(name) => std::path::Path::new(name).to_path_buf(),
126        FileName::Real(path) => {
127            let root = root_dir?;
128            path.strip_prefix(root).ok()?.to_path_buf()
129        }
130    };
131
132    // Convert path components (e.g., "foo/bar") into symbols: ["foo", "bar"]
133    let mut key: Vec<Symbol> =
134        path.components().map(|comp| Symbol::intern(&comp.as_os_str().to_string_lossy())).collect();
135
136    // Strip the file extension from the last component (e.g., "bar.leo" → "bar")
137    if let Some(last) = path.file_name() {
138        if let Some(stem) = std::path::Path::new(last).file_stem() {
139            key.pop(); // Remove "bar.leo"
140            key.push(Symbol::intern(&stem.to_string_lossy())); // Add "bar"
141        }
142    }
143
144    Some(key)
145}
146
147pub fn parse_module(
148    handler: Handler,
149    node_builder: &NodeBuilder,
150    mod_path: &[Symbol],
151    source: &str,
152    start_pos: u32,
153    network: NetworkName,
154) -> Result<Module> {
155    let mut context = ParserContext::new(&handler, node_builder, crate::tokenize(source, start_pos)?, None, network);
156
157    let module = context.parse_module(mod_path)?;
158    if context.token.token == Token::Eof {
159        Ok(module)
160    } else {
161        Err(ParserError::unexpected(context.token.token, Token::Eof, context.token.span).into())
162    }
163}
164
165pub fn parse_expression(
166    handler: Handler,
167    node_builder: &NodeBuilder,
168    source: &str,
169    start_pos: u32,
170    network: NetworkName,
171) -> Result<Expression> {
172    let mut context = ParserContext::new(&handler, node_builder, crate::tokenize(source, start_pos)?, None, network);
173
174    let expression = context.parse_expression()?;
175    if context.token.token == Token::Eof {
176        Ok(expression)
177    } else {
178        Err(ParserError::unexpected(context.token.token, Token::Eof, context.token.span).into())
179    }
180}
181
182pub fn parse_statement(
183    handler: Handler,
184    node_builder: &NodeBuilder,
185    source: &str,
186    start_pos: u32,
187    network: NetworkName,
188) -> Result<Statement> {
189    let mut context = ParserContext::new(&handler, node_builder, crate::tokenize(source, start_pos)?, None, network);
190
191    let statement = context.parse_statement()?;
192    if context.token.token == Token::Eof {
193        Ok(statement)
194    } else {
195        Err(ParserError::unexpected(context.token.token, Token::Eof, context.token.span).into())
196    }
197}