1extern crate num_bigint_dig as num_bigint;
2extern crate num_traits;
3extern crate serde;
4extern crate serde_derive;
5
6#[macro_use]
7extern crate lalrpop_util;
8
9lalrpop_mod!(#[allow(clippy::all)] pub lang);
11
12use log::debug;
13
14mod errors;
15mod include_logic;
16mod parser_logic;
17mod syntax_sugar_traits;
18mod syntax_sugar_remover;
19
20pub use parser_logic::parse_definition;
21
22use include_logic::FileStack;
23use program_structure::ast::{Version, AST};
24use program_structure::report::{Report, ReportCollection};
25use program_structure::file_definition::{FileID, FileLibrary};
26use program_structure::program_archive::ProgramArchive;
27use program_structure::template_library::TemplateLibrary;
28use std::collections::HashMap;
29use std::path::{Path, PathBuf};
30
31pub enum ParseResult {
33 Program(Box<ProgramArchive>, ReportCollection),
35 Library(Box<TemplateLibrary>, ReportCollection),
37}
38
39pub fn parse_files(
40 file_paths: &[PathBuf],
41 libraries: &[PathBuf],
42 compiler_version: &Version,
43) -> ParseResult {
44 let mut reports = ReportCollection::new();
45 let mut file_stack = FileStack::new(file_paths, libraries, &mut reports);
46 let mut file_library = FileLibrary::new();
47 let mut definitions = HashMap::new();
48 let mut main_components = Vec::new();
49 while let Some(file_path) = FileStack::take_next(&mut file_stack) {
50 match parse_file(&file_path, &mut file_stack, &mut file_library, compiler_version) {
51 Ok((file_id, program, mut warnings)) => {
52 if let Some(main_component) = program.main_component {
53 main_components.push((file_id, main_component, program.custom_gates));
54 }
55 debug!(
56 "adding {} definitions from `{}`",
57 program.definitions.iter().map(|x| x.name()).collect::<Vec<_>>().join(", "),
58 file_path.display(),
59 );
60 definitions.insert(file_id, program.definitions);
61 reports.append(&mut warnings);
62 }
63 Err(error) => {
64 reports.push(*error);
65 }
66 }
67 }
68 let mut result = match &main_components[..] {
70 [(main_id, main_component, custom_gates)] => {
71 match ProgramArchive::new(
73 file_library,
74 *main_id,
75 main_component,
76 &definitions,
77 *custom_gates,
78 ) {
79 Ok(program_archive) => ParseResult::Program(Box::new(program_archive), reports),
80 Err((file_library, mut errors)) => {
81 reports.append(&mut errors);
82 let template_library = TemplateLibrary::new(definitions, file_library);
83 ParseResult::Library(Box::new(template_library), reports)
84 }
85 }
86 }
87 [] => {
88 let template_library = TemplateLibrary::new(definitions, file_library);
90 ParseResult::Library(Box::new(template_library), reports)
91 }
92 _ => {
93 reports.push(errors::MultipleMainError::produce_report());
94 let template_library = TemplateLibrary::new(definitions, file_library);
95 ParseResult::Library(Box::new(template_library), reports)
96 }
97 };
98 match &mut result {
102 ParseResult::Program(program_archive, reports) => {
103 if program_archive.main_expression().is_anonymous_component() {
104 reports.push(
105 errors::AnonymousComponentError::new(
106 Some(program_archive.main_expression().meta()),
107 "The main component cannot contain an anonymous call.",
108 Some("Main component defined here."),
109 )
110 .into_report(),
111 );
112 }
113 let (new_templates, new_functions) = syntax_sugar_remover::remove_syntactic_sugar(
114 &program_archive.templates,
115 &program_archive.functions,
116 &program_archive.file_library,
117 reports,
118 );
119 program_archive.templates = new_templates;
120 program_archive.functions = new_functions;
121 }
122 ParseResult::Library(template_library, reports) => {
123 let (new_templates, new_functions) = syntax_sugar_remover::remove_syntactic_sugar(
124 &template_library.templates,
125 &template_library.functions,
126 &template_library.file_library,
127 reports,
128 );
129 template_library.templates = new_templates;
130 template_library.functions = new_functions;
131 }
132 }
133 result
134}
135
136pub fn parse_file(
137 file_path: &PathBuf,
138 file_stack: &mut FileStack,
139 file_library: &mut FileLibrary,
140 compiler_version: &Version,
141) -> Result<(FileID, AST, ReportCollection), Box<Report>> {
142 let mut reports = ReportCollection::new();
143
144 debug!("reading file `{}`", file_path.display());
145 let (path_str, file_content) = open_file(file_path)?;
146 let is_user_input = file_stack.is_user_input(file_path);
147 let file_id = file_library.add_file(path_str, file_content.clone(), is_user_input);
148
149 debug!("parsing file `{}`", file_path.display());
150 let program = parser_logic::parse_file(&file_content, file_id)?;
151 match check_compiler_version(file_path, program.compiler_version, compiler_version) {
152 Ok(warnings) => reports.extend(warnings),
153 Err(error) => reports.push(*error),
154 }
155 for include in &program.includes {
156 if let Err(report) = file_stack.add_include(include) {
157 reports.push(*report);
158 }
159 }
160 Ok((file_id, program, reports))
161}
162
163fn open_file(file_path: &PathBuf) -> Result<(String, String), Box<Report>> {
164 use errors::FileOsError;
165 use std::fs::read_to_string;
166 let path_str = format!("{}", file_path.display());
167 read_to_string(file_path)
168 .map(|contents| (path_str.clone(), contents))
169 .map_err(|_| FileOsError { path: path_str.clone() })
170 .map_err(|error| Box::new(error.into_report()))
171}
172
173fn check_compiler_version(
174 file_path: &Path,
175 required_version: Option<Version>,
176 compiler_version: &Version,
177) -> Result<ReportCollection, Box<Report>> {
178 use errors::{CompilerVersionError, NoCompilerVersionWarning};
179 if let Some(required_version) = required_version {
180 if (required_version.0 == compiler_version.0 && required_version.1 < compiler_version.1)
181 || (required_version.0 == compiler_version.0
182 && required_version.1 == compiler_version.1
183 && required_version.2 <= compiler_version.2)
184 {
185 Ok(vec![])
186 } else {
187 let error = CompilerVersionError {
188 path: format!("{}", file_path.display()),
189 required_version,
190 version: *compiler_version,
191 };
192 Err(Box::new(error.into_report()))
193 }
194 } else {
195 let report = NoCompilerVersionWarning::produce_report(NoCompilerVersionWarning {
196 path: format!("{}", file_path.display()),
197 version: *compiler_version,
198 });
199 Ok(vec![report])
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use std::path::PathBuf;
206
207 use crate::check_compiler_version;
208
209 #[test]
210 fn test_compiler_version() {
211 let path = PathBuf::from("example.circom");
212
213 assert!(check_compiler_version(&path, None, &(2, 1, 2)).is_ok());
214 assert!(check_compiler_version(&path, Some((2, 0, 0)), &(2, 1, 2)).is_ok());
215 assert!(check_compiler_version(&path, Some((2, 0, 8)), &(2, 1, 2)).is_ok());
216 assert!(check_compiler_version(&path, Some((2, 1, 2)), &(2, 1, 2)).is_ok());
217
218 assert!(check_compiler_version(&path, Some((1, 0, 0)), &(2, 0, 8)).is_err());
220 assert!(check_compiler_version(&path, Some((2, 1, 2)), &(2, 0, 8)).is_err());
221 assert!(check_compiler_version(&path, Some((2, 1, 4)), &(2, 1, 2)).is_err());
222 }
223}