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..30 {
487                    s.push_str(&format!("    cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r{j} as [u128; 64u32];\n"));
488                }
489                s.push_str(&format!("    output r{} as [u128; 32u32];\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..250 {
500                    s.push_str(&format!("    cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r{j} as [u128; 64u32];\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");
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}