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