circomspect_parser/
parser_logic.rs

1use super::errors::{ParsingError, UnclosedCommentError};
2use super::lang;
3
4use program_structure::ast::AST;
5use program_structure::report::Report;
6use program_structure::file_definition::FileID;
7
8pub fn preprocess(expr: &str, file_id: FileID) -> Result<String, Box<Report>> {
9    let mut pp = String::new();
10    let mut state = 0;
11    let mut loc = 0;
12    let mut block_start = 0;
13
14    let mut it = expr.chars();
15    while let Some(c0) = it.next() {
16        loc += 1;
17        match (state, c0) {
18            (0, '/') => {
19                loc += 1;
20                match it.next() {
21                    Some('/') => {
22                        state = 1;
23                        pp.push(' ');
24                        pp.push(' ');
25                    }
26                    Some('*') => {
27                        block_start = loc;
28                        state = 2;
29                        pp.push(' ');
30                        pp.push(' ');
31                    }
32                    Some(c1) => {
33                        pp.push(c0);
34                        pp.push(c1);
35                    }
36                    None => {
37                        pp.push(c0);
38                        break;
39                    }
40                }
41            }
42            (0, _) => pp.push(c0),
43            (1, '\n') => {
44                pp.push(c0);
45                state = 0;
46            }
47            (2, '*') => {
48                loc += 1;
49                match it.next() {
50                    Some('/') => {
51                        pp.push(' ');
52                        pp.push(' ');
53                        state = 0;
54                    }
55                    Some(c) => {
56                        pp.push(' ');
57                        for _i in 0..c.len_utf8() {
58                            pp.push(' ');
59                        }
60                    }
61                    None => {
62                        let error =
63                            UnclosedCommentError { location: block_start..block_start, file_id };
64                        return Err(Box::new(error.into_report()));
65                    }
66                }
67            }
68            (_, c) => {
69                for _i in 0..c.len_utf8() {
70                    pp.push(' ');
71                }
72            }
73        }
74    }
75    Ok(pp)
76}
77
78pub fn parse_file(src: &str, file_id: FileID) -> Result<AST, Box<Report>> {
79    use lalrpop_util::ParseError::*;
80    lang::ParseAstParser::new()
81        .parse(&preprocess(src, file_id)?)
82        .map(|mut ast| {
83            // Set file ID for better error reporting.
84            for include in &mut ast.includes {
85                include.meta.set_file_id(file_id);
86            }
87            ast
88        })
89        .map_err(|parse_error| match parse_error {
90            InvalidToken { location } => ParsingError {
91                file_id,
92                message: "Invalid token found.".to_string(),
93                location: location..location,
94            },
95            UnrecognizedToken { ref token, ref expected } => ParsingError {
96                file_id,
97                message: format!(
98                    "Unrecognized token `{}` found.{}",
99                    token.1,
100                    format_expected(expected)
101                ),
102                location: token.0..token.2,
103            },
104            ExtraToken { ref token } => ParsingError {
105                file_id,
106                message: format!("Extra token `{}` found.", token.2),
107                location: token.0..token.2,
108            },
109            _ => ParsingError { file_id, message: format!("{parse_error}"), location: 0..0 },
110        })
111        .map_err(|error| Box::new(error.into_report()))
112}
113
114pub fn parse_string(src: &str) -> Option<AST> {
115    let src = preprocess(src, 0).ok()?;
116    lang::ParseAstParser::new().parse(&src).ok()
117}
118
119/// Parse a single (function or template) definition for testing purposes.
120use program_structure::ast::Definition;
121
122pub fn parse_definition(src: &str) -> Option<Definition> {
123    match parse_string(src) {
124        Some(AST { mut definitions, .. }) if definitions.len() == 1 => definitions.pop(),
125        _ => None,
126    }
127}
128
129#[must_use]
130fn format_expected(tokens: &[String]) -> String {
131    if tokens.is_empty() {
132        String::new()
133    } else {
134        let tokens = tokens
135            .iter()
136            .enumerate()
137            .map(|(index, token)| {
138                if index == 0 {
139                    token.replace('\"', "`")
140                } else if index < tokens.len() - 1 {
141                    format!(", {}", token.replace('\"', "`"))
142                } else {
143                    format!(" or {}", token.replace('\"', "`"))
144                }
145            })
146            .collect::<Vec<_>>()
147            .join("");
148        format!(" Expected one of {tokens}.")
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::parse_string;
155
156    #[test]
157    fn test_parse_string() {
158        let function = r#"
159            function f(m) {
160                // This is a comment.
161                var x = 1024;
162                var y = 16;
163                while (x < m) {
164                    x += y;
165                }
166                if (x == m) {
167                    x = 0;
168                }
169                /* This is another comment. */
170                return x;
171            }
172        "#;
173        let _ = parse_string(function);
174
175        let template = r#"
176            template T(m) {
177                signal input in[m];
178                signal output out;
179
180                var sum = 0;
181                for (var i = 0; i < m; i++) {
182                    sum += in[i];
183                }
184                out <== sum;
185            }
186        "#;
187        let _ = parse_string(template);
188    }
189}