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 }