Skip to main content

leo_parser_rowan/parser/
grammar.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//! Grammar entry points for the Leo parser.
18//!
19//! This module provides the public parsing functions that dispatch to
20//! the appropriate grammar rules.
21
22use super::{Parse, Parser};
23use crate::{lexer::lex, syntax_kind::SyntaxKind::*};
24
25/// Parse a complete Leo source file.
26///
27/// This handles imports followed by a program declaration.
28pub fn parse_file(source: &str) -> Parse {
29    let (tokens, lex_errors) = lex(source);
30    let mut parser = Parser::new(source, &tokens);
31
32    let root = parser.start();
33    parser.parse_file_items();
34    root.complete(&mut parser, ROOT);
35
36    parser.finish(lex_errors)
37}
38
39/// Parse a single expression.
40pub fn parse_expression_entry(source: &str) -> Parse {
41    let (tokens, lex_errors) = lex(source);
42    let mut parser = Parser::new(source, &tokens);
43
44    let root = parser.start();
45    let errors_before = parser.error_count();
46    parser.parse_expr();
47    // Report error for any remaining non-error tokens after the expression.
48    // Skip ERROR tokens as those are already reported by the lexer.
49    // Don't add trailing-token errors if errors already occurred during parsing,
50    // since leftover tokens are a secondary effect of error recovery.
51    if !parser.at_eof() && !parser.at(ERROR) && parser.error_count() == errors_before {
52        let expected: Vec<&str> = Parser::EXPR_CONTINUATION.iter().map(|k| k.user_friendly_name()).collect();
53        parser.error_unexpected(parser.current(), &expected);
54    }
55    root.complete(&mut parser, ROOT);
56
57    parser.finish(lex_errors)
58}
59
60/// Parse module contents (const, struct, inline declarations only).
61pub fn parse_module_entry(source: &str) -> Parse {
62    let (tokens, lex_errors) = lex(source);
63    let mut parser = Parser::new(source, &tokens);
64
65    let root = parser.start();
66    parser.parse_module_items();
67    root.complete(&mut parser, ROOT);
68
69    parser.finish(lex_errors)
70}
71
72/// Parse a single statement.
73pub fn parse_statement_entry(source: &str) -> Parse {
74    let (tokens, lex_errors) = lex(source);
75    let mut parser = Parser::new(source, &tokens);
76
77    let root = parser.start();
78    let errors_before = parser.error_count();
79    parser.parse_stmt();
80    // Report error for any remaining non-error tokens after the statement,
81    // but only if no errors were already emitted during parsing. If errors
82    // occurred, leftover tokens are a secondary effect of error recovery.
83    if !parser.at_eof() && !parser.at(ERROR) && parser.error_count() == errors_before {
84        let mut expected: Vec<&str> = Parser::EXPR_CONTINUATION.iter().map(|k| k.user_friendly_name()).collect();
85        expected.push("';'");
86        parser.error_unexpected(parser.current(), &expected);
87    }
88    root.complete(&mut parser, ROOT);
89
90    parser.finish(lex_errors)
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use expect_test::{Expect, expect};
97
98    fn check_file(input: &str, expect: Expect) {
99        let parse = parse_file(input);
100        let output = format!("{:#?}", parse.syntax());
101        expect.assert_eq(&output);
102    }
103
104    #[test]
105    fn parse_file_empty() {
106        check_file("", expect![[r#"
107            ROOT@0..0
108        "#]]);
109    }
110
111    #[test]
112    fn parse_file_trivial() {
113        check_file("program test.aleo { }", expect![[r#"
114                ROOT@0..21
115                  PROGRAM_DECL@0..21
116                    KW_PROGRAM@0..7 "program"
117                    WHITESPACE@7..8 " "
118                    IDENT@8..12 "test"
119                    DOT@12..13 "."
120                    KW_ALEO@13..17 "aleo"
121                    WHITESPACE@17..18 " "
122                    L_BRACE@18..19 "{"
123                    WHITESPACE@19..20 " "
124                    R_BRACE@20..21 "}"
125            "#]]);
126    }
127
128    fn check_module(input: &str, expect: Expect) {
129        let parse = parse_module_entry(input);
130        let output = format!("{:#?}", parse.syntax());
131        expect.assert_eq(&output);
132    }
133
134    fn check_module_no_errors(input: &str) {
135        let parse = parse_module_entry(input);
136        if !parse.errors().is_empty() {
137            for err in parse.errors() {
138                eprintln!("error at {:?}: {}", err.range, err.message);
139            }
140            eprintln!("tree:\n{:#?}", parse.syntax());
141            panic!("module parse had {} error(s)", parse.errors().len());
142        }
143    }
144
145    #[test]
146    fn parse_module_empty() {
147        check_module("", expect![[r#"
148            ROOT@0..0
149        "#]]);
150    }
151
152    #[test]
153    fn parse_module_const() {
154        check_module_no_errors("const X: u32 = 32u32;");
155    }
156
157    #[test]
158    fn parse_module_struct() {
159        check_module_no_errors("struct Data { values: u32, }");
160    }
161
162    #[test]
163    fn parse_module_inline_fn() {
164        check_module_no_errors("fn helper() -> u32 { return 0u32; }");
165    }
166
167    #[test]
168    fn parse_module_mixed_items() {
169        check_module_no_errors(
170            "const X: u32 = 3;\n\
171             struct Data { values: u32, }\n\
172             fn helper() -> u32 { return 0u32; }",
173        );
174    }
175
176    #[test]
177    fn parse_module_with_comments() {
178        // Module sections in test files are separated by comments.
179        // Comments are trivia and should be skipped.
180        check_module_no_errors(
181            "// --- Next Module: dep.leo --- //\n\
182             const X: u32 = 32u32;\n\
183             // --- Next Module: dep/inner.leo --- //\n\
184             const Y: u32 = 64u32;",
185        );
186    }
187
188    // =========================================================================
189    // File with Imports and Program Items (5a)
190    // =========================================================================
191
192    #[test]
193    fn parse_file_full() {
194        check_file("import credits.aleo;\nprogram test.aleo { fn main() { } }", expect![[r#"
195                ROOT@0..56
196                  IMPORT@0..20
197                    KW_IMPORT@0..6 "import"
198                    WHITESPACE@6..7 " "
199                    IDENT@7..14 "credits"
200                    DOT@14..15 "."
201                    KW_ALEO@15..19 "aleo"
202                    SEMICOLON@19..20 ";"
203                  LINEBREAK@20..21 "\n"
204                  PROGRAM_DECL@21..56
205                    KW_PROGRAM@21..28 "program"
206                    WHITESPACE@28..29 " "
207                    IDENT@29..33 "test"
208                    DOT@33..34 "."
209                    KW_ALEO@34..38 "aleo"
210                    WHITESPACE@38..39 " "
211                    L_BRACE@39..40 "{"
212                    FUNCTION_DEF@40..54
213                      WHITESPACE@40..41 " "
214                      KW_FN@41..43 "fn"
215                      WHITESPACE@43..44 " "
216                      IDENT@44..48 "main"
217                      PARAM_LIST@48..50
218                        L_PAREN@48..49 "("
219                        R_PAREN@49..50 ")"
220                      WHITESPACE@50..51 " "
221                      BLOCK@51..54
222                        L_BRACE@51..52 "{"
223                        WHITESPACE@52..53 " "
224                        R_BRACE@53..54 "}"
225                    WHITESPACE@54..55 " "
226                    R_BRACE@55..56 "}"
227            "#]]);
228    }
229
230    // =========================================================================
231    // Module Recovery after Bad Item (5b)
232    // =========================================================================
233
234    #[test]
235    fn parse_module_recovery() {
236        // An unrecognized token (`42`) triggers module-level recovery, which
237        // skips to the next module item start (`KW_STRUCT`). The struct
238        // should parse successfully after recovery.
239        let parse = parse_module_entry("42 struct Foo { x: u32, }");
240        assert!(!parse.errors().is_empty(), "expected errors from unrecognized token");
241        let tree = format!("{:#?}", parse.syntax());
242        assert!(tree.contains("STRUCT_DEF"), "expected struct to be recovered, got:\n{tree}");
243    }
244
245    #[test]
246    fn parse_file_with_module_sections() {
247        // Multi-section test files: program block followed by module items.
248        let source = "\
249program test.aleo {
250    fn foo() -> u32 { return 0u32; }
251}
252
253// --- Next Module: dep.leo --- //
254
255const X: u32 = 32u32;
256
257// --- Next Module: dep/inner.leo --- //
258
259const Y: u32 = 64u32;";
260
261        let parse = parse_file(source);
262        if !parse.errors().is_empty() {
263            for err in parse.errors() {
264                eprintln!("error at {:?}: {}", err.range, err.message);
265            }
266            eprintln!("tree:\n{:#?}", parse.syntax());
267            panic!("file parse had {} error(s)", parse.errors().len());
268        }
269    }
270}