1#![warn(missing_docs)]
34#![warn(clippy::all)]
35
36use pest::Parser;
37use pest_derive::Parser;
38
39pub use lolli_core::{Formula, Sequent, TwoSidedSequent};
40
41#[derive(Parser)]
42#[grammar = "grammar.pest"]
43struct LolliParser;
44
45#[derive(Debug, thiserror::Error)]
47pub enum ParseError {
48 #[error("Unexpected token: {0}")]
50 UnexpectedToken(String),
51
52 #[error("Unknown operator: {0}")]
54 UnknownOperator(String),
55
56 #[error("Unexpected rule: {0}")]
58 UnexpectedRule(String),
59
60 #[error("Parse error: {0}")]
62 PestError(#[from] pest::error::Error<Rule>),
63
64 #[error("Empty input")]
66 EmptyInput,
67}
68
69pub fn parse_formula(input: &str) -> Result<Formula, ParseError> {
90 let pairs = LolliParser::parse(Rule::formula, input)?;
91 let pair = pairs.into_iter().next().ok_or(ParseError::EmptyInput)?;
92 build_formula(pair)
93}
94
95pub fn parse_sequent(input: &str) -> Result<TwoSidedSequent, ParseError> {
113 let pairs = LolliParser::parse(Rule::sequent, input)?;
114 let pair = pairs.into_iter().next().ok_or(ParseError::EmptyInput)?;
115 build_sequent(pair)
116}
117
118use pest::iterators::Pair;
119
120fn build_formula(pair: Pair<Rule>) -> Result<Formula, ParseError> {
121 match pair.as_rule() {
122 Rule::formula => {
123 let inner = pair.into_inner().next().ok_or(ParseError::EmptyInput)?;
124 build_formula(inner)
125 }
126 Rule::lolli_expr => build_lolli_expr(pair),
127 Rule::par_expr => build_left_assoc_binary(pair, Rule::par_op, Formula::par),
128 Rule::tensor_expr => build_left_assoc_binary(pair, Rule::tensor_op, Formula::tensor),
129 Rule::plus_expr => build_left_assoc_binary(pair, Rule::plus_op, Formula::plus),
130 Rule::with_expr => build_left_assoc_binary(pair, Rule::with_op, Formula::with),
131 Rule::unary_expr => build_unary_expr(pair),
132 Rule::primary_expr => build_primary_expr(pair),
133 Rule::ident => Ok(Formula::Atom(pair.as_str().to_string())),
134 Rule::one => Ok(Formula::One),
135 Rule::bottom => Ok(Formula::Bottom),
136 Rule::top => Ok(Formula::Top),
137 Rule::zero => Ok(Formula::Zero),
138 _ => Err(ParseError::UnexpectedRule(format!("{:?}", pair.as_rule()))),
139 }
140}
141
142fn build_lolli_expr(pair: Pair<Rule>) -> Result<Formula, ParseError> {
143 let mut inner = pair.into_inner();
144 let first = inner.next().ok_or(ParseError::EmptyInput)?;
145 let mut result = build_formula(first)?;
146
147 while let Some(op_or_expr) = inner.next() {
149 if op_or_expr.as_rule() == Rule::lolli_op {
150 let right = inner.next().ok_or(ParseError::EmptyInput)?;
152 let right_formula = build_formula(right)?;
153 result = Formula::lolli(result, right_formula);
154 } else {
155 let right_formula = build_formula(op_or_expr)?;
157 result = Formula::lolli(result, right_formula);
158 }
159 }
160
161 Ok(result)
162}
163
164fn build_left_assoc_binary<F>(
165 pair: Pair<Rule>,
166 _op_rule: Rule,
167 constructor: F,
168) -> Result<Formula, ParseError>
169where
170 F: Fn(Formula, Formula) -> Formula,
171{
172 let mut inner = pair.into_inner();
173 let first = inner.next().ok_or(ParseError::EmptyInput)?;
174 let mut result = build_formula(first)?;
175
176 while let Some(next) = inner.next() {
177 if next.as_rule() == _op_rule {
179 continue;
180 }
181 let right = build_formula(next)?;
182 result = constructor(result, right);
183 }
184
185 Ok(result)
186}
187
188fn build_unary_expr(pair: Pair<Rule>) -> Result<Formula, ParseError> {
189 let mut inner = pair.into_inner().peekable();
190
191 let first = inner.peek().ok_or(ParseError::EmptyInput)?;
193
194 match first.as_rule() {
195 Rule::bang_op => {
196 inner.next(); let operand = inner.next().ok_or(ParseError::EmptyInput)?;
198 let formula = build_formula(operand)?;
199 Ok(Formula::of_course(formula))
200 }
201 Rule::whynot_op => {
202 inner.next(); let operand = inner.next().ok_or(ParseError::EmptyInput)?;
204 let formula = build_formula(operand)?;
205 Ok(Formula::why_not(formula))
206 }
207 _ => {
208 let primary = inner.next().ok_or(ParseError::EmptyInput)?;
210 let mut formula = build_formula(primary)?;
211
212 if let Some(suffix) = inner.next() {
214 if suffix.as_rule() == Rule::negation_suffix {
215 formula = formula.negate();
216 }
217 }
218
219 Ok(formula)
220 }
221 }
222}
223
224fn build_primary_expr(pair: Pair<Rule>) -> Result<Formula, ParseError> {
225 let inner = pair.into_inner().next().ok_or(ParseError::EmptyInput)?;
226 build_formula(inner)
227}
228
229fn build_sequent(pair: Pair<Rule>) -> Result<TwoSidedSequent, ParseError> {
230 let mut antecedent = Vec::new();
231 let mut succedent = Vec::new();
232 let mut seen_turnstile = false;
233
234 for inner in pair.into_inner() {
235 match inner.as_rule() {
236 Rule::turnstile => {
237 seen_turnstile = true;
238 }
239 Rule::formula_list => {
240 let formulas = build_formula_list(inner)?;
241 if seen_turnstile {
242 succedent = formulas;
243 } else {
244 antecedent = formulas;
245 }
246 }
247 _ => {}
248 }
249 }
250
251 Ok(TwoSidedSequent::new(antecedent, succedent))
252}
253
254fn build_formula_list(pair: Pair<Rule>) -> Result<Vec<Formula>, ParseError> {
255 let mut formulas = Vec::new();
256 for inner in pair.into_inner() {
257 if inner.as_rule() == Rule::formula {
258 formulas.push(build_formula(inner)?);
259 }
260 }
261 Ok(formulas)
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_parse_atoms() {
270 let f = parse_formula("A").unwrap();
271 assert_eq!(f, Formula::atom("A"));
272
273 let f = parse_formula("foo").unwrap();
274 assert_eq!(f, Formula::atom("foo"));
275
276 let f = parse_formula("FileHandle").unwrap();
277 assert_eq!(f, Formula::atom("FileHandle"));
278 }
279
280 #[test]
281 fn test_parse_units() {
282 assert_eq!(parse_formula("1").unwrap(), Formula::One);
283 assert_eq!(parse_formula("one").unwrap(), Formula::One);
284 assert_eq!(parse_formula("bot").unwrap(), Formula::Bottom);
285 assert_eq!(parse_formula("bottom").unwrap(), Formula::Bottom);
286 assert_eq!(parse_formula("top").unwrap(), Formula::Top);
287 assert_eq!(parse_formula("0").unwrap(), Formula::Zero);
288 assert_eq!(parse_formula("zero").unwrap(), Formula::Zero);
289 }
290
291 #[test]
292 fn test_parse_negation() {
293 let f = parse_formula("A^").unwrap();
294 assert_eq!(f, Formula::neg_atom("A"));
295
296 let f = parse_formula("A⊥").unwrap();
297 assert_eq!(f, Formula::neg_atom("A"));
298 }
299
300 #[test]
301 fn test_parse_unary() {
302 let f = parse_formula("!A").unwrap();
303 assert_eq!(f, Formula::of_course(Formula::atom("A")));
304
305 let f = parse_formula("?A").unwrap();
306 assert_eq!(f, Formula::why_not(Formula::atom("A")));
307
308 let f = parse_formula("!!A").unwrap();
309 assert_eq!(
310 f,
311 Formula::of_course(Formula::of_course(Formula::atom("A")))
312 );
313 }
314
315 #[test]
316 fn test_parse_lolli() {
317 let f = parse_formula("A -o B").unwrap();
318 assert_eq!(f, Formula::lolli(Formula::atom("A"), Formula::atom("B")));
319
320 let f = parse_formula("A -o B -o C").unwrap();
322 assert_eq!(
323 f,
324 Formula::lolli(
325 Formula::atom("A"),
326 Formula::lolli(Formula::atom("B"), Formula::atom("C"))
327 )
328 );
329 }
330
331 #[test]
332 fn test_parse_tensor() {
333 let f = parse_formula("A * B").unwrap();
334 assert_eq!(f, Formula::tensor(Formula::atom("A"), Formula::atom("B")));
335
336 let f = parse_formula("A * B * C").unwrap();
338 assert_eq!(
339 f,
340 Formula::tensor(
341 Formula::tensor(Formula::atom("A"), Formula::atom("B")),
342 Formula::atom("C")
343 )
344 );
345 }
346
347 #[test]
348 fn test_parse_plus() {
349 let f = parse_formula("A + B").unwrap();
350 assert_eq!(f, Formula::plus(Formula::atom("A"), Formula::atom("B")));
351 }
352
353 #[test]
354 fn test_parse_with() {
355 let f = parse_formula("A & B").unwrap();
356 assert_eq!(f, Formula::with(Formula::atom("A"), Formula::atom("B")));
357 }
358
359 #[test]
360 fn test_parse_parentheses() {
361 let f = parse_formula("(A -o B)").unwrap();
362 assert_eq!(f, Formula::lolli(Formula::atom("A"), Formula::atom("B")));
363
364 let f = parse_formula("(A + B) * C").unwrap();
366 assert_eq!(
367 f,
368 Formula::tensor(
369 Formula::plus(Formula::atom("A"), Formula::atom("B")),
370 Formula::atom("C")
371 )
372 );
373 }
374
375 #[test]
376 fn test_parse_precedence() {
377 let f = parse_formula("A * B -o C").unwrap();
380 assert_eq!(
381 f,
382 Formula::lolli(
383 Formula::tensor(Formula::atom("A"), Formula::atom("B")),
384 Formula::atom("C")
385 )
386 );
387
388 let f = parse_formula("A + B * C").unwrap();
393 assert_eq!(
394 f,
395 Formula::tensor(
396 Formula::plus(Formula::atom("A"), Formula::atom("B")),
397 Formula::atom("C")
398 )
399 );
400
401 let f = parse_formula("A & B + C").unwrap();
404 assert_eq!(
405 f,
406 Formula::plus(
407 Formula::with(Formula::atom("A"), Formula::atom("B")),
408 Formula::atom("C")
409 )
410 );
411 }
412
413 #[test]
414 fn test_parse_complex() {
415 let f = parse_formula("!A * B -o ?C + D").unwrap();
416 assert_eq!(
420 f,
421 Formula::lolli(
422 Formula::tensor(
423 Formula::of_course(Formula::atom("A")),
424 Formula::atom("B")
425 ),
426 Formula::plus(Formula::why_not(Formula::atom("C")), Formula::atom("D"))
427 )
428 );
429 }
430
431 #[test]
432 fn test_parse_sequent() {
433 let s = parse_sequent("A, B |- C").unwrap();
434 assert_eq!(s.antecedent.len(), 2);
435 assert_eq!(s.succedent.len(), 1);
436 assert_eq!(s.antecedent[0], Formula::atom("A"));
437 assert_eq!(s.antecedent[1], Formula::atom("B"));
438 assert_eq!(s.succedent[0], Formula::atom("C"));
439 }
440
441 #[test]
442 fn test_parse_sequent_one_sided() {
443 let s = parse_sequent("|- A, B").unwrap();
444 assert_eq!(s.antecedent.len(), 0);
445 assert_eq!(s.succedent.len(), 2);
446 }
447
448 #[test]
449 fn test_parse_sequent_complex() {
450 let s = parse_sequent("A * B, C -o D |- E + F").unwrap();
451 assert_eq!(s.antecedent.len(), 2);
452 assert_eq!(s.succedent.len(), 1);
453 }
454
455 #[test]
456 fn test_roundtrip() {
457 let formulas = vec![
458 "A",
459 "A -o B",
460 "A * B",
461 "A + B",
462 "A & B",
463 "!A",
464 "?A",
465 "1",
466 "bot",
467 "top",
468 "0",
469 "A * B -o C",
470 "!A * ?B -o C + D",
471 ];
472
473 for input in formulas {
474 let f = parse_formula(input).unwrap();
475 let _ = f.pretty();
477 }
478 }
479}