snarkvm_synthesizer_program/
parse.rs1use super::*;
17
18impl<N: Network> Parser for ProgramCore<N> {
19 #[inline]
21 fn parse(string: &str) -> ParserResult<Self> {
22 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 let (string, imports) = many0(Import::parse)(string)?;
35 let (string, _) = Sanitizer::parse(string)?;
37 let (string, _) = tag(Self::type_name())(string)?;
39 let (string, _) = Sanitizer::parse_whitespaces(string)?;
41 let (string, id) = ProgramID::parse(string)?;
43 let (string, _) = Sanitizer::parse_whitespaces(string)?;
45 let (string, _) = tag(";")(string)?;
47
48 fn intermediate<N: Network>(string: &str) -> ParserResult<P<N>> {
49 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 let (string, components) = many1(intermediate)(string)?;
73 let (string, _) = Sanitizer::parse(string)?;
75
76 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 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 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 fn from_str(string: &str) -> Result<Self> {
126 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!(remainder.is_empty(), "Failed to parse string. Remaining invalid string is: \"{remainder}\"");
133 Ok(object)
135 }
136 Err(error) => bail!("Failed to parse string. {error}"),
137 }
138 }
139}
140
141impl<N: Network> Debug for ProgramCore<N> {
142 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
144 Display::fmt(self, f)
145 }
146}
147
148impl<N: Network> Display for ProgramCore<N> {
149 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
151 if !self.imports.is_empty() {
152 for import in self.imports.values() {
154 writeln!(f, "{import}")?;
155 }
156
157 writeln!(f)?;
159 }
160
161 write!(f, "{} {};\n\n", Self::type_name(), self.id)?;
163
164 let mut components_iter = self.components.iter().peekable();
166 while let Some((label, definition)) = components_iter.next() {
167 match label {
168 ProgramLabel::Constructor => {
169 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 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 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 assert!(program.contains_struct(&Identifier::from_str("message")?));
242 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 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 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 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 let program = Program::<CurrentNetwork>::from_str(expected)?;
426 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 let var_name = "a";
438
439 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 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 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 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 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 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 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 test_parse(&gen_import_string(CurrentNetwork::MAX_IMPORTS), &gen_struct_string(1), true);
518 test_parse(&gen_import_string(CurrentNetwork::MAX_IMPORTS + 1), &gen_struct_string(1), false);
520 test_parse("", &gen_struct_string(CurrentNetwork::MAX_STRUCTS), true);
522 test_parse("", &gen_struct_string(CurrentNetwork::MAX_STRUCTS + 1), false);
524 test_parse("", &gen_record_string(CurrentNetwork::MAX_RECORDS), true);
526 test_parse("", &gen_record_string(CurrentNetwork::MAX_RECORDS + 1), false);
528 test_parse("", &gen_mapping_string(CurrentNetwork::MAX_MAPPINGS), true);
530 test_parse("", &gen_mapping_string(CurrentNetwork::MAX_MAPPINGS + 1), false);
532 test_parse("", &gen_closure_string(CurrentNetwork::MAX_CLOSURES), true);
534 test_parse("", &gen_closure_string(CurrentNetwork::MAX_CLOSURES + 1), false);
536 test_parse("", &gen_function_string(CurrentNetwork::MAX_FUNCTIONS), true);
538 test_parse("", &gen_function_string(CurrentNetwork::MAX_FUNCTIONS + 1), false);
540
541 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 test_parse("", &program_too_big, false);
552 }
553}