circomspect_parser/
lib.rs

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
9// Silence clippy warnings for generated code.
10lalrpop_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
31/// A result from the Circom parser.
32pub enum ParseResult {
33    /// The program was successfully parsed without issues.
34    Program(Box<ProgramArchive>, ReportCollection),
35    /// The parser failed to parse a complete program.
36    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    // Create a parse result.
69    let mut result = match &main_components[..] {
70        [(main_id, main_component, custom_gates)] => {
71            // TODO: This calls FillMeta::fill a second time.
72            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            // TODO: Maybe use a flag to ensure that a main component must be present.
89            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    // Remove anonymous components and tuples.
99    //
100    // TODO: This could be moved to the lifting phase.
101    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>> /* path, src*/ {
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        // We don't support Circom 1.
219        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}