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