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..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 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 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 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}