circomspect_parser/
parser_logic.rs1use 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 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
119use 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}