Skip to main content

snarkvm_synthesizer_program/
parse.rs

1// Copyright (c) 2019-2026 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            V(ViewCore<N>),
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>(string: &str) -> ParserResult<P<N>> {
49            // Parse the whitespace and comments from the string.
50            let (string, _) = Sanitizer::parse(string)?;
51
52            if string.starts_with(ConstructorCore::<N>::type_name()) {
53                map(ConstructorCore::parse, |constructor| P::<N>::Constructor(constructor))(string)
54            } else if string.starts_with(Mapping::<N>::type_name()) {
55                map(Mapping::parse, |mapping| P::<N>::M(mapping))(string)
56            } else if string.starts_with(StructType::<N>::type_name()) {
57                map(StructType::parse, |struct_| P::<N>::S(struct_))(string)
58            } else if string.starts_with(RecordType::<N>::type_name()) {
59                map(RecordType::parse, |record| P::<N>::R(record))(string)
60            } else if string.starts_with(ClosureCore::<N>::type_name()) {
61                map(ClosureCore::parse, |closure| P::<N>::C(closure))(string)
62            } else if string.starts_with(FunctionCore::<N>::type_name()) {
63                map(FunctionCore::parse, |function| P::<N>::F(function))(string)
64            } else if string.starts_with(ViewCore::<N>::type_name()) {
65                map(ViewCore::parse, |view| P::<N>::V(view))(string)
66            } else {
67                Err(Err::Error(make_error(string, ErrorKind::Alt)))
68            }
69        }
70
71        // Parse the struct or function from the string.
72        let (string, components) = many1(intermediate)(string)?;
73        // Parse the whitespace and comments from the string.
74        let (string, _) = Sanitizer::parse(string)?;
75
76        // Initialize a new program.
77        let mut program = match ProgramCore::<N>::new(id) {
78            Ok(program) => program,
79            Err(error) => {
80                eprintln!("{error}");
81                return map_res(take(0usize), Err)(string);
82            }
83        };
84
85        // Add the imports (if any) to the program.
86        for import in imports {
87            match program.add_import(import) {
88                Ok(_) => (),
89                Err(error) => {
90                    eprintln!("{error}");
91                    return map_res(take(0usize), Err)(string);
92                }
93            }
94        }
95
96        // Construct the program with the parsed components.
97        for component in components {
98            let result = match component {
99                P::Constructor(constructor) => program.add_constructor(constructor),
100                P::M(mapping) => program.add_mapping(mapping),
101                P::S(struct_) => program.add_struct(struct_),
102                P::R(record) => program.add_record(record),
103                P::C(closure) => program.add_closure(closure),
104                P::F(function) => program.add_function(function),
105                P::V(view) => program.add_view(view),
106            };
107
108            match result {
109                Ok(_) => (),
110                Err(error) => {
111                    eprintln!("{error}");
112                    return map_res(take(0usize), Err)(string);
113                }
114            }
115        }
116
117        Ok((string, program))
118    }
119}
120
121impl<N: Network> FromStr for ProgramCore<N> {
122    type Err = Error;
123
124    /// Returns a program from a string literal.
125    fn from_str(string: &str) -> Result<Self> {
126        // Ensure the raw program string is less than MAX_PROGRAM_SIZE.
127        ensure!(string.len() <= N::LATEST_MAX_PROGRAM_SIZE(), "Program length exceeds N::MAX_PROGRAM_SIZE.");
128
129        match Self::parse(string) {
130            Ok((remainder, object)) => {
131                // Ensure the remainder is empty.
132                ensure!(remainder.is_empty(), "Failed to parse string. Remaining invalid string is: \"{remainder}\"");
133                // Return the object.
134                Ok(object)
135            }
136            Err(error) => bail!("Failed to parse string. {error}"),
137        }
138    }
139}
140
141impl<N: Network> Debug for ProgramCore<N> {
142    /// Prints the program as a string.
143    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
144        Display::fmt(self, f)
145    }
146}
147
148impl<N: Network> Display for ProgramCore<N> {
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        // Write the components.
165        let mut components_iter = self.components.iter().peekable();
166        while let Some((label, definition)) = components_iter.next() {
167            match label {
168                ProgramLabel::Constructor => {
169                    // Write the constructor, if it exists.
170                    if let Some(constructor) = &self.constructor {
171                        writeln!(f, "{constructor}")?;
172                    }
173                }
174                ProgramLabel::Identifier(identifier) => match definition {
175                    ProgramDefinition::Constructor => return Err(fmt::Error),
176                    ProgramDefinition::Mapping => match self.mappings.get(identifier) {
177                        Some(mapping) => writeln!(f, "{mapping}")?,
178                        None => return Err(fmt::Error),
179                    },
180                    ProgramDefinition::Struct => match self.structs.get(identifier) {
181                        Some(struct_) => writeln!(f, "{struct_}")?,
182                        None => return Err(fmt::Error),
183                    },
184                    ProgramDefinition::Record => match self.records.get(identifier) {
185                        Some(record) => writeln!(f, "{record}")?,
186                        None => return Err(fmt::Error),
187                    },
188                    ProgramDefinition::Closure => match self.closures.get(identifier) {
189                        Some(closure) => writeln!(f, "{closure}")?,
190                        None => return Err(fmt::Error),
191                    },
192                    ProgramDefinition::Function => match self.functions.get(identifier) {
193                        Some(function) => writeln!(f, "{function}")?,
194                        None => return Err(fmt::Error),
195                    },
196                    ProgramDefinition::View => match self.views.get(identifier) {
197                        Some(view) => writeln!(f, "{view}")?,
198                        None => return Err(fmt::Error),
199                    },
200                },
201            }
202
203            // Omit the last newline.
204            if components_iter.peek().is_some() {
205                writeln!(f)?;
206            }
207        }
208
209        Ok(())
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use crate::Program;
217    use console::network::MainnetV0;
218
219    type CurrentNetwork = MainnetV0;
220
221    #[test]
222    fn test_program_parse() -> Result<()> {
223        // Initialize a new program.
224        let (string, program) = Program::<CurrentNetwork>::parse(
225            r"
226program to_parse.aleo;
227
228struct message:
229    first as field;
230    second as field;
231
232function compute:
233    input r0 as message.private;
234    add r0.first r0.second into r1;
235    output r1 as field.private;",
236        )
237        .unwrap();
238        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
239
240        // Ensure the program contains the struct.
241        assert!(program.contains_struct(&Identifier::from_str("message")?));
242        // Ensure the program contains the function.
243        assert!(program.contains_function(&Identifier::from_str("compute")?));
244
245        Ok(())
246    }
247
248    #[test]
249    fn test_program_parse_with_view_zero_inputs() -> Result<()> {
250        let program = Program::<CurrentNetwork>::from_str(
251            r"
252program qy_zeroin.aleo;
253
254function noop:
255    input r0 as u64.private;
256    output r0 as u64.private;
257
258view fixed_value:
259    add 0u64 1234u64 into r0;
260    output r0 as u64.public;",
261        )?;
262        assert_eq!(program.views().len(), 1);
263        Ok(())
264    }
265
266    #[test]
267    fn test_program_parse_with_view() -> Result<()> {
268        let (string, program) = Program::<CurrentNetwork>::parse(
269            r"
270program token_with_view.aleo;
271
272mapping balances:
273    key as address.public;
274    value as u64.public;
275
276function noop:
277    input r0 as u64.private;
278    output r0 as u64.private;
279
280view total_balance:
281    input r0 as address.public;
282    get.or_use balances[r0] 0u64 into r1;
283    output r1 as u64.public;",
284        )
285        .unwrap();
286        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
287
288        // The program should expose the view by name.
289        let view_name = Identifier::from_str("total_balance")?;
290        assert!(program.contains_view(&view_name));
291        assert_eq!(1, program.views().len());
292        let view = program.get_view_ref(&view_name)?;
293        assert_eq!(1, view.inputs().len());
294        assert_eq!(1, view.commands().len());
295        assert_eq!(1, view.outputs().len());
296
297        Ok(())
298    }
299
300    #[test]
301    fn test_program_view_rejects_writes() {
302        let result = Program::<CurrentNetwork>::from_str(
303            r"
304program bad_view.aleo;
305
306mapping balances:
307    key as address.public;
308    value as u64.public;
309
310view mutate:
311    input r0 as address.public;
312    set 1u64 into balances[r0];
313    output r0 as address.public;",
314        );
315        assert!(result.is_err(), "expected program parse to fail when view contains 'set'");
316    }
317
318    #[test]
319    fn test_program_view_rejects_call() {
320        let result = Program::<CurrentNetwork>::from_str(
321            r"
322program bad_view.aleo;
323
324closure helper:
325    input r0 as field;
326    output r0 as field;
327
328view uses_call:
329    input r0 as field.public;
330    call helper r0 into r1;
331    output r1 as field.public;",
332        );
333        assert!(result.is_err(), "expected program parse to fail when view contains 'call'");
334    }
335
336    #[test]
337    fn test_program_rejects_duplicate_view_names() {
338        let result = Program::<CurrentNetwork>::from_str(
339            r"
340program dup_view.aleo;
341
342view foo:
343    add 0u64 1u64 into r0;
344    output r0 as u64.public;
345
346view foo:
347    add 0u64 2u64 into r0;
348    output r0 as u64.public;",
349        );
350        assert!(result.is_err(), "expected program parse to fail with two views named 'foo'");
351    }
352
353    #[test]
354    fn test_program_rejects_view_name_colliding_with_function() {
355        let result = Program::<CurrentNetwork>::from_str(
356            r"
357program qf_collision.aleo;
358
359function foo:
360    input r0 as u64.private;
361    output r0 as u64.private;
362
363view foo:
364    add 0u64 1u64 into r0;
365    output r0 as u64.public;",
366        );
367        assert!(result.is_err(), "expected program parse to fail when a view reuses a function name");
368    }
369
370    #[test]
371    fn test_program_rejects_view_name_colliding_with_closure() {
372        let result = Program::<CurrentNetwork>::from_str(
373            r"
374program qc_collision.aleo;
375
376closure foo:
377    input r0 as field;
378    output r0 as field;
379
380function noop:
381    input r0 as u64.private;
382    output r0 as u64.private;
383
384view foo:
385    add 0u64 1u64 into r0;
386    output r0 as u64.public;",
387        );
388        assert!(result.is_err(), "expected program parse to fail when a view reuses a closure name");
389    }
390
391    #[test]
392    fn test_program_parse_function_zero_inputs() -> Result<()> {
393        // Initialize a new program.
394        let (string, program) = Program::<CurrentNetwork>::parse(
395            r"
396program to_parse.aleo;
397
398function compute:
399    add 1u32 2u32 into r0;
400    output r0 as u32.private;",
401        )
402        .unwrap();
403        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
404
405        // Ensure the program contains the function.
406        assert!(program.contains_function(&Identifier::from_str("compute")?));
407
408        Ok(())
409    }
410
411    #[test]
412    fn test_program_display() -> Result<()> {
413        let expected = r"program to_parse.aleo;
414
415struct message:
416    first as field;
417    second as field;
418
419function compute:
420    input r0 as message.private;
421    add r0.first r0.second into r1;
422    output r1 as field.private;
423";
424        // Parse a new program.
425        let program = Program::<CurrentNetwork>::from_str(expected)?;
426        // Ensure the program string matches.
427        assert_eq!(expected, format!("{program}"));
428
429        Ok(())
430    }
431
432    #[test]
433    fn test_program_size() {
434        let max_program_size: usize = CurrentNetwork::LATEST_MAX_PROGRAM_SIZE();
435
436        // Define variable name for easy experimentation with program sizes.
437        let var_name = "a";
438
439        // Helper function to generate imports.
440        let gen_import_string = |n: usize| -> String {
441            let mut s = String::new();
442            for i in 0..n {
443                s.push_str(&format!("import foo{i}.aleo;\n"));
444            }
445            s
446        };
447
448        // Helper function to generate large structs.
449        let gen_struct_string = |n: usize| -> String {
450            let mut s = String::with_capacity(max_program_size);
451            for i in 0..n {
452                s.push_str(&format!("struct m{i}:\n"));
453                for j in 0..10 {
454                    s.push_str(&format!("    {var_name}{j} as u128;\n"));
455                }
456            }
457            s
458        };
459
460        // Helper function to generate large records.
461        let gen_record_string = |n: usize| -> String {
462            let mut s = String::with_capacity(max_program_size);
463            for i in 0..n {
464                s.push_str(&format!("record r{i}:\n    owner as address.private;\n"));
465                for j in 0..10 {
466                    s.push_str(&format!("    {var_name}{j} as u128.private;\n"));
467                }
468            }
469            s
470        };
471
472        // Helper function to generate large mappings.
473        let gen_mapping_string = |n: usize| -> String {
474            let mut s = String::with_capacity(max_program_size);
475            for i in 0..n {
476                s.push_str(&format!("mapping {var_name}{i}:\n    key as field.public;\n    value as field.public;\n"));
477            }
478            s
479        };
480
481        // Helper function to generate large closures.
482        let gen_closure_string = |n: usize| -> String {
483            let mut s = String::with_capacity(max_program_size);
484            for i in 0..n {
485                s.push_str(&format!("closure c{i}:\n    input r0 as u128;\n"));
486                for j in 0..10 {
487                    s.push_str(&format!("    add r0 r0 into r{j};\n"));
488                }
489                s.push_str(&format!("    output r{} as u128;\n", 4000));
490            }
491            s
492        };
493
494        // Helper function to generate large functions.
495        let gen_function_string = |n: usize| -> String {
496            let mut s = String::with_capacity(max_program_size);
497            for i in 0..n {
498                s.push_str(&format!("function f{i}:\n    add 1u128 1u128 into r0;\n"));
499                for j in 0..500 {
500                    s.push_str(&format!("    add r0 r0 into r{j};\n"));
501                }
502            }
503            s
504        };
505
506        // Helper function to generate and parse a program.
507        let test_parse = |imports: &str, body: &str, should_succeed: bool| {
508            let program = format!("{imports}\nprogram to_parse.aleo;\n\n{body}");
509            let result = Program::<CurrentNetwork>::from_str(&program);
510            if result.is_ok() != should_succeed {
511                println!("Program failed to parse: {program}");
512            }
513            assert_eq!(result.is_ok(), should_succeed);
514        };
515
516        // A program with MAX_IMPORTS should succeed.
517        test_parse(&gen_import_string(CurrentNetwork::MAX_IMPORTS), &gen_struct_string(1), true);
518        // A program with more than MAX_IMPORTS should fail.
519        test_parse(&gen_import_string(CurrentNetwork::MAX_IMPORTS + 1), &gen_struct_string(1), false);
520        // A program with MAX_STRUCTS should succeed.
521        test_parse("", &gen_struct_string(CurrentNetwork::MAX_STRUCTS), true);
522        // A program with more than MAX_STRUCTS should fail.
523        test_parse("", &gen_struct_string(CurrentNetwork::MAX_STRUCTS + 1), false);
524        // A program with MAX_RECORDS should succeed.
525        test_parse("", &gen_record_string(CurrentNetwork::MAX_RECORDS), true);
526        // A program with more than MAX_RECORDS should fail.
527        test_parse("", &gen_record_string(CurrentNetwork::MAX_RECORDS + 1), false);
528        // A program with MAX_MAPPINGS should succeed.
529        test_parse("", &gen_mapping_string(CurrentNetwork::MAX_MAPPINGS), true);
530        // A program with more than MAX_MAPPINGS should fail.
531        test_parse("", &gen_mapping_string(CurrentNetwork::MAX_MAPPINGS + 1), false);
532        // A program with MAX_CLOSURES should succeed.
533        test_parse("", &gen_closure_string(CurrentNetwork::MAX_CLOSURES), true);
534        // A program with more than MAX_CLOSURES should fail.
535        test_parse("", &gen_closure_string(CurrentNetwork::MAX_CLOSURES + 1), false);
536        // A program with MAX_FUNCTIONS should succeed.
537        test_parse("", &gen_function_string(CurrentNetwork::MAX_FUNCTIONS), true);
538        // A program with more than MAX_FUNCTIONS should fail.
539        test_parse("", &gen_function_string(CurrentNetwork::MAX_FUNCTIONS + 1), false);
540
541        // Initialize a program which is too big.
542        let program_too_big = format!(
543            "{} {} {} {} {}",
544            gen_struct_string(CurrentNetwork::MAX_STRUCTS),
545            gen_record_string(CurrentNetwork::MAX_RECORDS),
546            gen_mapping_string(CurrentNetwork::MAX_MAPPINGS),
547            gen_closure_string(CurrentNetwork::MAX_CLOSURES),
548            gen_function_string(CurrentNetwork::MAX_FUNCTIONS)
549        );
550        // A program which is too big should fail.
551        test_parse("", &program_too_big, false);
552    }
553}