Skip to main content

snarkvm_synthesizer_program/
parse.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<N: Network> Parser for ProgramCore<N> {
19    /// Parses a string into a program.
20    #[inline]
21    fn parse(string: &str) -> ParserResult<Self> {
22        // A helper to parse a program.
23        enum P<N: Network> {
24            Constructor(ConstructorCore<N>),
25            M(Mapping<N>),
26            S(StructType<N>),
27            R(RecordType<N>),
28            C(ClosureCore<N>),
29            F(FunctionCore<N>),
30        }
31
32        // Parse the imports from the string.
33        let (string, imports) = many0(Import::parse)(string)?;
34        // Parse the whitespace and comments from the string.
35        let (string, _) = Sanitizer::parse(string)?;
36        // Parse the 'program' keyword from the string.
37        let (string, _) = tag(Self::type_name())(string)?;
38        // Parse the whitespace from the string.
39        let (string, _) = Sanitizer::parse_whitespaces(string)?;
40        // Parse the program ID from the string.
41        let (string, id) = ProgramID::parse(string)?;
42        // Parse the whitespace from the string.
43        let (string, _) = Sanitizer::parse_whitespaces(string)?;
44        // Parse the semicolon ';' keyword from the string.
45        let (string, _) = tag(";")(string)?;
46
47        fn intermediate<N: Network>(string: &str) -> ParserResult<P<N>> {
48            // Parse the whitespace and comments from the string.
49            let (string, _) = Sanitizer::parse(string)?;
50
51            if string.starts_with(ConstructorCore::<N>::type_name()) {
52                map(ConstructorCore::parse, |constructor| P::<N>::Constructor(constructor))(string)
53            } else if string.starts_with(Mapping::<N>::type_name()) {
54                map(Mapping::parse, |mapping| P::<N>::M(mapping))(string)
55            } else if string.starts_with(StructType::<N>::type_name()) {
56                map(StructType::parse, |struct_| P::<N>::S(struct_))(string)
57            } else if string.starts_with(RecordType::<N>::type_name()) {
58                map(RecordType::parse, |record| P::<N>::R(record))(string)
59            } else if string.starts_with(ClosureCore::<N>::type_name()) {
60                map(ClosureCore::parse, |closure| P::<N>::C(closure))(string)
61            } else if string.starts_with(FunctionCore::<N>::type_name()) {
62                map(FunctionCore::parse, |function| P::<N>::F(function))(string)
63            } else {
64                Err(Err::Error(make_error(string, ErrorKind::Alt)))
65            }
66        }
67
68        // Parse the struct or function from the string.
69        let (string, components) = many1(intermediate)(string)?;
70        // Parse the whitespace and comments from the string.
71        let (string, _) = Sanitizer::parse(string)?;
72
73        // Initialize a new program.
74        let mut program = match ProgramCore::<N>::new(id) {
75            Ok(program) => program,
76            Err(error) => {
77                eprintln!("{error}");
78                return map_res(take(0usize), Err)(string);
79            }
80        };
81
82        // Add the imports (if any) to the program.
83        for import in imports {
84            match program.add_import(import) {
85                Ok(_) => (),
86                Err(error) => {
87                    eprintln!("{error}");
88                    return map_res(take(0usize), Err)(string);
89                }
90            }
91        }
92
93        // Construct the program with the parsed components.
94        for component in components {
95            let result = match component {
96                P::Constructor(constructor) => program.add_constructor(constructor),
97                P::M(mapping) => program.add_mapping(mapping),
98                P::S(struct_) => program.add_struct(struct_),
99                P::R(record) => program.add_record(record),
100                P::C(closure) => program.add_closure(closure),
101                P::F(function) => program.add_function(function),
102            };
103
104            match result {
105                Ok(_) => (),
106                Err(error) => {
107                    eprintln!("{error}");
108                    return map_res(take(0usize), Err)(string);
109                }
110            }
111        }
112
113        Ok((string, program))
114    }
115}
116
117impl<N: Network> FromStr for ProgramCore<N> {
118    type Err = Error;
119
120    /// Returns a program from a string literal.
121    fn from_str(string: &str) -> Result<Self> {
122        // Ensure the raw program string is less than MAX_PROGRAM_SIZE.
123        ensure!(string.len() <= N::MAX_PROGRAM_SIZE, "Program length exceeds N::MAX_PROGRAM_SIZE.");
124
125        match Self::parse(string) {
126            Ok((remainder, object)) => {
127                // Ensure the remainder is empty.
128                ensure!(remainder.is_empty(), "Failed to parse string. Remaining invalid string is: \"{remainder}\"");
129                // Return the object.
130                Ok(object)
131            }
132            Err(error) => bail!("Failed to parse string. {error}"),
133        }
134    }
135}
136
137impl<N: Network> Debug for ProgramCore<N> {
138    /// Prints the program as a string.
139    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
140        Display::fmt(self, f)
141    }
142}
143
144impl<N: Network> Display for ProgramCore<N> {
145    /// Prints the program as a string.
146    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
147        if !self.imports.is_empty() {
148            // Print the imports.
149            for import in self.imports.values() {
150                writeln!(f, "{import}")?;
151            }
152
153            // Print a newline.
154            writeln!(f)?;
155        }
156
157        // Print the program name.
158        write!(f, "{} {};\n\n", Self::type_name(), self.id)?;
159
160        // Write the components.
161        let mut components_iter = self.components.iter().peekable();
162        while let Some((label, definition)) = components_iter.next() {
163            match label {
164                ProgramLabel::Constructor => {
165                    // Write the constructor, if it exists.
166                    if let Some(constructor) = &self.constructor {
167                        writeln!(f, "{constructor}")?;
168                    }
169                }
170                ProgramLabel::Identifier(identifier) => match definition {
171                    ProgramDefinition::Constructor => return Err(fmt::Error),
172                    ProgramDefinition::Mapping => match self.mappings.get(identifier) {
173                        Some(mapping) => writeln!(f, "{mapping}")?,
174                        None => return Err(fmt::Error),
175                    },
176                    ProgramDefinition::Struct => match self.structs.get(identifier) {
177                        Some(struct_) => writeln!(f, "{struct_}")?,
178                        None => return Err(fmt::Error),
179                    },
180                    ProgramDefinition::Record => match self.records.get(identifier) {
181                        Some(record) => writeln!(f, "{record}")?,
182                        None => return Err(fmt::Error),
183                    },
184                    ProgramDefinition::Closure => match self.closures.get(identifier) {
185                        Some(closure) => writeln!(f, "{closure}")?,
186                        None => return Err(fmt::Error),
187                    },
188                    ProgramDefinition::Function => match self.functions.get(identifier) {
189                        Some(function) => writeln!(f, "{function}")?,
190                        None => return Err(fmt::Error),
191                    },
192                },
193            }
194
195            // Omit the last newline.
196            if components_iter.peek().is_some() {
197                writeln!(f)?;
198            }
199        }
200
201        Ok(())
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use crate::Program;
209    use console::network::MainnetV0;
210
211    type CurrentNetwork = MainnetV0;
212
213    #[test]
214    fn test_program_parse() -> Result<()> {
215        // Initialize a new program.
216        let (string, program) = Program::<CurrentNetwork>::parse(
217            r"
218program to_parse.aleo;
219
220struct message:
221    first as field;
222    second as field;
223
224function compute:
225    input r0 as message.private;
226    add r0.first r0.second into r1;
227    output r1 as field.private;",
228        )
229        .unwrap();
230        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
231
232        // Ensure the program contains the struct.
233        assert!(program.contains_struct(&Identifier::from_str("message")?));
234        // Ensure the program contains the function.
235        assert!(program.contains_function(&Identifier::from_str("compute")?));
236
237        Ok(())
238    }
239
240    #[test]
241    fn test_program_parse_function_zero_inputs() -> Result<()> {
242        // Initialize a new program.
243        let (string, program) = Program::<CurrentNetwork>::parse(
244            r"
245program to_parse.aleo;
246
247function compute:
248    add 1u32 2u32 into r0;
249    output r0 as u32.private;",
250        )
251        .unwrap();
252        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
253
254        // Ensure the program contains the function.
255        assert!(program.contains_function(&Identifier::from_str("compute")?));
256
257        Ok(())
258    }
259
260    #[test]
261    fn test_program_display() -> Result<()> {
262        let expected = r"program to_parse.aleo;
263
264struct message:
265    first as field;
266    second as field;
267
268function compute:
269    input r0 as message.private;
270    add r0.first r0.second into r1;
271    output r1 as field.private;
272";
273        // Parse a new program.
274        let program = Program::<CurrentNetwork>::from_str(expected)?;
275        // Ensure the program string matches.
276        assert_eq!(expected, format!("{program}"));
277
278        Ok(())
279    }
280
281    #[test]
282    fn test_program_size() {
283        // Define variable name for easy experimentation with program sizes.
284        let var_name = "a";
285
286        // Helper function to generate imports.
287        let gen_import_string = |n: usize| -> String {
288            let mut s = String::new();
289            for i in 0..n {
290                s.push_str(&format!("import foo{i}.aleo;\n"));
291            }
292            s
293        };
294
295        // Helper function to generate large structs.
296        let gen_struct_string = |n: usize| -> String {
297            let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE);
298            for i in 0..n {
299                s.push_str(&format!("struct m{i}:\n"));
300                for j in 0..10 {
301                    s.push_str(&format!("    {var_name}{j} as u128;\n"));
302                }
303            }
304            s
305        };
306
307        // Helper function to generate large records.
308        let gen_record_string = |n: usize| -> String {
309            let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE);
310            for i in 0..n {
311                s.push_str(&format!("record r{i}:\n    owner as address.private;\n"));
312                for j in 0..10 {
313                    s.push_str(&format!("    {var_name}{j} as u128.private;\n"));
314                }
315            }
316            s
317        };
318
319        // Helper function to generate large mappings.
320        let gen_mapping_string = |n: usize| -> String {
321            let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE);
322            for i in 0..n {
323                s.push_str(&format!("mapping {var_name}{i}:\n    key as field.public;\n    value as field.public;\n"));
324            }
325            s
326        };
327
328        // Helper function to generate large closures.
329        let gen_closure_string = |n: usize| -> String {
330            let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE);
331            for i in 0..n {
332                s.push_str(&format!("closure c{i}:\n    input r0 as u128;\n"));
333                for j in 0..10 {
334                    s.push_str(&format!("    add r0 r0 into r{j};\n"));
335                }
336                s.push_str(&format!("    output r{} as u128;\n", 4000));
337            }
338            s
339        };
340
341        // Helper function to generate large functions.
342        let gen_function_string = |n: usize| -> String {
343            let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE);
344            for i in 0..n {
345                s.push_str(&format!("function f{i}:\n    add 1u128 1u128 into r0;\n"));
346                for j in 0..10 {
347                    s.push_str(&format!("    add r0 r0 into r{j};\n"));
348                }
349            }
350            s
351        };
352
353        // Helper function to generate and parse a program.
354        let test_parse = |imports: &str, body: &str, should_succeed: bool| {
355            let program = format!("{imports}\nprogram to_parse.aleo;\n\n{body}");
356            let result = Program::<CurrentNetwork>::from_str(&program);
357            if result.is_ok() != should_succeed {
358                println!("Program failed to parse: {program}");
359            }
360            assert_eq!(result.is_ok(), should_succeed);
361        };
362
363        // A program with MAX_IMPORTS should succeed.
364        test_parse(&gen_import_string(CurrentNetwork::MAX_IMPORTS), &gen_struct_string(1), true);
365        // A program with more than MAX_IMPORTS should fail.
366        test_parse(&gen_import_string(CurrentNetwork::MAX_IMPORTS + 1), &gen_struct_string(1), false);
367        // A program with MAX_STRUCTS should succeed.
368        test_parse("", &gen_struct_string(CurrentNetwork::MAX_STRUCTS), true);
369        // A program with more than MAX_STRUCTS should fail.
370        test_parse("", &gen_struct_string(CurrentNetwork::MAX_STRUCTS + 1), false);
371        // A program with MAX_RECORDS should succeed.
372        test_parse("", &gen_record_string(CurrentNetwork::MAX_RECORDS), true);
373        // A program with more than MAX_RECORDS should fail.
374        test_parse("", &gen_record_string(CurrentNetwork::MAX_RECORDS + 1), false);
375        // A program with MAX_MAPPINGS should succeed.
376        test_parse("", &gen_mapping_string(CurrentNetwork::MAX_MAPPINGS), true);
377        // A program with more than MAX_MAPPINGS should fail.
378        test_parse("", &gen_mapping_string(CurrentNetwork::MAX_MAPPINGS + 1), false);
379        // A program with MAX_CLOSURES should succeed.
380        test_parse("", &gen_closure_string(CurrentNetwork::MAX_CLOSURES), true);
381        // A program with more than MAX_CLOSURES should fail.
382        test_parse("", &gen_closure_string(CurrentNetwork::MAX_CLOSURES + 1), false);
383        // A program with MAX_FUNCTIONS should succeed.
384        test_parse("", &gen_function_string(CurrentNetwork::MAX_FUNCTIONS), true);
385        // A program with more than MAX_FUNCTIONS should fail.
386        test_parse("", &gen_function_string(CurrentNetwork::MAX_FUNCTIONS + 1), false);
387
388        // Initialize a program which is too big.
389        let program_too_big = format!(
390            "{} {} {} {} {}",
391            gen_struct_string(CurrentNetwork::MAX_STRUCTS),
392            gen_record_string(CurrentNetwork::MAX_RECORDS),
393            gen_mapping_string(CurrentNetwork::MAX_MAPPINGS),
394            gen_closure_string(CurrentNetwork::MAX_CLOSURES),
395            gen_function_string(CurrentNetwork::MAX_FUNCTIONS)
396        );
397        // A program which is too big should fail.
398        test_parse("", &program_too_big, false);
399    }
400}