From 305f73f22a2d52bed4653b113b41443134407fcb Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Wed, 17 Aug 2022 16:50:32 +0200 Subject: [PATCH 01/38] Add parsing methods that logs errors --- atl-checker/src/lib.rs | 5 +- atl-checker/src/parsing/mod.rs | 264 +++++++++++++++++++++++++++++++++ 2 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 atl-checker/src/parsing/mod.rs diff --git a/atl-checker/src/lib.rs b/atl-checker/src/lib.rs index 800d1a70b..28cf3d6fa 100644 --- a/atl-checker/src/lib.rs +++ b/atl-checker/src/lib.rs @@ -1,8 +1,8 @@ #[macro_use] -extern crate serde; -#[macro_use] extern crate lazy_static; #[macro_use] +extern crate serde; +#[macro_use] extern crate tracing; #[macro_use] @@ -14,5 +14,6 @@ pub mod analyse; pub mod atl; pub mod edg; pub mod game_structure; +mod parsing; #[cfg(feature = "graph-printer")] pub mod printer; diff --git a/atl-checker/src/parsing/mod.rs b/atl-checker/src/parsing/mod.rs new file mode 100644 index 000000000..7a40d3f13 --- /dev/null +++ b/atl-checker/src/parsing/mod.rs @@ -0,0 +1,264 @@ +use pom::parser::*; +use std::cell::RefCell; +use std::cmp::{max, min}; +use std::fmt::Display; +use std::ops::Range; + +/// A `Span` describes the position of a slice of text in the original program. +/// Usually used to describe what text an AST node was created from. +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub struct Span { + pub begin: usize, + pub end: usize, +} + +impl Span { + /// Returns and equivalent range + pub fn to_range(&self) -> Range { + self.begin..self.end + } + + /// Merge two spans into a new span that contains the original spans and everything in between + pub fn merge(&self, other: Span) -> Span { + Span { + begin: min(self.begin, other.begin), + end: max(self.end, other.end), + } + } +} + +/// A trait that allows us to extent parser with a helper function that extracts the span of +/// the parsed piece of text +trait WithSpan<'a, I, O: 'a> { + fn with_span(self) -> Parser<'a, I, (Span, O)>; +} + +impl<'a, I, O: 'a> WithSpan<'a, I, O> for Parser<'a, I, O> { + /// Make the parser note the beginning and end position and + /// include it in the result as a `Span` + fn with_span(self) -> Parser<'a, I, (Span, O)> { + (empty().pos() + self + empty().pos()) + .map(|((begin, item), end)| (Span { begin, end }, item)) + } +} + +/// An error containing problematic text span and an error message +#[derive(Debug)] +pub struct ParseError { + span: Span, + msg: String, +} + +/// The `ParseState` is a struct carried around during parsing contained extra information +/// about the state of the parser, such as the errors encountered +#[derive(Debug, Default)] +pub(crate) struct ParseState { + errors: RefCell>, +} + +impl ParseState { + /// Saves and error to display when parsing is done + pub fn report_err(&self, err: ParseError) { + self.errors.borrow_mut().push(err) + } +} + +/// Create a lazy parser used for recursive definitions. +/// This function is similar to `call` but allows passing one argument to the parser function. +pub(crate) fn call1<'a, I, O, A, P: Fn(&'a A) -> Parser>( + parser: &'a P, + expr_parser: &'a A, +) -> Parser<'a, I, O> { + Parser::new(move |input: &'_ [I], start: usize| (parser(expr_parser).method)(input, start)) +} + +/// Create a lazy parser used for recursive definitions. +/// This function is similar to `call` but allows passing two arguments to the parser function. +pub(crate) fn call2<'a, I, O, A, B, P: Fn(&'a A, &'a B) -> Parser<'a, I, O>>( + parser: &'a P, + arg1: &'a A, + arg2: &'a B, +) -> Parser<'a, I, O> { + Parser::new(move |input: &'_ [I], start: usize| (parser(arg1, arg2).method)(input, start)) +} + +/// Creates a parser that will run the given parser. +/// If the given parser fails to parse, an error is reported using the given error message, +/// and parsing will continue with no input consumed. +pub(crate) fn parse_or_skip<'a, I: Eq + Display, O: 'a>( + parse_state: &'a ParseState, + parser: Parser<'a, I, O>, + err_msg: &'a str, +) -> Parser<'a, I, Option> { + Parser::new( + move |input: &'_ [I], start: usize| match (parser.method)(input, start) { + Ok((out, pos)) => Ok((Some(out), pos)), + Err(_) => { + parse_state.report_err(ParseError { + span: Span { + begin: start, + end: start + 1, + }, + msg: err_msg.to_string(), + }); + Ok((None, start)) + } + }, + ) +} + +/// Creates a parser that will run the given parser. +/// If the given parser fails to parse, an error is reported using the given error message, +/// and parsing will continue at EOF. +pub(crate) fn parse_or_abort<'a, I: Eq + Display, O: 'a>( + parse_state: &'a ParseState, + parser: Parser<'a, I, O>, + err_msg: &'a str, +) -> Parser<'a, I, Option> { + Parser::new( + move |input: &'_ [I], start: usize| match (parser.method)(input, start) { + Ok((out, pos)) => Ok((Some(out), pos)), + Err(_) => { + parse_state.report_err(ParseError { + span: Span { + begin: start, + end: input.len(), + }, + msg: err_msg.to_string(), + }); + Ok((None, input.len())) + } + }, + ) +} + +/// Creates a parser that will run the given parser and then consume if synchronization token. +/// If the given parser fails to parse, an error is reported using the given error message, +/// and parsing will continue at the next occurrence of the synchronization token. +pub(crate) fn parse_or_sync<'a, I: Eq + Display, O: 'a>( + parse_state: &'a ParseState, + parser: Parser<'a, I, O>, + sync: I, + err_msg: &'a str, +) -> Parser<'a, I, Option> { + Parser::new( + move |input: &'_ [I], start: usize| match (parser.method)(input, start) { + Ok((out, pos)) => { + if input.get(pos) == Some(&sync) { + Ok((Some(out), pos + 1)) + } else { + parse_state.report_err(ParseError { + span: Span { + begin: pos, + end: input.len(), + }, + msg: format!("Missing {}", sync), + }); + Ok((Some(out), input.len())) + } + } + Err(_) => { + let sync_pos = input[start..] + .iter() + .position(|i| i == &sync) + .map(|p| p + start + 1) + .unwrap_or(input.len()); + parse_state.report_err(ParseError { + span: Span { + begin: start, + end: sync_pos, + }, + msg: err_msg.to_string(), + }); + Ok((None, sync_pos)) + } + }, + ) +} + +#[cfg(test)] +mod test { + use crate::parsing::{parse_or_abort, parse_or_skip, parse_or_sync, ParseState}; + use pom::parser::{end, seq, sym}; + use pom::set::Set; + + #[test] + fn parse_or_skip_001() { + let state = ParseState::default(); + let parser = (parse_or_skip(&state, seq(b"foo"), "error").map(|res| res.unwrap_or(b"")) + + seq(b"bar")) + .convert(|(fst, snd)| String::from_utf8([fst, snd].concat().to_vec())) + - end(); + + // Input is expected string "foobar" + let res = parser.parse(b"foobar").expect("Parser must not fail"); + assert_eq!("foobar", res); + assert!(state.errors.borrow().is_empty()); + } + + #[test] + fn parse_or_skip_002() { + let state = ParseState::default(); + let parser = (parse_or_skip(&state, seq(b"foo"), "error").map(|res| res.unwrap_or(b"")) + + seq(b"bar")) + .convert(|(fst, snd)| String::from_utf8([fst, snd].concat().to_vec())) + - end(); + + // Input is unexpected, but parser will continue + let res = parser.parse(b"bar").expect("Parser must not fail"); + assert_eq!("bar", res); + assert_eq!(1, state.errors.borrow().len()); + } + + #[test] + fn parse_or_abort_001() { + let state = ParseState::default(); + let parser = (parse_or_abort(&state, seq(b"foo"), "error").map(|res| res.unwrap_or(b"")) + + parse_or_abort(&state, seq(b"bar"), "error").map(|res| res.unwrap_or(b""))) + .convert(|(fst, snd)| String::from_utf8([fst, snd].concat().to_vec())) + - end(); + + // Input is expected string "foobar" + let res = parser.parse(b"foobar").expect("Parser must not fail"); + assert_eq!("foobar", res); + assert!(state.errors.borrow().is_empty()); + } + + #[test] + fn parse_or_abort_002() { + let state = ParseState::default(); + let parser = (parse_or_abort(&state, seq(b"foo"), "error").map(|res| res.unwrap_or(b"")) + + parse_or_abort(&state, seq(b"bar"), "error").map(|res| res.unwrap_or(b""))) + .convert(|(fst, snd)| String::from_utf8([fst, snd].concat().to_vec())) + - end(); + + // Input is unexpected, so parser will abort + let res = parser.parse(b"goobar").expect("Parser must not fail"); + assert_eq!("", res); + assert_eq!(2, state.errors.borrow().len()); + } + + #[test] + fn parse_or_sync_001() { + let state = ParseState::default(); + let foo = seq(b"foo"); + let parser = sym(b'(') * parse_or_sync(&state, foo, b')', "error") - end(); + + // Input is expected string "(foo)" + let res = parser.parse(b"(foo)").expect("Parser must not fail"); + assert_eq!(b"foo", res.unwrap()); + assert!(state.errors.borrow().is_empty()); + } + + #[test] + fn parse_or_sync_002() { + let state = ParseState::default(); + let foo = seq(b"foo"); + let parser = sym(b'(') * parse_or_sync(&state, foo, b')', "error") - end(); + + // Input is unexpected, but parser will recover + let res = parser.parse(b"(bar)").expect("Parser must not fail"); + assert!(res.is_none()); + assert_eq!(1, state.errors.borrow().len()); + } +} From f445d66e902e3226b21364ee6c0f2d04ba0893a3 Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Sun, 21 Aug 2022 16:01:27 +0200 Subject: [PATCH 02/38] Add state to ATL parser --- atl-checker/src/atl/parser.rs | 544 ++++++++++++------ atl-checker/src/game_structure/eager.rs | 5 +- .../game_structure/lcgs/ir/intermediate.rs | 5 +- atl-checker/src/parsing/mod.rs | 10 +- 4 files changed, 373 insertions(+), 191 deletions(-) diff --git a/atl-checker/src/atl/parser.rs b/atl-checker/src/atl/parser.rs index ea8b91fad..0bf890a93 100644 --- a/atl-checker/src/atl/parser.rs +++ b/atl-checker/src/atl/parser.rs @@ -6,16 +6,23 @@ use pom::parser::{list, one_of, seq, sym}; use super::Phi; use crate::game_structure::{Player, Proposition}; +use crate::parsing::{call2, ParseState}; /// Parse an ATL formula pub fn parse_phi<'a, 'b: 'a, A: AtlExpressionParser>( expr_parser: &'b A, input: &'a str, ) -> Result { - let formula = ws() * phi(expr_parser) - ws() - end(); - formula + let state = ParseState::default(); + let formula = ws() * conjunction(&state, expr_parser) - ws() - end(); + let phi = formula .parse(input.as_bytes()) - .map_err(|err| err.to_string()) + .expect("Parser may not fail"); + if state.has_errors() { + Err(state.errors_as_str(input)) + } else { + Ok(phi) + } } /// Allows a CGS model to define custom player and proposition expressions. For instance, @@ -23,9 +30,9 @@ pub fn parse_phi<'a, 'b: 'a, A: AtlExpressionParser>( /// in json, players and propositions are numbers. pub trait AtlExpressionParser { /// A parser that parses a player name - fn player_parser(&self) -> Parser; + fn player_parser(&self, state: &ParseState) -> Parser; /// A parser that parses a proposition name - fn proposition_parser(&self) -> Parser; + fn proposition_parser(&self, state: &ParseState) -> Parser; } /// Whitespace @@ -33,91 +40,112 @@ fn ws<'a>() -> Parser<'a, u8, ()> { one_of(b" \t\r\n").repeat(0..).discard() } -/// A lazy parser used for recursive definitions. -/// Normally we make recursive parsers with the `call(phi)` that wraps a parser with lazy -/// invocation. But the `call` method does not allow us to pass our converter. So we -/// make our own lazy parser. -fn lazy<'a, A: AtlExpressionParser, P: Fn(&'a A) -> Parser>( - parser: &'a P, +/// Parses an ATL formula (without whitespace around it) +pub(crate) fn conjunction<'a, A: AtlExpressionParser>( + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - Parser::new(move |input: &'_ [u8], start: usize| (parser(expr_parser).method)(input, start)) -} - -/// Parses an ATL formula (without whitespace around it) -pub(crate) fn phi(expr_parser: &A) -> Parser { - // We have to take left-recursion and precedence into account when making parsers. - // In ATL formulas, only AND and OR is subject to left-recursion, where AND have higher - // precedence. So we split ATL formulas into layers: phi s (can contain AND), - // terms (can contain OR), and primaries (stuff with low precedence and no left-recursion). - let and = (term(expr_parser) - ws() - sym(b'&') - ws() + lazy(&phi, expr_parser)) - .map(|(lhs, rhs)| Phi::And(Arc::new(lhs), Arc::new(rhs))); - and | term(expr_parser) + let conjunc = (disjunction(state, expr_parser) - ws() - sym(b'&') - ws() + + call2(&conjunction, state, expr_parser)) + .map(|(lhs, rhs)| Phi::And(Arc::new(lhs), Arc::new(rhs))); + conjunc | disjunction(state, expr_parser) } /// Parses an ATL term (Can't contain AND and without whitespace around it) -fn term(expr_parser: &A) -> Parser { - let or = (primary(expr_parser) - ws() - sym(b'|') - ws() + lazy(&term, expr_parser)) - .map(|(lhs, rhs)| Phi::Or(Arc::new(lhs), Arc::new(rhs))); - or | primary(expr_parser) +fn disjunction<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + let dicjunc = (primary(state, expr_parser) - ws() - sym(b'|') - ws() + + call2(&disjunction, state, expr_parser)) + .map(|(lhs, rhs)| Phi::Or(Arc::new(lhs), Arc::new(rhs))); + dicjunc | primary(state, expr_parser) } /// Parses a primary ATL formula (no ANDs or ORs) -fn primary(expr_parser: &A) -> Parser { - paren(expr_parser) +fn primary<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + paren(state, expr_parser) | boolean() - | proposition(expr_parser) - | not(expr_parser) - | enforce_next(expr_parser) - | enforce_until(expr_parser) - | enforce_eventually(expr_parser) - | enforce_invariant(expr_parser) - | despite_next(expr_parser) - | despite_until(expr_parser) - | despite_eventually(expr_parser) - | despite_invariant(expr_parser) + | proposition(state, expr_parser) + | not(state, expr_parser) + | enforce_next(state, expr_parser) + | enforce_until(state, expr_parser) + | enforce_eventually(state, expr_parser) + | enforce_invariant(state, expr_parser) + | despite_next(state, expr_parser) + | despite_until(state, expr_parser) + | despite_eventually(state, expr_parser) + | despite_invariant(state, expr_parser) } /// Parses an ATL formula in parenthesis -fn paren(expr_parser: &A) -> Parser { - sym(b'(') * ws() * lazy(&phi, expr_parser) - ws() - sym(b')') +fn paren<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + sym(b'(') * ws() * call2(&conjunction, state, expr_parser) - ws() - sym(b')') } /// Parses an enforce-coalition (path qualifier) -fn enforce_players(expr_parser: &A) -> Parser> { - seq(b"<<") * ws() * players(expr_parser) - ws() - seq(b">>") +fn enforce_players<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Vec> { + seq(b"<<") * ws() * players(state, expr_parser) - ws() - seq(b">>") } /// Parses a despite-coalition (path qualifier) -fn despite_players(expr_parser: &A) -> Parser> { - seq(b"[[") * ws() * players(expr_parser) - ws() - seq(b"]]") +fn despite_players<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Vec> { + seq(b"[[") * ws() * players(state, expr_parser) - ws() - seq(b"]]") } /// Parses an path formula starting with the NEXT (X) operator -fn next(expr_parser: &A) -> Parser { - sym(b'X') * ws() * lazy(&phi, expr_parser) +fn next<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + sym(b'X') * ws() * call2(&primary, state, expr_parser) } /// Parses an path formula with the UNTIL operator -fn until(expr_parser: &A) -> Parser { - sym(b'(') * ws() * lazy(&phi, expr_parser) - ws() - sym(b'U') - ws() + lazy(&phi, expr_parser) +fn until<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, (Phi, Phi)> { + sym(b'(') * ws() * call2(&primary, state, expr_parser) - ws() - sym(b'U') - ws() + + call2(&primary, state, expr_parser) - ws() - sym(b')') } /// Parses an path formula starting with the EVENTUALLY (F/finally) operator -fn eventually(expr_parser: &A) -> Parser { - sym(b'F') * ws() * lazy(&phi, expr_parser) +fn eventually<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + sym(b'F') * ws() * call2(&primary, state, expr_parser) } /// Parses an path formula starting with the INVARIANT (G/global) operator -fn invariant(expr_parser: &A) -> Parser { - sym(b'G') * ws() * lazy(&phi, expr_parser) +fn invariant<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + sym(b'G') * ws() * call2(&primary, state, expr_parser) } /// Parses an ENFORCE-NEXT ATL formula -fn enforce_next(expr_parser: &A) -> Parser { - (enforce_players(expr_parser) - ws() + next(expr_parser)).map(|(players, phi)| { +fn enforce_next<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + (enforce_players(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { Phi::EnforceNext { players, formula: Arc::new(phi), @@ -126,39 +154,51 @@ fn enforce_next(expr_parser: &A) -> Parser { } /// Parses an ENFORCE-UNTIL ATL formula -fn enforce_until(expr_parser: &A) -> Parser { - (enforce_players(expr_parser) - ws() + until(expr_parser)).map(|(players, (l, r))| { - Phi::EnforceUntil { +fn enforce_until<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + (enforce_players(state, expr_parser) - ws() + until(state, expr_parser)).map( + |(players, (l, r))| Phi::EnforceUntil { players, pre: Arc::new(l), until: Arc::new(r), - } - }) + }, + ) } /// Parses an ENFORCE-EVENTUALLY ATL formula -fn enforce_eventually(expr_parser: &A) -> Parser { - (enforce_players(expr_parser) - ws() + eventually(expr_parser)).map(|(players, phi)| { - Phi::EnforceEventually { +fn enforce_eventually<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + (enforce_players(state, expr_parser) - ws() + eventually(state, expr_parser)).map( + |(players, phi)| Phi::EnforceEventually { players, formula: Arc::new(phi), - } - }) + }, + ) } /// Parses an ENFORCE-INVARIANT ATL formula -fn enforce_invariant(expr_parser: &A) -> Parser { - (enforce_players(expr_parser) - ws() + invariant(expr_parser)).map(|(players, phi)| { - Phi::EnforceInvariant { +fn enforce_invariant<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + (enforce_players(state, expr_parser) - ws() + invariant(state, expr_parser)).map( + |(players, phi)| Phi::EnforceInvariant { players, formula: Arc::new(phi), - } - }) + }, + ) } /// Parses an DESPITE-NEXT ATL formula -fn despite_next(expr_parser: &A) -> Parser { - (despite_players(expr_parser) - ws() + next(expr_parser)).map(|(players, phi)| { +fn despite_next<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + (despite_players(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { Phi::DespiteNext { players, formula: Arc::new(phi), @@ -167,44 +207,59 @@ fn despite_next(expr_parser: &A) -> Parser { } /// Parses an DESPITE-UNTIL ATL formula -fn despite_until(expr_parser: &A) -> Parser { - (despite_players(expr_parser) - ws() + until(expr_parser)).map(|(players, (l, r))| { - Phi::DespiteUntil { +fn despite_until<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + (despite_players(state, expr_parser) - ws() + until(state, expr_parser)).map( + |(players, (l, r))| Phi::DespiteUntil { players, pre: Arc::new(l), until: Arc::new(r), - } - }) + }, + ) } /// Parses an DESPITE-EVENTUALLY ATL formula -fn despite_eventually(expr_parser: &A) -> Parser { - (despite_players(expr_parser) - ws() + eventually(expr_parser)).map(|(players, phi)| { - Phi::DespiteEventually { +fn despite_eventually<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + (despite_players(state, expr_parser) - ws() + eventually(state, expr_parser)).map( + |(players, phi)| Phi::DespiteEventually { players, formula: Arc::new(phi), - } - }) + }, + ) } /// Parses an DESPITE-INVARIANT ATL formula -fn despite_invariant(expr_parser: &A) -> Parser { - (despite_players(expr_parser) - ws() + invariant(expr_parser)).map(|(players, phi)| { - Phi::DespiteInvariant { +fn despite_invariant<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + (despite_players(state, expr_parser) - ws() + invariant(state, expr_parser)).map( + |(players, phi)| Phi::DespiteInvariant { players, formula: Arc::new(phi), - } - }) + }, + ) } /// Parses a proposition using the given [ATLExpressionParser]. -fn proposition(expr_parser: &A) -> Parser { - expr_parser.proposition_parser().map(Phi::Proposition) +fn proposition<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + expr_parser.proposition_parser(state).map(Phi::Proposition) } /// Parses a negated ATL formula -fn not(expr_parser: &A) -> Parser { - (sym(b'!') * ws() * lazy(&phi, expr_parser)).map(|phi| Phi::Not(Arc::new(phi))) +fn not<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Phi> { + (sym(b'!') * ws() * call2(&primary, state, expr_parser)).map(|phi| Phi::Not(Arc::new(phi))) } /// Parses a boolean, either full uppercase or full lowercase @@ -216,8 +271,11 @@ fn boolean<'a>() -> Parser<'a, u8, Phi> { } /// Parses a comma-separated list of players using the given [ATLExpressionParser]. -fn players(expr_parser: &A) -> Parser> { - list(expr_parser.player_parser(), ws() * sym(b',') * ws()) +fn players<'a, A: AtlExpressionParser>( + state: &'a ParseState, + expr_parser: &'a A, +) -> Parser<'a, u8, Vec> { + list(expr_parser.player_parser(state), ws() * sym(b',') * ws()) } /// Parses a letter @@ -260,440 +318,549 @@ mod test { use pom::parser::Parser; use crate::atl::parser::{ - boolean, despite_eventually, despite_invariant, despite_next, despite_players, - despite_until, enforce_eventually, enforce_invariant, enforce_next, enforce_players, - enforce_until, eventually, invariant, next, not, number, paren, phi, proposition, term, - until, AtlExpressionParser, + boolean, conjunction, despite_eventually, despite_invariant, despite_next, despite_players, + despite_until, disjunction, enforce_eventually, enforce_invariant, enforce_next, + enforce_players, enforce_until, eventually, invariant, next, not, number, paren, + proposition, until, AtlExpressionParser, }; use crate::atl::{parse_phi, Phi}; use crate::game_structure::lcgs::ir::intermediate::IntermediateLcgs; use crate::game_structure::lcgs::parse::parse_lcgs; use crate::game_structure::{Player, Proposition}; + use crate::parsing::ParseState; struct TestModel; impl AtlExpressionParser for TestModel { - fn player_parser(&self) -> Parser { + fn player_parser(&self, state: &ParseState) -> Parser { number() } - fn proposition_parser(&self) -> Parser { + fn proposition_parser(&self, state: &ParseState) -> Parser { number() } } #[test] fn paren_1() { - assert_eq!(paren(&TestModel).parse(b"(true)"), Ok(Phi::True)) + let state = ParseState::default(); + assert!(!state.has_errors()); + assert_eq!(paren(&state, &TestModel).parse(b"(true)"), Ok(Phi::True)); } /// Test for a single player #[test] fn enforce_players_1() { + let state = ParseState::default(); assert_eq!( - enforce_players(&TestModel).parse(b"<<1>>"), + enforce_players(&state, &TestModel).parse(b"<<1>>"), Ok(vec![1usize]) - ) + ); + assert!(!state.has_errors()); } /// Test for two players #[test] fn enforce_players_2() { + let state = ParseState::default(); assert_eq!( - enforce_players(&TestModel).parse(b"<<4,9>>"), + enforce_players(&state, &TestModel).parse(b"<<4,9>>"), Ok(vec![4usize, 9usize]) - ) + ); + assert!(!state.has_errors()); } /// Test for three players #[test] fn enforce_players_3() { + let state = ParseState::default(); assert_eq!( - enforce_players(&TestModel).parse(b"<<203,23,4>>"), + enforce_players(&state, &TestModel).parse(b"<<203,23,4>>"), Ok(vec![203usize, 23usize, 4usize]) - ) + ); + assert!(!state.has_errors()); } /// The list of players is allowed to have whitespace after the separator #[test] fn enforce_players_4() { + let state = ParseState::default(); assert_eq!( - enforce_players(&TestModel).parse(b"<<203, 23>>"), + enforce_players(&state, &TestModel).parse(b"<<203, 23>>"), Ok(vec![203usize, 23usize]) - ) + ); + assert!(!state.has_errors()); } /// Test for no players, should be valid #[test] fn enforce_players_5() { - assert_eq!(enforce_players(&TestModel).parse(b"<<>>"), Ok(vec![])) + let state = ParseState::default(); + assert_eq!( + enforce_players(&state, &TestModel).parse(b"<<>>"), + Ok(vec![]) + ); + assert!(!state.has_errors()); } /// Test for a single player #[test] fn despite_players_1() { + let state = ParseState::default(); assert_eq!( - despite_players(&TestModel).parse(b"[[1]]"), + despite_players(&state, &TestModel).parse(b"[[1]]"), Ok(vec![1usize]) - ) + ); + assert!(!state.has_errors()); } /// Test for two players #[test] fn despite_players_2() { + let state = ParseState::default(); assert_eq!( - despite_players(&TestModel).parse(b"[[4,9]]"), + despite_players(&state, &TestModel).parse(b"[[4,9]]"), Ok(vec![4usize, 9usize]) - ) + ); + assert!(!state.has_errors()); } /// Test for three players #[test] fn despite_players_3() { + let state = ParseState::default(); assert_eq!( - despite_players(&TestModel).parse(b"[[203,23,4]]"), + despite_players(&state, &TestModel).parse(b"[[203,23,4]]"), Ok(vec![203usize, 23usize, 4usize]) - ) + ); + assert!(!state.has_errors()); } /// The list of players is allowed to have whitespace after the seperator #[test] fn despite_players_4() { + let state = ParseState::default(); assert_eq!( - despite_players(&TestModel).parse(b"[[203, 23]]"), + despite_players(&state, &TestModel).parse(b"[[203, 23]]"), Ok(vec![203usize, 23usize]) - ) + ); + assert!(!state.has_errors()); } /// Test for no players, should be valid #[test] fn despite_players_5() { - assert_eq!(despite_players(&TestModel).parse(b"[[]]"), Ok(vec![])) + let state = ParseState::default(); + assert_eq!( + despite_players(&state, &TestModel).parse(b"[[]]"), + Ok(vec![]) + ); + assert!(!state.has_errors()); } #[test] fn next_1() { - assert_eq!(next(&TestModel).parse(b"X true"), Ok(Phi::True)) + let state = ParseState::default(); + assert_eq!(next(&state, &TestModel).parse(b"X true"), Ok(Phi::True)); + assert!(!state.has_errors()); } #[test] fn next_2() { - assert_eq!(next(&TestModel).parse(b"Xtrue"), Ok(Phi::True)) + let state = ParseState::default(); + assert_eq!(next(&state, &TestModel).parse(b"Xtrue"), Ok(Phi::True)); + assert!(!state.has_errors()); } #[test] fn until_1() { + let state = ParseState::default(); assert_eq!( - until(&TestModel).parse(b"( true U true )"), + until(&state, &TestModel).parse(b"( true U true )"), Ok((Phi::True, Phi::True)) - ) + ); + assert!(!state.has_errors()); } #[test] fn until_2() { + let state = ParseState::default(); assert_eq!( - until(&TestModel).parse(b"(trueUtrue)"), + until(&state, &TestModel).parse(b"(trueUtrue)"), Ok((Phi::True, Phi::True)) - ) + ); + assert!(!state.has_errors()); } #[test] fn eventually_1() { - assert_eq!(eventually(&TestModel).parse(b"F true"), Ok(Phi::True)) + let state = ParseState::default(); + assert_eq!( + eventually(&state, &TestModel).parse(b"F true"), + Ok(Phi::True) + ); + assert!(!state.has_errors()); } #[test] fn eventually_2() { - assert_eq!(eventually(&TestModel).parse(b"Ftrue"), Ok(Phi::True)) + let state = ParseState::default(); + assert_eq!( + eventually(&state, &TestModel).parse(b"Ftrue"), + Ok(Phi::True) + ); + assert!(!state.has_errors()); } #[test] fn invariant_1() { - assert_eq!(invariant(&TestModel).parse(b"G true"), Ok(Phi::True)) + let state = ParseState::default(); + assert_eq!( + invariant(&state, &TestModel).parse(b"G true"), + Ok(Phi::True) + ); + assert!(!state.has_errors()); } #[test] fn invariant_2() { - assert_eq!(invariant(&TestModel).parse(b"Gtrue"), Ok(Phi::True)) + let state = ParseState::default(); + assert_eq!(invariant(&state, &TestModel).parse(b"Gtrue"), Ok(Phi::True)); + assert!(!state.has_errors()); } #[test] fn enforce_next_1() { + let state = ParseState::default(); assert_eq!( - enforce_next(&TestModel).parse(b"<<1 , 2>> X true"), + enforce_next(&state, &TestModel).parse(b"<<1 , 2>> X true"), Ok(Phi::EnforceNext { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn enforce_next_2() { + let state = ParseState::default(); assert_eq!( - enforce_next(&TestModel).parse(b"<<1,2>>Xtrue"), + enforce_next(&state, &TestModel).parse(b"<<1,2>>Xtrue"), Ok(Phi::EnforceNext { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn enforce_until_1() { + let state = ParseState::default(); assert_eq!( - enforce_until(&TestModel).parse(b"<<1 , 2>> ( true U true )"), + enforce_until(&state, &TestModel).parse(b"<<1 , 2>> ( true U true )"), Ok(Phi::EnforceUntil { players: vec![1, 2], pre: Arc::new(Phi::True), until: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn enforce_until_2() { + let state = ParseState::default(); assert_eq!( - enforce_until(&TestModel).parse(b"<<1,2>>(trueUtrue)"), + enforce_until(&state, &TestModel).parse(b"<<1,2>>(trueUtrue)"), Ok(Phi::EnforceUntil { players: vec![1, 2], pre: Arc::new(Phi::True), until: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn enforce_eventually_1() { + let state = ParseState::default(); assert_eq!( - enforce_eventually(&TestModel).parse(b"<<1 , 2>> F true"), + enforce_eventually(&state, &TestModel).parse(b"<<1 , 2>> F true"), Ok(Phi::EnforceEventually { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn enforce_eventually_2() { + let state = ParseState::default(); assert_eq!( - enforce_eventually(&TestModel).parse(b"<<1,2>>Ftrue"), + enforce_eventually(&state, &TestModel).parse(b"<<1,2>>Ftrue"), Ok(Phi::EnforceEventually { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn enforce_invariant_1() { + let state = ParseState::default(); assert_eq!( - enforce_invariant(&TestModel).parse(b"<<1 , 2>> G true"), + enforce_invariant(&state, &TestModel).parse(b"<<1 , 2>> G true"), Ok(Phi::EnforceInvariant { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn enforce_invariant_2() { + let state = ParseState::default(); assert_eq!( - enforce_invariant(&TestModel).parse(b"<<1,2>>Gtrue"), + enforce_invariant(&state, &TestModel).parse(b"<<1,2>>Gtrue"), Ok(Phi::EnforceInvariant { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn despite_next_1() { + let state = ParseState::default(); assert_eq!( - despite_next(&TestModel).parse(b"[[1 , 2]] X true"), + despite_next(&state, &TestModel).parse(b"[[1 , 2]] X true"), Ok(Phi::DespiteNext { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn despite_next_2() { + let state = ParseState::default(); assert_eq!( - despite_next(&TestModel).parse(b"[[1,2]]Xtrue"), + despite_next(&state, &TestModel).parse(b"[[1,2]]Xtrue"), Ok(Phi::DespiteNext { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn despite_until_1() { + let state = ParseState::default(); assert_eq!( - despite_until(&TestModel).parse(b"[[1 , 2]] ( true U true )"), + despite_until(&state, &TestModel).parse(b"[[1 , 2]] ( true U true )"), Ok(Phi::DespiteUntil { players: vec![1, 2], pre: Arc::new(Phi::True), until: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn despite_until_2() { + let state = ParseState::default(); assert_eq!( - despite_until(&TestModel).parse(b"[[1,2]](trueUtrue)"), + despite_until(&state, &TestModel).parse(b"[[1,2]](trueUtrue)"), Ok(Phi::DespiteUntil { players: vec![1, 2], pre: Arc::new(Phi::True), until: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn despite_eventually_1() { + let state = ParseState::default(); assert_eq!( - despite_eventually(&TestModel).parse(b"[[1 , 2]] F true"), + despite_eventually(&state, &TestModel).parse(b"[[1 , 2]] F true"), Ok(Phi::DespiteEventually { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn despite_eventually_2() { + let state = ParseState::default(); assert_eq!( - despite_eventually(&TestModel).parse(b"[[1,2]]Ftrue"), + despite_eventually(&state, &TestModel).parse(b"[[1,2]]Ftrue"), Ok(Phi::DespiteEventually { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn despite_invariant_1() { + let state = ParseState::default(); assert_eq!( - despite_invariant(&TestModel).parse(b"[[1 , 2]] G true"), + despite_invariant(&state, &TestModel).parse(b"[[1 , 2]] G true"), Ok(Phi::DespiteInvariant { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn despite_invariant_2() { + let state = ParseState::default(); assert_eq!( - despite_invariant(&TestModel).parse(b"[[1,2]]Gtrue"), + despite_invariant(&state, &TestModel).parse(b"[[1,2]]Gtrue"), Ok(Phi::DespiteInvariant { players: vec![1, 2], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] fn proposition_1() { + let state = ParseState::default(); assert_eq!( - proposition(&TestModel).parse(b"1"), + proposition(&state, &TestModel).parse(b"1"), Ok(Phi::Proposition(1usize)) - ) + ); + assert!(!state.has_errors()); } #[test] fn proposition_2() { + let state = ParseState::default(); assert_eq!( - proposition(&TestModel).parse(b"1432"), + proposition(&state, &TestModel).parse(b"1432"), Ok(Phi::Proposition(1432usize)) - ) + ); + assert!(!state.has_errors()); } #[test] fn proposition_3() { - assert!(proposition(&TestModel).parse(b"abc").is_err()) + let state = ParseState::default(); + assert!(proposition(&state, &TestModel).parse(b"abc").is_err()); + assert!(!state.has_errors()); } #[test] fn not_1() { + let state = ParseState::default(); assert_eq!( - not(&TestModel).parse(b"! true"), + not(&state, &TestModel).parse(b"! true"), Ok(Phi::Not(Arc::new(Phi::True))) - ) + ); + assert!(!state.has_errors()); } #[test] fn not_2() { + let state = ParseState::default(); assert_eq!( - not(&TestModel).parse(b"!true"), + not(&state, &TestModel).parse(b"!true"), Ok(Phi::Not(Arc::new(Phi::True))) - ) + ); + assert!(!state.has_errors()); } #[test] fn and_1() { + let state = ParseState::default(); assert_eq!( - phi(&TestModel).parse(b"true & false"), + conjunction(&state, &TestModel).parse(b"true & false"), Ok(Phi::And(Arc::new(Phi::True), Arc::new(Phi::False))) - ) + ); + assert!(!state.has_errors()); } #[test] fn and_2() { + let state = ParseState::default(); assert_eq!( - phi(&TestModel).parse(b"true&false"), + conjunction(&state, &TestModel).parse(b"true&false"), Ok(Phi::And(Arc::new(Phi::True), Arc::new(Phi::False))) - ) + ); + assert!(!state.has_errors()); } #[test] fn and_3() { // Right recursive? + let state = ParseState::default(); assert_eq!( - phi(&TestModel).parse(b"true&false&true"), + conjunction(&state, &TestModel).parse(b"true&false&true"), Ok(Phi::And( Arc::new(Phi::True), Arc::new(Phi::And(Arc::new(Phi::False), Arc::new(Phi::True))) )) - ) + ); + assert!(!state.has_errors()); } #[test] fn or_1() { + let state = ParseState::default(); assert_eq!( - term(&TestModel).parse(b"true | false"), + disjunction(&state, &TestModel).parse(b"true | false"), Ok(Phi::Or(Arc::new(Phi::True), Arc::new(Phi::False))) - ) + ); + assert!(!state.has_errors()); } #[test] fn or_2() { + let state = ParseState::default(); assert_eq!( - term(&TestModel).parse(b"true|false"), + disjunction(&state, &TestModel).parse(b"true|false"), Ok(Phi::Or(Arc::new(Phi::True), Arc::new(Phi::False))) - ) + ); + assert!(!state.has_errors()); } #[test] fn or_3() { // Right recursive? + let state = ParseState::default(); assert_eq!( - term(&TestModel).parse(b"true|false |true"), + disjunction(&state, &TestModel).parse(b"true|false |true"), Ok(Phi::Or( Arc::new(Phi::True), Arc::new(Phi::Or(Arc::new(Phi::False), Arc::new(Phi::True))) )) - ) + ); + assert!(!state.has_errors()); } #[test] fn test_and_or_precedence_01() { + let state = ParseState::default(); assert_eq!( - phi(&TestModel).parse(b"true & false | true | false & true"), + conjunction(&state, &TestModel).parse(b"true & false | true | false & true"), Ok(Phi::And( Arc::new(Phi::True), Arc::new(Phi::And( @@ -704,7 +871,8 @@ mod test { Arc::new(Phi::True) )) )) - ) + ); + assert!(!state.has_errors()); } #[test] @@ -729,13 +897,15 @@ mod test { #[test] fn test_phi_01() { + let state = ParseState::default(); assert_eq!( - phi(&TestModel).parse(b"<<0>> F true"), + conjunction(&state, &TestModel).parse(b"<<0>> F true"), Ok(Phi::EnforceEventually { players: vec![0usize], formula: Arc::new(Phi::True) }) - ) + ); + assert!(!state.has_errors()); } #[test] @@ -751,8 +921,10 @@ mod test { let lcgs = IntermediateLcgs::create(parse_lcgs(lcgs_program).unwrap()).unwrap(); let atl_formula = "<>"; - let phi = enforce_players(&lcgs).parse(&atl_formula.as_bytes()); + let state = ParseState::default(); + let phi = enforce_players(&state, &lcgs).parse(&atl_formula.as_bytes()); assert_eq!(phi, Ok(vec![0])); + assert!(!state.has_errors()); } #[test] diff --git a/atl-checker/src/game_structure/eager.rs b/atl-checker/src/game_structure/eager.rs index 381d1c3bd..7278891fe 100644 --- a/atl-checker/src/game_structure/eager.rs +++ b/atl-checker/src/game_structure/eager.rs @@ -3,6 +3,7 @@ use std::collections::HashSet; use crate::atl::{number, AtlExpressionParser}; use crate::game_structure::{transition_lookup, DynVec, GameStructure, Player, Proposition, State}; +use crate::parsing::ParseState; use pom::parser::Parser; use std::str::{self}; @@ -83,7 +84,7 @@ impl GameStructure for EagerGameStructure { } impl AtlExpressionParser for EagerGameStructure { - fn player_parser(&self) -> Parser { + fn player_parser(&self, state: &ParseState) -> Parser { // In ATL, players are just their index number().convert(move |i| { if i <= self.max_player() { @@ -94,7 +95,7 @@ impl AtlExpressionParser for EagerGameStructure { }) } - fn proposition_parser(&self) -> Parser { + fn proposition_parser(&self, state: &ParseState) -> Parser { // In ATL, proposition are just their index. All numbers are valid propositions, // but they might not be true anywhere. number() diff --git a/atl-checker/src/game_structure/lcgs/ir/intermediate.rs b/atl-checker/src/game_structure/lcgs/ir/intermediate.rs index 02e91f380..d62b94c50 100644 --- a/atl-checker/src/game_structure/lcgs/ir/intermediate.rs +++ b/atl-checker/src/game_structure/lcgs/ir/intermediate.rs @@ -10,6 +10,7 @@ use crate::game_structure::lcgs::ir::relabeling::Relabeler; use crate::game_structure::lcgs::ir::symbol_checker::{CheckMode, SymbolChecker, SymbolError}; use crate::game_structure::lcgs::ir::symbol_table::{Owner, SymbolIdentifier, SymbolTable}; use crate::game_structure::{Action, GameStructure, Proposition}; +use crate::parsing::ParseState; use pom::parser::{sym, Parser}; use std::fmt::{Display, Formatter}; @@ -536,7 +537,7 @@ impl GameStructure for IntermediateLcgs { } impl AtlExpressionParser for IntermediateLcgs { - fn player_parser(&self) -> Parser { + fn player_parser(&self, state: &ParseState) -> Parser { // In ATL, players are referred to using their name, i.e. an identifier identifier().convert(move |name| { // We check if a declaration with the given name exists, @@ -557,7 +558,7 @@ impl AtlExpressionParser for IntermediateLcgs { }) } - fn proposition_parser(&self) -> Parser { + fn proposition_parser(&self, state: &ParseState) -> Parser { // In ATL, propositions are either "something" where "something" must be a label declared // in the global scope, or "someone.something" where "something" is a label owned by // a player of name "someone". diff --git a/atl-checker/src/parsing/mod.rs b/atl-checker/src/parsing/mod.rs index 7a40d3f13..797334057 100644 --- a/atl-checker/src/parsing/mod.rs +++ b/atl-checker/src/parsing/mod.rs @@ -52,7 +52,7 @@ pub struct ParseError { /// The `ParseState` is a struct carried around during parsing contained extra information /// about the state of the parser, such as the errors encountered #[derive(Debug, Default)] -pub(crate) struct ParseState { +pub struct ParseState { errors: RefCell>, } @@ -61,6 +61,14 @@ impl ParseState { pub fn report_err(&self, err: ParseError) { self.errors.borrow_mut().push(err) } + + pub fn has_errors(&self) -> bool { + !self.errors.borrow().is_empty() + } + + pub fn errors_as_str(&self, input: &str) -> String { + unimplemented!("Nicely formatted errors") + } } /// Create a lazy parser used for recursive definitions. From dc5b8cd0d8b004a65a9d12b13e20c5a659b2e7f2 Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Mon, 5 Jun 2023 17:21:08 +0200 Subject: [PATCH 03/38] Small fixes --- atl-checker/src/game_structure/eager.rs | 6 +++--- atl-checker/src/parsing/mod.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/atl-checker/src/game_structure/eager.rs b/atl-checker/src/game_structure/eager.rs index 4c2b2b856..456e26f18 100644 --- a/atl-checker/src/game_structure/eager.rs +++ b/atl-checker/src/game_structure/eager.rs @@ -84,7 +84,7 @@ impl GameStructure for EagerGameStructure { impl AtlExpressionParser for EagerGameStructure { fn player_parser(&self, state: &ParseState) -> Parser { - // In ATL, players are just their index + // In EagerGameStructure ATL, players are just their index number().convert(move |i| { if i <= self.max_player() { Ok(i) @@ -95,8 +95,8 @@ impl AtlExpressionParser for EagerGameStructure { } fn proposition_parser(&self, state: &ParseState) -> Parser { - // In ATL, proposition are just their index. All numbers are valid propositions, - // but they might not be true anywhere. + // In EagerGameStructure ATL, proposition are just their index. + // All numbers are valid propositions, but they might not be true anywhere. number() } } diff --git a/atl-checker/src/parsing/mod.rs b/atl-checker/src/parsing/mod.rs index 797334057..d2b629406 100644 --- a/atl-checker/src/parsing/mod.rs +++ b/atl-checker/src/parsing/mod.rs @@ -14,7 +14,7 @@ pub struct Span { impl Span { /// Returns and equivalent range - pub fn to_range(&self) -> Range { + pub fn to_range(self) -> Range { self.begin..self.end } @@ -49,7 +49,7 @@ pub struct ParseError { msg: String, } -/// The `ParseState` is a struct carried around during parsing contained extra information +/// The `ParseState` is a struct carried around during parsing containing extra information /// about the state of the parser, such as the errors encountered #[derive(Debug, Default)] pub struct ParseState { @@ -57,7 +57,7 @@ pub struct ParseState { } impl ParseState { - /// Saves and error to display when parsing is done + /// Saves an error to display when parsing is done pub fn report_err(&self, err: ParseError) { self.errors.borrow_mut().push(err) } @@ -75,9 +75,9 @@ impl ParseState { /// This function is similar to `call` but allows passing one argument to the parser function. pub(crate) fn call1<'a, I, O, A, P: Fn(&'a A) -> Parser>( parser: &'a P, - expr_parser: &'a A, + arg: &'a A, ) -> Parser<'a, I, O> { - Parser::new(move |input: &'_ [I], start: usize| (parser(expr_parser).method)(input, start)) + Parser::new(move |input: &'_ [I], start: usize| (parser(arg).method)(input, start)) } /// Create a lazy parser used for recursive definitions. @@ -140,7 +140,7 @@ pub(crate) fn parse_or_abort<'a, I: Eq + Display, O: 'a>( ) } -/// Creates a parser that will run the given parser and then consume if synchronization token. +/// Creates a parser that will run the given parser and then consume the synchronization token. /// If the given parser fails to parse, an error is reported using the given error message, /// and parsing will continue at the next occurrence of the synchronization token. pub(crate) fn parse_or_sync<'a, I: Eq + Display, O: 'a>( From 267dfa2c4262d79e6e26e150ab6f39b1426faab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Sun, 20 Aug 2023 12:49:23 +0200 Subject: [PATCH 04/38] Beginning of homemade Lexer --- cgaal-engine/src/parsing/lexer.rs | 129 ++++++++++++++++++++++++++++++ cgaal-engine/src/parsing/mod.rs | 2 + 2 files changed, 131 insertions(+) create mode 100644 cgaal-engine/src/parsing/lexer.rs diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs new file mode 100644 index 000000000..ae3c54235 --- /dev/null +++ b/cgaal-engine/src/parsing/lexer.rs @@ -0,0 +1,129 @@ + +#[derive(Debug, Clone, PartialEq, Eq)] +enum Token { + // Delimiters + Lparen, + Rparen, + Lbrace, + Rbrace, + Langle, + Rangle, + + // Operators + Plus, + Minus, + Star, + Slash, + EqEq, + Neq, + Geq, + Leq, + + // Other symbols + Eq, + + // Literals + Num(i32), + Word(String), + + // Utility + Err, +} + +struct Lexer<'a> { + input: &'a [u8], + pos: usize, +} + +impl<'a> Lexer<'a> { + pub fn new(input: &'a [u8]) -> Self { + Lexer { + input, + pos: 0, + } + } + + fn skip_ws(&mut self) { + while self.pos < self.input.len() && self.input[self.pos].is_ascii_whitespace() { + self.pos += 1; + } + } + + #[inline] + fn peek(&self, offset: usize) -> Option { + if self.pos + offset < self.input.len() { + Some(self.input[self.pos + offset]) + } else { + None + } + } + + #[inline] + fn token(&mut self, len: usize, token: Token) -> Token { + self.pos += len; + token + } + + fn lex_alpha(&mut self) -> Token { + let mut len = 1; + while self.peek(len).map_or(false, |c| c.is_ascii_alphanumeric() || c == b'_') { + len += 1; + } + let word = std::str::from_utf8(&self.input[self.pos..self.pos + len]).unwrap(); + self.token(len, Token::Word(word.to_string())) + } + + fn lex_num(&mut self) -> Token { + let mut len = 1; + while self.peek(len).map_or(false, |c| c.is_ascii_digit()) { + len += 1; + } + let val: i32 = std::str::from_utf8(&self.input[self.pos..self.pos + len]) + .unwrap() + .parse() + .unwrap(); + self.token(len, Token::Num(val)) + } +} + +impl<'a> Iterator for Lexer<'a> { + type Item = Token; + + fn next(&mut self) -> Option { + self.skip_ws(); + let tk = match self.peek(0)? { + b'(' => self.token(1, Token::Lparen), + b')' => self.token(1, Token::Rparen), + b'{' => self.token(1, Token::Lbrace), + b'}' => self.token(1, Token::Rbrace), + b'<' => if self.peek(1) == Some(b'=') { + self.token(2, Token::Leq) + } else { + self.token(1, Token::Langle) + }, + b'>' => if self.peek(1) == Some(b'=') { + self.token(2, Token::Geq) + } else { + self.token(1, Token::Rangle) + }, + b'+' => self.token(1, Token::Plus), + b'-' => self.token(1, Token::Minus), + b'*' => self.token(1, Token::Star), + b'/' => self.token(1, Token::Slash), + b'=' => if self.peek(1) == Some(b'=') { + self.token(2, Token::EqEq) + } else { + self.token(1, Token::Eq) + }, + b'!' => if self.peek(1) == Some(b'=') { + self.token(2, Token::Neq) + } else { + self.token(1, Token::Err) + }, + b'a'..=b'z' | b'A'..=b'Z' => self.lex_alpha(), + b'0'..=b'9' => self.lex_num(), + _ => self.token(1, Token::Err), + }; + Some(tk) + } +} \ No newline at end of file diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index d2b629406..7be2c367d 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -1,3 +1,5 @@ +mod lexer; + use pom::parser::*; use std::cell::RefCell; use std::cmp::{max, min}; From f75d7fc9823242a44baa43e235e075b81e24bbda Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Sun, 20 Aug 2023 13:45:36 +0200 Subject: [PATCH 05/38] Lexer utils cleanup and test --- cgaal-engine/src/atl/parser.rs | 12 +-- cgaal-engine/src/parsing/lexer.rs | 139 +++++++++++++++--------------- cgaal-engine/src/parsing/mod.rs | 32 ++----- cgaal-engine/src/parsing/span.rs | 48 +++++++++++ cgaal-engine/src/parsing/token.rs | 92 ++++++++++++++++++++ 5 files changed, 220 insertions(+), 103 deletions(-) create mode 100644 cgaal-engine/src/parsing/span.rs create mode 100644 cgaal-engine/src/parsing/token.rs diff --git a/cgaal-engine/src/atl/parser.rs b/cgaal-engine/src/atl/parser.rs index 69b5d6515..d81dd553a 100644 --- a/cgaal-engine/src/atl/parser.rs +++ b/cgaal-engine/src/atl/parser.rs @@ -910,7 +910,7 @@ mod test { #[test] fn general_precedence_01() { - let formula = phi(&TestModel).parse(b"<<>> G true & false").unwrap(); + let formula = parse_phi(&TestModel, "<<>> G true & false").unwrap(); let expected = Phi::EnforceInvariant { players: vec![], formula: Arc::new(Phi::And(Arc::new(Phi::True), Arc::new(Phi::False))), @@ -920,7 +920,7 @@ mod test { #[test] fn general_precedence_02() { - let formula = phi(&TestModel).parse(b"!<<>> F !true & false").unwrap(); + let formula = parse_phi(&TestModel, "!<<>> F !true & false").unwrap(); let expected = Phi::Not(Arc::new(Phi::EnforceEventually { players: vec![], formula: Arc::new(Phi::And( @@ -933,9 +933,7 @@ mod test { #[test] fn general_precedence_03() { - let formula = phi(&TestModel) - .parse(b"[[]] F false | <<>> G true") - .unwrap(); + let formula = parse_phi(&TestModel, "[[]] F false | <<>> G true").unwrap(); let expected = Phi::DespiteEventually { players: vec![], formula: Arc::new(Phi::Or( @@ -951,9 +949,7 @@ mod test { #[test] fn general_precedence_04() { - let formula = phi(&TestModel) - .parse(b"!false & [[]] F <<>> G false | true") - .unwrap(); + let formula = parse_phi(&TestModel, "!false & [[]] F <<>> G false | true").unwrap(); let expected = Phi::And( Arc::new(Phi::Not(Arc::new(Phi::False))), Arc::new(Phi::DespiteEventually { diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs index ae3c54235..ee5f0f937 100644 --- a/cgaal-engine/src/parsing/lexer.rs +++ b/cgaal-engine/src/parsing/lexer.rs @@ -1,35 +1,7 @@ +use crate::parsing::span::Span; +use crate::parsing::token::{Token, TokenKind}; -#[derive(Debug, Clone, PartialEq, Eq)] -enum Token { - // Delimiters - Lparen, - Rparen, - Lbrace, - Rbrace, - Langle, - Rangle, - - // Operators - Plus, - Minus, - Star, - Slash, - EqEq, - Neq, - Geq, - Leq, - - // Other symbols - Eq, - - // Literals - Num(i32), - Word(String), - - // Utility - Err, -} - +#[derive(Clone, Eq, PartialEq)] struct Lexer<'a> { input: &'a [u8], pos: usize, @@ -37,10 +9,7 @@ struct Lexer<'a> { impl<'a> Lexer<'a> { pub fn new(input: &'a [u8]) -> Self { - Lexer { - input, - pos: 0, - } + Lexer { input, pos: 0 } } fn skip_ws(&mut self) { @@ -59,18 +28,22 @@ impl<'a> Lexer<'a> { } #[inline] - fn token(&mut self, len: usize, token: Token) -> Token { + fn token(&mut self, len: usize, token: TokenKind) -> Token { + let span = Span::new(self.pos, self.pos + len); self.pos += len; - token + Token::new(token, span) } fn lex_alpha(&mut self) -> Token { let mut len = 1; - while self.peek(len).map_or(false, |c| c.is_ascii_alphanumeric() || c == b'_') { + while self + .peek(len) + .map_or(false, |c| c.is_ascii_alphanumeric() || c == b'_') + { len += 1; } let word = std::str::from_utf8(&self.input[self.pos..self.pos + len]).unwrap(); - self.token(len, Token::Word(word.to_string())) + self.token(len, TokenKind::Word(word.to_string())) } fn lex_num(&mut self) -> Token { @@ -82,7 +55,7 @@ impl<'a> Lexer<'a> { .unwrap() .parse() .unwrap(); - self.token(len, Token::Num(val)) + self.token(len, TokenKind::Num(val)) } } @@ -92,38 +65,64 @@ impl<'a> Iterator for Lexer<'a> { fn next(&mut self) -> Option { self.skip_ws(); let tk = match self.peek(0)? { - b'(' => self.token(1, Token::Lparen), - b')' => self.token(1, Token::Rparen), - b'{' => self.token(1, Token::Lbrace), - b'}' => self.token(1, Token::Rbrace), - b'<' => if self.peek(1) == Some(b'=') { - self.token(2, Token::Leq) - } else { - self.token(1, Token::Langle) - }, - b'>' => if self.peek(1) == Some(b'=') { - self.token(2, Token::Geq) - } else { - self.token(1, Token::Rangle) - }, - b'+' => self.token(1, Token::Plus), - b'-' => self.token(1, Token::Minus), - b'*' => self.token(1, Token::Star), - b'/' => self.token(1, Token::Slash), - b'=' => if self.peek(1) == Some(b'=') { - self.token(2, Token::EqEq) - } else { - self.token(1, Token::Eq) - }, - b'!' => if self.peek(1) == Some(b'=') { - self.token(2, Token::Neq) - } else { - self.token(1, Token::Err) - }, + b'(' => self.token(1, TokenKind::Lparen), + b')' => self.token(1, TokenKind::Rparen), + b'{' => self.token(1, TokenKind::Lbrace), + b'}' => self.token(1, TokenKind::Rbrace), + b'<' => { + if self.peek(1) == Some(b'=') { + self.token(2, TokenKind::Leq) + } else { + self.token(1, TokenKind::Langle) + } + } + b'>' => { + if self.peek(1) == Some(b'=') { + self.token(2, TokenKind::Geq) + } else { + self.token(1, TokenKind::Rangle) + } + } + b'+' => self.token(1, TokenKind::Plus), + b'-' => self.token(1, TokenKind::Minus), + b'*' => self.token(1, TokenKind::Star), + b'/' => self.token(1, TokenKind::Slash), + b'=' => { + if self.peek(1) == Some(b'=') { + self.token(2, TokenKind::EqEq) + } else { + self.token(1, TokenKind::Eq) + } + } + b'!' => { + if self.peek(1) == Some(b'=') { + self.token(2, TokenKind::Neq) + } else { + self.token(1, TokenKind::Err) + } + } b'a'..=b'z' | b'A'..=b'Z' => self.lex_alpha(), b'0'..=b'9' => self.lex_num(), - _ => self.token(1, Token::Err), + _ => self.token(1, TokenKind::Err), }; Some(tk) } -} \ No newline at end of file +} + +#[cfg(test)] +mod tests { + use crate::parsing::lexer::Lexer; + use std::fmt::Write; + + #[test] + fn lexing001() { + let input = "==4 /* - x (var01 > 0)"; + let lexer = Lexer::new(input.as_bytes()); + let mut res = String::new(); + lexer.for_each(|tk| write!(res, "{tk:?}").unwrap()); + assert_eq!( + "'=='(0,2)'4'(2,3)'/'(4,5)'*'(5,6)'-'(7,8)'x'(9,10)'('(11,12)'var01'(12,17)'>'(18,19)'0'(20,21)')'(21,22)", + &res + ) + } +} diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 7be2c367d..b5328ab8e 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -1,33 +1,11 @@ mod lexer; +mod span; +mod token; +use crate::parsing::span::Span; use pom::parser::*; use std::cell::RefCell; -use std::cmp::{max, min}; use std::fmt::Display; -use std::ops::Range; - -/// A `Span` describes the position of a slice of text in the original program. -/// Usually used to describe what text an AST node was created from. -#[derive(Eq, PartialEq, Debug, Copy, Clone)] -pub struct Span { - pub begin: usize, - pub end: usize, -} - -impl Span { - /// Returns and equivalent range - pub fn to_range(self) -> Range { - self.begin..self.end - } - - /// Merge two spans into a new span that contains the original spans and everything in between - pub fn merge(&self, other: Span) -> Span { - Span { - begin: min(self.begin, other.begin), - end: max(self.end, other.end), - } - } -} /// A trait that allows us to extent parser with a helper function that extracts the span of /// the parsed piece of text @@ -75,6 +53,7 @@ impl ParseState { /// Create a lazy parser used for recursive definitions. /// This function is similar to `call` but allows passing one argument to the parser function. +#[allow(unused)] pub(crate) fn call1<'a, I, O, A, P: Fn(&'a A) -> Parser>( parser: &'a P, arg: &'a A, @@ -95,6 +74,7 @@ pub(crate) fn call2<'a, I, O, A, B, P: Fn(&'a A, &'a B) -> Parser<'a, I, O>>( /// Creates a parser that will run the given parser. /// If the given parser fails to parse, an error is reported using the given error message, /// and parsing will continue with no input consumed. +#[allow(unused)] pub(crate) fn parse_or_skip<'a, I: Eq + Display, O: 'a>( parse_state: &'a ParseState, parser: Parser<'a, I, O>, @@ -120,6 +100,7 @@ pub(crate) fn parse_or_skip<'a, I: Eq + Display, O: 'a>( /// Creates a parser that will run the given parser. /// If the given parser fails to parse, an error is reported using the given error message, /// and parsing will continue at EOF. +#[allow(unused)] pub(crate) fn parse_or_abort<'a, I: Eq + Display, O: 'a>( parse_state: &'a ParseState, parser: Parser<'a, I, O>, @@ -145,6 +126,7 @@ pub(crate) fn parse_or_abort<'a, I: Eq + Display, O: 'a>( /// Creates a parser that will run the given parser and then consume the synchronization token. /// If the given parser fails to parse, an error is reported using the given error message, /// and parsing will continue at the next occurrence of the synchronization token. +#[allow(unused)] pub(crate) fn parse_or_sync<'a, I: Eq + Display, O: 'a>( parse_state: &'a ParseState, parser: Parser<'a, I, O>, diff --git a/cgaal-engine/src/parsing/span.rs b/cgaal-engine/src/parsing/span.rs new file mode 100644 index 000000000..6ceef81a9 --- /dev/null +++ b/cgaal-engine/src/parsing/span.rs @@ -0,0 +1,48 @@ +use std::cmp::{max, min}; +use std::fmt::{Display, Formatter}; +use std::ops::Range; + +/// A `Span` describes the position of a slice of text in the original program. +/// Usually used to describe what text an AST node was created from. +#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)] +pub struct Span { + pub begin: usize, + pub end: usize, +} + +impl Span { + pub fn new(begin: usize, end: usize) -> Self { + Span { begin, end } + } + + /// Returns and equivalent range + pub fn to_range(&self) -> Range { + self.begin..self.end + } + + /// Merge two spans into a new span that contains the original spans and everything in between + pub fn merge(&self, other: Span) -> Span { + Span { + begin: min(self.begin, other.begin), + end: max(self.end, other.end), + } + } +} + +impl Display for Span { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "({},{})", self.begin, self.end) + } +} + +impl From for Range { + fn from(span: Span) -> Self { + span.to_range() + } +} + +impl From> for Span { + fn from(range: Range) -> Self { + Span::new(range.start, range.end) + } +} diff --git a/cgaal-engine/src/parsing/token.rs b/cgaal-engine/src/parsing/token.rs new file mode 100644 index 000000000..8a85bbe0a --- /dev/null +++ b/cgaal-engine/src/parsing/token.rs @@ -0,0 +1,92 @@ +use crate::parsing::span::Span; +use std::fmt::{Debug, Display, Formatter}; + +#[derive(Clone, PartialEq, Eq)] +pub struct Token { + span: Span, + kind: TokenKind, +} + +impl Token { + pub fn new(kind: TokenKind, span: Span) -> Self { + Token { kind, span } + } + + #[allow(unused)] + pub fn span(&self) -> &Span { + &self.span + } + + #[allow(unused)] + pub fn kind(&self) -> &TokenKind { + &self.kind + } +} + +impl Display for Token { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.kind) + } +} + +impl Debug for Token { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "'{}'{}", self.kind, self.span) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TokenKind { + // Delimiters + Lparen, + Rparen, + Lbrace, + Rbrace, + Langle, + Rangle, + + // Operators + Plus, + Minus, + Star, + Slash, + EqEq, + Neq, + Geq, + Leq, + + // Other symbols + Eq, + + // Literals + Num(i32), + Word(String), + + // Utility + Err, +} + +impl Display for TokenKind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TokenKind::Lparen => write!(f, "("), + TokenKind::Rparen => write!(f, ")"), + TokenKind::Lbrace => write!(f, "{{"), + TokenKind::Rbrace => write!(f, "}}"), + TokenKind::Langle => write!(f, "<"), + TokenKind::Rangle => write!(f, ">"), + TokenKind::Plus => write!(f, "+"), + TokenKind::Minus => write!(f, "-"), + TokenKind::Star => write!(f, "*"), + TokenKind::Slash => write!(f, "/"), + TokenKind::EqEq => write!(f, "=="), + TokenKind::Neq => write!(f, "!="), + TokenKind::Geq => write!(f, ">="), + TokenKind::Leq => write!(f, "<="), + TokenKind::Eq => write!(f, "="), + TokenKind::Num(n) => write!(f, "{n}"), + TokenKind::Word(w) => write!(f, "{w}"), + TokenKind::Err => write!(f, "@err"), + } + } +} From fa0423efa7001dd056b5d26f876410be699dd0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Wed, 23 Aug 2023 16:10:05 +0200 Subject: [PATCH 06/38] Add remaing tokens and lexing thereof --- cgaal-engine/src/parsing/lexer.rs | 100 +++++++++++++++++++++--------- cgaal-engine/src/parsing/token.rs | 66 ++++++++++++++++++-- 2 files changed, 131 insertions(+), 35 deletions(-) diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs index ee5f0f937..83c48e4e0 100644 --- a/cgaal-engine/src/parsing/lexer.rs +++ b/cgaal-engine/src/parsing/lexer.rs @@ -43,7 +43,20 @@ impl<'a> Lexer<'a> { len += 1; } let word = std::str::from_utf8(&self.input[self.pos..self.pos + len]).unwrap(); - self.token(len, TokenKind::Word(word.to_string())) + let kind = match word { + "const" => TokenKind::KwConst, + "label" => TokenKind::KwLabel, + "player" => TokenKind::KwPlayer, + "template" => TokenKind::KwTemplate, + "endtemplate" => TokenKind::KwEndTemplate, + "init" => TokenKind::KwInit, + "min" => TokenKind::KwMin, + "max" => TokenKind::KwMax, + "true" => TokenKind::True, + "false" => TokenKind::False, + _ => TokenKind::Word(word.to_string()), + }; + self.token(len, kind) } fn lex_num(&mut self) -> Token { @@ -69,38 +82,57 @@ impl<'a> Iterator for Lexer<'a> { b')' => self.token(1, TokenKind::Rparen), b'{' => self.token(1, TokenKind::Lbrace), b'}' => self.token(1, TokenKind::Rbrace), - b'<' => { - if self.peek(1) == Some(b'=') { - self.token(2, TokenKind::Leq) - } else { - self.token(1, TokenKind::Langle) - } + b'<' => match self.peek(1) { + Some(b'<') => self.token(2, TokenKind::Llangle), + Some(b'=') => self.token(2, TokenKind::Leq), + _ => self.token(1, TokenKind::Langle), + }, + b'>' => match self.peek(1) { + Some(b'>') => self.token(2, TokenKind::Rrangle), + Some(b'=') => self.token(2, TokenKind::Geq), + _ => self.token(1, TokenKind::Rangle), + }, + b'[' => match self.peek(1) { + Some(b'[') => self.token(2, TokenKind::Llbracket), + _ => self.token(1, TokenKind::Lbracket), } - b'>' => { - if self.peek(1) == Some(b'=') { - self.token(2, TokenKind::Geq) - } else { - self.token(1, TokenKind::Rangle) - } + b']' => match self.peek(1) { + Some(b']') => self.token(2, TokenKind::Rrbracket), + _ => self.token(1, TokenKind::Rbracket), } b'+' => self.token(1, TokenKind::Plus), - b'-' => self.token(1, TokenKind::Minus), + b'-' => match self.peek(1) { + Some(b'>') => self.token(2, TokenKind::Arrow), + _ => self.token(1, TokenKind::Minus), + }, b'*' => self.token(1, TokenKind::Star), b'/' => self.token(1, TokenKind::Slash), - b'=' => { - if self.peek(1) == Some(b'=') { - self.token(2, TokenKind::EqEq) - } else { - self.token(1, TokenKind::Eq) - } + b'&' => match self.peek(1) { + Some(b'&') => self.token(2, TokenKind::AmpAmp), + _ => self.token(1, TokenKind::Err), + } + b'|' => match self.peek(1) { + Some(b'|') => self.token(2, TokenKind::PipePipe), + _ => self.token(1, TokenKind::Err), + } + b'^' => self.token(1, TokenKind::Hat), + b'?' => self.token(1, TokenKind::Question), + b'!' => match self.peek(1) { + Some(b'=') => self.token(2, TokenKind::Neq), + _ => self.token(1, TokenKind::Bang), } - b'!' => { - if self.peek(1) == Some(b'=') { - self.token(2, TokenKind::Neq) - } else { - self.token(1, TokenKind::Err) - } + b'=' => match self.peek(1) { + Some(b'=') => self.token(2, TokenKind::Eq), + _ => self.token(1, TokenKind::Assign), } + b',' => self.token(1, TokenKind::Comma), + b'.' => match self.peek(1) { + Some(b'.') => self.token(2, TokenKind::DotDot), + _ => self.token(1, TokenKind::Dot), + } + b';' => self.token(1, TokenKind::Semi), + b':' => self.token(1, TokenKind::Colon), + b'\'' => self.token(1, TokenKind::Prime), b'a'..=b'z' | b'A'..=b'Z' => self.lex_alpha(), b'0'..=b'9' => self.lex_num(), _ => self.token(1, TokenKind::Err), @@ -115,14 +147,20 @@ mod tests { use std::fmt::Write; #[test] - fn lexing001() { + fn lexing_001() { let input = "==4 /* - x (var01 > 0)"; let lexer = Lexer::new(input.as_bytes()); let mut res = String::new(); lexer.for_each(|tk| write!(res, "{tk:?}").unwrap()); - assert_eq!( - "'=='(0,2)'4'(2,3)'/'(4,5)'*'(5,6)'-'(7,8)'x'(9,10)'('(11,12)'var01'(12,17)'>'(18,19)'0'(20,21)')'(21,22)", - &res - ) + assert_eq!(&res, "'=='(0,2)'4'(2,3)'/'(4,5)'*'(5,6)'-'(7,8)'x'(9,10)'('(11,12)'var01'(12,17)'>'(18,19)'0'(20,21)')'(21,22)") + } + + #[test] + fn lexing_002() { + let input = " !player ->i [..]<< init>>"; + let lexer = Lexer::new(input.as_bytes()); + let mut res = String::new(); + lexer.for_each(|tk| write!(res, "{tk:?}").unwrap()); + assert_eq!(&res, "'!'(2,3)'player'(3,9)'->'(10,12)'i'(12,13)'['(14,15)'..'(15,17)']'(17,18)'<<'(18,20)'init'(21,25)'>>'(25,27)") } } diff --git a/cgaal-engine/src/parsing/token.rs b/cgaal-engine/src/parsing/token.rs index 8a85bbe0a..8a720b4dc 100644 --- a/cgaal-engine/src/parsing/token.rs +++ b/cgaal-engine/src/parsing/token.rs @@ -44,19 +44,49 @@ pub enum TokenKind { Rbrace, Langle, Rangle, + Lbracket, + Rbracket, + Llangle, + Rrangle, + Llbracket, + Rrbracket, // Operators Plus, Minus, Star, Slash, - EqEq, + Eq, Neq, Geq, Leq, + Bang, + AmpAmp, + PipePipe, + Hat, + Arrow, + Question, // Other symbols - Eq, + Assign, + Comma, + Dot, + DotDot, + Semi, + Colon, + Prime, + + // Keywords + KwConst, + KwLabel, + KwPlayer, + KwTemplate, + KwEndTemplate, + KwInit, + KwMin, + KwMax, + True, + False, // Literals Num(i32), @@ -75,15 +105,43 @@ impl Display for TokenKind { TokenKind::Rbrace => write!(f, "}}"), TokenKind::Langle => write!(f, "<"), TokenKind::Rangle => write!(f, ">"), + TokenKind::Lbracket => write!(f, "["), + TokenKind::Rbracket => write!(f, "]"), + TokenKind::Llangle => write!(f, "<<"), + TokenKind::Rrangle => write!(f, ">>"), + TokenKind::Llbracket => write!(f, "[["), + TokenKind::Rrbracket => write!(f, "]]"), TokenKind::Plus => write!(f, "+"), TokenKind::Minus => write!(f, "-"), TokenKind::Star => write!(f, "*"), TokenKind::Slash => write!(f, "/"), - TokenKind::EqEq => write!(f, "=="), + TokenKind::Eq => write!(f, "=="), TokenKind::Neq => write!(f, "!="), TokenKind::Geq => write!(f, ">="), TokenKind::Leq => write!(f, "<="), - TokenKind::Eq => write!(f, "="), + TokenKind::Bang => write!(f, "!"), + TokenKind::AmpAmp => write!(f, "&&"), + TokenKind::PipePipe => write!(f, "||"), + TokenKind::Hat => write!(f, "^"), + TokenKind::Arrow => write!(f, "->"), + TokenKind::Question => write!(f, "?"), + TokenKind::Assign => write!(f, "="), + TokenKind::Comma => write!(f, ","), + TokenKind::Dot => write!(f, "."), + TokenKind::DotDot => write!(f, ".."), + TokenKind::Semi => write!(f, ";"), + TokenKind::Colon => write!(f, ":"), + TokenKind::Prime => write!(f, "'"), + TokenKind::KwConst => write!(f, "const"), + TokenKind::KwLabel => write!(f, "label"), + TokenKind::KwPlayer => write!(f, "player"), + TokenKind::KwTemplate => write!(f, "template"), + TokenKind::KwEndTemplate => write!(f, "endtemplate"), + TokenKind::KwInit => write!(f, "init"), + TokenKind::KwMin => write!(f, "min"), + TokenKind::KwMax => write!(f, "max"), + TokenKind::True => write!(f, "true"), + TokenKind::False => write!(f, "false"), TokenKind::Num(n) => write!(f, "{n}"), TokenKind::Word(w) => write!(f, "{w}"), TokenKind::Err => write!(f, "@err"), From 5fbe324af249c4025f656cb9d503337bac26b2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Fri, 25 Aug 2023 09:47:51 +0200 Subject: [PATCH 07/38] Add nice error formatting --- cgaal-engine/src/parsing/errors.rs | 146 +++++++++++++++++++++++++++++ cgaal-engine/src/parsing/mod.rs | 1 + cgaal-engine/src/parsing/span.rs | 35 +++++++ 3 files changed, 182 insertions(+) create mode 100644 cgaal-engine/src/parsing/errors.rs diff --git a/cgaal-engine/src/parsing/errors.rs b/cgaal-engine/src/parsing/errors.rs new file mode 100644 index 000000000..0db6efec7 --- /dev/null +++ b/cgaal-engine/src/parsing/errors.rs @@ -0,0 +1,146 @@ +use std::cmp::{max, min}; +use crate::parsing::span::Span; + +#[derive(Debug, Default)] +pub struct ErrorLog { + errors: Vec, +} + +impl ErrorLog { + pub fn new() -> Self { + Default::default() + } + + pub fn log(&mut self, entry: ErrorEntry) { + self.errors.push(entry); + } + + pub fn log_msg(&mut self, msg: String) { + self.errors.push(ErrorEntry::msg_only(msg)); + } + + pub fn has_errors(&self) -> bool { + !self.errors.is_empty() + } + + pub fn is_empty(&self) -> bool { + self.errors.is_empty() + } + + pub fn write_detailed(&self, orig_input: &str, f: &mut dyn std::fmt::Write) -> std::fmt::Result { + for entry in &self.errors { + match &entry.span { + Some(span) => { + // Example: + // """ + // 1:13 Error: Unknown identifier 'robot' + // | player p1 = robot[x=0, y=1]; + // | ^^^^^ + // """ + + // Find the line and column of the error + let mut i = 0; + let mut col = 1; + let mut line = 1; + let mut line_start = 0; + let chars = orig_input.bytes().collect::>(); + while i < span.begin { + if chars[i] == b'\n' { + line += 1; + line_start = i + 1; + col = 1; + } else { + col += 1; + } + i += 1; + } + writeln!(f, "\x1b[93m{}:{} Error:\x1b[0m {}", line, col, entry.msg)?; + + // Print the lines with the error, and underline the error span + while line_start < span.end && i <= chars.len() { + if i == chars.len() || chars[i] == b'\n' { + writeln!(f, "| {}", &orig_input[line_start..i])?; + let highlight_start = max(line_start, span.begin); + let highlight_end = min(i, span.end); + write!(f, "| ")?; + write!(f, "{}", " ".repeat(highlight_start - line_start))?; + writeln!(f, "{}", "^".repeat(highlight_end - highlight_start))?; + line += 1; + line_start = i + 1; + } + i += 1; + } + } + None => { + writeln!(f, "\x1b[93m@ Error:\x1b[0m {}", entry.msg)?; + } + } + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct ErrorEntry { + span: Option, + msg: String, +} + +impl ErrorEntry { + pub fn new(span: Span, msg: String) -> Self { + ErrorEntry { span: Some(span), msg } + } + + pub fn msg_only(msg: String) -> Self { + ErrorEntry { span: None, msg } + } +} + +#[cfg(test)] +mod tests { + use crate::parsing::errors::{ErrorEntry, ErrorLog}; + use crate::parsing::span::Span; + + #[test] + fn error_fmt_001() { + // Does errors spanning one line print as expected + let input = "\ + \n// Player declarations\ + \nplayer p1 = robot[x=0, y=1];\ + \n\ + "; + let mut log = ErrorLog::new(); + let i = input.find("robot").unwrap(); + log.log(ErrorEntry::new(Span::new(i, i + 5), "Unknown identifier 'robot'".to_string())); + let mut out = String::new(); + log.write_detailed(input, &mut out).unwrap(); + assert_eq!( + out, + "\x1b[93m3:13 Error:\x1b[0m Unknown identifier 'robot'\n\ + | player p1 = robot[x=0, y=1];\n\ + | ^^^^^\n" + ); + } + + #[test] + fn error_fmt_002() { + // Does errors spanning two lines print as expected + let input = "\ + \nlabel x = 123 +\ + \n true;\ + "; + let mut log = ErrorLog::new(); + let span = Span::new(input.find("123").unwrap(), input.find(";").unwrap()); + log.log(ErrorEntry::new(span, "RHS of '+' must be integer, found bool".to_string())); + let mut out = String::new(); + log.write_detailed(input, &mut out).unwrap(); + assert_eq!( + out, + "\x1b[93m2:11 Error:\x1b[0m RHS of '+' must be integer, found bool\n\ + | label x = 123 +\n\ + | ^^^^^\n\ + | true;\n\ + | ^^^^^^^^^^^^^^\n" + ); + } +} \ No newline at end of file diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index b5328ab8e..06f2428f6 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -1,6 +1,7 @@ mod lexer; mod span; mod token; +mod errors; use crate::parsing::span::Span; use pom::parser::*; diff --git a/cgaal-engine/src/parsing/span.rs b/cgaal-engine/src/parsing/span.rs index 6ceef81a9..1bdcec0ff 100644 --- a/cgaal-engine/src/parsing/span.rs +++ b/cgaal-engine/src/parsing/span.rs @@ -46,3 +46,38 @@ impl From> for Span { Span::new(range.start, range.end) } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LineColumn { + pub line: usize, + pub column: usize, +} + +impl LineColumn { + pub fn new(line: usize, column: usize) -> Self { + LineColumn { line, column } + } + + pub fn from_pos(pos: usize, orig_input: &str) -> Self { + let mut line = 1; + let mut column = 1; + for (i, c) in orig_input.bytes().enumerate() { + if i == pos { + return LineColumn { line, column }; + } + if c == b'\n' { + line += 1; + column = 1; + } else { + column += 1; + } + } + LineColumn { line, column } + } +} + +impl Display for LineColumn { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.line, self.column) + } +} \ No newline at end of file From 25797a366032271a9deb99daa4652232462a0f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Fri, 25 Aug 2023 10:56:24 +0200 Subject: [PATCH 08/38] Fix parse error introduced in latest merge --- cgaal-engine/src/atl/parser.rs | 60 +++++++++++++++--------------- cgaal-engine/src/parsing/errors.rs | 10 +++-- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/cgaal-engine/src/atl/parser.rs b/cgaal-engine/src/atl/parser.rs index d81dd553a..33c1bcaab 100644 --- a/cgaal-engine/src/atl/parser.rs +++ b/cgaal-engine/src/atl/parser.rs @@ -56,10 +56,10 @@ fn disjunction<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - let dicjunc = (primary(state, expr_parser) - ws() - sym(b'|') - ws() + let disjunc = (primary(state, expr_parser) - ws() - sym(b'|') - ws() + call2(&disjunction, state, expr_parser)) .map(|(lhs, rhs)| Phi::Or(Arc::new(lhs), Arc::new(rhs))); - dicjunc | primary(state, expr_parser) + disjunc | primary(state, expr_parser) } /// Parses a primary ATL formula (no ANDs or ORs) @@ -90,7 +90,7 @@ fn paren<'a, A: AtlExpressionParser>( } /// Parses an enforce-coalition (path qualifier) -fn enforce_players<'a, A: AtlExpressionParser>( +fn enforce_coalition<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Vec> { @@ -98,7 +98,7 @@ fn enforce_players<'a, A: AtlExpressionParser>( } /// Parses a despite-coalition (path qualifier) -fn despite_players<'a, A: AtlExpressionParser>( +fn despite_coalition<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Vec> { @@ -110,7 +110,7 @@ fn next<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - sym(b'X') * ws() * call2(&primary, state, expr_parser) + sym(b'X') * ws() * call2(&conjunction, state, expr_parser) } /// Parses an path formula with the UNTIL operator @@ -118,8 +118,8 @@ fn until<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, (Phi, Phi)> { - sym(b'(') * ws() * call2(&primary, state, expr_parser) - ws() - sym(b'U') - ws() - + call2(&primary, state, expr_parser) + sym(b'(') * ws() * call2(&conjunction, state, expr_parser) - ws() - sym(b'U') - ws() + + call2(&conjunction, state, expr_parser) - ws() - sym(b')') } @@ -129,7 +129,7 @@ fn eventually<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - sym(b'F') * ws() * call2(&primary, state, expr_parser) + sym(b'F') * ws() * call2(&conjunction, state, expr_parser) } /// Parses an path formula starting with the INVARIANT (G/global) operator @@ -137,7 +137,7 @@ fn invariant<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - sym(b'G') * ws() * call2(&primary, state, expr_parser) + sym(b'G') * ws() * call2(&conjunction, state, expr_parser) } /// Parses an ENFORCE-NEXT ATL formula @@ -145,7 +145,7 @@ fn enforce_next<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - (enforce_players(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { + (enforce_coalition(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { Phi::EnforceNext { players, formula: Arc::new(phi), @@ -158,7 +158,7 @@ fn enforce_until<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - (enforce_players(state, expr_parser) - ws() + until(state, expr_parser)).map( + (enforce_coalition(state, expr_parser) - ws() + until(state, expr_parser)).map( |(players, (l, r))| Phi::EnforceUntil { players, pre: Arc::new(l), @@ -172,7 +172,7 @@ fn enforce_eventually<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - (enforce_players(state, expr_parser) - ws() + eventually(state, expr_parser)).map( + (enforce_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)).map( |(players, phi)| Phi::EnforceEventually { players, formula: Arc::new(phi), @@ -185,7 +185,7 @@ fn enforce_invariant<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - (enforce_players(state, expr_parser) - ws() + invariant(state, expr_parser)).map( + (enforce_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)).map( |(players, phi)| Phi::EnforceInvariant { players, formula: Arc::new(phi), @@ -198,7 +198,7 @@ fn despite_next<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - (despite_players(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { + (despite_coalition(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { Phi::DespiteNext { players, formula: Arc::new(phi), @@ -211,7 +211,7 @@ fn despite_until<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - (despite_players(state, expr_parser) - ws() + until(state, expr_parser)).map( + (despite_coalition(state, expr_parser) - ws() + until(state, expr_parser)).map( |(players, (l, r))| Phi::DespiteUntil { players, pre: Arc::new(l), @@ -225,7 +225,7 @@ fn despite_eventually<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - (despite_players(state, expr_parser) - ws() + eventually(state, expr_parser)).map( + (despite_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)).map( |(players, phi)| Phi::DespiteEventually { players, formula: Arc::new(phi), @@ -238,7 +238,7 @@ fn despite_invariant<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { - (despite_players(state, expr_parser) - ws() + invariant(state, expr_parser)).map( + (despite_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)).map( |(players, phi)| Phi::DespiteInvariant { players, formula: Arc::new(phi), @@ -318,9 +318,9 @@ mod test { use pom::parser::Parser; use crate::atl::parser::{ - boolean, conjunction, despite_eventually, despite_invariant, despite_next, despite_players, + boolean, conjunction, despite_eventually, despite_invariant, despite_next, despite_coalition, despite_until, disjunction, enforce_eventually, enforce_invariant, enforce_next, - enforce_players, enforce_until, eventually, invariant, next, not, number, paren, + enforce_coalition, enforce_until, eventually, invariant, next, not, number, paren, proposition, until, AtlExpressionParser, }; use crate::atl::{parse_phi, Phi}; @@ -353,7 +353,7 @@ mod test { fn enforce_players_1() { let state = ParseState::default(); assert_eq!( - enforce_players(&state, &TestModel).parse(b"<<1>>"), + enforce_coalition(&state, &TestModel).parse(b"<<1>>"), Ok(vec![1usize]) ); assert!(!state.has_errors()); @@ -364,7 +364,7 @@ mod test { fn enforce_players_2() { let state = ParseState::default(); assert_eq!( - enforce_players(&state, &TestModel).parse(b"<<4,9>>"), + enforce_coalition(&state, &TestModel).parse(b"<<4,9>>"), Ok(vec![4usize, 9usize]) ); assert!(!state.has_errors()); @@ -375,7 +375,7 @@ mod test { fn enforce_players_3() { let state = ParseState::default(); assert_eq!( - enforce_players(&state, &TestModel).parse(b"<<203,23,4>>"), + enforce_coalition(&state, &TestModel).parse(b"<<203,23,4>>"), Ok(vec![203usize, 23usize, 4usize]) ); assert!(!state.has_errors()); @@ -386,7 +386,7 @@ mod test { fn enforce_players_4() { let state = ParseState::default(); assert_eq!( - enforce_players(&state, &TestModel).parse(b"<<203, 23>>"), + enforce_coalition(&state, &TestModel).parse(b"<<203, 23>>"), Ok(vec![203usize, 23usize]) ); assert!(!state.has_errors()); @@ -397,7 +397,7 @@ mod test { fn enforce_players_5() { let state = ParseState::default(); assert_eq!( - enforce_players(&state, &TestModel).parse(b"<<>>"), + enforce_coalition(&state, &TestModel).parse(b"<<>>"), Ok(vec![]) ); assert!(!state.has_errors()); @@ -408,7 +408,7 @@ mod test { fn despite_players_1() { let state = ParseState::default(); assert_eq!( - despite_players(&state, &TestModel).parse(b"[[1]]"), + despite_coalition(&state, &TestModel).parse(b"[[1]]"), Ok(vec![1usize]) ); assert!(!state.has_errors()); @@ -419,7 +419,7 @@ mod test { fn despite_players_2() { let state = ParseState::default(); assert_eq!( - despite_players(&state, &TestModel).parse(b"[[4,9]]"), + despite_coalition(&state, &TestModel).parse(b"[[4,9]]"), Ok(vec![4usize, 9usize]) ); assert!(!state.has_errors()); @@ -430,7 +430,7 @@ mod test { fn despite_players_3() { let state = ParseState::default(); assert_eq!( - despite_players(&state, &TestModel).parse(b"[[203,23,4]]"), + despite_coalition(&state, &TestModel).parse(b"[[203,23,4]]"), Ok(vec![203usize, 23usize, 4usize]) ); assert!(!state.has_errors()); @@ -441,7 +441,7 @@ mod test { fn despite_players_4() { let state = ParseState::default(); assert_eq!( - despite_players(&state, &TestModel).parse(b"[[203, 23]]"), + despite_coalition(&state, &TestModel).parse(b"[[203, 23]]"), Ok(vec![203usize, 23usize]) ); assert!(!state.has_errors()); @@ -452,7 +452,7 @@ mod test { fn despite_players_5() { let state = ParseState::default(); assert_eq!( - despite_players(&state, &TestModel).parse(b"[[]]"), + despite_coalition(&state, &TestModel).parse(b"[[]]"), Ok(vec![]) ); assert!(!state.has_errors()); @@ -977,7 +977,7 @@ mod test { let atl_formula = "<>"; let state = ParseState::default(); - let phi = enforce_players(&state, &lcgs).parse(&atl_formula.as_bytes()); + let phi = enforce_coalition(&state, &lcgs).parse(&atl_formula.as_bytes()); assert_eq!(phi, Ok(vec![0])); assert!(!state.has_errors()); } diff --git a/cgaal-engine/src/parsing/errors.rs b/cgaal-engine/src/parsing/errors.rs index 0db6efec7..f6e09bc7b 100644 --- a/cgaal-engine/src/parsing/errors.rs +++ b/cgaal-engine/src/parsing/errors.rs @@ -11,7 +11,11 @@ impl ErrorLog { Default::default() } - pub fn log(&mut self, entry: ErrorEntry) { + pub fn log(&mut self, span: Span, msg: String) { + self.errors.push(ErrorEntry::new(span, msg)); + } + + pub fn log_entry(&mut self, entry: ErrorEntry) { self.errors.push(entry); } @@ -111,7 +115,7 @@ mod tests { "; let mut log = ErrorLog::new(); let i = input.find("robot").unwrap(); - log.log(ErrorEntry::new(Span::new(i, i + 5), "Unknown identifier 'robot'".to_string())); + log.log(Span::new(i, i + 5), "Unknown identifier 'robot'".to_string()); let mut out = String::new(); log.write_detailed(input, &mut out).unwrap(); assert_eq!( @@ -131,7 +135,7 @@ mod tests { "; let mut log = ErrorLog::new(); let span = Span::new(input.find("123").unwrap(), input.find(";").unwrap()); - log.log(ErrorEntry::new(span, "RHS of '+' must be integer, found bool".to_string())); + log.log(span, "RHS of '+' must be integer, found bool".to_string()); let mut out = String::new(); log.write_detailed(input, &mut out).unwrap(); assert_eq!( From 8a3eea48b53dc3d78504fb2e4cc6a9a56388ddeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Fri, 25 Aug 2023 12:35:09 +0200 Subject: [PATCH 09/38] Allow parse_or_sync to be nested --- cgaal-engine/src/atl/parser.rs | 148 +++++++++--------- cgaal-engine/src/game_structure/eager.rs | 4 +- .../game_structure/lcgs/ir/intermediate.rs | 4 +- cgaal-engine/src/parsing/mod.rs | 82 +++++++--- 4 files changed, 136 insertions(+), 102 deletions(-) diff --git a/cgaal-engine/src/atl/parser.rs b/cgaal-engine/src/atl/parser.rs index 33c1bcaab..f0243d64c 100644 --- a/cgaal-engine/src/atl/parser.rs +++ b/cgaal-engine/src/atl/parser.rs @@ -30,9 +30,9 @@ pub fn parse_phi<'a, 'b: 'a, A: AtlExpressionParser>( /// in json, players and propositions are numbers. pub trait AtlExpressionParser { /// A parser that parses a player name - fn player_parser(&self, state: &ParseState) -> Parser; + fn player_parser(&self, state: &ParseState) -> Parser; /// A parser that parses a proposition name - fn proposition_parser(&self, state: &ParseState) -> Parser; + fn proposition_parser(&self, state: &ParseState) -> Parser; } /// Whitespace @@ -42,7 +42,7 @@ fn ws<'a>() -> Parser<'a, u8, ()> { /// Parses an ATL formula (without whitespace around it) pub(crate) fn conjunction<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { let conjunc = (disjunction(state, expr_parser) - ws() - sym(b'&') - ws() @@ -53,7 +53,7 @@ pub(crate) fn conjunction<'a, A: AtlExpressionParser>( /// Parses an ATL term (Can't contain AND and without whitespace around it) fn disjunction<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { let disjunc = (primary(state, expr_parser) - ws() - sym(b'|') - ws() @@ -64,7 +64,7 @@ fn disjunction<'a, A: AtlExpressionParser>( /// Parses a primary ATL formula (no ANDs or ORs) fn primary<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { paren(state, expr_parser) @@ -83,7 +83,7 @@ fn primary<'a, A: AtlExpressionParser>( /// Parses an ATL formula in parenthesis fn paren<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { sym(b'(') * ws() * call2(&conjunction, state, expr_parser) - ws() - sym(b')') @@ -91,7 +91,7 @@ fn paren<'a, A: AtlExpressionParser>( /// Parses an enforce-coalition (path qualifier) fn enforce_coalition<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Vec> { seq(b"<<") * ws() * players(state, expr_parser) - ws() - seq(b">>") @@ -99,7 +99,7 @@ fn enforce_coalition<'a, A: AtlExpressionParser>( /// Parses a despite-coalition (path qualifier) fn despite_coalition<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Vec> { seq(b"[[") * ws() * players(state, expr_parser) - ws() - seq(b"]]") @@ -107,7 +107,7 @@ fn despite_coalition<'a, A: AtlExpressionParser>( /// Parses an path formula starting with the NEXT (X) operator fn next<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { sym(b'X') * ws() * call2(&conjunction, state, expr_parser) @@ -115,7 +115,7 @@ fn next<'a, A: AtlExpressionParser>( /// Parses an path formula with the UNTIL operator fn until<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, (Phi, Phi)> { sym(b'(') * ws() * call2(&conjunction, state, expr_parser) - ws() - sym(b'U') - ws() @@ -126,7 +126,7 @@ fn until<'a, A: AtlExpressionParser>( /// Parses an path formula starting with the EVENTUALLY (F/finally) operator fn eventually<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { sym(b'F') * ws() * call2(&conjunction, state, expr_parser) @@ -134,7 +134,7 @@ fn eventually<'a, A: AtlExpressionParser>( /// Parses an path formula starting with the INVARIANT (G/global) operator fn invariant<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { sym(b'G') * ws() * call2(&conjunction, state, expr_parser) @@ -142,7 +142,7 @@ fn invariant<'a, A: AtlExpressionParser>( /// Parses an ENFORCE-NEXT ATL formula fn enforce_next<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { (enforce_coalition(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { @@ -155,7 +155,7 @@ fn enforce_next<'a, A: AtlExpressionParser>( /// Parses an ENFORCE-UNTIL ATL formula fn enforce_until<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { (enforce_coalition(state, expr_parser) - ws() + until(state, expr_parser)).map( @@ -169,7 +169,7 @@ fn enforce_until<'a, A: AtlExpressionParser>( /// Parses an ENFORCE-EVENTUALLY ATL formula fn enforce_eventually<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { (enforce_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)).map( @@ -182,7 +182,7 @@ fn enforce_eventually<'a, A: AtlExpressionParser>( /// Parses an ENFORCE-INVARIANT ATL formula fn enforce_invariant<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { (enforce_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)).map( @@ -195,7 +195,7 @@ fn enforce_invariant<'a, A: AtlExpressionParser>( /// Parses an DESPITE-NEXT ATL formula fn despite_next<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { (despite_coalition(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { @@ -208,7 +208,7 @@ fn despite_next<'a, A: AtlExpressionParser>( /// Parses an DESPITE-UNTIL ATL formula fn despite_until<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { (despite_coalition(state, expr_parser) - ws() + until(state, expr_parser)).map( @@ -222,7 +222,7 @@ fn despite_until<'a, A: AtlExpressionParser>( /// Parses an DESPITE-EVENTUALLY ATL formula fn despite_eventually<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { (despite_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)).map( @@ -235,7 +235,7 @@ fn despite_eventually<'a, A: AtlExpressionParser>( /// Parses an DESPITE-INVARIANT ATL formula fn despite_invariant<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { (despite_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)).map( @@ -248,7 +248,7 @@ fn despite_invariant<'a, A: AtlExpressionParser>( /// Parses a proposition using the given [ATLExpressionParser]. fn proposition<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { expr_parser.proposition_parser(state).map(Phi::Proposition) @@ -256,7 +256,7 @@ fn proposition<'a, A: AtlExpressionParser>( /// Parses a negated ATL formula fn not<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Phi> { (sym(b'!') * ws() * call2(&primary, state, expr_parser)).map(|phi| Phi::Not(Arc::new(phi))) @@ -272,7 +272,7 @@ fn boolean<'a>() -> Parser<'a, u8, Phi> { /// Parses a comma-separated list of players using the given [ATLExpressionParser]. fn players<'a, A: AtlExpressionParser>( - state: &'a ParseState, + state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Vec> { list(expr_parser.player_parser(state), ws() * sym(b',') * ws()) @@ -332,18 +332,18 @@ mod test { struct TestModel; impl AtlExpressionParser for TestModel { - fn player_parser(&self, state: &ParseState) -> Parser { + fn player_parser(&self, state: &ParseState) -> Parser { number() } - fn proposition_parser(&self, state: &ParseState) -> Parser { + fn proposition_parser(&self, state: &ParseState) -> Parser { number() } } #[test] fn paren_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert!(!state.has_errors()); assert_eq!(paren(&state, &TestModel).parse(b"(true)"), Ok(Phi::True)); } @@ -351,7 +351,7 @@ mod test { /// Test for a single player #[test] fn enforce_players_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_coalition(&state, &TestModel).parse(b"<<1>>"), Ok(vec![1usize]) @@ -362,7 +362,7 @@ mod test { /// Test for two players #[test] fn enforce_players_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_coalition(&state, &TestModel).parse(b"<<4,9>>"), Ok(vec![4usize, 9usize]) @@ -373,7 +373,7 @@ mod test { /// Test for three players #[test] fn enforce_players_3() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_coalition(&state, &TestModel).parse(b"<<203,23,4>>"), Ok(vec![203usize, 23usize, 4usize]) @@ -384,7 +384,7 @@ mod test { /// The list of players is allowed to have whitespace after the separator #[test] fn enforce_players_4() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_coalition(&state, &TestModel).parse(b"<<203, 23>>"), Ok(vec![203usize, 23usize]) @@ -395,7 +395,7 @@ mod test { /// Test for no players, should be valid #[test] fn enforce_players_5() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_coalition(&state, &TestModel).parse(b"<<>>"), Ok(vec![]) @@ -406,7 +406,7 @@ mod test { /// Test for a single player #[test] fn despite_players_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_coalition(&state, &TestModel).parse(b"[[1]]"), Ok(vec![1usize]) @@ -417,7 +417,7 @@ mod test { /// Test for two players #[test] fn despite_players_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_coalition(&state, &TestModel).parse(b"[[4,9]]"), Ok(vec![4usize, 9usize]) @@ -428,7 +428,7 @@ mod test { /// Test for three players #[test] fn despite_players_3() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_coalition(&state, &TestModel).parse(b"[[203,23,4]]"), Ok(vec![203usize, 23usize, 4usize]) @@ -439,7 +439,7 @@ mod test { /// The list of players is allowed to have whitespace after the seperator #[test] fn despite_players_4() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_coalition(&state, &TestModel).parse(b"[[203, 23]]"), Ok(vec![203usize, 23usize]) @@ -450,7 +450,7 @@ mod test { /// Test for no players, should be valid #[test] fn despite_players_5() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_coalition(&state, &TestModel).parse(b"[[]]"), Ok(vec![]) @@ -460,21 +460,21 @@ mod test { #[test] fn next_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!(next(&state, &TestModel).parse(b"X true"), Ok(Phi::True)); assert!(!state.has_errors()); } #[test] fn next_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!(next(&state, &TestModel).parse(b"Xtrue"), Ok(Phi::True)); assert!(!state.has_errors()); } #[test] fn until_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( until(&state, &TestModel).parse(b"( true U true )"), Ok((Phi::True, Phi::True)) @@ -484,7 +484,7 @@ mod test { #[test] fn until_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( until(&state, &TestModel).parse(b"(trueUtrue)"), Ok((Phi::True, Phi::True)) @@ -494,7 +494,7 @@ mod test { #[test] fn eventually_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( eventually(&state, &TestModel).parse(b"F true"), Ok(Phi::True) @@ -504,7 +504,7 @@ mod test { #[test] fn eventually_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( eventually(&state, &TestModel).parse(b"Ftrue"), Ok(Phi::True) @@ -514,7 +514,7 @@ mod test { #[test] fn invariant_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( invariant(&state, &TestModel).parse(b"G true"), Ok(Phi::True) @@ -524,14 +524,14 @@ mod test { #[test] fn invariant_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!(invariant(&state, &TestModel).parse(b"Gtrue"), Ok(Phi::True)); assert!(!state.has_errors()); } #[test] fn enforce_next_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_next(&state, &TestModel).parse(b"<<1 , 2>> X true"), Ok(Phi::EnforceNext { @@ -544,7 +544,7 @@ mod test { #[test] fn enforce_next_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_next(&state, &TestModel).parse(b"<<1,2>>Xtrue"), Ok(Phi::EnforceNext { @@ -557,7 +557,7 @@ mod test { #[test] fn enforce_until_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_until(&state, &TestModel).parse(b"<<1 , 2>> ( true U true )"), Ok(Phi::EnforceUntil { @@ -571,7 +571,7 @@ mod test { #[test] fn enforce_until_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_until(&state, &TestModel).parse(b"<<1,2>>(trueUtrue)"), Ok(Phi::EnforceUntil { @@ -585,7 +585,7 @@ mod test { #[test] fn enforce_eventually_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_eventually(&state, &TestModel).parse(b"<<1 , 2>> F true"), Ok(Phi::EnforceEventually { @@ -598,7 +598,7 @@ mod test { #[test] fn enforce_eventually_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_eventually(&state, &TestModel).parse(b"<<1,2>>Ftrue"), Ok(Phi::EnforceEventually { @@ -611,7 +611,7 @@ mod test { #[test] fn enforce_invariant_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_invariant(&state, &TestModel).parse(b"<<1 , 2>> G true"), Ok(Phi::EnforceInvariant { @@ -624,7 +624,7 @@ mod test { #[test] fn enforce_invariant_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( enforce_invariant(&state, &TestModel).parse(b"<<1,2>>Gtrue"), Ok(Phi::EnforceInvariant { @@ -637,7 +637,7 @@ mod test { #[test] fn despite_next_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_next(&state, &TestModel).parse(b"[[1 , 2]] X true"), Ok(Phi::DespiteNext { @@ -650,7 +650,7 @@ mod test { #[test] fn despite_next_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_next(&state, &TestModel).parse(b"[[1,2]]Xtrue"), Ok(Phi::DespiteNext { @@ -663,7 +663,7 @@ mod test { #[test] fn despite_until_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_until(&state, &TestModel).parse(b"[[1 , 2]] ( true U true )"), Ok(Phi::DespiteUntil { @@ -677,7 +677,7 @@ mod test { #[test] fn despite_until_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_until(&state, &TestModel).parse(b"[[1,2]](trueUtrue)"), Ok(Phi::DespiteUntil { @@ -691,7 +691,7 @@ mod test { #[test] fn despite_eventually_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_eventually(&state, &TestModel).parse(b"[[1 , 2]] F true"), Ok(Phi::DespiteEventually { @@ -704,7 +704,7 @@ mod test { #[test] fn despite_eventually_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_eventually(&state, &TestModel).parse(b"[[1,2]]Ftrue"), Ok(Phi::DespiteEventually { @@ -717,7 +717,7 @@ mod test { #[test] fn despite_invariant_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_invariant(&state, &TestModel).parse(b"[[1 , 2]] G true"), Ok(Phi::DespiteInvariant { @@ -730,7 +730,7 @@ mod test { #[test] fn despite_invariant_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( despite_invariant(&state, &TestModel).parse(b"[[1,2]]Gtrue"), Ok(Phi::DespiteInvariant { @@ -743,7 +743,7 @@ mod test { #[test] fn proposition_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( proposition(&state, &TestModel).parse(b"1"), Ok(Phi::Proposition(1usize)) @@ -753,7 +753,7 @@ mod test { #[test] fn proposition_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( proposition(&state, &TestModel).parse(b"1432"), Ok(Phi::Proposition(1432usize)) @@ -763,14 +763,14 @@ mod test { #[test] fn proposition_3() { - let state = ParseState::default(); + let state = ParseState::::default(); assert!(proposition(&state, &TestModel).parse(b"abc").is_err()); assert!(!state.has_errors()); } #[test] fn not_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( not(&state, &TestModel).parse(b"! true"), Ok(Phi::Not(Arc::new(Phi::True))) @@ -780,7 +780,7 @@ mod test { #[test] fn not_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( not(&state, &TestModel).parse(b"!true"), Ok(Phi::Not(Arc::new(Phi::True))) @@ -790,7 +790,7 @@ mod test { #[test] fn and_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( conjunction(&state, &TestModel).parse(b"true & false"), Ok(Phi::And(Arc::new(Phi::True), Arc::new(Phi::False))) @@ -800,7 +800,7 @@ mod test { #[test] fn and_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( conjunction(&state, &TestModel).parse(b"true&false"), Ok(Phi::And(Arc::new(Phi::True), Arc::new(Phi::False))) @@ -811,7 +811,7 @@ mod test { #[test] fn and_3() { // Right recursive? - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( conjunction(&state, &TestModel).parse(b"true&false&true"), Ok(Phi::And( @@ -824,7 +824,7 @@ mod test { #[test] fn or_1() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( disjunction(&state, &TestModel).parse(b"true | false"), Ok(Phi::Or(Arc::new(Phi::True), Arc::new(Phi::False))) @@ -834,7 +834,7 @@ mod test { #[test] fn or_2() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( disjunction(&state, &TestModel).parse(b"true|false"), Ok(Phi::Or(Arc::new(Phi::True), Arc::new(Phi::False))) @@ -845,7 +845,7 @@ mod test { #[test] fn or_3() { // Right recursive? - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( disjunction(&state, &TestModel).parse(b"true|false |true"), Ok(Phi::Or( @@ -858,7 +858,7 @@ mod test { #[test] fn test_and_or_precedence_01() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( conjunction(&state, &TestModel).parse(b"true & false | true | false & true"), Ok(Phi::And( @@ -897,7 +897,7 @@ mod test { #[test] fn test_phi_01() { - let state = ParseState::default(); + let state = ParseState::::default(); assert_eq!( conjunction(&state, &TestModel).parse(b"<<0>> F true"), Ok(Phi::EnforceEventually { @@ -976,7 +976,7 @@ mod test { let lcgs = IntermediateLcgs::create(parse_lcgs(lcgs_program).unwrap()).unwrap(); let atl_formula = "<>"; - let state = ParseState::default(); + let state = ParseState::::default(); let phi = enforce_coalition(&state, &lcgs).parse(&atl_formula.as_bytes()); assert_eq!(phi, Ok(vec![0])); assert!(!state.has_errors()); diff --git a/cgaal-engine/src/game_structure/eager.rs b/cgaal-engine/src/game_structure/eager.rs index 19e0f8a15..1b9e8db2a 100644 --- a/cgaal-engine/src/game_structure/eager.rs +++ b/cgaal-engine/src/game_structure/eager.rs @@ -90,7 +90,7 @@ impl GameStructure for EagerGameStructure { } impl AtlExpressionParser for EagerGameStructure { - fn player_parser(&self, state: &ParseState) -> Parser { + fn player_parser(&self, state: &ParseState) -> Parser { // In EagerGameStructure ATL, players are just their index number().convert(move |i| { if i <= self.max_player() { @@ -101,7 +101,7 @@ impl AtlExpressionParser for EagerGameStructure { }) } - fn proposition_parser(&self, state: &ParseState) -> Parser { + fn proposition_parser(&self, state: &ParseState) -> Parser { // In EagerGameStructure ATL, proposition are just their index. // All numbers are valid propositions, but they might not be true anywhere. number() diff --git a/cgaal-engine/src/game_structure/lcgs/ir/intermediate.rs b/cgaal-engine/src/game_structure/lcgs/ir/intermediate.rs index a13a60b6e..86737917f 100644 --- a/cgaal-engine/src/game_structure/lcgs/ir/intermediate.rs +++ b/cgaal-engine/src/game_structure/lcgs/ir/intermediate.rs @@ -541,7 +541,7 @@ impl GameStructure for IntermediateLcgs { } impl AtlExpressionParser for IntermediateLcgs { - fn player_parser(&self, state: &ParseState) -> Parser { + fn player_parser(&self, state: &ParseState) -> Parser { // In ATL, players are referred to using their name, i.e. an identifier identifier().convert(move |name| { // We check if a declaration with the given name exists, @@ -562,7 +562,7 @@ impl AtlExpressionParser for IntermediateLcgs { }) } - fn proposition_parser(&self, state: &ParseState) -> Parser { + fn proposition_parser(&self, state: &ParseState) -> Parser { // In ATL, propositions are either "something" where "something" must be a label declared // in the global scope, or "someone.something" where "something" is a label owned by // a player of name "someone". diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 06f2428f6..c64a64520 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -1,7 +1,7 @@ +mod errors; mod lexer; mod span; mod token; -mod errors; use crate::parsing::span::Span; use pom::parser::*; @@ -33,11 +33,12 @@ pub struct ParseError { /// The `ParseState` is a struct carried around during parsing containing extra information /// about the state of the parser, such as the errors encountered #[derive(Debug, Default)] -pub struct ParseState { +pub struct ParseState { errors: RefCell>, + sync_tokens: RefCell>, } -impl ParseState { +impl ParseState { /// Saves an error to display when parsing is done pub fn report_err(&self, err: ParseError) { self.errors.borrow_mut().push(err) @@ -77,7 +78,7 @@ pub(crate) fn call2<'a, I, O, A, B, P: Fn(&'a A, &'a B) -> Parser<'a, I, O>>( /// and parsing will continue with no input consumed. #[allow(unused)] pub(crate) fn parse_or_skip<'a, I: Eq + Display, O: 'a>( - parse_state: &'a ParseState, + parse_state: &'a ParseState, parser: Parser<'a, I, O>, err_msg: &'a str, ) -> Parser<'a, I, Option> { @@ -103,7 +104,7 @@ pub(crate) fn parse_or_skip<'a, I: Eq + Display, O: 'a>( /// and parsing will continue at EOF. #[allow(unused)] pub(crate) fn parse_or_abort<'a, I: Eq + Display, O: 'a>( - parse_state: &'a ParseState, + parse_state: &'a ParseState, parser: Parser<'a, I, O>, err_msg: &'a str, ) -> Parser<'a, I, Option> { @@ -128,14 +129,15 @@ pub(crate) fn parse_or_abort<'a, I: Eq + Display, O: 'a>( /// If the given parser fails to parse, an error is reported using the given error message, /// and parsing will continue at the next occurrence of the synchronization token. #[allow(unused)] -pub(crate) fn parse_or_sync<'a, I: Eq + Display, O: 'a>( - parse_state: &'a ParseState, +pub(crate) fn parse_or_sync<'a, I: Copy + Eq + Display, O: 'a>( + parse_state: &'a ParseState, parser: Parser<'a, I, O>, sync: I, err_msg: &'a str, ) -> Parser<'a, I, Option> { - Parser::new( - move |input: &'_ [I], start: usize| match (parser.method)(input, start) { + Parser::new(move |input: &'_ [I], start: usize| { + parse_state.sync_tokens.borrow_mut().push(sync); + let res = match (parser.method)(input, start) { Ok((out, pos)) => { if input.get(pos) == Some(&sync) { Ok((Some(out), pos + 1)) @@ -143,19 +145,23 @@ pub(crate) fn parse_or_sync<'a, I: Eq + Display, O: 'a>( parse_state.report_err(ParseError { span: Span { begin: pos, - end: input.len(), + end: pos + 1, }, - msg: format!("Missing {}", sync), + msg: format!("Missing '{}'", sync), }); - Ok((Some(out), input.len())) + Ok((Some(out), pos)) } } Err(_) => { let sync_pos = input[start..] .iter() - .position(|i| i == &sync) + .position(|c| c == &sync) .map(|p| p + start + 1) - .unwrap_or(input.len()); + .or(input[start..] + .iter() + .position(|c| parse_state.sync_tokens.borrow().contains(c)) + .map(|p| p + start) + ).unwrap_or(input.len()); parse_state.report_err(ParseError { span: Span { begin: start, @@ -165,8 +171,10 @@ pub(crate) fn parse_or_sync<'a, I: Eq + Display, O: 'a>( }); Ok((None, sync_pos)) } - }, - ) + }; + parse_state.sync_tokens.borrow_mut().pop(); + res + }) } #[cfg(test)] @@ -233,25 +241,51 @@ mod test { #[test] fn parse_or_sync_001() { + // parse_or_sync works on happy path let state = ParseState::default(); - let foo = seq(b"foo"); - let parser = sym(b'(') * parse_or_sync(&state, foo, b')', "error") - end(); + let inner = seq(b"foo"); + let parser = sym(b'(') * parse_or_sync(&state, inner, b')', "error") - seq(b"ok") - end(); - // Input is expected string "(foo)" - let res = parser.parse(b"(foo)").expect("Parser must not fail"); + let res = parser.parse(b"(foo)ok").expect("Parser must not fail"); assert_eq!(b"foo", res.unwrap()); assert!(state.errors.borrow().is_empty()); } #[test] fn parse_or_sync_002() { + // parse_or_sync synchronizes when inner parser fails let state = ParseState::default(); - let foo = seq(b"foo"); - let parser = sym(b'(') * parse_or_sync(&state, foo, b')', "error") - end(); + let inner = seq(b"foo"); + let parser = sym(b'(') * parse_or_sync(&state, inner, b')', "error") - seq(b"ok") - end(); - // Input is unexpected, but parser will recover - let res = parser.parse(b"(bar)").expect("Parser must not fail"); + let res = parser.parse(b"(bar)ok").expect("Parser must not fail"); assert!(res.is_none()); assert_eq!(1, state.errors.borrow().len()); } + + #[test] + fn parse_or_sync_003() { + // nested parse_or_sync synchronizes when inner parser fails + let state = ParseState::default(); + let inner = seq(b"foo"); + let middle = sym(b'(') * parse_or_sync(&state, inner, b')', "error"); + let parser = sym(b'(') * parse_or_sync(&state, middle, b')', "error") - seq(b"ok") - end(); + + let res = parser.parse(b"((bar))ok").expect("Parser must not fail"); + assert!(res.is_some()); // Outer parser succeeds despite inner parser failing + assert_eq!(1, state.errors.borrow().len()); + } + + #[test] + fn parse_or_sync_004() { + // nested parse_or_sync synchronizes at first sync token + let state = ParseState::default(); + let inner = seq(b"foo"); + let middle = sym(b'[') * parse_or_sync(&state, inner, b']', "error"); + let parser = sym(b'(') * parse_or_sync(&state, middle, b')', "error") - seq(b"ok") - end(); + + let res = parser.parse(b"([bar)ok").expect("Parser must not fail"); + assert!(res.is_some()); // Outer parser succeeds despite inner parser failing + assert_eq!(1, state.errors.borrow().len()); + } } From 3a6fd911e64e1b503883cd0c3a97c1c433295617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Fri, 25 Aug 2023 13:05:17 +0200 Subject: [PATCH 10/38] Created AstAtl with spans --- cgaal-engine/src/atl/ast.rs | 62 +++++++++++++++++++++++++++++++++ cgaal-engine/src/atl/mod.rs | 1 + cgaal-engine/src/parsing/mod.rs | 2 +- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 cgaal-engine/src/atl/ast.rs diff --git a/cgaal-engine/src/atl/ast.rs b/cgaal-engine/src/atl/ast.rs new file mode 100644 index 000000000..8ad5d281b --- /dev/null +++ b/cgaal-engine/src/atl/ast.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; +use crate::atl::Phi; +use crate::game_structure::{Player, Proposition}; +use crate::parsing::span::Span; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AstAtl { + span: Span, + kind: AstAtlKind, +} + +pub enum AstAtlKind { + True, + False, + Proposition(Proposition), + Not(Arc), + And(Arc, Arc), + Or(Arc, Arc), + DespiteNext { players: Vec, expr: Arc }, + EnforceNext { players: Vec, expr: Arc }, + DespiteUntil { players: Vec, pre_expr: Arc, end_expr: Arc }, + EnforceUntil { players: Vec, pre_expr: Arc, end_expr: Arc }, + DespiteInvariantly { players: Vec, expr: Arc }, + EnforceInvariantly { players: Vec, expr: Arc }, + DespiteEventually { players: Vec, expr: Arc }, + EnforceEventually { players: Vec, expr: Arc }, + Error, +} + +impl AstAtl { + pub fn new(span: Span, kind: AstAtlKind) -> Self { + AstAtl { span, kind } + } + + pub fn span(&self) -> &Span { + &self.span + } + + pub fn kind(&self) -> &AstAtlKind { + &self.kind + } + + pub fn convert(self) -> Phi { + match self.kind { + AstAtlKind::True => Phi::True, + AstAtlKind::False => Phi::False, + AstAtlKind::Proposition(p) => Phi::Proposition(p), + AstAtlKind::Not(expr) => Phi::Not(Arc::new(expr.convert())), + AstAtlKind::And(lhs, rhs) => Phi::And(Arc::new(lhs.convert()), Arc::new(rhs.convert())), + AstAtlKind::Or(lhs, rhs) => Phi::Or(Arc::new(lhs.convert()), Arc::new(rhs.convert())), + AstAtlKind::DespiteNext { players, expr } => Phi::DespiteNext { players, formula: Arc::new(expr.convert()) }, + AstAtlKind::EnforceNext { players, expr } => Phi::EnforceNext { players, formula: Arc::new(expr.convert()) }, + AstAtlKind::DespiteUntil { players, pre_expr, end_expr } => Phi::DespiteUntil { players, pre: Arc::new(pre_expr.convert()), until: Arc::new(end_expr.convert()) }, + AstAtlKind::EnforceUntil { players, pre_expr, end_expr } => Phi::EnforceUntil { players, pre: Arc::new(pre_expr.convert()), until: Arc::new(end_expr.convert()) }, + AstAtlKind::DespiteInvariantly { players, expr } => Phi::DespiteInvariant { players, formula: Arc::new(expr.convert()) }, + AstAtlKind::EnforceInvariantly { players, expr } => Phi::EnforceInvariant { players, formula: Arc::new(expr.convert()) }, + AstAtlKind::DespiteEventually { players, expr } => Phi::DespiteEventually { players, formula: Arc::new(expr.convert()) }, + AstAtlKind::EnforceEventually { players, expr } => Phi::EnforceEventually { players, formula: Arc::new(expr.convert()) }, + AstAtlKind::Error => panic!("Error in AST"), + } + } +} \ No newline at end of file diff --git a/cgaal-engine/src/atl/mod.rs b/cgaal-engine/src/atl/mod.rs index 0a27a73c3..bc58f0905 100644 --- a/cgaal-engine/src/atl/mod.rs +++ b/cgaal-engine/src/atl/mod.rs @@ -10,6 +10,7 @@ use crate::game_structure::{GameStructure, Player, Proposition}; pub mod game_formula; pub mod parser; +pub mod ast; /// Alternating-time Temporal Logic formula #[derive(Hash, Eq, PartialEq, Clone, Debug, Deserialize, Serialize)] diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index c64a64520..5a6789006 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -1,6 +1,6 @@ mod errors; mod lexer; -mod span; +pub mod span; mod token; use crate::parsing::span::Span; From cbccb7013b1af8b1e382a4ffec807d823dec2d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Sun, 27 Aug 2023 15:57:21 +0200 Subject: [PATCH 11/38] Parser with errors attempt --- cgaal-engine/src/atl/ast.rs | 35 ++- cgaal-engine/src/atl/parser.rs | 204 ++++++++++-------- cgaal-engine/src/game_structure/lcgs/parse.rs | 27 +-- cgaal-engine/src/parsing/errors.rs | 6 +- cgaal-engine/src/parsing/mod.rs | 109 +++++----- cgaal-engine/src/parsing/span.rs | 10 +- lcgs-examples/robot_grid/test.atl | 1 + 7 files changed, 202 insertions(+), 190 deletions(-) create mode 100644 lcgs-examples/robot_grid/test.atl diff --git a/cgaal-engine/src/atl/ast.rs b/cgaal-engine/src/atl/ast.rs index 8ad5d281b..1fc99b774 100644 --- a/cgaal-engine/src/atl/ast.rs +++ b/cgaal-engine/src/atl/ast.rs @@ -5,10 +5,11 @@ use crate::parsing::span::Span; #[derive(Debug, Clone, PartialEq, Eq)] pub struct AstAtl { - span: Span, - kind: AstAtlKind, + pub span: Span, + pub kind: AstAtlKind, } +#[derive(Debug, Clone, PartialEq, Eq)] pub enum AstAtlKind { True, False, @@ -32,30 +33,22 @@ impl AstAtl { AstAtl { span, kind } } - pub fn span(&self) -> &Span { - &self.span - } - - pub fn kind(&self) -> &AstAtlKind { - &self.kind - } - - pub fn convert(self) -> Phi { - match self.kind { + pub fn convert(&self) -> Phi { + match &self.kind { AstAtlKind::True => Phi::True, AstAtlKind::False => Phi::False, - AstAtlKind::Proposition(p) => Phi::Proposition(p), + AstAtlKind::Proposition(p) => Phi::Proposition(*p), AstAtlKind::Not(expr) => Phi::Not(Arc::new(expr.convert())), AstAtlKind::And(lhs, rhs) => Phi::And(Arc::new(lhs.convert()), Arc::new(rhs.convert())), AstAtlKind::Or(lhs, rhs) => Phi::Or(Arc::new(lhs.convert()), Arc::new(rhs.convert())), - AstAtlKind::DespiteNext { players, expr } => Phi::DespiteNext { players, formula: Arc::new(expr.convert()) }, - AstAtlKind::EnforceNext { players, expr } => Phi::EnforceNext { players, formula: Arc::new(expr.convert()) }, - AstAtlKind::DespiteUntil { players, pre_expr, end_expr } => Phi::DespiteUntil { players, pre: Arc::new(pre_expr.convert()), until: Arc::new(end_expr.convert()) }, - AstAtlKind::EnforceUntil { players, pre_expr, end_expr } => Phi::EnforceUntil { players, pre: Arc::new(pre_expr.convert()), until: Arc::new(end_expr.convert()) }, - AstAtlKind::DespiteInvariantly { players, expr } => Phi::DespiteInvariant { players, formula: Arc::new(expr.convert()) }, - AstAtlKind::EnforceInvariantly { players, expr } => Phi::EnforceInvariant { players, formula: Arc::new(expr.convert()) }, - AstAtlKind::DespiteEventually { players, expr } => Phi::DespiteEventually { players, formula: Arc::new(expr.convert()) }, - AstAtlKind::EnforceEventually { players, expr } => Phi::EnforceEventually { players, formula: Arc::new(expr.convert()) }, + AstAtlKind::DespiteNext { players, expr } => Phi::DespiteNext { players: players.clone(), formula: Arc::new(expr.convert()) }, + AstAtlKind::EnforceNext { players, expr } => Phi::EnforceNext { players: players.clone(), formula: Arc::new(expr.convert()) }, + AstAtlKind::DespiteUntil { players, pre_expr, end_expr } => Phi::DespiteUntil { players: players.clone(), pre: Arc::new(pre_expr.convert()), until: Arc::new(end_expr.convert()) }, + AstAtlKind::EnforceUntil { players, pre_expr, end_expr } => Phi::EnforceUntil { players: players.clone(), pre: Arc::new(pre_expr.convert()), until: Arc::new(end_expr.convert()) }, + AstAtlKind::DespiteInvariantly { players, expr } => Phi::DespiteInvariant { players: players.clone(), formula: Arc::new(expr.convert()) }, + AstAtlKind::EnforceInvariantly { players, expr } => Phi::EnforceInvariant { players: players.clone(), formula: Arc::new(expr.convert()) }, + AstAtlKind::DespiteEventually { players, expr } => Phi::DespiteEventually { players: players.clone(), formula: Arc::new(expr.convert()) }, + AstAtlKind::EnforceEventually { players, expr } => Phi::EnforceEventually { players: players.clone(), formula: Arc::new(expr.convert()) }, AstAtlKind::Error => panic!("Error in AST"), } } diff --git a/cgaal-engine/src/atl/parser.rs b/cgaal-engine/src/atl/parser.rs index f0243d64c..b6e25e0b9 100644 --- a/cgaal-engine/src/atl/parser.rs +++ b/cgaal-engine/src/atl/parser.rs @@ -1,12 +1,13 @@ use std::str::{self, FromStr}; use std::sync::Arc; +use crate::atl::ast::{AstAtl, AstAtlKind}; +use crate::atl::Phi; use pom::parser::{end, Parser}; use pom::parser::{list, one_of, seq, sym}; -use super::Phi; use crate::game_structure::{Player, Proposition}; -use crate::parsing::{call2, ParseState}; +use crate::parsing::{call2, parse_or_sync, ParseState, WithSpan}; /// Parse an ATL formula pub fn parse_phi<'a, 'b: 'a, A: AtlExpressionParser>( @@ -14,14 +15,21 @@ pub fn parse_phi<'a, 'b: 'a, A: AtlExpressionParser>( input: &'a str, ) -> Result { let state = ParseState::default(); + println!("Parsing ..."); let formula = ws() * conjunction(&state, expr_parser) - ws() - end(); let phi = formula .parse(input.as_bytes()) .expect("Parser may not fail"); - if state.has_errors() { - Err(state.errors_as_str(input)) + if state.errors.borrow().has_errors() { + let mut error_report = String::new(); + state + .errors + .borrow() + .write_detailed(input, &mut error_report) + .unwrap(); + Err(error_report) } else { - Ok(phi) + Ok(phi.convert()) } } @@ -44,10 +52,10 @@ fn ws<'a>() -> Parser<'a, u8, ()> { pub(crate) fn conjunction<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { +) -> Parser<'a, u8, AstAtl> { let conjunc = (disjunction(state, expr_parser) - ws() - sym(b'&') - ws() + call2(&conjunction, state, expr_parser)) - .map(|(lhs, rhs)| Phi::And(Arc::new(lhs), Arc::new(rhs))); + .map(|(lhs, rhs)| AstAtl::new(lhs.span + rhs.span, AstAtlKind::And(lhs.into(), rhs.into()))); conjunc | disjunction(state, expr_parser) } @@ -55,10 +63,10 @@ pub(crate) fn conjunction<'a, A: AtlExpressionParser>( fn disjunction<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { +) -> Parser<'a, u8, AstAtl> { let disjunc = (primary(state, expr_parser) - ws() - sym(b'|') - ws() + call2(&disjunction, state, expr_parser)) - .map(|(lhs, rhs)| Phi::Or(Arc::new(lhs), Arc::new(rhs))); + .map(|(lhs, rhs)| AstAtl::new(lhs.span + rhs.span, AstAtlKind::Or(lhs.into(), rhs.into()))); disjunc | primary(state, expr_parser) } @@ -66,7 +74,7 @@ fn disjunction<'a, A: AtlExpressionParser>( fn primary<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { +) -> Parser<'a, u8, AstAtl> { paren(state, expr_parser) | boolean() | proposition(state, expr_parser) @@ -85,8 +93,11 @@ fn primary<'a, A: AtlExpressionParser>( fn paren<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - sym(b'(') * ws() * call2(&conjunction, state, expr_parser) - ws() - sym(b')') +) -> Parser<'a, u8, AstAtl> { + let inner = ws() * call2(&conjunction, state, expr_parser) - ws(); + sym(b'(') + * parse_or_sync(state, inner, b')', "Invalid ATL formula") + .map(|res| res.unwrap_or_else(|span| AstAtl::new(span, AstAtlKind::Error))) } /// Parses an enforce-coalition (path qualifier) @@ -94,7 +105,10 @@ fn enforce_coalition<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Vec> { - seq(b"<<") * ws() * players(state, expr_parser) - ws() - seq(b">>") + let inner = ws() * players(state, expr_parser) - ws(); + // FIXME: Hacky sync token can cause parser crashes + (seq(b"<<") * parse_or_sync(state, inner, b'>', "Invalid player coalition") - sym(b'>')) + .map(|res| res.unwrap_or_else(|_| Vec::new())) } /// Parses a despite-coalition (path qualifier) @@ -102,14 +116,17 @@ fn despite_coalition<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, ) -> Parser<'a, u8, Vec> { - seq(b"[[") * ws() * players(state, expr_parser) - ws() - seq(b"]]") + let inner = ws() * players(state, expr_parser) - ws(); + // FIXME: Hacky sync token can cause parser crashes + (seq(b"[[") * parse_or_sync(state, inner, b']', "Invalid player coalition") - sym(b']')) + .map(|res| res.unwrap_or_else(|_| Vec::new())) } /// Parses an path formula starting with the NEXT (X) operator fn next<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { +) -> Parser<'a, u8, AstAtl> { sym(b'X') * ws() * call2(&conjunction, state, expr_parser) } @@ -117,7 +134,7 @@ fn next<'a, A: AtlExpressionParser>( fn until<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, (Phi, Phi)> { +) -> Parser<'a, u8, (AstAtl, AstAtl)> { sym(b'(') * ws() * call2(&conjunction, state, expr_parser) - ws() - sym(b'U') - ws() + call2(&conjunction, state, expr_parser) - ws() @@ -128,7 +145,7 @@ fn until<'a, A: AtlExpressionParser>( fn eventually<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { +) -> Parser<'a, u8, AstAtl> { sym(b'F') * ws() * call2(&conjunction, state, expr_parser) } @@ -136,7 +153,7 @@ fn eventually<'a, A: AtlExpressionParser>( fn invariant<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { +) -> Parser<'a, u8, AstAtl> { sym(b'G') * ws() * call2(&conjunction, state, expr_parser) } @@ -144,130 +161,149 @@ fn invariant<'a, A: AtlExpressionParser>( fn enforce_next<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - (enforce_coalition(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { - Phi::EnforceNext { - players, - formula: Arc::new(phi), - } - }) +) -> Parser<'a, u8, AstAtl> { + (enforce_coalition(state, expr_parser) - ws() + next(state, expr_parser)) + .with_span() + .map(|(span, (players, expr))| { + AstAtl::new( + span, + AstAtlKind::EnforceNext { + players, + expr: expr.into(), + }, + ) + }) } /// Parses an ENFORCE-UNTIL ATL formula fn enforce_until<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - (enforce_coalition(state, expr_parser) - ws() + until(state, expr_parser)).map( - |(players, (l, r))| Phi::EnforceUntil { - players, - pre: Arc::new(l), - until: Arc::new(r), - }, - ) +) -> Parser<'a, u8, AstAtl> { + (enforce_coalition(state, expr_parser) - ws() + until(state, expr_parser)) + .with_span() + .map(|(span, (players, (l, r)))| { + AstAtl::new( + span, + AstAtlKind::EnforceUntil { + players, + pre_expr: Arc::new(l), + end_expr: Arc::new(r), + }, + ) + }) } /// Parses an ENFORCE-EVENTUALLY ATL formula fn enforce_eventually<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - (enforce_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)).map( - |(players, phi)| Phi::EnforceEventually { - players, - formula: Arc::new(phi), - }, - ) +) -> Parser<'a, u8, AstAtl> { + (enforce_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)) + .with_span() + .map(|(span, (players, expr))| { + AstAtl::new( + span, + AstAtlKind::EnforceEventually { + players, + expr: Arc::new(expr), + }, + ) + }) } /// Parses an ENFORCE-INVARIANT ATL formula fn enforce_invariant<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - (enforce_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)).map( - |(players, phi)| Phi::EnforceInvariant { +) -> Parser<'a, u8, AstAtl> { + (enforce_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)) + .with_span() + .map(|(span, (players, expr))| AstAtl::new(span, AstAtlKind::EnforceInvariantly { players, - formula: Arc::new(phi), - }, - ) + expr: Arc::new(expr), + })) } /// Parses an DESPITE-NEXT ATL formula fn despite_next<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - (despite_coalition(state, expr_parser) - ws() + next(state, expr_parser)).map(|(players, phi)| { - Phi::DespiteNext { +) -> Parser<'a, u8, AstAtl> { + (despite_coalition(state, expr_parser) - ws() + next(state, expr_parser)) + .with_span() + .map(|(span, (players, phi))| AstAtl::new(span, AstAtlKind::DespiteNext { players, - formula: Arc::new(phi), - } - }) + expr: Arc::new(phi), + })) } /// Parses an DESPITE-UNTIL ATL formula fn despite_until<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - (despite_coalition(state, expr_parser) - ws() + until(state, expr_parser)).map( - |(players, (l, r))| Phi::DespiteUntil { +) -> Parser<'a, u8, AstAtl> { + (despite_coalition(state, expr_parser) - ws() + until(state, expr_parser)) + .with_span() + .map(|(span, (players, (l, r)))| AstAtl::new(span, AstAtlKind::DespiteUntil { players, - pre: Arc::new(l), - until: Arc::new(r), - }, - ) + pre_expr: Arc::new(l), + end_expr: Arc::new(r), + })) } /// Parses an DESPITE-EVENTUALLY ATL formula fn despite_eventually<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - (despite_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)).map( - |(players, phi)| Phi::DespiteEventually { +) -> Parser<'a, u8, AstAtl> { + (despite_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)) + .with_span() + .map(|(span, (players, phi))| AstAtl::new(span, AstAtlKind::DespiteEventually { players, - formula: Arc::new(phi), - }, - ) + expr: Arc::new(phi), + })) } /// Parses an DESPITE-INVARIANT ATL formula fn despite_invariant<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - (despite_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)).map( - |(players, phi)| Phi::DespiteInvariant { +) -> Parser<'a, u8, AstAtl> { + (despite_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)) + .with_span() + .map(|(span, (players, phi))| AstAtl::new(span, AstAtlKind::DespiteInvariantly { players, - formula: Arc::new(phi), - }, - ) + expr: Arc::new(phi), + })) } /// Parses a proposition using the given [ATLExpressionParser]. fn proposition<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - expr_parser.proposition_parser(state).map(Phi::Proposition) +) -> Parser<'a, u8, AstAtl> { + expr_parser + .proposition_parser(state) + .with_span() + .map(|(span, p)| AstAtl::new(span, AstAtlKind::Proposition(p))) } /// Parses a negated ATL formula fn not<'a, A: AtlExpressionParser>( state: &'a ParseState, expr_parser: &'a A, -) -> Parser<'a, u8, Phi> { - (sym(b'!') * ws() * call2(&primary, state, expr_parser)).map(|phi| Phi::Not(Arc::new(phi))) +) -> Parser<'a, u8, AstAtl> { + (sym(b'!') * ws() * call2(&primary, state, expr_parser)) + .with_span() + .map(|(span, expr)| AstAtl::new(span, AstAtlKind::Not(Arc::new(expr)))) } /// Parses a boolean, either full uppercase or full lowercase -fn boolean<'a>() -> Parser<'a, u8, Phi> { - seq(b"true").map(|_| Phi::True) - | seq(b"TRUE").map(|_| Phi::True) - | seq(b"false").map(|_| Phi::False) - | seq(b"FALSE").map(|_| Phi::False) +fn boolean<'a>() -> Parser<'a, u8, AstAtl> { + let tru = (seq(b"true") | seq(b"TRUE")).map(|_| AstAtlKind::True); + let fal = (seq(b"false") | seq(b"FALSE")).map(|_| AstAtlKind::False); + (tru | fal).with_span().map(|(span, kind)| AstAtl::new(span, kind)) } /// Parses a comma-separated list of players using the given [ATLExpressionParser]. @@ -318,10 +354,10 @@ mod test { use pom::parser::Parser; use crate::atl::parser::{ - boolean, conjunction, despite_eventually, despite_invariant, despite_next, despite_coalition, - despite_until, disjunction, enforce_eventually, enforce_invariant, enforce_next, - enforce_coalition, enforce_until, eventually, invariant, next, not, number, paren, - proposition, until, AtlExpressionParser, + boolean, conjunction, despite_coalition, despite_eventually, despite_invariant, + despite_next, despite_until, disjunction, enforce_coalition, enforce_eventually, + enforce_invariant, enforce_next, enforce_until, eventually, invariant, next, not, number, + paren, proposition, until, AtlExpressionParser, }; use crate::atl::{parse_phi, Phi}; use crate::game_structure::lcgs::ir::intermediate::IntermediateLcgs; diff --git a/cgaal-engine/src/game_structure/lcgs/parse.rs b/cgaal-engine/src/game_structure/lcgs/parse.rs index 3ee37c0c2..1dbbdaafd 100644 --- a/cgaal-engine/src/game_structure/lcgs/parse.rs +++ b/cgaal-engine/src/game_structure/lcgs/parse.rs @@ -30,27 +30,6 @@ const RESERVED_KEYWORDS: [&str; 10] = [ "max", ]; -/// A `Span` describes the position of a slice of text in the original program. -/// Usually used to describe what text an AST node was created from. -#[derive(Eq, PartialEq, Debug, Copy, Clone)] -struct Span { - begin: usize, - end: usize, -} - -trait WithSpan<'a, I, O: 'a> { - fn with_span(self) -> Parser<'a, I, (Span, O)>; -} - -impl<'a, I, O: 'a> WithSpan<'a, I, O> for Parser<'a, I, O> { - /// Make the parser note the beginning and end position and - /// include it in the result as a `Span` - fn with_span(self) -> Parser<'a, I, (Span, O)> { - (empty().pos() + self + empty().pos()) - .map(|((begin, item), end)| (Span { begin, end }, item)) - } -} - trait SemiTerminated<'a, I, O: 'a> { fn with_semi(self) -> Parser<'a, I, O>; } @@ -100,8 +79,7 @@ fn number<'a>() -> Parser<'a, u8, Expr> { .convert(str::from_utf8) .convert(i32::from_str); parsed - .with_span() - .map(|(_span, v)| Expr { kind: Number(v) }) + .map(|v| Expr { kind: Number(v) }) .name("number") } @@ -136,8 +114,7 @@ fn identifier<'a>() -> Parser<'a, u8, Identifier> { fn owned_identifier<'a>() -> Parser<'a, u8, Identifier> { let identifier = (name() - sym(b'.')).opt() + name(); identifier - .with_span() - .map(|(_span, (owner, name))| Identifier::OptionalOwner { owner, name }) + .map(|(owner, name)| Identifier::OptionalOwner { owner, name }) .name("owned identifier") } diff --git a/cgaal-engine/src/parsing/errors.rs b/cgaal-engine/src/parsing/errors.rs index f6e09bc7b..04207817c 100644 --- a/cgaal-engine/src/parsing/errors.rs +++ b/cgaal-engine/src/parsing/errors.rs @@ -23,6 +23,10 @@ impl ErrorLog { self.errors.push(ErrorEntry::msg_only(msg)); } + pub fn len(&self) -> usize { + self.errors.len() + } + pub fn has_errors(&self) -> bool { !self.errors.is_empty() } @@ -102,7 +106,7 @@ impl ErrorEntry { #[cfg(test)] mod tests { - use crate::parsing::errors::{ErrorEntry, ErrorLog}; + use crate::parsing::errors::ErrorLog; use crate::parsing::span::Span; #[test] diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 5a6789006..51e1f04fe 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -3,14 +3,16 @@ mod lexer; pub mod span; mod token; +use crate::parsing::errors::ErrorLog; use crate::parsing::span::Span; use pom::parser::*; use std::cell::RefCell; use std::fmt::Display; +use crate::atl::{identifier, number}; /// A trait that allows us to extent parser with a helper function that extracts the span of /// the parsed piece of text -trait WithSpan<'a, I, O: 'a> { +pub trait WithSpan<'a, I, O: 'a> { fn with_span(self) -> Parser<'a, I, (Span, O)>; } @@ -23,36 +25,14 @@ impl<'a, I, O: 'a> WithSpan<'a, I, O> for Parser<'a, I, O> { } } -/// An error containing problematic text span and an error message -#[derive(Debug)] -pub struct ParseError { - span: Span, - msg: String, -} - /// The `ParseState` is a struct carried around during parsing containing extra information /// about the state of the parser, such as the errors encountered #[derive(Debug, Default)] pub struct ParseState { - errors: RefCell>, + pub errors: RefCell, sync_tokens: RefCell>, } -impl ParseState { - /// Saves an error to display when parsing is done - pub fn report_err(&self, err: ParseError) { - self.errors.borrow_mut().push(err) - } - - pub fn has_errors(&self) -> bool { - !self.errors.borrow().is_empty() - } - - pub fn errors_as_str(&self, input: &str) -> String { - unimplemented!("Nicely formatted errors") - } -} - /// Create a lazy parser used for recursive definitions. /// This function is similar to `call` but allows passing one argument to the parser function. #[allow(unused)] @@ -86,13 +66,11 @@ pub(crate) fn parse_or_skip<'a, I: Eq + Display, O: 'a>( move |input: &'_ [I], start: usize| match (parser.method)(input, start) { Ok((out, pos)) => Ok((Some(out), pos)), Err(_) => { - parse_state.report_err(ParseError { - span: Span { - begin: start, - end: start + 1, - }, - msg: err_msg.to_string(), - }); + let span = Span::new(start, start + 1); + parse_state + .errors + .borrow_mut() + .log(span, err_msg.to_string()); Ok((None, start)) } }, @@ -112,44 +90,61 @@ pub(crate) fn parse_or_abort<'a, I: Eq + Display, O: 'a>( move |input: &'_ [I], start: usize| match (parser.method)(input, start) { Ok((out, pos)) => Ok((Some(out), pos)), Err(_) => { - parse_state.report_err(ParseError { - span: Span { - begin: start, - end: input.len(), - }, - msg: err_msg.to_string(), - }); + let span = Span::new(start, input.len()); + parse_state + .errors + .borrow_mut() + .log(span, err_msg.to_string()); Ok((None, input.len())) } }, ) } +pub(crate) fn recover<'a, I: Eq, O: 'a>(state: &'a ParseState) -> Parser<'a, I, Result<(), Span>> { + Parser::new(|input: &'_ [I], start: usize| { + let sync_pos = input[start..] + .iter() + .position(|c| state.sync_tokens.borrow().contains(c)) + .map(|p| p + start) + .unwrap_or(input.len()); + let span = Span::new(start, sync_pos); + Ok((Err(span), sync_pos)) + }) +} + +pub(crate) fn unexpected_abort<'a, O: 'a + Clone>(state: &'a ParseState, res: O) -> Parser<'a, u8, O> { + (identifier().discard() | number().discard() | any().discard()).with_span() + .map(move |(span, _)| { + state.errors.borrow_mut().log(span, "Unexpected token".to_string()); + res.clone() + }) - any().repeat(..) +} + /// Creates a parser that will run the given parser and then consume the synchronization token. /// If the given parser fails to parse, an error is reported using the given error message, /// and parsing will continue at the next occurrence of the synchronization token. +/// The resulting Err will contained the skipped span. #[allow(unused)] pub(crate) fn parse_or_sync<'a, I: Copy + Eq + Display, O: 'a>( parse_state: &'a ParseState, parser: Parser<'a, I, O>, sync: I, err_msg: &'a str, -) -> Parser<'a, I, Option> { +) -> Parser<'a, I, Result> { Parser::new(move |input: &'_ [I], start: usize| { parse_state.sync_tokens.borrow_mut().push(sync); let res = match (parser.method)(input, start) { Ok((out, pos)) => { if input.get(pos) == Some(&sync) { - Ok((Some(out), pos + 1)) + Ok((Ok(out), pos + 1)) } else { - parse_state.report_err(ParseError { - span: Span { - begin: pos, - end: pos + 1, - }, - msg: format!("Missing '{}'", sync), - }); - Ok((Some(out), pos)) + let span = Span::new(pos, pos + 1); + parse_state + .errors + .borrow_mut() + .log(span, format!("Missing '{}'", sync)); + Ok((Ok(out), pos)) } } Err(_) => { @@ -160,16 +155,14 @@ pub(crate) fn parse_or_sync<'a, I: Copy + Eq + Display, O: 'a>( .or(input[start..] .iter() .position(|c| parse_state.sync_tokens.borrow().contains(c)) - .map(|p| p + start) - ).unwrap_or(input.len()); - parse_state.report_err(ParseError { - span: Span { - begin: start, - end: sync_pos, - }, - msg: err_msg.to_string(), - }); - Ok((None, sync_pos)) + .map(|p| p + start)) + .unwrap_or(input.len()); + let span = Span::new(start, sync_pos); + parse_state + .errors + .borrow_mut() + .log(span, err_msg.to_string()); + Ok((Err(span), sync_pos)) } }; parse_state.sync_tokens.borrow_mut().pop(); diff --git a/cgaal-engine/src/parsing/span.rs b/cgaal-engine/src/parsing/span.rs index 1bdcec0ff..d36f34b79 100644 --- a/cgaal-engine/src/parsing/span.rs +++ b/cgaal-engine/src/parsing/span.rs @@ -1,6 +1,6 @@ use std::cmp::{max, min}; use std::fmt::{Display, Formatter}; -use std::ops::Range; +use std::ops::{Add, Range}; /// A `Span` describes the position of a slice of text in the original program. /// Usually used to describe what text an AST node was created from. @@ -47,6 +47,14 @@ impl From> for Span { } } +impl Add for Span { + type Output = Span; + + fn add(self, rhs: Self) -> Self::Output { + self.merge(rhs) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct LineColumn { pub line: usize, diff --git a/lcgs-examples/robot_grid/test.atl b/lcgs-examples/robot_grid/test.atl new file mode 100644 index 000000000..5d49abc99 --- /dev/null +++ b/lcgs-examples/robot_grid/test.atl @@ -0,0 +1 @@ +<< p1 >> F true \ No newline at end of file From dbbe06614065f8bd735e861fc1ef01d7a06bff91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Thu, 31 Aug 2023 14:50:37 +0200 Subject: [PATCH 12/38] Add Parser, almost done for ATL --- cgaal-engine/src/atl/ast.rs | 55 - cgaal-engine/src/atl/mod.rs | 1 - cgaal-engine/src/atl/parser.rs | 1090 +---------------- cgaal-engine/src/game_structure/eager.rs | 22 - .../game_structure/lcgs/ir/intermediate.rs | 61 - cgaal-engine/src/parsing/ast.rs | 115 ++ cgaal-engine/src/parsing/lexer.rs | 2 +- cgaal-engine/src/parsing/mod.rs | 282 +---- cgaal-engine/src/parsing/parser.rs | 201 +++ cgaal-engine/src/parsing/token.rs | 4 +- 10 files changed, 326 insertions(+), 1507 deletions(-) delete mode 100644 cgaal-engine/src/atl/ast.rs create mode 100644 cgaal-engine/src/parsing/ast.rs create mode 100644 cgaal-engine/src/parsing/parser.rs diff --git a/cgaal-engine/src/atl/ast.rs b/cgaal-engine/src/atl/ast.rs deleted file mode 100644 index 1fc99b774..000000000 --- a/cgaal-engine/src/atl/ast.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::sync::Arc; -use crate::atl::Phi; -use crate::game_structure::{Player, Proposition}; -use crate::parsing::span::Span; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AstAtl { - pub span: Span, - pub kind: AstAtlKind, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum AstAtlKind { - True, - False, - Proposition(Proposition), - Not(Arc), - And(Arc, Arc), - Or(Arc, Arc), - DespiteNext { players: Vec, expr: Arc }, - EnforceNext { players: Vec, expr: Arc }, - DespiteUntil { players: Vec, pre_expr: Arc, end_expr: Arc }, - EnforceUntil { players: Vec, pre_expr: Arc, end_expr: Arc }, - DespiteInvariantly { players: Vec, expr: Arc }, - EnforceInvariantly { players: Vec, expr: Arc }, - DespiteEventually { players: Vec, expr: Arc }, - EnforceEventually { players: Vec, expr: Arc }, - Error, -} - -impl AstAtl { - pub fn new(span: Span, kind: AstAtlKind) -> Self { - AstAtl { span, kind } - } - - pub fn convert(&self) -> Phi { - match &self.kind { - AstAtlKind::True => Phi::True, - AstAtlKind::False => Phi::False, - AstAtlKind::Proposition(p) => Phi::Proposition(*p), - AstAtlKind::Not(expr) => Phi::Not(Arc::new(expr.convert())), - AstAtlKind::And(lhs, rhs) => Phi::And(Arc::new(lhs.convert()), Arc::new(rhs.convert())), - AstAtlKind::Or(lhs, rhs) => Phi::Or(Arc::new(lhs.convert()), Arc::new(rhs.convert())), - AstAtlKind::DespiteNext { players, expr } => Phi::DespiteNext { players: players.clone(), formula: Arc::new(expr.convert()) }, - AstAtlKind::EnforceNext { players, expr } => Phi::EnforceNext { players: players.clone(), formula: Arc::new(expr.convert()) }, - AstAtlKind::DespiteUntil { players, pre_expr, end_expr } => Phi::DespiteUntil { players: players.clone(), pre: Arc::new(pre_expr.convert()), until: Arc::new(end_expr.convert()) }, - AstAtlKind::EnforceUntil { players, pre_expr, end_expr } => Phi::EnforceUntil { players: players.clone(), pre: Arc::new(pre_expr.convert()), until: Arc::new(end_expr.convert()) }, - AstAtlKind::DespiteInvariantly { players, expr } => Phi::DespiteInvariant { players: players.clone(), formula: Arc::new(expr.convert()) }, - AstAtlKind::EnforceInvariantly { players, expr } => Phi::EnforceInvariant { players: players.clone(), formula: Arc::new(expr.convert()) }, - AstAtlKind::DespiteEventually { players, expr } => Phi::DespiteEventually { players: players.clone(), formula: Arc::new(expr.convert()) }, - AstAtlKind::EnforceEventually { players, expr } => Phi::EnforceEventually { players: players.clone(), formula: Arc::new(expr.convert()) }, - AstAtlKind::Error => panic!("Error in AST"), - } - } -} \ No newline at end of file diff --git a/cgaal-engine/src/atl/mod.rs b/cgaal-engine/src/atl/mod.rs index bc58f0905..0a27a73c3 100644 --- a/cgaal-engine/src/atl/mod.rs +++ b/cgaal-engine/src/atl/mod.rs @@ -10,7 +10,6 @@ use crate::game_structure::{GameStructure, Player, Proposition}; pub mod game_formula; pub mod parser; -pub mod ast; /// Alternating-time Temporal Logic formula #[derive(Hash, Eq, PartialEq, Clone, Debug, Deserialize, Serialize)] diff --git a/cgaal-engine/src/atl/parser.rs b/cgaal-engine/src/atl/parser.rs index b6e25e0b9..7d08513c3 100644 --- a/cgaal-engine/src/atl/parser.rs +++ b/cgaal-engine/src/atl/parser.rs @@ -1,1090 +1,10 @@ -use std::str::{self, FromStr}; -use std::sync::Arc; +use std::str::{self}; -use crate::atl::ast::{AstAtl, AstAtlKind}; use crate::atl::Phi; -use pom::parser::{end, Parser}; -use pom::parser::{list, one_of, seq, sym}; - -use crate::game_structure::{Player, Proposition}; -use crate::parsing::{call2, parse_or_sync, ParseState, WithSpan}; /// Parse an ATL formula -pub fn parse_phi<'a, 'b: 'a, A: AtlExpressionParser>( - expr_parser: &'b A, - input: &'a str, +pub fn parse_phi( + _input: &str, ) -> Result { - let state = ParseState::default(); - println!("Parsing ..."); - let formula = ws() * conjunction(&state, expr_parser) - ws() - end(); - let phi = formula - .parse(input.as_bytes()) - .expect("Parser may not fail"); - if state.errors.borrow().has_errors() { - let mut error_report = String::new(); - state - .errors - .borrow() - .write_detailed(input, &mut error_report) - .unwrap(); - Err(error_report) - } else { - Ok(phi.convert()) - } -} - -/// Allows a CGS model to define custom player and proposition expressions. For instance, -/// in LCGS we want to be able to write "p2" as a player and "p2.alive" as a proposition, while -/// in json, players and propositions are numbers. -pub trait AtlExpressionParser { - /// A parser that parses a player name - fn player_parser(&self, state: &ParseState) -> Parser; - /// A parser that parses a proposition name - fn proposition_parser(&self, state: &ParseState) -> Parser; -} - -/// Whitespace -fn ws<'a>() -> Parser<'a, u8, ()> { - one_of(b" \t\r\n").repeat(0..).discard() -} - -/// Parses an ATL formula (without whitespace around it) -pub(crate) fn conjunction<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - let conjunc = (disjunction(state, expr_parser) - ws() - sym(b'&') - ws() - + call2(&conjunction, state, expr_parser)) - .map(|(lhs, rhs)| AstAtl::new(lhs.span + rhs.span, AstAtlKind::And(lhs.into(), rhs.into()))); - conjunc | disjunction(state, expr_parser) -} - -/// Parses an ATL term (Can't contain AND and without whitespace around it) -fn disjunction<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - let disjunc = (primary(state, expr_parser) - ws() - sym(b'|') - ws() - + call2(&disjunction, state, expr_parser)) - .map(|(lhs, rhs)| AstAtl::new(lhs.span + rhs.span, AstAtlKind::Or(lhs.into(), rhs.into()))); - disjunc | primary(state, expr_parser) -} - -/// Parses a primary ATL formula (no ANDs or ORs) -fn primary<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - paren(state, expr_parser) - | boolean() - | proposition(state, expr_parser) - | not(state, expr_parser) - | enforce_next(state, expr_parser) - | enforce_until(state, expr_parser) - | enforce_eventually(state, expr_parser) - | enforce_invariant(state, expr_parser) - | despite_next(state, expr_parser) - | despite_until(state, expr_parser) - | despite_eventually(state, expr_parser) - | despite_invariant(state, expr_parser) -} - -/// Parses an ATL formula in parenthesis -fn paren<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - let inner = ws() * call2(&conjunction, state, expr_parser) - ws(); - sym(b'(') - * parse_or_sync(state, inner, b')', "Invalid ATL formula") - .map(|res| res.unwrap_or_else(|span| AstAtl::new(span, AstAtlKind::Error))) -} - -/// Parses an enforce-coalition (path qualifier) -fn enforce_coalition<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, Vec> { - let inner = ws() * players(state, expr_parser) - ws(); - // FIXME: Hacky sync token can cause parser crashes - (seq(b"<<") * parse_or_sync(state, inner, b'>', "Invalid player coalition") - sym(b'>')) - .map(|res| res.unwrap_or_else(|_| Vec::new())) -} - -/// Parses a despite-coalition (path qualifier) -fn despite_coalition<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, Vec> { - let inner = ws() * players(state, expr_parser) - ws(); - // FIXME: Hacky sync token can cause parser crashes - (seq(b"[[") * parse_or_sync(state, inner, b']', "Invalid player coalition") - sym(b']')) - .map(|res| res.unwrap_or_else(|_| Vec::new())) -} - -/// Parses an path formula starting with the NEXT (X) operator -fn next<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - sym(b'X') * ws() * call2(&conjunction, state, expr_parser) -} - -/// Parses an path formula with the UNTIL operator -fn until<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, (AstAtl, AstAtl)> { - sym(b'(') * ws() * call2(&conjunction, state, expr_parser) - ws() - sym(b'U') - ws() - + call2(&conjunction, state, expr_parser) - - ws() - - sym(b')') -} - -/// Parses an path formula starting with the EVENTUALLY (F/finally) operator -fn eventually<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - sym(b'F') * ws() * call2(&conjunction, state, expr_parser) -} - -/// Parses an path formula starting with the INVARIANT (G/global) operator -fn invariant<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - sym(b'G') * ws() * call2(&conjunction, state, expr_parser) -} - -/// Parses an ENFORCE-NEXT ATL formula -fn enforce_next<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - (enforce_coalition(state, expr_parser) - ws() + next(state, expr_parser)) - .with_span() - .map(|(span, (players, expr))| { - AstAtl::new( - span, - AstAtlKind::EnforceNext { - players, - expr: expr.into(), - }, - ) - }) -} - -/// Parses an ENFORCE-UNTIL ATL formula -fn enforce_until<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - (enforce_coalition(state, expr_parser) - ws() + until(state, expr_parser)) - .with_span() - .map(|(span, (players, (l, r)))| { - AstAtl::new( - span, - AstAtlKind::EnforceUntil { - players, - pre_expr: Arc::new(l), - end_expr: Arc::new(r), - }, - ) - }) -} - -/// Parses an ENFORCE-EVENTUALLY ATL formula -fn enforce_eventually<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - (enforce_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)) - .with_span() - .map(|(span, (players, expr))| { - AstAtl::new( - span, - AstAtlKind::EnforceEventually { - players, - expr: Arc::new(expr), - }, - ) - }) -} - -/// Parses an ENFORCE-INVARIANT ATL formula -fn enforce_invariant<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - (enforce_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)) - .with_span() - .map(|(span, (players, expr))| AstAtl::new(span, AstAtlKind::EnforceInvariantly { - players, - expr: Arc::new(expr), - })) -} - -/// Parses an DESPITE-NEXT ATL formula -fn despite_next<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - (despite_coalition(state, expr_parser) - ws() + next(state, expr_parser)) - .with_span() - .map(|(span, (players, phi))| AstAtl::new(span, AstAtlKind::DespiteNext { - players, - expr: Arc::new(phi), - })) -} - -/// Parses an DESPITE-UNTIL ATL formula -fn despite_until<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - (despite_coalition(state, expr_parser) - ws() + until(state, expr_parser)) - .with_span() - .map(|(span, (players, (l, r)))| AstAtl::new(span, AstAtlKind::DespiteUntil { - players, - pre_expr: Arc::new(l), - end_expr: Arc::new(r), - })) -} - -/// Parses an DESPITE-EVENTUALLY ATL formula -fn despite_eventually<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - (despite_coalition(state, expr_parser) - ws() + eventually(state, expr_parser)) - .with_span() - .map(|(span, (players, phi))| AstAtl::new(span, AstAtlKind::DespiteEventually { - players, - expr: Arc::new(phi), - })) -} - -/// Parses an DESPITE-INVARIANT ATL formula -fn despite_invariant<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - (despite_coalition(state, expr_parser) - ws() + invariant(state, expr_parser)) - .with_span() - .map(|(span, (players, phi))| AstAtl::new(span, AstAtlKind::DespiteInvariantly { - players, - expr: Arc::new(phi), - })) -} - -/// Parses a proposition using the given [ATLExpressionParser]. -fn proposition<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - expr_parser - .proposition_parser(state) - .with_span() - .map(|(span, p)| AstAtl::new(span, AstAtlKind::Proposition(p))) -} - -/// Parses a negated ATL formula -fn not<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, AstAtl> { - (sym(b'!') * ws() * call2(&primary, state, expr_parser)) - .with_span() - .map(|(span, expr)| AstAtl::new(span, AstAtlKind::Not(Arc::new(expr)))) -} - -/// Parses a boolean, either full uppercase or full lowercase -fn boolean<'a>() -> Parser<'a, u8, AstAtl> { - let tru = (seq(b"true") | seq(b"TRUE")).map(|_| AstAtlKind::True); - let fal = (seq(b"false") | seq(b"FALSE")).map(|_| AstAtlKind::False); - (tru | fal).with_span().map(|(span, kind)| AstAtl::new(span, kind)) -} - -/// Parses a comma-separated list of players using the given [ATLExpressionParser]. -fn players<'a, A: AtlExpressionParser>( - state: &'a ParseState, - expr_parser: &'a A, -) -> Parser<'a, u8, Vec> { - list(expr_parser.player_parser(state), ws() * sym(b',') * ws()) -} - -/// Parses a letter -fn alpha<'a>() -> Parser<'a, u8, u8> { - one_of(b"abcdefghijklmnopqrstuvwxyz") | one_of(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ") -} - -/// Parses an identifier, starting with a letter but otherwise consisting of letters, numbers, -/// and underscores -pub fn identifier<'a>() -> Parser<'a, u8, String> { - let chars = alpha() - (alpha() | digit() | sym(b'_')).repeat(0..); - chars.collect().convert(|s| String::from_utf8(s.to_vec())) -} - -/// Parser that parses a single digit 0-9 -#[inline] -pub fn digit<'a>() -> Parser<'a, u8, u8> { - one_of(b"0123456789") -} - -/// Parser that parses a single digit 1-9 (not 0) -#[inline] -pub fn non_0_digit<'a>() -> Parser<'a, u8, u8> { - one_of(b"123456789") -} - -/// Parser that parses a typical positive integer number -pub fn number<'a>() -> Parser<'a, u8, usize> { - let integer = (non_0_digit() - digit().repeat(0..)) | sym(b'0'); - integer - .collect() - .convert(str::from_utf8) - .convert(usize::from_str) -} - -#[cfg(test)] -mod test { - use std::sync::Arc; - - use pom::parser::Parser; - - use crate::atl::parser::{ - boolean, conjunction, despite_coalition, despite_eventually, despite_invariant, - despite_next, despite_until, disjunction, enforce_coalition, enforce_eventually, - enforce_invariant, enforce_next, enforce_until, eventually, invariant, next, not, number, - paren, proposition, until, AtlExpressionParser, - }; - use crate::atl::{parse_phi, Phi}; - use crate::game_structure::lcgs::ir::intermediate::IntermediateLcgs; - use crate::game_structure::lcgs::parse::parse_lcgs; - use crate::game_structure::{Player, Proposition}; - use crate::parsing::ParseState; - - struct TestModel; - - impl AtlExpressionParser for TestModel { - fn player_parser(&self, state: &ParseState) -> Parser { - number() - } - - fn proposition_parser(&self, state: &ParseState) -> Parser { - number() - } - } - - #[test] - fn paren_1() { - let state = ParseState::::default(); - assert!(!state.has_errors()); - assert_eq!(paren(&state, &TestModel).parse(b"(true)"), Ok(Phi::True)); - } - - /// Test for a single player - #[test] - fn enforce_players_1() { - let state = ParseState::::default(); - assert_eq!( - enforce_coalition(&state, &TestModel).parse(b"<<1>>"), - Ok(vec![1usize]) - ); - assert!(!state.has_errors()); - } - - /// Test for two players - #[test] - fn enforce_players_2() { - let state = ParseState::::default(); - assert_eq!( - enforce_coalition(&state, &TestModel).parse(b"<<4,9>>"), - Ok(vec![4usize, 9usize]) - ); - assert!(!state.has_errors()); - } - - /// Test for three players - #[test] - fn enforce_players_3() { - let state = ParseState::::default(); - assert_eq!( - enforce_coalition(&state, &TestModel).parse(b"<<203,23,4>>"), - Ok(vec![203usize, 23usize, 4usize]) - ); - assert!(!state.has_errors()); - } - - /// The list of players is allowed to have whitespace after the separator - #[test] - fn enforce_players_4() { - let state = ParseState::::default(); - assert_eq!( - enforce_coalition(&state, &TestModel).parse(b"<<203, 23>>"), - Ok(vec![203usize, 23usize]) - ); - assert!(!state.has_errors()); - } - - /// Test for no players, should be valid - #[test] - fn enforce_players_5() { - let state = ParseState::::default(); - assert_eq!( - enforce_coalition(&state, &TestModel).parse(b"<<>>"), - Ok(vec![]) - ); - assert!(!state.has_errors()); - } - - /// Test for a single player - #[test] - fn despite_players_1() { - let state = ParseState::::default(); - assert_eq!( - despite_coalition(&state, &TestModel).parse(b"[[1]]"), - Ok(vec![1usize]) - ); - assert!(!state.has_errors()); - } - - /// Test for two players - #[test] - fn despite_players_2() { - let state = ParseState::::default(); - assert_eq!( - despite_coalition(&state, &TestModel).parse(b"[[4,9]]"), - Ok(vec![4usize, 9usize]) - ); - assert!(!state.has_errors()); - } - - /// Test for three players - #[test] - fn despite_players_3() { - let state = ParseState::::default(); - assert_eq!( - despite_coalition(&state, &TestModel).parse(b"[[203,23,4]]"), - Ok(vec![203usize, 23usize, 4usize]) - ); - assert!(!state.has_errors()); - } - - /// The list of players is allowed to have whitespace after the seperator - #[test] - fn despite_players_4() { - let state = ParseState::::default(); - assert_eq!( - despite_coalition(&state, &TestModel).parse(b"[[203, 23]]"), - Ok(vec![203usize, 23usize]) - ); - assert!(!state.has_errors()); - } - - /// Test for no players, should be valid - #[test] - fn despite_players_5() { - let state = ParseState::::default(); - assert_eq!( - despite_coalition(&state, &TestModel).parse(b"[[]]"), - Ok(vec![]) - ); - assert!(!state.has_errors()); - } - - #[test] - fn next_1() { - let state = ParseState::::default(); - assert_eq!(next(&state, &TestModel).parse(b"X true"), Ok(Phi::True)); - assert!(!state.has_errors()); - } - - #[test] - fn next_2() { - let state = ParseState::::default(); - assert_eq!(next(&state, &TestModel).parse(b"Xtrue"), Ok(Phi::True)); - assert!(!state.has_errors()); - } - - #[test] - fn until_1() { - let state = ParseState::::default(); - assert_eq!( - until(&state, &TestModel).parse(b"( true U true )"), - Ok((Phi::True, Phi::True)) - ); - assert!(!state.has_errors()); - } - - #[test] - fn until_2() { - let state = ParseState::::default(); - assert_eq!( - until(&state, &TestModel).parse(b"(trueUtrue)"), - Ok((Phi::True, Phi::True)) - ); - assert!(!state.has_errors()); - } - - #[test] - fn eventually_1() { - let state = ParseState::::default(); - assert_eq!( - eventually(&state, &TestModel).parse(b"F true"), - Ok(Phi::True) - ); - assert!(!state.has_errors()); - } - - #[test] - fn eventually_2() { - let state = ParseState::::default(); - assert_eq!( - eventually(&state, &TestModel).parse(b"Ftrue"), - Ok(Phi::True) - ); - assert!(!state.has_errors()); - } - - #[test] - fn invariant_1() { - let state = ParseState::::default(); - assert_eq!( - invariant(&state, &TestModel).parse(b"G true"), - Ok(Phi::True) - ); - assert!(!state.has_errors()); - } - - #[test] - fn invariant_2() { - let state = ParseState::::default(); - assert_eq!(invariant(&state, &TestModel).parse(b"Gtrue"), Ok(Phi::True)); - assert!(!state.has_errors()); - } - - #[test] - fn enforce_next_1() { - let state = ParseState::::default(); - assert_eq!( - enforce_next(&state, &TestModel).parse(b"<<1 , 2>> X true"), - Ok(Phi::EnforceNext { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn enforce_next_2() { - let state = ParseState::::default(); - assert_eq!( - enforce_next(&state, &TestModel).parse(b"<<1,2>>Xtrue"), - Ok(Phi::EnforceNext { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn enforce_until_1() { - let state = ParseState::::default(); - assert_eq!( - enforce_until(&state, &TestModel).parse(b"<<1 , 2>> ( true U true )"), - Ok(Phi::EnforceUntil { - players: vec![1, 2], - pre: Arc::new(Phi::True), - until: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn enforce_until_2() { - let state = ParseState::::default(); - assert_eq!( - enforce_until(&state, &TestModel).parse(b"<<1,2>>(trueUtrue)"), - Ok(Phi::EnforceUntil { - players: vec![1, 2], - pre: Arc::new(Phi::True), - until: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn enforce_eventually_1() { - let state = ParseState::::default(); - assert_eq!( - enforce_eventually(&state, &TestModel).parse(b"<<1 , 2>> F true"), - Ok(Phi::EnforceEventually { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn enforce_eventually_2() { - let state = ParseState::::default(); - assert_eq!( - enforce_eventually(&state, &TestModel).parse(b"<<1,2>>Ftrue"), - Ok(Phi::EnforceEventually { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn enforce_invariant_1() { - let state = ParseState::::default(); - assert_eq!( - enforce_invariant(&state, &TestModel).parse(b"<<1 , 2>> G true"), - Ok(Phi::EnforceInvariant { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn enforce_invariant_2() { - let state = ParseState::::default(); - assert_eq!( - enforce_invariant(&state, &TestModel).parse(b"<<1,2>>Gtrue"), - Ok(Phi::EnforceInvariant { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn despite_next_1() { - let state = ParseState::::default(); - assert_eq!( - despite_next(&state, &TestModel).parse(b"[[1 , 2]] X true"), - Ok(Phi::DespiteNext { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn despite_next_2() { - let state = ParseState::::default(); - assert_eq!( - despite_next(&state, &TestModel).parse(b"[[1,2]]Xtrue"), - Ok(Phi::DespiteNext { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn despite_until_1() { - let state = ParseState::::default(); - assert_eq!( - despite_until(&state, &TestModel).parse(b"[[1 , 2]] ( true U true )"), - Ok(Phi::DespiteUntil { - players: vec![1, 2], - pre: Arc::new(Phi::True), - until: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn despite_until_2() { - let state = ParseState::::default(); - assert_eq!( - despite_until(&state, &TestModel).parse(b"[[1,2]](trueUtrue)"), - Ok(Phi::DespiteUntil { - players: vec![1, 2], - pre: Arc::new(Phi::True), - until: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn despite_eventually_1() { - let state = ParseState::::default(); - assert_eq!( - despite_eventually(&state, &TestModel).parse(b"[[1 , 2]] F true"), - Ok(Phi::DespiteEventually { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn despite_eventually_2() { - let state = ParseState::::default(); - assert_eq!( - despite_eventually(&state, &TestModel).parse(b"[[1,2]]Ftrue"), - Ok(Phi::DespiteEventually { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn despite_invariant_1() { - let state = ParseState::::default(); - assert_eq!( - despite_invariant(&state, &TestModel).parse(b"[[1 , 2]] G true"), - Ok(Phi::DespiteInvariant { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn despite_invariant_2() { - let state = ParseState::::default(); - assert_eq!( - despite_invariant(&state, &TestModel).parse(b"[[1,2]]Gtrue"), - Ok(Phi::DespiteInvariant { - players: vec![1, 2], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn proposition_1() { - let state = ParseState::::default(); - assert_eq!( - proposition(&state, &TestModel).parse(b"1"), - Ok(Phi::Proposition(1usize)) - ); - assert!(!state.has_errors()); - } - - #[test] - fn proposition_2() { - let state = ParseState::::default(); - assert_eq!( - proposition(&state, &TestModel).parse(b"1432"), - Ok(Phi::Proposition(1432usize)) - ); - assert!(!state.has_errors()); - } - - #[test] - fn proposition_3() { - let state = ParseState::::default(); - assert!(proposition(&state, &TestModel).parse(b"abc").is_err()); - assert!(!state.has_errors()); - } - - #[test] - fn not_1() { - let state = ParseState::::default(); - assert_eq!( - not(&state, &TestModel).parse(b"! true"), - Ok(Phi::Not(Arc::new(Phi::True))) - ); - assert!(!state.has_errors()); - } - - #[test] - fn not_2() { - let state = ParseState::::default(); - assert_eq!( - not(&state, &TestModel).parse(b"!true"), - Ok(Phi::Not(Arc::new(Phi::True))) - ); - assert!(!state.has_errors()); - } - - #[test] - fn and_1() { - let state = ParseState::::default(); - assert_eq!( - conjunction(&state, &TestModel).parse(b"true & false"), - Ok(Phi::And(Arc::new(Phi::True), Arc::new(Phi::False))) - ); - assert!(!state.has_errors()); - } - - #[test] - fn and_2() { - let state = ParseState::::default(); - assert_eq!( - conjunction(&state, &TestModel).parse(b"true&false"), - Ok(Phi::And(Arc::new(Phi::True), Arc::new(Phi::False))) - ); - assert!(!state.has_errors()); - } - - #[test] - fn and_3() { - // Right recursive? - let state = ParseState::::default(); - assert_eq!( - conjunction(&state, &TestModel).parse(b"true&false&true"), - Ok(Phi::And( - Arc::new(Phi::True), - Arc::new(Phi::And(Arc::new(Phi::False), Arc::new(Phi::True))) - )) - ); - assert!(!state.has_errors()); - } - - #[test] - fn or_1() { - let state = ParseState::::default(); - assert_eq!( - disjunction(&state, &TestModel).parse(b"true | false"), - Ok(Phi::Or(Arc::new(Phi::True), Arc::new(Phi::False))) - ); - assert!(!state.has_errors()); - } - - #[test] - fn or_2() { - let state = ParseState::::default(); - assert_eq!( - disjunction(&state, &TestModel).parse(b"true|false"), - Ok(Phi::Or(Arc::new(Phi::True), Arc::new(Phi::False))) - ); - assert!(!state.has_errors()); - } - - #[test] - fn or_3() { - // Right recursive? - let state = ParseState::::default(); - assert_eq!( - disjunction(&state, &TestModel).parse(b"true|false |true"), - Ok(Phi::Or( - Arc::new(Phi::True), - Arc::new(Phi::Or(Arc::new(Phi::False), Arc::new(Phi::True))) - )) - ); - assert!(!state.has_errors()); - } - - #[test] - fn test_and_or_precedence_01() { - let state = ParseState::::default(); - assert_eq!( - conjunction(&state, &TestModel).parse(b"true & false | true | false & true"), - Ok(Phi::And( - Arc::new(Phi::True), - Arc::new(Phi::And( - Arc::new(Phi::Or( - Arc::new(Phi::False), - Arc::new(Phi::Or(Arc::new(Phi::True), Arc::new(Phi::False))) - )), - Arc::new(Phi::True) - )) - )) - ); - assert!(!state.has_errors()); - } - - #[test] - fn bool_true_upper() { - assert_eq!(boolean().parse(b"TRUE"), Ok(Phi::True)) - } - - #[test] - fn bool_true_lower() { - assert_eq!(boolean().parse(b"true"), Ok(Phi::True)) - } - - #[test] - fn bool_false_upper() { - assert_eq!(boolean().parse(b"FALSE"), Ok(Phi::False)) - } - - #[test] - fn bool_false_lower() { - assert_eq!(boolean().parse(b"false"), Ok(Phi::False)) - } - - #[test] - fn test_phi_01() { - let state = ParseState::::default(); - assert_eq!( - conjunction(&state, &TestModel).parse(b"<<0>> F true"), - Ok(Phi::EnforceEventually { - players: vec![0usize], - formula: Arc::new(Phi::True) - }) - ); - assert!(!state.has_errors()); - } - - #[test] - fn general_precedence_01() { - let formula = parse_phi(&TestModel, "<<>> G true & false").unwrap(); - let expected = Phi::EnforceInvariant { - players: vec![], - formula: Arc::new(Phi::And(Arc::new(Phi::True), Arc::new(Phi::False))), - }; - assert_eq!(formula, expected) - } - - #[test] - fn general_precedence_02() { - let formula = parse_phi(&TestModel, "!<<>> F !true & false").unwrap(); - let expected = Phi::Not(Arc::new(Phi::EnforceEventually { - players: vec![], - formula: Arc::new(Phi::And( - Arc::new(Phi::Not(Arc::from(Phi::True))), - Arc::new(Phi::False), - )), - })); - assert_eq!(formula, expected) - } - - #[test] - fn general_precedence_03() { - let formula = parse_phi(&TestModel, "[[]] F false | <<>> G true").unwrap(); - let expected = Phi::DespiteEventually { - players: vec![], - formula: Arc::new(Phi::Or( - Arc::new(Phi::False), - Arc::new(Phi::EnforceInvariant { - players: vec![], - formula: Arc::new(Phi::True), - }), - )), - }; - assert_eq!(formula, expected) - } - - #[test] - fn general_precedence_04() { - let formula = parse_phi(&TestModel, "!false & [[]] F <<>> G false | true").unwrap(); - let expected = Phi::And( - Arc::new(Phi::Not(Arc::new(Phi::False))), - Arc::new(Phi::DespiteEventually { - players: vec![], - formula: Arc::new(Phi::EnforceInvariant { - players: vec![], - formula: Arc::new(Phi::Or(Arc::new(Phi::False), Arc::new(Phi::True))), - }), - }), - ); - assert_eq!(formula, expected) - } - - #[test] - fn test_atl_lcgs_01() { - // Can we parse ATL coalitions that mentions players in an LCGS program - let lcgs_program = " - player p1 = something; - - template something - [wait] 1; - endtemplate - "; - let lcgs = IntermediateLcgs::create(parse_lcgs(lcgs_program).unwrap()).unwrap(); - - let atl_formula = "<>"; - let state = ParseState::::default(); - let phi = enforce_coalition(&state, &lcgs).parse(&atl_formula.as_bytes()); - assert_eq!(phi, Ok(vec![0])); - assert!(!state.has_errors()); - } - - #[test] - fn test_atl_lcgs_02() { - // Can we parse ATL formulas that mentions players in an LCGS program - let lcgs_program = " - player p1 = something; - - template something - [wait] 1; - endtemplate - "; - let lcgs = IntermediateLcgs::create(parse_lcgs(lcgs_program).unwrap()).unwrap(); - - let atl_formula = "<> F true"; - let phi = parse_phi(&lcgs, &atl_formula); - assert_eq!( - phi, - Ok(Phi::EnforceEventually { - players: vec![0], - formula: Arc::new(Phi::True) - }) - ) - } - - #[test] - fn test_atl_lcgs_03() { - // Can we parse ATL formulas that mentions labels in an LCGS program - let lcgs_program = " - player p1 = something; - label test = 1; - template something - [wait] 1; - endtemplate - "; - let lcgs = IntermediateLcgs::create(parse_lcgs(lcgs_program).unwrap()).unwrap(); - - let atl_formula = "<<>> F test"; - let phi = parse_phi(&lcgs, &atl_formula); - assert_eq!( - phi, - Ok(Phi::EnforceEventually { - players: vec![], - formula: Arc::new(Phi::Proposition(0)) - }) - ) - } - - #[test] - fn test_atl_lcgs_04() { - // Can we parse ATL formulas that mentions players and labels in an LCGS program - let lcgs_program = " - player p1 = something; - - template something - label test = 1; - [wait] 1; - endtemplate - "; - let lcgs = IntermediateLcgs::create(parse_lcgs(lcgs_program).unwrap()).unwrap(); - - let atl_formula = "<> F p1.test"; - let phi = parse_phi(&lcgs, &atl_formula); - assert_eq!( - phi, - Ok(Phi::EnforceEventually { - players: vec![0], - formula: Arc::new(Phi::Proposition(0)) - }) - ) - } -} + Ok(Phi::True) +} \ No newline at end of file diff --git a/cgaal-engine/src/game_structure/eager.rs b/cgaal-engine/src/game_structure/eager.rs index 1b9e8db2a..d491f0fa2 100644 --- a/cgaal-engine/src/game_structure/eager.rs +++ b/cgaal-engine/src/game_structure/eager.rs @@ -1,10 +1,7 @@ use std::collections::hash_map::RandomState; use std::collections::HashSet; -use crate::atl::{number, AtlExpressionParser}; use crate::game_structure::{transition_lookup, DynVec, GameStructure, Player, Proposition, State}; -use crate::parsing::ParseState; -use pom::parser::Parser; use std::str::{self}; #[derive(Clone, Debug, Deserialize)] @@ -88,22 +85,3 @@ impl GameStructure for EagerGameStructure { action.to_string() } } - -impl AtlExpressionParser for EagerGameStructure { - fn player_parser(&self, state: &ParseState) -> Parser { - // In EagerGameStructure ATL, players are just their index - number().convert(move |i| { - if i <= self.max_player() { - Ok(i) - } else { - Err(format!("Player index '{}' out of bounds.", i)) - } - }) - } - - fn proposition_parser(&self, state: &ParseState) -> Parser { - // In EagerGameStructure ATL, proposition are just their index. - // All numbers are valid propositions, but they might not be true anywhere. - number() - } -} diff --git a/cgaal-engine/src/game_structure/lcgs/ir/intermediate.rs b/cgaal-engine/src/game_structure/lcgs/ir/intermediate.rs index 86737917f..891262bf2 100644 --- a/cgaal-engine/src/game_structure/lcgs/ir/intermediate.rs +++ b/cgaal-engine/src/game_structure/lcgs/ir/intermediate.rs @@ -1,7 +1,6 @@ use std::borrow::BorrowMut; use std::collections::{HashMap, HashSet}; -use crate::atl::{identifier, AtlExpressionParser}; use crate::game_structure; use crate::game_structure::lcgs::ast::{ConstDecl, Decl, DeclKind, ExprKind, Identifier, Root}; use crate::game_structure::lcgs::ir::error::Error; @@ -10,8 +9,6 @@ use crate::game_structure::lcgs::ir::relabeling::Relabeler; use crate::game_structure::lcgs::ir::symbol_checker::{CheckMode, SymbolChecker, SymbolError}; use crate::game_structure::lcgs::ir::symbol_table::{Owner, SymbolIdentifier, SymbolTable}; use crate::game_structure::{Action, GameStructure, Proposition}; -use crate::parsing::ParseState; -use pom::parser::{sym, Parser}; use std::fmt::{Display, Formatter}; /// A struct that holds information about players for the intermediate representation @@ -540,64 +537,6 @@ impl GameStructure for IntermediateLcgs { } } -impl AtlExpressionParser for IntermediateLcgs { - fn player_parser(&self, state: &ParseState) -> Parser { - // In ATL, players are referred to using their name, i.e. an identifier - identifier().convert(move |name| { - // We check if a declaration with the given name exists, - // and that it is a player declaration - let symbol = Owner::Global.symbol_id(&name); - if let Some(decl) = self.symbols.get(&symbol) { - if let DeclKind::Player(player) = &decl.kind { - Ok(player.index) - } else { - Err(format!("The declaration '{}' is not a player.", name)) - } - } else { - Err(format!( - "The LCGS program does not contain any player named '{}'.", - name - )) - } - }) - } - - fn proposition_parser(&self, state: &ParseState) -> Parser { - // In ATL, propositions are either "something" where "something" must be a label declared - // in the global scope, or "someone.something" where "something" is a label owned by - // a player of name "someone". - let parser = identifier() + (sym(b'.') * identifier()).opt(); - parser.convert(move |(name_or_owner, name)| { - // We infer whether the label should be found in local (player) or global scope. - // full_name is used for error descriptions. - let (symbol, full_name) = if let Some(name) = name { - let owner = name_or_owner; - ( - Owner::Player(owner.clone()).symbol_id(&name), - format!("{}.{}", owner, name), - ) - } else { - let name = name_or_owner; - (Owner::Global.symbol_id(&name), name) - }; - - // We check if such a symbol exists, and is it a player declaration - if let Some(decl) = self.symbols.get(&symbol) { - if let DeclKind::Label(label) = &decl.kind { - Ok(label.index) - } else { - Err(format!("The declaration '{}' is not a label.", full_name)) - } - } else { - Err(format!( - "The LCGS program does not contain any label with the name '{}'", - full_name - )) - } - }) - } -} - #[cfg(test)] mod test { use crate::game_structure::lcgs::ast::DeclKind; diff --git a/cgaal-engine/src/parsing/ast.rs b/cgaal-engine/src/parsing/ast.rs new file mode 100644 index 000000000..5903fbbc0 --- /dev/null +++ b/cgaal-engine/src/parsing/ast.rs @@ -0,0 +1,115 @@ +use crate::game_structure::Proposition; +use crate::parsing::span::Span; +use std::sync::Arc; +use crate::parsing::token::TokenKind; + +#[derive(Debug, Clone)] +pub struct Expr { + pub span: Span, + pub kind: ExprKind, +} + +impl Expr { + pub fn new(span: Span, kind: ExprKind) -> Self { + Expr { span, kind } + } + + pub fn new_error() -> Self { + Expr::new(Span::new(0, 0), ExprKind::Error) + } +} + +#[derive(Debug, Clone)] +pub enum ExprKind { + True, + False, + Paren(Arc), + Ident(String), + Prop(Proposition), + Unary(UnaryOpKind, Arc), + Binary(BinaryOpKind, Arc, Arc), + Coalition(Coalition), + Error, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum UnaryOpKind { + Not, + + // Temporal operators + Next, + Eventually, + Invariantly, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BinaryOpKind { + And, + Or, + + // Temporal operators + Until, +} + +impl BinaryOpKind { + pub fn associativity(&self) -> Associativity { + match self { + _ => Associativity::LeftToRight, + } + } + + pub fn is_right_associative(&self) -> bool { + self.associativity() == Associativity::RightToLeft + } + + pub fn precedence(&self) -> u8 { + match self { + BinaryOpKind::Or => 0, + BinaryOpKind::And => 1, + BinaryOpKind::Until => 2, + } + } +} + +impl TryFrom for BinaryOpKind { + type Error = (); + + fn try_from(value: TokenKind) -> Result { + match value { + TokenKind::AmpAmp => Ok(BinaryOpKind::And), + TokenKind::PipePipe => Ok(BinaryOpKind::Or), + _ => Err(()) + } + } +} + +#[derive(Debug, Clone)] +pub struct Coalition { + pub span: Span, + pub players: Vec, + pub kind: CoalitionKind, + pub expr: Arc, +} + +impl Coalition { + pub fn new(span: Span, players: Vec, kind: CoalitionKind, expr: Arc) -> Self { + Coalition { + span, + players, + kind, + expr, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CoalitionKind { + Despite, + Enforce, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Associativity { + LeftToRight, + RightToLeft, +} \ No newline at end of file diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs index 83c48e4e0..006157fb7 100644 --- a/cgaal-engine/src/parsing/lexer.rs +++ b/cgaal-engine/src/parsing/lexer.rs @@ -2,7 +2,7 @@ use crate::parsing::span::Span; use crate::parsing::token::{Token, TokenKind}; #[derive(Clone, Eq, PartialEq)] -struct Lexer<'a> { +pub struct Lexer<'a> { input: &'a [u8], pos: usize, } diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 51e1f04fe..97942b7b7 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -2,283 +2,5 @@ mod errors; mod lexer; pub mod span; mod token; - -use crate::parsing::errors::ErrorLog; -use crate::parsing::span::Span; -use pom::parser::*; -use std::cell::RefCell; -use std::fmt::Display; -use crate::atl::{identifier, number}; - -/// A trait that allows us to extent parser with a helper function that extracts the span of -/// the parsed piece of text -pub trait WithSpan<'a, I, O: 'a> { - fn with_span(self) -> Parser<'a, I, (Span, O)>; -} - -impl<'a, I, O: 'a> WithSpan<'a, I, O> for Parser<'a, I, O> { - /// Make the parser note the beginning and end position and - /// include it in the result as a `Span` - fn with_span(self) -> Parser<'a, I, (Span, O)> { - (empty().pos() + self + empty().pos()) - .map(|((begin, item), end)| (Span { begin, end }, item)) - } -} - -/// The `ParseState` is a struct carried around during parsing containing extra information -/// about the state of the parser, such as the errors encountered -#[derive(Debug, Default)] -pub struct ParseState { - pub errors: RefCell, - sync_tokens: RefCell>, -} - -/// Create a lazy parser used for recursive definitions. -/// This function is similar to `call` but allows passing one argument to the parser function. -#[allow(unused)] -pub(crate) fn call1<'a, I, O, A, P: Fn(&'a A) -> Parser>( - parser: &'a P, - arg: &'a A, -) -> Parser<'a, I, O> { - Parser::new(move |input: &'_ [I], start: usize| (parser(arg).method)(input, start)) -} - -/// Create a lazy parser used for recursive definitions. -/// This function is similar to `call` but allows passing two arguments to the parser function. -pub(crate) fn call2<'a, I, O, A, B, P: Fn(&'a A, &'a B) -> Parser<'a, I, O>>( - parser: &'a P, - arg1: &'a A, - arg2: &'a B, -) -> Parser<'a, I, O> { - Parser::new(move |input: &'_ [I], start: usize| (parser(arg1, arg2).method)(input, start)) -} - -/// Creates a parser that will run the given parser. -/// If the given parser fails to parse, an error is reported using the given error message, -/// and parsing will continue with no input consumed. -#[allow(unused)] -pub(crate) fn parse_or_skip<'a, I: Eq + Display, O: 'a>( - parse_state: &'a ParseState, - parser: Parser<'a, I, O>, - err_msg: &'a str, -) -> Parser<'a, I, Option> { - Parser::new( - move |input: &'_ [I], start: usize| match (parser.method)(input, start) { - Ok((out, pos)) => Ok((Some(out), pos)), - Err(_) => { - let span = Span::new(start, start + 1); - parse_state - .errors - .borrow_mut() - .log(span, err_msg.to_string()); - Ok((None, start)) - } - }, - ) -} - -/// Creates a parser that will run the given parser. -/// If the given parser fails to parse, an error is reported using the given error message, -/// and parsing will continue at EOF. -#[allow(unused)] -pub(crate) fn parse_or_abort<'a, I: Eq + Display, O: 'a>( - parse_state: &'a ParseState, - parser: Parser<'a, I, O>, - err_msg: &'a str, -) -> Parser<'a, I, Option> { - Parser::new( - move |input: &'_ [I], start: usize| match (parser.method)(input, start) { - Ok((out, pos)) => Ok((Some(out), pos)), - Err(_) => { - let span = Span::new(start, input.len()); - parse_state - .errors - .borrow_mut() - .log(span, err_msg.to_string()); - Ok((None, input.len())) - } - }, - ) -} - -pub(crate) fn recover<'a, I: Eq, O: 'a>(state: &'a ParseState) -> Parser<'a, I, Result<(), Span>> { - Parser::new(|input: &'_ [I], start: usize| { - let sync_pos = input[start..] - .iter() - .position(|c| state.sync_tokens.borrow().contains(c)) - .map(|p| p + start) - .unwrap_or(input.len()); - let span = Span::new(start, sync_pos); - Ok((Err(span), sync_pos)) - }) -} - -pub(crate) fn unexpected_abort<'a, O: 'a + Clone>(state: &'a ParseState, res: O) -> Parser<'a, u8, O> { - (identifier().discard() | number().discard() | any().discard()).with_span() - .map(move |(span, _)| { - state.errors.borrow_mut().log(span, "Unexpected token".to_string()); - res.clone() - }) - any().repeat(..) -} - -/// Creates a parser that will run the given parser and then consume the synchronization token. -/// If the given parser fails to parse, an error is reported using the given error message, -/// and parsing will continue at the next occurrence of the synchronization token. -/// The resulting Err will contained the skipped span. -#[allow(unused)] -pub(crate) fn parse_or_sync<'a, I: Copy + Eq + Display, O: 'a>( - parse_state: &'a ParseState, - parser: Parser<'a, I, O>, - sync: I, - err_msg: &'a str, -) -> Parser<'a, I, Result> { - Parser::new(move |input: &'_ [I], start: usize| { - parse_state.sync_tokens.borrow_mut().push(sync); - let res = match (parser.method)(input, start) { - Ok((out, pos)) => { - if input.get(pos) == Some(&sync) { - Ok((Ok(out), pos + 1)) - } else { - let span = Span::new(pos, pos + 1); - parse_state - .errors - .borrow_mut() - .log(span, format!("Missing '{}'", sync)); - Ok((Ok(out), pos)) - } - } - Err(_) => { - let sync_pos = input[start..] - .iter() - .position(|c| c == &sync) - .map(|p| p + start + 1) - .or(input[start..] - .iter() - .position(|c| parse_state.sync_tokens.borrow().contains(c)) - .map(|p| p + start)) - .unwrap_or(input.len()); - let span = Span::new(start, sync_pos); - parse_state - .errors - .borrow_mut() - .log(span, err_msg.to_string()); - Ok((Err(span), sync_pos)) - } - }; - parse_state.sync_tokens.borrow_mut().pop(); - res - }) -} - -#[cfg(test)] -mod test { - use crate::parsing::{parse_or_abort, parse_or_skip, parse_or_sync, ParseState}; - use pom::parser::{end, seq, sym}; - use pom::set::Set; - - #[test] - fn parse_or_skip_001() { - let state = ParseState::default(); - let parser = (parse_or_skip(&state, seq(b"foo"), "error").map(|res| res.unwrap_or(b"")) - + seq(b"bar")) - .convert(|(fst, snd)| String::from_utf8([fst, snd].concat().to_vec())) - - end(); - - // Input is expected string "foobar" - let res = parser.parse(b"foobar").expect("Parser must not fail"); - assert_eq!("foobar", res); - assert!(state.errors.borrow().is_empty()); - } - - #[test] - fn parse_or_skip_002() { - let state = ParseState::default(); - let parser = (parse_or_skip(&state, seq(b"foo"), "error").map(|res| res.unwrap_or(b"")) - + seq(b"bar")) - .convert(|(fst, snd)| String::from_utf8([fst, snd].concat().to_vec())) - - end(); - - // Input is unexpected, but parser will continue - let res = parser.parse(b"bar").expect("Parser must not fail"); - assert_eq!("bar", res); - assert_eq!(1, state.errors.borrow().len()); - } - - #[test] - fn parse_or_abort_001() { - let state = ParseState::default(); - let parser = (parse_or_abort(&state, seq(b"foo"), "error").map(|res| res.unwrap_or(b"")) - + parse_or_abort(&state, seq(b"bar"), "error").map(|res| res.unwrap_or(b""))) - .convert(|(fst, snd)| String::from_utf8([fst, snd].concat().to_vec())) - - end(); - - // Input is expected string "foobar" - let res = parser.parse(b"foobar").expect("Parser must not fail"); - assert_eq!("foobar", res); - assert!(state.errors.borrow().is_empty()); - } - - #[test] - fn parse_or_abort_002() { - let state = ParseState::default(); - let parser = (parse_or_abort(&state, seq(b"foo"), "error").map(|res| res.unwrap_or(b"")) - + parse_or_abort(&state, seq(b"bar"), "error").map(|res| res.unwrap_or(b""))) - .convert(|(fst, snd)| String::from_utf8([fst, snd].concat().to_vec())) - - end(); - - // Input is unexpected, so parser will abort - let res = parser.parse(b"goobar").expect("Parser must not fail"); - assert_eq!("", res); - assert_eq!(2, state.errors.borrow().len()); - } - - #[test] - fn parse_or_sync_001() { - // parse_or_sync works on happy path - let state = ParseState::default(); - let inner = seq(b"foo"); - let parser = sym(b'(') * parse_or_sync(&state, inner, b')', "error") - seq(b"ok") - end(); - - let res = parser.parse(b"(foo)ok").expect("Parser must not fail"); - assert_eq!(b"foo", res.unwrap()); - assert!(state.errors.borrow().is_empty()); - } - - #[test] - fn parse_or_sync_002() { - // parse_or_sync synchronizes when inner parser fails - let state = ParseState::default(); - let inner = seq(b"foo"); - let parser = sym(b'(') * parse_or_sync(&state, inner, b')', "error") - seq(b"ok") - end(); - - let res = parser.parse(b"(bar)ok").expect("Parser must not fail"); - assert!(res.is_none()); - assert_eq!(1, state.errors.borrow().len()); - } - - #[test] - fn parse_or_sync_003() { - // nested parse_or_sync synchronizes when inner parser fails - let state = ParseState::default(); - let inner = seq(b"foo"); - let middle = sym(b'(') * parse_or_sync(&state, inner, b')', "error"); - let parser = sym(b'(') * parse_or_sync(&state, middle, b')', "error") - seq(b"ok") - end(); - - let res = parser.parse(b"((bar))ok").expect("Parser must not fail"); - assert!(res.is_some()); // Outer parser succeeds despite inner parser failing - assert_eq!(1, state.errors.borrow().len()); - } - - #[test] - fn parse_or_sync_004() { - // nested parse_or_sync synchronizes at first sync token - let state = ParseState::default(); - let inner = seq(b"foo"); - let middle = sym(b'[') * parse_or_sync(&state, inner, b']', "error"); - let parser = sym(b'(') * parse_or_sync(&state, middle, b')', "error") - seq(b"ok") - end(); - - let res = parser.parse(b"([bar)ok").expect("Parser must not fail"); - assert!(res.is_some()); // Outer parser succeeds despite inner parser failing - assert_eq!(1, state.errors.borrow().len()); - } -} +pub mod parser; +pub mod ast; diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs new file mode 100644 index 000000000..eb4afc624 --- /dev/null +++ b/cgaal-engine/src/parsing/parser.rs @@ -0,0 +1,201 @@ +use crate::parsing::ast::{BinaryOpKind, Coalition, CoalitionKind, Expr, ExprKind, UnaryOpKind}; +use crate::parsing::errors::ErrorLog; +use crate::parsing::lexer::Lexer; +use crate::parsing::parser::ParseError::Unexpected; +use crate::parsing::span::Span; +use crate::parsing::token::{Token, TokenKind}; +use std::iter::Peekable; +use std::sync::Arc; + +const SYNC_TOKEN_HIERARCHY: [TokenKind; 5] = [ + TokenKind::Rrbracket, + TokenKind::Rrangle, + TokenKind::Rparen, + TokenKind::Semi, + TokenKind::KwEndTemplate, +]; + +pub struct Parser<'a> { + lexer: Peekable>, + pub errors: ErrorLog, +} + +impl<'a> Parser<'a> { + pub fn new(lexer: Lexer<'a>) -> Parser { + Parser { + lexer: lexer.peekable(), + errors: ErrorLog::new(), + } + } + + pub fn expr(&mut self, min_prec: u8) -> Expr { + // Pratt parsing/precedence climbing: https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing + let mut lhs = self.term(); + let span_start = lhs.span; + loop { + let Some(op): Option = self.lexer.peek().and_then(|t| (*t.kind()).try_into().ok()) else { return lhs }; + if op.precedence() < min_prec { + return lhs; + } + self.lexer.next(); + let new_prec = op.precedence() + if op.is_right_associative() { 0 } else { 1 }; + let rhs = self.expr(new_prec); + let span = span_start + rhs.span; + let kind = ExprKind::Binary(op, lhs.into(), rhs.into()); + lhs = Expr::new(span, kind); + } + } + + pub fn path_expr(&mut self) -> Expr { + match self.lexer.peek().map(|t| t.kind()) { + Some(TokenKind::Lparen) => { + let begin = self.lexer.next().unwrap().span; + let lhs = self.expr(BinaryOpKind::Until.precedence()); + let Ok(_) = self.expect_and_consume(TokenKind::Word("U".to_string())) else { + return Expr::new_error(); + }; + let rhs = self.expr(0); + let end = self.expect_and_consume(TokenKind::Rparen).unwrap(); + Expr::new(begin + end, ExprKind::Binary(BinaryOpKind::Until, lhs.into(), rhs.into())) + }, + Some(TokenKind::Word(w)) if w == "F" => { + let begin = self.lexer.next().unwrap().span; + let expr = self.expr(0); + Expr::new(begin + expr.span, ExprKind::Unary(UnaryOpKind::Eventually, expr.into())) + }, + + Some(TokenKind::Word(w)) if w == "G" => { + let begin = self.lexer.next().unwrap().span; + let expr = self.expr(0); + Expr::new(begin + expr.span, ExprKind::Unary(UnaryOpKind::Invariantly, expr.into())) + }, + // Unexpected + Some(t) => Err(Unexpected(self.lexer.next())), + None => Err(Unexpected(None)), + } + } + + pub fn term(&mut self) -> Expr { + match self.lexer.peek().map(|t| t.kind()) { + Some(TokenKind::Lparen) => self.paren(), + Some(TokenKind::True) => { + let tok = self.lexer.next().unwrap(); + Expr::new(tok.span, ExprKind::True) + } + Some(TokenKind::False) => { + let tok = self.lexer.next().unwrap(); + Expr::new(tok.span, ExprKind::False) + } + Some(TokenKind::Word(_)) => self.ident(), + Some(TokenKind::Llangle) => self.enforce_coalition(), + Some(TokenKind::Llbracket) => self.despite_coalition(), + // Unexpected + Some(t) => Err(Unexpected(self.lexer.next())), + None => Err(Unexpected(None)), + } + } + + pub fn paren(&mut self) -> Expr { + let Ok(begin) = self.expect_and_consume(TokenKind::Llangle) else { + return Expr::new_error(); + }; + let expr = self.expr(0); + let Ok(end) = self.expect_and_consume(TokenKind::Rrangle) else { + return Expr::new_error(); + }; + Expr::new(begin + end, ExprKind::Paren(Arc::new(expr))) + } + + pub fn ident(&mut self) -> Expr { + let tok = self.lexer.next().unwrap(); + Expr::new(tok.span, ExprKind::Ident(tok.kind.to_string())) + } + + pub fn enforce_coalition(&mut self) -> Expr { + let Ok(coal_begin) = self.expect_and_consume(TokenKind::Llangle) else { + return Expr::new_error(); + }; + let players = self.players(); + let Ok(coal_end) = self.expect_and_consume(TokenKind::Rrangle) else { + return Expr::new_error(); + }; + let expr = self.path_expr(); + Expr::new( + coal_begin + expr.span, + ExprKind::Coalition(Coalition::new( + coal_begin + coal_end, + players, + CoalitionKind::Enforce, + Arc::new(expr), + )), + ) + } + + pub fn despite_coalition(&mut self) -> Expr { + let Ok(coal_begin) = self.expect_and_consume(TokenKind::Llbracket) else { + return Expr::new_error(); + }; + let players = self.players(); + let Ok(coal_end) = self.expect_and_consume(TokenKind::Rrbracket) else { + return Expr::new_error(); + }; + let expr = self.path_expr(); + Expr::new( + coal_begin + expr.span, + ExprKind::Coalition(Coalition::new( + coal_begin + coal_end, + players, + CoalitionKind::Despite, + Arc::new(expr), + )), + ) + } + + pub fn players(&mut self) -> Vec { + let mut players = vec![]; + loop { + match self.lexer.peek().map(|t| t.kind()) { + Some(TokenKind::Word(w)) => { + let tok = self.lexer.next().unwrap(); + let p = Expr::new(tok.span, ExprKind::Ident(w.to_string())); + players.push(p); + } + _ => break, + } + if self.next_is(TokenKind::Comma) { + self.lexer.next(); + } else { + break; + } + } + players + } + + fn next_is(&mut self, kind: TokenKind) -> bool { + matches!(self.lexer.peek().map(|t| t.kind()), Some(k) if k == &kind) + } + + fn expect_and_consume(&mut self, kind: TokenKind) -> Result { + match self.lexer.peek().map(|t| t.kind()) { + Some(k) if k == &kind => Ok(self.lexer.next().unwrap().span), + Some(_) => { + let tok = self.lexer.next().unwrap(); + self.errors.log( + tok.span, + format!("Expected '{}', found '{}'", kind, tok.kind), + ); + Err(Unexpected(Some(tok))) + } + None => { + self.errors + .log_msg(format!("Expected '{}', found EOF", kind)); + Err(Unexpected(self.lexer.peek().cloned())) + } + } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub enum ParseError { + Unexpected(Option), +} diff --git a/cgaal-engine/src/parsing/token.rs b/cgaal-engine/src/parsing/token.rs index 8a720b4dc..7038bcc4b 100644 --- a/cgaal-engine/src/parsing/token.rs +++ b/cgaal-engine/src/parsing/token.rs @@ -3,8 +3,8 @@ use std::fmt::{Debug, Display, Formatter}; #[derive(Clone, PartialEq, Eq)] pub struct Token { - span: Span, - kind: TokenKind, + pub span: Span, + pub kind: TokenKind, } impl Token { From 6c1114d89355070136697afc2c500bde1f52c4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Fri, 1 Sep 2023 10:53:07 +0200 Subject: [PATCH 13/38] Working ATL parser --- cgaal-cli/src/main.rs | 11 +- cgaal-engine/src/lib.rs | 2 +- cgaal-engine/src/parsing/ast.rs | 12 +- cgaal-engine/src/parsing/mod.rs | 2 +- cgaal-engine/src/parsing/parser.rs | 78 ++++-- cgaal-engine/tests/parsing.rs | 259 ++++++++++++++++++ .../tests/partial_strategy_synthesis.rs | 2 +- 7 files changed, 334 insertions(+), 32 deletions(-) create mode 100644 cgaal-engine/tests/parsing.rs diff --git a/cgaal-cli/src/main.rs b/cgaal-cli/src/main.rs index 03f5da9b5..f7f07aa50 100644 --- a/cgaal-cli/src/main.rs +++ b/cgaal-cli/src/main.rs @@ -18,7 +18,7 @@ use tracing::trace; use cgaal_engine::algorithms::global::multithread::MultithreadedGlobalAlgorithm; use cgaal_engine::algorithms::global::singlethread::SinglethreadedGlobalAlgorithm; use cgaal_engine::analyse::analyse; -use cgaal_engine::atl::{AtlExpressionParser, Phi}; +use cgaal_engine::atl::Phi; use cgaal_engine::edg::atledg::vertex::AtlVertex; use cgaal_engine::edg::atledg::AtlDependencyGraph; use cgaal_engine::edg::ExtendedDependencyGraph; @@ -335,10 +335,9 @@ fn main_inner() -> Result<(), String> { /// Reads a formula in JSON format from a file and returns the formula as a string /// and as a parsed Phi struct. /// This function will exit the program if it encounters an error. -fn load_formula( +fn load_formula( path: &str, formula_type: FormulaType, - expr_parser: &A, ) -> Phi { let mut file = File::open(path).unwrap_or_else(|err| { eprintln!("Failed to open formula file\n\nError:\n{}", err); @@ -357,7 +356,7 @@ fn load_formula( exit(1); }), FormulaType::Atl => { - let result = cgaal_engine::atl::parse_phi(expr_parser, &raw_phi); + let result = cgaal_engine::atl::parse_phi(&raw_phi); result.unwrap_or_else(|err| { eprintln!("Invalid ATL formula provided:\n\n{}", err); exit(1) @@ -457,7 +456,7 @@ fn load( let game_structure = serde_json::from_str(content.as_str()) .map_err(|err| format!("Failed to deserialize input model.\n{}", err))?; - let phi = load_formula(formula_path, formula_format, &game_structure); + let phi = load_formula(formula_path, formula_format); Ok(ModelAndFormula::Json { model: game_structure, @@ -471,7 +470,7 @@ fn load( let game_structure = IntermediateLcgs::create(lcgs) .map_err(|err| format!("Invalid LCGS program.\n{}", err))?; - let phi = load_formula(formula_path, formula_format, &game_structure); + let phi = load_formula(formula_path, formula_format); Ok(ModelAndFormula::Lcgs { model: game_structure, diff --git a/cgaal-engine/src/lib.rs b/cgaal-engine/src/lib.rs index 0bbc21080..a297faba8 100644 --- a/cgaal-engine/src/lib.rs +++ b/cgaal-engine/src/lib.rs @@ -12,6 +12,6 @@ pub mod analyse; pub mod atl; pub mod edg; pub mod game_structure; -mod parsing; +pub mod parsing; #[cfg(feature = "graph-printer")] pub mod printer; diff --git a/cgaal-engine/src/parsing/ast.rs b/cgaal-engine/src/parsing/ast.rs index 5903fbbc0..40a0154bd 100644 --- a/cgaal-engine/src/parsing/ast.rs +++ b/cgaal-engine/src/parsing/ast.rs @@ -3,7 +3,7 @@ use crate::parsing::span::Span; use std::sync::Arc; use crate::parsing::token::TokenKind; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Expr { pub span: Span, pub kind: ExprKind, @@ -19,7 +19,7 @@ impl Expr { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ExprKind { True, False, @@ -64,9 +64,9 @@ impl BinaryOpKind { pub fn precedence(&self) -> u8 { match self { - BinaryOpKind::Or => 0, - BinaryOpKind::And => 1, - BinaryOpKind::Until => 2, + BinaryOpKind::And => 2, + BinaryOpKind::Or => 1, + BinaryOpKind::Until => 0, } } } @@ -83,7 +83,7 @@ impl TryFrom for BinaryOpKind { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Coalition { pub span: Span, pub players: Vec, diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 97942b7b7..72e9d84c7 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -1,5 +1,5 @@ mod errors; -mod lexer; +pub mod lexer; pub mod span; mod token; pub mod parser; diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index eb4afc624..192f3a899 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -28,12 +28,22 @@ impl<'a> Parser<'a> { } } + pub fn end(&mut self) { + match self.lexer.next() { + None => {} + Some(tok) => { + self.errors.log(tok.span, format!("Unexpected token '{}', expected EOF", tok.kind)); + } + } + while let Some(_) = self.lexer.next() {}; + } + pub fn expr(&mut self, min_prec: u8) -> Expr { // Pratt parsing/precedence climbing: https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing let mut lhs = self.term(); let span_start = lhs.span; loop { - let Some(op): Option = self.lexer.peek().and_then(|t| (*t.kind()).try_into().ok()) else { return lhs }; + let Some(op): Option = self.lexer.peek().and_then(|t| t.kind().clone().try_into().ok()) else { return lhs }; if op.precedence() < min_prec { return lhs; } @@ -56,22 +66,42 @@ impl<'a> Parser<'a> { }; let rhs = self.expr(0); let end = self.expect_and_consume(TokenKind::Rparen).unwrap(); - Expr::new(begin + end, ExprKind::Binary(BinaryOpKind::Until, lhs.into(), rhs.into())) - }, + Expr::new( + begin + end, + ExprKind::Binary(BinaryOpKind::Until, lhs.into(), rhs.into()), + ) + } Some(TokenKind::Word(w)) if w == "F" => { let begin = self.lexer.next().unwrap().span; let expr = self.expr(0); - Expr::new(begin + expr.span, ExprKind::Unary(UnaryOpKind::Eventually, expr.into())) - }, + Expr::new( + begin + expr.span, + ExprKind::Unary(UnaryOpKind::Eventually, expr.into()), + ) + } Some(TokenKind::Word(w)) if w == "G" => { let begin = self.lexer.next().unwrap().span; let expr = self.expr(0); - Expr::new(begin + expr.span, ExprKind::Unary(UnaryOpKind::Invariantly, expr.into())) - }, + Expr::new( + begin + expr.span, + ExprKind::Unary(UnaryOpKind::Invariantly, expr.into()), + ) + } // Unexpected - Some(t) => Err(Unexpected(self.lexer.next())), - None => Err(Unexpected(None)), + Some(_) => { + let tok = self.lexer.next().unwrap(); + self.errors.log( + tok.span, + format!("Unexpected token '{}', expected path expression", tok.kind), + ); + Expr::new_error() + } + None => { + self.errors + .log_msg("Unexpected EOF, expected path expression".to_string()); + Expr::new_error() + } } } @@ -90,17 +120,28 @@ impl<'a> Parser<'a> { Some(TokenKind::Llangle) => self.enforce_coalition(), Some(TokenKind::Llbracket) => self.despite_coalition(), // Unexpected - Some(t) => Err(Unexpected(self.lexer.next())), - None => Err(Unexpected(None)), + Some(_) => { + let tok = self.lexer.next().unwrap(); + self.errors.log( + tok.span, + format!("Unexpected token '{}', expected expression term", tok.kind), + ); + Expr::new_error() + } + None => { + self.errors + .log_msg("Unexpected EOF, expected expression term".to_string()); + Expr::new_error() + } } } pub fn paren(&mut self) -> Expr { - let Ok(begin) = self.expect_and_consume(TokenKind::Llangle) else { + let Ok(begin) = self.expect_and_consume(TokenKind::Lparen) else { return Expr::new_error(); }; let expr = self.expr(0); - let Ok(end) = self.expect_and_consume(TokenKind::Rrangle) else { + let Ok(end) = self.expect_and_consume(TokenKind::Rparen) else { return Expr::new_error(); }; Expr::new(begin + end, ExprKind::Paren(Arc::new(expr))) @@ -154,11 +195,14 @@ impl<'a> Parser<'a> { pub fn players(&mut self) -> Vec { let mut players = vec![]; loop { - match self.lexer.peek().map(|t| t.kind()) { - Some(TokenKind::Word(w)) => { - let tok = self.lexer.next().unwrap(); - let p = Expr::new(tok.span, ExprKind::Ident(w.to_string())); + match self.lexer.peek() { + Some(Token { + span, + kind: TokenKind::Word(w), + }) => { + let p = Expr::new(span.clone(), ExprKind::Ident(w.to_string())); players.push(p); + self.lexer.next().unwrap(); } _ => break, } diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing.rs new file mode 100644 index 000000000..d12a92bc7 --- /dev/null +++ b/cgaal-engine/tests/parsing.rs @@ -0,0 +1,259 @@ +use cgaal_engine::parsing::ast::*; +use cgaal_engine::parsing::lexer::*; +use cgaal_engine::parsing::parser::*; +use cgaal_engine::parsing::span::*; + +#[test] +fn basic_expr_001() { + let input = "true && false"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.end(); + assert!( + parser.errors.is_empty(), + "ErrorLog is not empty: {:?}", + parser.errors + ); + assert_eq!( + expr, + Expr::new( + Span::new(0, 13), + ExprKind::Binary( + BinaryOpKind::And, + Expr::new(Span::new(0, 4), ExprKind::True).into(), + Expr::new(Span::new(8, 13), ExprKind::False).into() + ) + ) + ); +} + +#[test] +fn basic_expr_002() { + let input = "true && false || true"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.end(); + assert!( + parser.errors.is_empty(), + "ErrorLog is not empty: {:?}", + parser.errors + ); + assert_eq!( + expr, + Expr::new( + Span::new(0, 21), + ExprKind::Binary( + BinaryOpKind::Or, + Expr::new( + Span::new(0, 13), + ExprKind::Binary( + BinaryOpKind::And, + Expr::new(Span::new(0, 4), ExprKind::True).into(), + Expr::new(Span::new(8, 13), ExprKind::False).into() + ) + ) + .into(), + Expr::new(Span::new(17, 21), ExprKind::True).into() + ) + ) + ); +} + +#[test] +fn basic_expr_003() { + let input = "foo || true && bar || (true || false)"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.end(); + assert!( + parser.errors.is_empty(), + "ErrorLog is not empty: {:?}", + parser.errors + ); + assert_eq!( + expr, + Expr::new( + Span::new(0, 37), + ExprKind::Binary( + BinaryOpKind::Or, + Expr::new( + Span::new(0, 18), + ExprKind::Binary( + BinaryOpKind::Or, + Expr::new(Span::new(0, 3), ExprKind::Ident("foo".to_string())).into(), + Expr::new( + Span::new(7, 18), + ExprKind::Binary( + BinaryOpKind::And, + Expr::new(Span::new(7, 11), ExprKind::True).into(), + Expr::new(Span::new(15, 18), ExprKind::Ident("bar".to_string())) + .into() + ) + ) + .into() + ) + ) + .into(), + Expr::new( + Span::new(22, 37), + ExprKind::Paren( + Expr::new( + Span::new(23, 36), + ExprKind::Binary( + BinaryOpKind::Or, + Expr::new(Span::new(23, 27), ExprKind::True).into(), + Expr::new(Span::new(31, 36), ExprKind::False).into() + ) + ) + .into() + ) + ) + .into() + ) + .into() + ) + ); +} + +#[test] +fn atl_expr_001() { + let input = "<> F goal"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.end(); + assert!( + parser.errors.is_empty(), + "ErrorLog is not empty: {:?}", + parser.errors + ); + assert_eq!( + expr, + Expr::new( + Span::new(0, 13), + ExprKind::Coalition(Coalition::new( + Span::new(0, 6), + vec![Expr::new( + Span::new(2, 4), + ExprKind::Ident("p1".to_string()) + )], + CoalitionKind::Enforce, + Expr::new( + Span::new(7, 13), + ExprKind::Unary( + UnaryOpKind::Eventually, + Expr::new(Span::new(9, 13), ExprKind::Ident("goal".to_string())).into() + ) + ) + .into() + )) + ) + ); +} + +#[test] +fn atl_expr_002() { + let input = "[[p1, p2]] G safe"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.end(); + assert!( + parser.errors.is_empty(), + "ErrorLog is not empty: {:?}", + parser.errors + ); + assert_eq!( + expr, + Expr::new( + Span::new(0, 17), + ExprKind::Coalition(Coalition::new( + Span::new(0, 10), + vec![ + Expr::new(Span::new(2, 4), ExprKind::Ident("p1".to_string())), + Expr::new(Span::new(6, 8), ExprKind::Ident("p2".to_string())), + ], + CoalitionKind::Despite, + Expr::new( + Span::new(11, 17), + ExprKind::Unary( + UnaryOpKind::Invariantly, + Expr::new(Span::new(13, 17), ExprKind::Ident("safe".to_string())).into() + ) + ) + .into() + )) + ) + ); +} + +#[test] +fn atl_expr_003() { + let input = "<<>> (safe U goal)"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.end(); + assert!( + parser.errors.is_empty(), + "ErrorLog is not empty: {:?}", + parser.errors + ); + assert_eq!( + expr, + Expr::new( + Span::new(0, 18), + ExprKind::Coalition(Coalition::new( + Span::new(0, 4), + vec![], + CoalitionKind::Enforce, + Expr::new( + Span::new(5, 18), + ExprKind::Binary( + BinaryOpKind::Until, + Expr::new(Span::new(6, 10), ExprKind::Ident("safe".to_string())).into(), + Expr::new(Span::new(13, 17), ExprKind::Ident("goal".to_string())).into() + ) + ) + .into() + )) + ) + ); +} + +#[test] +fn erroneous_expr_001() { + let input = "true && "; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.end(); + assert!(parser.errors.has_errors()); + let mut out = String::new(); + parser.errors.write_detailed(input, &mut out).unwrap(); + assert_eq!( + out, + "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected expression term\n" + ); +} + +#[test] +fn erroneous_expr_002() { + let input = "foo bar"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.end(); + assert!(parser.errors.has_errors()); + let mut out = String::new(); + parser.errors.write_detailed(input, &mut out).unwrap(); + assert_eq!( + out, + "\u{1b}[93m1:5 Error:\u{1b}[0m Unexpected token 'bar', expected EOF\n\ + | foo bar\n\ + | ^^^\n" + ); +} \ No newline at end of file diff --git a/cgaal-engine/tests/partial_strategy_synthesis.rs b/cgaal-engine/tests/partial_strategy_synthesis.rs index 85a5343fd..503c3b8ce 100644 --- a/cgaal-engine/tests/partial_strategy_synthesis.rs +++ b/cgaal-engine/tests/partial_strategy_synthesis.rs @@ -147,7 +147,7 @@ macro_rules! strat_synthesis_test { ($game:expr, $phi:expr, $($rest:tt)*) => { let ast = parse_lcgs($game).unwrap(); let game = IntermediateLcgs::create(ast).unwrap(); - let phi = parse_phi(&game, $phi).unwrap(); + let phi = parse_phi($phi).unwrap(); let v0 = AtlVertex::Full { state: game.initial_state_index(), formula: phi.into(), From ab32955debb2e3c936550e2faf83b0ce019613ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Fri, 1 Sep 2023 13:02:49 +0200 Subject: [PATCH 14/38] Keep parsing to EOF --- cgaal-engine/src/parsing/parser.rs | 2 +- cgaal-engine/tests/parsing.rs | 45 ++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index 192f3a899..133512511 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -28,7 +28,7 @@ impl<'a> Parser<'a> { } } - pub fn end(&mut self) { + pub fn expect_end(&mut self) { match self.lexer.next() { None => {} Some(tok) => { diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing.rs index d12a92bc7..1e6a16316 100644 --- a/cgaal-engine/tests/parsing.rs +++ b/cgaal-engine/tests/parsing.rs @@ -9,7 +9,7 @@ fn basic_expr_001() { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); let expr = parser.expr(0); - parser.end(); + parser.expect_end(); assert!( parser.errors.is_empty(), "ErrorLog is not empty: {:?}", @@ -34,7 +34,7 @@ fn basic_expr_002() { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); let expr = parser.expr(0); - parser.end(); + parser.expect_end(); assert!( parser.errors.is_empty(), "ErrorLog is not empty: {:?}", @@ -67,7 +67,7 @@ fn basic_expr_003() { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); let expr = parser.expr(0); - parser.end(); + parser.expect_end(); assert!( parser.errors.is_empty(), "ErrorLog is not empty: {:?}", @@ -124,7 +124,7 @@ fn atl_expr_001() { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); let expr = parser.expr(0); - parser.end(); + parser.expect_end(); assert!( parser.errors.is_empty(), "ErrorLog is not empty: {:?}", @@ -160,7 +160,7 @@ fn atl_expr_002() { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); let expr = parser.expr(0); - parser.end(); + parser.expect_end(); assert!( parser.errors.is_empty(), "ErrorLog is not empty: {:?}", @@ -196,7 +196,7 @@ fn atl_expr_003() { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); let expr = parser.expr(0); - parser.end(); + parser.expect_end(); assert!( parser.errors.is_empty(), "ErrorLog is not empty: {:?}", @@ -230,7 +230,7 @@ fn erroneous_expr_001() { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); let expr = parser.expr(0); - parser.end(); + parser.expect_end(); assert!(parser.errors.has_errors()); let mut out = String::new(); parser.errors.write_detailed(input, &mut out).unwrap(); @@ -238,6 +238,17 @@ fn erroneous_expr_001() { out, "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected expression term\n" ); + assert_eq!( + expr, + Expr::new( + Span::new(0, 4), + ExprKind::Binary( + BinaryOpKind::And, + Expr::new(Span::new(0, 4), ExprKind::True).into(), + Expr::new_error().into() + ) + ) + ); } #[test] @@ -246,7 +257,7 @@ fn erroneous_expr_002() { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); let expr = parser.expr(0); - parser.end(); + parser.expect_end(); assert!(parser.errors.has_errors()); let mut out = String::new(); parser.errors.write_detailed(input, &mut out).unwrap(); @@ -256,4 +267,20 @@ fn erroneous_expr_002() { | foo bar\n\ | ^^^\n" ); -} \ No newline at end of file + assert_eq!( + expr, + Expr::new(Span::new(0, 3), ExprKind::Ident("foo".to_string())) + ); +} + +#[test] +fn erroneous_expr_003() { + let input = "(foo false) && true"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.expect_end(); + assert!(parser.errors.has_errors()); + let mut out = String::new(); + parser.errors.write_detailed(input, &mut out).unwrap(); +} From 84ff548348b83c4ee376fbc5ce4f9cb6f2624e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Fri, 1 Sep 2023 14:11:15 +0200 Subject: [PATCH 15/38] Add simple parser recovery --- cgaal-engine/src/parsing/parser.rs | 45 ++++++++-------- cgaal-engine/tests/parsing.rs | 86 +++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index 133512511..f06932464 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -7,14 +7,6 @@ use crate::parsing::token::{Token, TokenKind}; use std::iter::Peekable; use std::sync::Arc; -const SYNC_TOKEN_HIERARCHY: [TokenKind; 5] = [ - TokenKind::Rrbracket, - TokenKind::Rrangle, - TokenKind::Rparen, - TokenKind::Semi, - TokenKind::KwEndTemplate, -]; - pub struct Parser<'a> { lexer: Peekable>, pub errors: ErrorLog, @@ -32,10 +24,11 @@ impl<'a> Parser<'a> { match self.lexer.next() { None => {} Some(tok) => { - self.errors.log(tok.span, format!("Unexpected token '{}', expected EOF", tok.kind)); + self.errors + .log(tok.span, format!("Unexpected '{}', expected EOF", tok.kind)); } } - while let Some(_) = self.lexer.next() {}; + while let Some(_) = self.lexer.next() {} } pub fn expr(&mut self, min_prec: u8) -> Expr { @@ -93,7 +86,7 @@ impl<'a> Parser<'a> { let tok = self.lexer.next().unwrap(); self.errors.log( tok.span, - format!("Unexpected token '{}', expected path expression", tok.kind), + format!("Unexpected '{}', expected path expression", tok.kind), ); Expr::new_error() } @@ -124,7 +117,7 @@ impl<'a> Parser<'a> { let tok = self.lexer.next().unwrap(); self.errors.log( tok.span, - format!("Unexpected token '{}', expected expression term", tok.kind), + format!("Unexpected '{}', expected expression term", tok.kind), ); Expr::new_error() } @@ -141,7 +134,7 @@ impl<'a> Parser<'a> { return Expr::new_error(); }; let expr = self.expr(0); - let Ok(end) = self.expect_and_consume(TokenKind::Rparen) else { + let Ok(end) = self.expect_consume_or_recover(TokenKind::Rparen) else { return Expr::new_error(); }; Expr::new(begin + end, ExprKind::Paren(Arc::new(expr))) @@ -216,27 +209,37 @@ impl<'a> Parser<'a> { } fn next_is(&mut self, kind: TokenKind) -> bool { - matches!(self.lexer.peek().map(|t| t.kind()), Some(k) if k == &kind) + matches!(self.lexer.peek(), Some(tok) if tok.kind == kind) } fn expect_and_consume(&mut self, kind: TokenKind) -> Result { - match self.lexer.peek().map(|t| t.kind()) { - Some(k) if k == &kind => Ok(self.lexer.next().unwrap().span), - Some(_) => { - let tok = self.lexer.next().unwrap(); + match self.lexer.next() { + Some(tok) if tok.kind == kind => Ok(tok.span), + Some(tok) => { self.errors.log( tok.span, - format!("Expected '{}', found '{}'", kind, tok.kind), + format!("Unexpected '{}', expected '{}'", tok.kind, kind), ); Err(Unexpected(Some(tok))) } None => { self.errors - .log_msg(format!("Expected '{}', found EOF", kind)); - Err(Unexpected(self.lexer.peek().cloned())) + .log_msg(format!("Unexpected EOF, expected '{}'", kind)); + Err(Unexpected(None)) } } } + + fn expect_consume_or_recover(&mut self, kind: TokenKind) -> Result { + self.expect_and_consume(kind.clone()).or_else(|err| { + while let Some(tok) = self.lexer.next() { + if tok.kind == kind { + return Ok(tok.span); + } + } + Err(err) + }) + } } #[derive(Debug, Eq, PartialEq)] diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing.rs index 1e6a16316..6f0dc5957 100644 --- a/cgaal-engine/tests/parsing.rs +++ b/cgaal-engine/tests/parsing.rs @@ -263,7 +263,7 @@ fn erroneous_expr_002() { parser.errors.write_detailed(input, &mut out).unwrap(); assert_eq!( out, - "\u{1b}[93m1:5 Error:\u{1b}[0m Unexpected token 'bar', expected EOF\n\ + "\x1b[93m1:5 Error:\x1b[0m Unexpected 'bar', expected EOF\n\ | foo bar\n\ | ^^^\n" ); @@ -283,4 +283,88 @@ fn erroneous_expr_003() { assert!(parser.errors.has_errors()); let mut out = String::new(); parser.errors.write_detailed(input, &mut out).unwrap(); + assert_eq!( + out, + "\x1b[93m1:6 Error:\x1b[0m Unexpected 'false', expected ')'\n\ + | (foo false) && true\n\ + | ^^^^^\n" + ); + assert_eq!( + expr, + Expr::new( + Span::new(0, 19), + ExprKind::Binary( + BinaryOpKind::And, + Expr::new( + Span::new(0, 11), + ExprKind::Paren( + Expr::new(Span::new(1, 4), ExprKind::Ident("foo".to_string()),).into() + ) + ) + .into(), + Expr::new(Span::new(15, 19), ExprKind::True).into() + ) + ) + ); +} + +#[test] +fn erroneous_expr_004() { + let input = "(true"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.expect_end(); + assert!(parser.errors.has_errors()); + let mut out = String::new(); + parser.errors.write_detailed(input, &mut out).unwrap(); + assert_eq!( + out, + "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected ')'\n" + ); + assert_eq!(expr, Expr::new_error()); +} + +#[test] +fn erroneous_expr_005() { + let input = "(foo bar"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.expect_end(); + assert!(parser.errors.has_errors()); + let mut out = String::new(); + parser.errors.write_detailed(input, &mut out).unwrap(); + assert_eq!( + out, + "\x1b[93m1:6 Error:\x1b[0m Unexpected 'bar', expected ')'\n\ + | (foo bar\n\ + | ^^^\n" + ); + assert_eq!(expr, Expr::new_error()); +} + +#[test] +fn erroneous_expr_006() { + let input = " ( << p1 err goal)"; + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0); + parser.expect_end(); + assert!(parser.errors.has_errors()); + let mut out = String::new(); + parser.errors.write_detailed(input, &mut out).unwrap(); + assert_eq!( + out, + "\x1b[93m1:10 Error:\x1b[0m Unexpected 'err', expected '>>'\n\ + | ( << p1 err goal)\n\ + | ^^^\n" + ); + assert_eq!( + expr, + Expr::new( + Span::new(1, 19), + ExprKind::Paren(Expr::new_error().into()) + ) + ); } From e74ee1eb3513b294061d846d8fffef167432d28e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Tue, 5 Sep 2023 08:55:03 +0200 Subject: [PATCH 16/38] Working parser --- cgaal-engine/src/parsing/parser.rs | 266 +++++++++++++++++++---------- cgaal-engine/tests/parsing.rs | 65 +++---- 2 files changed, 202 insertions(+), 129 deletions(-) diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index f06932464..0fc4804b4 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -1,15 +1,60 @@ use crate::parsing::ast::{BinaryOpKind, Coalition, CoalitionKind, Expr, ExprKind, UnaryOpKind}; use crate::parsing::errors::ErrorLog; use crate::parsing::lexer::Lexer; -use crate::parsing::parser::ParseError::Unexpected; use crate::parsing::span::Span; use crate::parsing::token::{Token, TokenKind}; use std::iter::Peekable; use std::sync::Arc; +macro_rules! recover { + ($self:expr, $val:expr, $recover_token:expr, $err_val:expr) => {{ + $self.recovery_tokens.push($recover_token); + let res = match $val { + Ok(v) => { + match $self.lexer.peek() { + Some(Token { kind, .. }) if kind == &$recover_token => { + // Happy path + let tok = $self.lexer.next().unwrap(); + $self.recovery_tokens.pop(); + Ok((tok.span, v)) + } + Some(Token { kind, span }) => { + $self.errors.log( + *span, + format!("Unexpected '{}', expected '{}'", kind, $recover_token), + ); + Err(ParseError) + } + _ => { + $self + .errors + .log_msg(format!("Unexpected EOF, expected '{}'", $recover_token)); + Err(ParseError) + } + } + } + Err(_) => Err(ParseError), + }; + res.or_else(|_| { + // Unhappy path + // Try recover + $self.skip_until_recovery(); + $self.recovery_tokens.pop(); + match $self.lexer.peek() { + Some(Token { kind, .. }) if kind == &$recover_token => { + let tok = $self.lexer.next().unwrap(); + Ok((tok.span, $err_val)) + } + _ => Err(ParseError), + } + }) + }}; +} + pub struct Parser<'a> { lexer: Peekable>, pub errors: ErrorLog, + recovery_tokens: Vec, } impl<'a> Parser<'a> { @@ -17,6 +62,7 @@ impl<'a> Parser<'a> { Parser { lexer: lexer.peekable(), errors: ErrorLog::new(), + recovery_tokens: vec![], } } @@ -31,55 +77,56 @@ impl<'a> Parser<'a> { while let Some(_) = self.lexer.next() {} } - pub fn expr(&mut self, min_prec: u8) -> Expr { + pub fn expr(&mut self, min_prec: u8) -> Result { // Pratt parsing/precedence climbing: https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing - let mut lhs = self.term(); + let mut lhs = self.term()?; let span_start = lhs.span; loop { - let Some(op): Option = self.lexer.peek().and_then(|t| t.kind().clone().try_into().ok()) else { return lhs }; + let Some(op): Option = self.lexer.peek().and_then(|t| t.kind().clone().try_into().ok()) else { return Ok(lhs) }; if op.precedence() < min_prec { - return lhs; + return Ok(lhs); } self.lexer.next(); let new_prec = op.precedence() + if op.is_right_associative() { 0 } else { 1 }; - let rhs = self.expr(new_prec); + let rhs = self.expr(new_prec)?; let span = span_start + rhs.span; let kind = ExprKind::Binary(op, lhs.into(), rhs.into()); lhs = Expr::new(span, kind); } } - pub fn path_expr(&mut self) -> Expr { + pub fn path_expr(&mut self) -> Result { match self.lexer.peek().map(|t| t.kind()) { Some(TokenKind::Lparen) => { let begin = self.lexer.next().unwrap().span; - let lhs = self.expr(BinaryOpKind::Until.precedence()); - let Ok(_) = self.expect_and_consume(TokenKind::Word("U".to_string())) else { - return Expr::new_error(); - }; - let rhs = self.expr(0); - let end = self.expect_and_consume(TokenKind::Rparen).unwrap(); - Expr::new( + let (_, lhs) = recover!( + self, + self.expr(BinaryOpKind::Until.precedence()), + TokenKind::Word("U".to_string()), + Expr::new_error() + )?; + let (end, rhs) = + recover!(self, self.expr(0), TokenKind::Rparen, Expr::new_error())?; + Ok(Expr::new( begin + end, ExprKind::Binary(BinaryOpKind::Until, lhs.into(), rhs.into()), - ) + )) } Some(TokenKind::Word(w)) if w == "F" => { let begin = self.lexer.next().unwrap().span; - let expr = self.expr(0); - Expr::new( + let expr = self.expr(0)?; + Ok(Expr::new( begin + expr.span, ExprKind::Unary(UnaryOpKind::Eventually, expr.into()), - ) + )) } - Some(TokenKind::Word(w)) if w == "G" => { let begin = self.lexer.next().unwrap().span; - let expr = self.expr(0); - Expr::new( + let expr = self.expr(0)?; + Ok(Expr::new( begin + expr.span, ExprKind::Unary(UnaryOpKind::Invariantly, expr.into()), - ) + )) } // Unexpected Some(_) => { @@ -88,104 +135,109 @@ impl<'a> Parser<'a> { tok.span, format!("Unexpected '{}', expected path expression", tok.kind), ); - Expr::new_error() + Err(ParseError) } None => { self.errors .log_msg("Unexpected EOF, expected path expression".to_string()); - Expr::new_error() + Err(ParseError) } } } - pub fn term(&mut self) -> Expr { + pub fn term(&mut self) -> Result { match self.lexer.peek().map(|t| t.kind()) { Some(TokenKind::Lparen) => self.paren(), Some(TokenKind::True) => { let tok = self.lexer.next().unwrap(); - Expr::new(tok.span, ExprKind::True) + Ok(Expr::new(tok.span, ExprKind::True)) } Some(TokenKind::False) => { let tok = self.lexer.next().unwrap(); - Expr::new(tok.span, ExprKind::False) + Ok(Expr::new(tok.span, ExprKind::False)) } Some(TokenKind::Word(_)) => self.ident(), Some(TokenKind::Llangle) => self.enforce_coalition(), Some(TokenKind::Llbracket) => self.despite_coalition(), // Unexpected Some(_) => { - let tok = self.lexer.next().unwrap(); + let tok = self.lexer.peek().unwrap(); self.errors.log( tok.span, format!("Unexpected '{}', expected expression term", tok.kind), ); - Expr::new_error() + Err(ParseError) } None => { self.errors .log_msg("Unexpected EOF, expected expression term".to_string()); - Expr::new_error() + Err(ParseError) } } } - pub fn paren(&mut self) -> Expr { - let Ok(begin) = self.expect_and_consume(TokenKind::Lparen) else { - return Expr::new_error(); - }; - let expr = self.expr(0); - let Ok(end) = self.expect_consume_or_recover(TokenKind::Rparen) else { - return Expr::new_error(); - }; - Expr::new(begin + end, ExprKind::Paren(Arc::new(expr))) + pub fn paren(&mut self) -> Result { + let begin = self.expect_and_consume(TokenKind::Lparen)?; + recover!(self, self.expr(0), TokenKind::Rparen, Expr::new_error()) + .map(|(end, expr)| Expr::new(begin + end, ExprKind::Paren(Arc::new(expr)))) } - pub fn ident(&mut self) -> Expr { - let tok = self.lexer.next().unwrap(); - Expr::new(tok.span, ExprKind::Ident(tok.kind.to_string())) + pub fn ident(&mut self) -> Result { + match self.lexer.peek() { + Some(Token { + kind: TokenKind::Word(_), + .. + }) => { + let tok = self.lexer.next().unwrap(); + Ok(Expr::new(tok.span, ExprKind::Ident(tok.kind.to_string()))) + } + Some(tok) => { + self.errors.log( + tok.span, + format!("Unexpected '{}', expected identifier", tok.kind), + ); + Err(ParseError) + } + None => { + self.errors + .log_msg("Unexpected EOF, expected identifier".to_string()); + Err(ParseError) + } + } } - pub fn enforce_coalition(&mut self) -> Expr { - let Ok(coal_begin) = self.expect_and_consume(TokenKind::Llangle) else { - return Expr::new_error(); - }; - let players = self.players(); - let Ok(coal_end) = self.expect_and_consume(TokenKind::Rrangle) else { - return Expr::new_error(); - }; - let expr = self.path_expr(); - Expr::new( - coal_begin + expr.span, + pub fn enforce_coalition(&mut self) -> Result { + let begin = self.expect_and_consume(TokenKind::Llangle)?; + let (end, players) = recover!(self, self.coalition_players(), TokenKind::Rrangle, vec![])?; + let expr = self.path_expr()?; + Ok(Expr::new( + begin + expr.span, ExprKind::Coalition(Coalition::new( - coal_begin + coal_end, + begin + end, players, CoalitionKind::Enforce, Arc::new(expr), )), - ) + )) } - pub fn despite_coalition(&mut self) -> Expr { - let Ok(coal_begin) = self.expect_and_consume(TokenKind::Llbracket) else { - return Expr::new_error(); - }; - let players = self.players(); - let Ok(coal_end) = self.expect_and_consume(TokenKind::Rrbracket) else { - return Expr::new_error(); - }; - let expr = self.path_expr(); - Expr::new( - coal_begin + expr.span, + pub fn despite_coalition(&mut self) -> Result { + let begin = self.expect_and_consume(TokenKind::Llbracket)?; + let (end, players) = + recover!(self, self.coalition_players(), TokenKind::Rrbracket, vec![])?; + let expr = self.path_expr()?; + Ok(Expr::new( + begin + expr.span, ExprKind::Coalition(Coalition::new( - coal_begin + coal_end, + begin + end, players, CoalitionKind::Despite, Arc::new(expr), )), - ) + )) } - pub fn players(&mut self) -> Vec { + pub fn coalition_players(&mut self) -> Result, ParseError> { let mut players = vec![]; loop { match self.lexer.peek() { @@ -199,19 +251,63 @@ impl<'a> Parser<'a> { } _ => break, } - if self.next_is(TokenKind::Comma) { - self.lexer.next(); - } else { - break; + match self.lexer.peek() { + Some(Token { + kind: TokenKind::Comma, + .. + }) => { + self.lexer.next().unwrap(); + } + Some(Token { + kind: TokenKind::Rrangle | TokenKind::Rrbracket, + .. + }) => break, + Some(tok) => { + self.errors.log( + tok.span, + format!( + "Unexpected '{}', expected '{}' or '{}'", + tok.kind, + TokenKind::Comma, + self.recovery_tokens.last().unwrap() + ), + ); + return Err(ParseError); + } + _ => { + self.errors.log_msg(format!( + "Unexpected EOF, expected '{}' or '{}'", + TokenKind::Comma, + self.recovery_tokens.last().unwrap() + )); + return Err(ParseError); + } } } - players + Ok(players) } fn next_is(&mut self, kind: TokenKind) -> bool { matches!(self.lexer.peek(), Some(tok) if tok.kind == kind) } + fn expect(&mut self, kind: TokenKind) -> Result<(), ParseError> { + match self.lexer.peek() { + None => { + self.errors + .log_msg(format!("Unexpected EOF, expected '{}'", kind)); + Err(ParseError) + } + Some(tok) => { + self.errors.log( + tok.span, + format!("Unexpected '{}', expected '{}'", tok.kind, kind), + ); + Err(ParseError) + } + } + } + fn expect_and_consume(&mut self, kind: TokenKind) -> Result { match self.lexer.next() { Some(tok) if tok.kind == kind => Ok(tok.span), @@ -220,29 +316,25 @@ impl<'a> Parser<'a> { tok.span, format!("Unexpected '{}', expected '{}'", tok.kind, kind), ); - Err(Unexpected(Some(tok))) + Err(ParseError) } None => { self.errors .log_msg(format!("Unexpected EOF, expected '{}'", kind)); - Err(Unexpected(None)) + Err(ParseError) } } } - fn expect_consume_or_recover(&mut self, kind: TokenKind) -> Result { - self.expect_and_consume(kind.clone()).or_else(|err| { - while let Some(tok) = self.lexer.next() { - if tok.kind == kind { - return Ok(tok.span); - } + fn skip_until_recovery(&mut self) { + while let Some(tok) = self.lexer.peek() { + if self.recovery_tokens.contains(&tok.kind) { + return; } - Err(err) - }) + self.lexer.next(); + } } } #[derive(Debug, Eq, PartialEq)] -pub enum ParseError { - Unexpected(Option), -} +pub struct ParseError; diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing.rs index 6f0dc5957..d308b91d2 100644 --- a/cgaal-engine/tests/parsing.rs +++ b/cgaal-engine/tests/parsing.rs @@ -8,7 +8,7 @@ fn basic_expr_001() { let input = "true && false"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); + let expr = parser.expr(0).expect("Failed to parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -33,7 +33,7 @@ fn basic_expr_002() { let input = "true && false || true"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); + let expr = parser.expr(0).expect("Failed to parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -66,7 +66,7 @@ fn basic_expr_003() { let input = "foo || true && bar || (true || false)"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); + let expr = parser.expr(0).expect("Failed to parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -123,7 +123,7 @@ fn atl_expr_001() { let input = "<> F goal"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); + let expr = parser.expr(0).expect("failed to parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -159,7 +159,7 @@ fn atl_expr_002() { let input = "[[p1, p2]] G safe"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); + let expr = parser.expr(0).expect("failed to parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -195,7 +195,7 @@ fn atl_expr_003() { let input = "<<>> (safe U goal)"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); + let expr = parser.expr(0).expect("failed to parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -229,8 +229,7 @@ fn erroneous_expr_001() { let input = "true && "; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); - parser.expect_end(); + assert!(parser.expr(0).is_err()); assert!(parser.errors.has_errors()); let mut out = String::new(); parser.errors.write_detailed(input, &mut out).unwrap(); @@ -238,17 +237,6 @@ fn erroneous_expr_001() { out, "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected expression term\n" ); - assert_eq!( - expr, - Expr::new( - Span::new(0, 4), - ExprKind::Binary( - BinaryOpKind::And, - Expr::new(Span::new(0, 4), ExprKind::True).into(), - Expr::new_error().into() - ) - ) - ); } #[test] @@ -256,7 +244,9 @@ fn erroneous_expr_002() { let input = "foo bar"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); + let expr = parser + .expr(0) + .expect("Failed to recover while parsing expression"); parser.expect_end(); assert!(parser.errors.has_errors()); let mut out = String::new(); @@ -278,7 +268,9 @@ fn erroneous_expr_003() { let input = "(foo false) && true"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); + let expr = parser + .expr(0) + .expect("Failed to recover while parsing expression"); parser.expect_end(); assert!(parser.errors.has_errors()); let mut out = String::new(); @@ -295,13 +287,7 @@ fn erroneous_expr_003() { Span::new(0, 19), ExprKind::Binary( BinaryOpKind::And, - Expr::new( - Span::new(0, 11), - ExprKind::Paren( - Expr::new(Span::new(1, 4), ExprKind::Ident("foo".to_string()),).into() - ) - ) - .into(), + Expr::new(Span::new(0, 11), ExprKind::Paren(Expr::new_error().into(),)).into(), Expr::new(Span::new(15, 19), ExprKind::True).into() ) ) @@ -313,8 +299,7 @@ fn erroneous_expr_004() { let input = "(true"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); - parser.expect_end(); + assert!(parser.expr(0).is_err()); assert!(parser.errors.has_errors()); let mut out = String::new(); parser.errors.write_detailed(input, &mut out).unwrap(); @@ -322,7 +307,6 @@ fn erroneous_expr_004() { out, "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected ')'\n" ); - assert_eq!(expr, Expr::new_error()); } #[test] @@ -330,8 +314,7 @@ fn erroneous_expr_005() { let input = "(foo bar"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); - parser.expect_end(); + assert!(parser.expr(0).is_err()); assert!(parser.errors.has_errors()); let mut out = String::new(); parser.errors.write_detailed(input, &mut out).unwrap(); @@ -341,30 +324,28 @@ fn erroneous_expr_005() { | (foo bar\n\ | ^^^\n" ); - assert_eq!(expr, Expr::new_error()); } #[test] fn erroneous_expr_006() { - let input = " ( << p1 err goal)"; + let input = " ( << p1 foo goal)"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0); + let expr = parser + .expr(0) + .expect("Failed to recover while parsing expression"); parser.expect_end(); assert!(parser.errors.has_errors()); let mut out = String::new(); parser.errors.write_detailed(input, &mut out).unwrap(); assert_eq!( out, - "\x1b[93m1:10 Error:\x1b[0m Unexpected 'err', expected '>>'\n\ - | ( << p1 err goal)\n\ + "\x1b[93m1:10 Error:\x1b[0m Unexpected 'foo', expected ',' or '>>'\n\ + | ( << p1 foo goal)\n\ | ^^^\n" ); assert_eq!( expr, - Expr::new( - Span::new(1, 19), - ExprKind::Paren(Expr::new_error().into()) - ) + Expr::new(Span::new(1, 18), ExprKind::Paren(Expr::new_error().into())) ); } From b2d955cfa854d50ed6db3f0b08c9cc69692943fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Tue, 5 Sep 2023 13:26:15 +0200 Subject: [PATCH 17/38] Clean up parser --- cgaal-cli/src/main.rs | 5 +-- cgaal-engine/src/atl/parser.rs | 6 +-- cgaal-engine/src/game_structure/lcgs/parse.rs | 4 +- cgaal-engine/src/parsing/ast.rs | 11 +++-- cgaal-engine/src/parsing/errors.rs | 33 +++++++++------ cgaal-engine/src/parsing/lexer.rs | 14 +++---- cgaal-engine/src/parsing/mod.rs | 21 +++++++++- cgaal-engine/src/parsing/parser.rs | 40 +++++------------- cgaal-engine/src/parsing/span.rs | 2 +- cgaal-engine/tests/parsing.rs | 42 +++++++------------ 10 files changed, 82 insertions(+), 96 deletions(-) diff --git a/cgaal-cli/src/main.rs b/cgaal-cli/src/main.rs index f7f07aa50..5f018777e 100644 --- a/cgaal-cli/src/main.rs +++ b/cgaal-cli/src/main.rs @@ -335,10 +335,7 @@ fn main_inner() -> Result<(), String> { /// Reads a formula in JSON format from a file and returns the formula as a string /// and as a parsed Phi struct. /// This function will exit the program if it encounters an error. -fn load_formula( - path: &str, - formula_type: FormulaType, -) -> Phi { +fn load_formula(path: &str, formula_type: FormulaType) -> Phi { let mut file = File::open(path).unwrap_or_else(|err| { eprintln!("Failed to open formula file\n\nError:\n{}", err); exit(1); diff --git a/cgaal-engine/src/atl/parser.rs b/cgaal-engine/src/atl/parser.rs index 7d08513c3..89acd90d9 100644 --- a/cgaal-engine/src/atl/parser.rs +++ b/cgaal-engine/src/atl/parser.rs @@ -3,8 +3,6 @@ use std::str::{self}; use crate::atl::Phi; /// Parse an ATL formula -pub fn parse_phi( - _input: &str, -) -> Result { +pub fn parse_phi(_input: &str) -> Result { Ok(Phi::True) -} \ No newline at end of file +} diff --git a/cgaal-engine/src/game_structure/lcgs/parse.rs b/cgaal-engine/src/game_structure/lcgs/parse.rs index 1dbbdaafd..e8103ccc3 100644 --- a/cgaal-engine/src/game_structure/lcgs/parse.rs +++ b/cgaal-engine/src/game_structure/lcgs/parse.rs @@ -78,9 +78,7 @@ fn number<'a>() -> Parser<'a, u8, Expr> { .collect() .convert(str::from_utf8) .convert(i32::from_str); - parsed - .map(|v| Expr { kind: Number(v) }) - .name("number") + parsed.map(|v| Expr { kind: Number(v) }).name("number") } /// Parser that parses a symbol name. It must start with an alpha character, but subsequent diff --git a/cgaal-engine/src/parsing/ast.rs b/cgaal-engine/src/parsing/ast.rs index 40a0154bd..ee8a31bfe 100644 --- a/cgaal-engine/src/parsing/ast.rs +++ b/cgaal-engine/src/parsing/ast.rs @@ -1,7 +1,7 @@ use crate::game_structure::Proposition; use crate::parsing::span::Span; -use std::sync::Arc; use crate::parsing::token::TokenKind; +use std::sync::Arc; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Expr { @@ -53,9 +53,8 @@ pub enum BinaryOpKind { impl BinaryOpKind { pub fn associativity(&self) -> Associativity { - match self { - _ => Associativity::LeftToRight, - } + // All operators are left-associative so far + Associativity::LeftToRight } pub fn is_right_associative(&self) -> bool { @@ -78,7 +77,7 @@ impl TryFrom for BinaryOpKind { match value { TokenKind::AmpAmp => Ok(BinaryOpKind::And), TokenKind::PipePipe => Ok(BinaryOpKind::Or), - _ => Err(()) + _ => Err(()), } } } @@ -112,4 +111,4 @@ pub enum CoalitionKind { pub enum Associativity { LeftToRight, RightToLeft, -} \ No newline at end of file +} diff --git a/cgaal-engine/src/parsing/errors.rs b/cgaal-engine/src/parsing/errors.rs index 04207817c..69603cb59 100644 --- a/cgaal-engine/src/parsing/errors.rs +++ b/cgaal-engine/src/parsing/errors.rs @@ -1,5 +1,6 @@ -use std::cmp::{max, min}; use crate::parsing::span::Span; +use std::cmp::{max, min}; +use std::fmt::Write; #[derive(Debug, Default)] pub struct ErrorLog { @@ -35,7 +36,8 @@ impl ErrorLog { self.errors.is_empty() } - pub fn write_detailed(&self, orig_input: &str, f: &mut dyn std::fmt::Write) -> std::fmt::Result { + pub fn to_string(&self, orig_input: &str) -> String { + let mut out = String::new(); for entry in &self.errors { match &entry.span { Some(span) => { @@ -62,17 +64,18 @@ impl ErrorLog { } i += 1; } - writeln!(f, "\x1b[93m{}:{} Error:\x1b[0m {}", line, col, entry.msg)?; + writeln!(out, "\x1b[93m{}:{} Error:\x1b[0m {}", line, col, entry.msg).unwrap(); // Print the lines with the error, and underline the error span while line_start < span.end && i <= chars.len() { if i == chars.len() || chars[i] == b'\n' { - writeln!(f, "| {}", &orig_input[line_start..i])?; + writeln!(out, "| {}", &orig_input[line_start..i]).unwrap(); let highlight_start = max(line_start, span.begin); let highlight_end = min(i, span.end); - write!(f, "| ")?; - write!(f, "{}", " ".repeat(highlight_start - line_start))?; - writeln!(f, "{}", "^".repeat(highlight_end - highlight_start))?; + write!(out, "| ").unwrap(); + write!(out, "{}", " ".repeat(highlight_start - line_start)).unwrap(); + writeln!(out, "{}", "^".repeat(highlight_end - highlight_start)) + .unwrap(); line += 1; line_start = i + 1; } @@ -80,11 +83,11 @@ impl ErrorLog { } } None => { - writeln!(f, "\x1b[93m@ Error:\x1b[0m {}", entry.msg)?; + writeln!(out, "\x1b[93m@ Error:\x1b[0m {}", entry.msg).unwrap(); } } } - Ok(()) + out } } @@ -96,7 +99,10 @@ pub struct ErrorEntry { impl ErrorEntry { pub fn new(span: Span, msg: String) -> Self { - ErrorEntry { span: Some(span), msg } + ErrorEntry { + span: Some(span), + msg, + } } pub fn msg_only(msg: String) -> Self { @@ -119,7 +125,10 @@ mod tests { "; let mut log = ErrorLog::new(); let i = input.find("robot").unwrap(); - log.log(Span::new(i, i + 5), "Unknown identifier 'robot'".to_string()); + log.log( + Span::new(i, i + 5), + "Unknown identifier 'robot'".to_string(), + ); let mut out = String::new(); log.write_detailed(input, &mut out).unwrap(); assert_eq!( @@ -151,4 +160,4 @@ mod tests { | ^^^^^^^^^^^^^^\n" ); } -} \ No newline at end of file +} diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs index 006157fb7..530573828 100644 --- a/cgaal-engine/src/parsing/lexer.rs +++ b/cgaal-engine/src/parsing/lexer.rs @@ -95,11 +95,11 @@ impl<'a> Iterator for Lexer<'a> { b'[' => match self.peek(1) { Some(b'[') => self.token(2, TokenKind::Llbracket), _ => self.token(1, TokenKind::Lbracket), - } + }, b']' => match self.peek(1) { Some(b']') => self.token(2, TokenKind::Rrbracket), _ => self.token(1, TokenKind::Rbracket), - } + }, b'+' => self.token(1, TokenKind::Plus), b'-' => match self.peek(1) { Some(b'>') => self.token(2, TokenKind::Arrow), @@ -110,26 +110,26 @@ impl<'a> Iterator for Lexer<'a> { b'&' => match self.peek(1) { Some(b'&') => self.token(2, TokenKind::AmpAmp), _ => self.token(1, TokenKind::Err), - } + }, b'|' => match self.peek(1) { Some(b'|') => self.token(2, TokenKind::PipePipe), _ => self.token(1, TokenKind::Err), - } + }, b'^' => self.token(1, TokenKind::Hat), b'?' => self.token(1, TokenKind::Question), b'!' => match self.peek(1) { Some(b'=') => self.token(2, TokenKind::Neq), _ => self.token(1, TokenKind::Bang), - } + }, b'=' => match self.peek(1) { Some(b'=') => self.token(2, TokenKind::Eq), _ => self.token(1, TokenKind::Assign), - } + }, b',' => self.token(1, TokenKind::Comma), b'.' => match self.peek(1) { Some(b'.') => self.token(2, TokenKind::DotDot), _ => self.token(1, TokenKind::Dot), - } + }, b';' => self.token(1, TokenKind::Semi), b':' => self.token(1, TokenKind::Colon), b'\'' => self.token(1, TokenKind::Prime), diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 72e9d84c7..75497a264 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -1,6 +1,23 @@ +use crate::parsing::ast::Expr; +use crate::parsing::errors::ErrorLog; +use crate::parsing::lexer::Lexer; +use crate::parsing::parser::Parser; + +pub mod ast; mod errors; pub mod lexer; +pub mod parser; pub mod span; mod token; -pub mod parser; -pub mod ast; + +pub fn parse_atl(input: &str) -> Result { + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer); + let expr = parser.expr(0).unwrap_or(Expr::new_error()); + parser.expect_end(); + if parser.errors.has_errors() { + Err(parser.errors) + } else { + Ok(expr) + } +} diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index 0fc4804b4..183bcfaf2 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -74,7 +74,7 @@ impl<'a> Parser<'a> { .log(tok.span, format!("Unexpected '{}', expected EOF", tok.kind)); } } - while let Some(_) = self.lexer.next() {} + for _ in self.lexer.by_ref() {}; } pub fn expr(&mut self, min_prec: u8) -> Result { @@ -177,7 +177,7 @@ impl<'a> Parser<'a> { } pub fn paren(&mut self) -> Result { - let begin = self.expect_and_consume(TokenKind::Lparen)?; + let begin = self.token(TokenKind::Lparen)?; recover!(self, self.expr(0), TokenKind::Rparen, Expr::new_error()) .map(|(end, expr)| Expr::new(begin + end, ExprKind::Paren(Arc::new(expr)))) } @@ -207,7 +207,7 @@ impl<'a> Parser<'a> { } pub fn enforce_coalition(&mut self) -> Result { - let begin = self.expect_and_consume(TokenKind::Llangle)?; + let begin = self.token(TokenKind::Llangle)?; let (end, players) = recover!(self, self.coalition_players(), TokenKind::Rrangle, vec![])?; let expr = self.path_expr()?; Ok(Expr::new( @@ -222,7 +222,7 @@ impl<'a> Parser<'a> { } pub fn despite_coalition(&mut self) -> Result { - let begin = self.expect_and_consume(TokenKind::Llbracket)?; + let begin = self.token(TokenKind::Llbracket)?; let (end, players) = recover!(self, self.coalition_players(), TokenKind::Rrbracket, vec![])?; let expr = self.path_expr()?; @@ -239,15 +239,16 @@ impl<'a> Parser<'a> { pub fn coalition_players(&mut self) -> Result, ParseError> { let mut players = vec![]; + #[allow(clippy::while_let_loop)] loop { match self.lexer.peek() { Some(Token { - span, - kind: TokenKind::Word(w), + kind: TokenKind::Word(_), + .. }) => { - let p = Expr::new(span.clone(), ExprKind::Ident(w.to_string())); + let tok = self.lexer.next().unwrap(); + let p = Expr::new(tok.span, ExprKind::Ident(tok.kind.to_string())); players.push(p); - self.lexer.next().unwrap(); } _ => break, } @@ -287,28 +288,7 @@ impl<'a> Parser<'a> { Ok(players) } - fn next_is(&mut self, kind: TokenKind) -> bool { - matches!(self.lexer.peek(), Some(tok) if tok.kind == kind) - } - - fn expect(&mut self, kind: TokenKind) -> Result<(), ParseError> { - match self.lexer.peek() { - None => { - self.errors - .log_msg(format!("Unexpected EOF, expected '{}'", kind)); - Err(ParseError) - } - Some(tok) => { - self.errors.log( - tok.span, - format!("Unexpected '{}', expected '{}'", tok.kind, kind), - ); - Err(ParseError) - } - } - } - - fn expect_and_consume(&mut self, kind: TokenKind) -> Result { + fn token(&mut self, kind: TokenKind) -> Result { match self.lexer.next() { Some(tok) if tok.kind == kind => Ok(tok.span), Some(tok) => { diff --git a/cgaal-engine/src/parsing/span.rs b/cgaal-engine/src/parsing/span.rs index d36f34b79..d70c7394d 100644 --- a/cgaal-engine/src/parsing/span.rs +++ b/cgaal-engine/src/parsing/span.rs @@ -88,4 +88,4 @@ impl Display for LineColumn { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}", self.line, self.column) } -} \ No newline at end of file +} diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing.rs index d308b91d2..067edb175 100644 --- a/cgaal-engine/tests/parsing.rs +++ b/cgaal-engine/tests/parsing.rs @@ -8,7 +8,7 @@ fn basic_expr_001() { let input = "true && false"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0).expect("Failed to parse expression"); + let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -33,7 +33,7 @@ fn basic_expr_002() { let input = "true && false || true"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0).expect("Failed to parse expression"); + let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -66,7 +66,7 @@ fn basic_expr_003() { let input = "foo || true && bar || (true || false)"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0).expect("Failed to parse expression"); + let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -123,7 +123,7 @@ fn atl_expr_001() { let input = "<> F goal"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0).expect("failed to parse expression"); + let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -159,7 +159,7 @@ fn atl_expr_002() { let input = "[[p1, p2]] G safe"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0).expect("failed to parse expression"); + let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -195,7 +195,7 @@ fn atl_expr_003() { let input = "<<>> (safe U goal)"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser.expr(0).expect("failed to parse expression"); + let expr = parser.expr(0).expect("Error should be recoverable"); parser.expect_end(); assert!( parser.errors.is_empty(), @@ -231,8 +231,7 @@ fn erroneous_expr_001() { let mut parser = Parser::new(lexer); assert!(parser.expr(0).is_err()); assert!(parser.errors.has_errors()); - let mut out = String::new(); - parser.errors.write_detailed(input, &mut out).unwrap(); + let out = parser.errors.to_string(input); assert_eq!( out, "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected expression term\n" @@ -244,13 +243,10 @@ fn erroneous_expr_002() { let input = "foo bar"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser - .expr(0) - .expect("Failed to recover while parsing expression"); + let expr = parser.expr(0).expect("Error should be recoverable"); parser.expect_end(); assert!(parser.errors.has_errors()); - let mut out = String::new(); - parser.errors.write_detailed(input, &mut out).unwrap(); + let out = parser.errors.to_string(input); assert_eq!( out, "\x1b[93m1:5 Error:\x1b[0m Unexpected 'bar', expected EOF\n\ @@ -268,13 +264,10 @@ fn erroneous_expr_003() { let input = "(foo false) && true"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser - .expr(0) - .expect("Failed to recover while parsing expression"); + let expr = parser.expr(0).expect("Error should be recoverable"); parser.expect_end(); assert!(parser.errors.has_errors()); - let mut out = String::new(); - parser.errors.write_detailed(input, &mut out).unwrap(); + let out = parser.errors.to_string(input); assert_eq!( out, "\x1b[93m1:6 Error:\x1b[0m Unexpected 'false', expected ')'\n\ @@ -301,8 +294,7 @@ fn erroneous_expr_004() { let mut parser = Parser::new(lexer); assert!(parser.expr(0).is_err()); assert!(parser.errors.has_errors()); - let mut out = String::new(); - parser.errors.write_detailed(input, &mut out).unwrap(); + let out = parser.errors.to_string(input); assert_eq!( out, "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected ')'\n" @@ -316,8 +308,7 @@ fn erroneous_expr_005() { let mut parser = Parser::new(lexer); assert!(parser.expr(0).is_err()); assert!(parser.errors.has_errors()); - let mut out = String::new(); - parser.errors.write_detailed(input, &mut out).unwrap(); + let out = parser.errors.to_string(input); assert_eq!( out, "\x1b[93m1:6 Error:\x1b[0m Unexpected 'bar', expected ')'\n\ @@ -331,13 +322,10 @@ fn erroneous_expr_006() { let input = " ( << p1 foo goal)"; let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer); - let expr = parser - .expr(0) - .expect("Failed to recover while parsing expression"); + let expr = parser.expr(0).expect("Error should be recoverable"); parser.expect_end(); assert!(parser.errors.has_errors()); - let mut out = String::new(); - parser.errors.write_detailed(input, &mut out).unwrap(); + let out = parser.errors.to_string(input); assert_eq!( out, "\x1b[93m1:10 Error:\x1b[0m Unexpected 'foo', expected ',' or '>>'\n\ From 4af0b2fa2321468bf542aebdbc9c5017ccbb48a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Wed, 6 Sep 2023 09:05:15 +0200 Subject: [PATCH 18/38] Add conversion to phi --- cgaal-cli/src/main.rs | 1 + cgaal-engine/src/atl/convert.rs | 141 ++++++++++++++++++++++++++++++++ cgaal-engine/src/atl/mod.rs | 3 +- cgaal-engine/src/atl/parser.rs | 8 -- cgaal-engine/src/parsing/ast.rs | 2 - cgaal-engine/src/parsing/mod.rs | 2 +- 6 files changed, 144 insertions(+), 13 deletions(-) create mode 100644 cgaal-engine/src/atl/convert.rs delete mode 100644 cgaal-engine/src/atl/parser.rs diff --git a/cgaal-cli/src/main.rs b/cgaal-cli/src/main.rs index 5f018777e..22f0e2c5e 100644 --- a/cgaal-cli/src/main.rs +++ b/cgaal-cli/src/main.rs @@ -27,6 +27,7 @@ use cgaal_engine::game_structure::lcgs::ir::intermediate::IntermediateLcgs; use cgaal_engine::game_structure::lcgs::ir::symbol_table::Owner; use cgaal_engine::game_structure::lcgs::parse::parse_lcgs; use cgaal_engine::game_structure::{EagerGameStructure, GameStructure}; +use cgaal_engine::parsing::parse_atl; #[cfg(feature = "graph-printer")] use cgaal_engine::printer::print_graph; diff --git a/cgaal-engine/src/atl/convert.rs b/cgaal-engine/src/atl/convert.rs new file mode 100644 index 000000000..0a92ed8b9 --- /dev/null +++ b/cgaal-engine/src/atl/convert.rs @@ -0,0 +1,141 @@ +use crate::atl::Phi; +use crate::game_structure::lcgs::ast::DeclKind; +use crate::game_structure::lcgs::ir::intermediate::IntermediateLcgs; +use crate::game_structure::lcgs::ir::symbol_table::Owner; +use crate::game_structure::Player; +use crate::parsing::ast::{BinaryOpKind, Coalition, CoalitionKind, Expr, ExprKind, UnaryOpKind}; +use crate::parsing::errors::ErrorLog; + +pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut ErrorLog) -> Option { + let Expr { span, kind } = expr; + match kind { + ExprKind::True => Some(Phi::True), + ExprKind::False => Some(Phi::False), + ExprKind::Paren(e) => convert_expr_to_phi(e, game, errors), + ExprKind::Ident(_) => todo!(), + ExprKind::Unary(op, e) => match op { + UnaryOpKind::Not => Some(Phi::Not(convert_expr_to_phi(e, game, errors)?.into())), + UnaryOpKind::Next | UnaryOpKind::Eventually | UnaryOpKind::Invariantly => { + errors.log( + span.clone(), + "Temporal operators are only allowed after a coalition".to_string(), + ); + None + } + }, + ExprKind::Binary(op, lhs, rhs) => match op { + BinaryOpKind::And => Some(Phi::And( + convert_expr_to_phi(lhs, game, errors)?.into(), + convert_expr_to_phi(rhs, game, errors)?.into(), + )), + BinaryOpKind::Or => Some(Phi::Or( + convert_expr_to_phi(lhs, game, errors)?.into(), + convert_expr_to_phi(rhs, game, errors)?.into(), + )), + BinaryOpKind::Until => { + errors.log( + span.clone(), + "Temporal operators are only allowed after a coalition".to_string(), + ); + None + } + }, + ExprKind::Coalition(Coalition { + players, + kind, + expr: path_expr, + .. + }) => { + match (kind, &path_expr.kind) { + (CoalitionKind::Enforce, ExprKind::Unary(UnaryOpKind::Next, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::EnforceNext { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Despite, ExprKind::Unary(UnaryOpKind::Next, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::DespiteNext { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Enforce, ExprKind::Unary(UnaryOpKind::Eventually, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::EnforceEventually { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Despite, ExprKind::Unary(UnaryOpKind::Eventually, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::DespiteEventually { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Enforce, ExprKind::Unary(UnaryOpKind::Invariantly, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::EnforceInvariant { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Despite, ExprKind::Unary(UnaryOpKind::Invariantly, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::DespiteInvariant { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Enforce, ExprKind::Binary(BinaryOpKind::Until, lhs, rhs)) => { + let lhs_phi = convert_expr_to_phi(lhs, game, errors)?; + let rhs_phi = convert_expr_to_phi(rhs, game, errors)?; + Some(Phi::EnforceUntil { + players: convert_players(players, game, errors)?, + pre: lhs_phi.into(), + until: rhs_phi.into(), + }) + } + (CoalitionKind::Despite, ExprKind::Binary(BinaryOpKind::Until, lhs, rhs)) => { + let lhs_phi = convert_expr_to_phi(lhs, game, errors)?; + let rhs_phi = convert_expr_to_phi(rhs, game, errors)?; + Some(Phi::DespiteUntil { + players: convert_players(players, game, errors)?, + pre: lhs_phi.into(), + until: rhs_phi.into(), + }) + } + _ => { + errors.log(path_expr.span.clone(), "Coalitions must be followed by a path formula".to_string()); + None + } + } + } + ExprKind::Error => None, + } +} + +fn convert_players(players: &[Expr], game: &IntermediateLcgs, errors: &mut ErrorLog) -> Option> { + players + .iter() + .map(|expr| match &expr.kind { + ExprKind::Ident(name) => match game.get_decl(&Owner::Global.symbol_id(name)).map(|d| &d.kind) { + Some(DeclKind::Player(p)) => Some(p.index), + Some(_) => { + errors.log(expr.span.clone(), format!("Expected player, '{}' is not a player", name)); + None + } + None => { + errors.log(expr.span.clone(), format!("Expected player, '{}' is not defined", name)); + None + } + } + _ => { + errors.log(expr.span.clone(), "Expected player name".to_string()); + None + } + }) + .collect() +} \ No newline at end of file diff --git a/cgaal-engine/src/atl/mod.rs b/cgaal-engine/src/atl/mod.rs index 0a27a73c3..30d52c2dc 100644 --- a/cgaal-engine/src/atl/mod.rs +++ b/cgaal-engine/src/atl/mod.rs @@ -5,11 +5,10 @@ use std::sync::Arc; use joinery::prelude::*; use crate::atl::game_formula::GamePhi; -pub use crate::atl::parser::*; use crate::game_structure::{GameStructure, Player, Proposition}; pub mod game_formula; -pub mod parser; +mod convert; /// Alternating-time Temporal Logic formula #[derive(Hash, Eq, PartialEq, Clone, Debug, Deserialize, Serialize)] diff --git a/cgaal-engine/src/atl/parser.rs b/cgaal-engine/src/atl/parser.rs deleted file mode 100644 index 89acd90d9..000000000 --- a/cgaal-engine/src/atl/parser.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::str::{self}; - -use crate::atl::Phi; - -/// Parse an ATL formula -pub fn parse_phi(_input: &str) -> Result { - Ok(Phi::True) -} diff --git a/cgaal-engine/src/parsing/ast.rs b/cgaal-engine/src/parsing/ast.rs index ee8a31bfe..96b1d1b25 100644 --- a/cgaal-engine/src/parsing/ast.rs +++ b/cgaal-engine/src/parsing/ast.rs @@ -1,4 +1,3 @@ -use crate::game_structure::Proposition; use crate::parsing::span::Span; use crate::parsing::token::TokenKind; use std::sync::Arc; @@ -25,7 +24,6 @@ pub enum ExprKind { False, Paren(Arc), Ident(String), - Prop(Proposition), Unary(UnaryOpKind, Arc), Binary(BinaryOpKind, Arc, Arc), Coalition(Coalition), diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 75497a264..f73eb1f72 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -4,7 +4,7 @@ use crate::parsing::lexer::Lexer; use crate::parsing::parser::Parser; pub mod ast; -mod errors; +pub mod errors; pub mod lexer; pub mod parser; pub mod span; From 5121d6da85c7760310a8c71ce35de72e3f1c2af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Wed, 6 Sep 2023 12:50:52 +0200 Subject: [PATCH 19/38] Also convert expr idents to propositions in conversion --- cgaal-engine/src/atl/convert.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cgaal-engine/src/atl/convert.rs b/cgaal-engine/src/atl/convert.rs index 0a92ed8b9..2b0d5fc6d 100644 --- a/cgaal-engine/src/atl/convert.rs +++ b/cgaal-engine/src/atl/convert.rs @@ -12,7 +12,20 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er ExprKind::True => Some(Phi::True), ExprKind::False => Some(Phi::False), ExprKind::Paren(e) => convert_expr_to_phi(e, game, errors), - ExprKind::Ident(_) => todo!(), + ExprKind::Ident(ident) => { + let decl = game.get_decl(&Owner::Global.symbol_id(ident)); + match &decl.map(|d| &d.kind) { + Some(DeclKind::Label(l)) => Some(Phi::Proposition(l.index)), + Some(_) => { + errors.log(span.clone(), format!("Expected proposition label, '{}' is not a label", ident)); + None + } + None => {4 + errors.log(span.clone(), format!("Expected proposition label, '{}' is not defined", ident)); + None + } + } + } ExprKind::Unary(op, e) => match op { UnaryOpKind::Not => Some(Phi::Not(convert_expr_to_phi(e, game, errors)?.into())), UnaryOpKind::Next | UnaryOpKind::Eventually | UnaryOpKind::Invariantly => { From d3eafbf489fb14eb8e7a93ce3d5971af8409c1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Wed, 6 Sep 2023 14:32:15 +0200 Subject: [PATCH 20/38] Remove accidental 4 --- cgaal-engine/src/atl/convert.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgaal-engine/src/atl/convert.rs b/cgaal-engine/src/atl/convert.rs index 2b0d5fc6d..7e163d2ba 100644 --- a/cgaal-engine/src/atl/convert.rs +++ b/cgaal-engine/src/atl/convert.rs @@ -20,7 +20,7 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er errors.log(span.clone(), format!("Expected proposition label, '{}' is not a label", ident)); None } - None => {4 + None => { errors.log(span.clone(), format!("Expected proposition label, '{}' is not defined", ident)); None } From f0c436a6c2c01c1aef93f919d5fc17d671c6c44c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Wed, 6 Sep 2023 15:14:56 +0200 Subject: [PATCH 21/38] Use new ATL parsing in cli --- cgaal-cli/src/main.rs | 23 ++++--- cgaal-engine/src/atl/mod.rs | 2 +- cgaal-engine/src/parsing/mod.rs | 8 +-- cgaal-engine/src/parsing/parser.rs | 6 +- cgaal-engine/tests/parsing.rs | 97 +++++++++++++----------------- lcgs-examples/robot_grid/test.atl | 2 +- 6 files changed, 68 insertions(+), 70 deletions(-) diff --git a/cgaal-cli/src/main.rs b/cgaal-cli/src/main.rs index 22f0e2c5e..7facef0fb 100644 --- a/cgaal-cli/src/main.rs +++ b/cgaal-cli/src/main.rs @@ -18,6 +18,7 @@ use tracing::trace; use cgaal_engine::algorithms::global::multithread::MultithreadedGlobalAlgorithm; use cgaal_engine::algorithms::global::singlethread::SinglethreadedGlobalAlgorithm; use cgaal_engine::analyse::analyse; +use cgaal_engine::atl::convert::convert_expr_to_phi; use cgaal_engine::atl::Phi; use cgaal_engine::edg::atledg::vertex::AtlVertex; use cgaal_engine::edg::atledg::AtlDependencyGraph; @@ -27,6 +28,7 @@ use cgaal_engine::game_structure::lcgs::ir::intermediate::IntermediateLcgs; use cgaal_engine::game_structure::lcgs::ir::symbol_table::Owner; use cgaal_engine::game_structure::lcgs::parse::parse_lcgs; use cgaal_engine::game_structure::{EagerGameStructure, GameStructure}; +use cgaal_engine::parsing::errors::ErrorLog; use cgaal_engine::parsing::parse_atl; #[cfg(feature = "graph-printer")] use cgaal_engine::printer::print_graph; @@ -336,7 +338,7 @@ fn main_inner() -> Result<(), String> { /// Reads a formula in JSON format from a file and returns the formula as a string /// and as a parsed Phi struct. /// This function will exit the program if it encounters an error. -fn load_formula(path: &str, formula_type: FormulaType) -> Phi { +fn load_formula(path: &str, formula_type: FormulaType, game: Option<&IntermediateLcgs>) -> Phi { let mut file = File::open(path).unwrap_or_else(|err| { eprintln!("Failed to open formula file\n\nError:\n{}", err); exit(1); @@ -354,11 +356,18 @@ fn load_formula(path: &str, formula_type: FormulaType) -> Phi { exit(1); }), FormulaType::Atl => { - let result = cgaal_engine::atl::parse_phi(&raw_phi); - result.unwrap_or_else(|err| { - eprintln!("Invalid ATL formula provided:\n\n{}", err); + let game = game.unwrap_or_else(|| { + eprintln!("Cannot parse ATL formula for non-LCGS models"); exit(1) - }) + }); + let mut errors = ErrorLog::new(); + parse_atl(&raw_phi, &mut errors) + .ok() + .and_then(|expr| convert_expr_to_phi(&expr, game, &mut errors)) + .unwrap_or_else(|| { + eprint!("Invalid ATL formula provided:\n{}", errors.to_string(&raw_phi)); + exit(1) + }) } } } @@ -454,7 +463,7 @@ fn load( let game_structure = serde_json::from_str(content.as_str()) .map_err(|err| format!("Failed to deserialize input model.\n{}", err))?; - let phi = load_formula(formula_path, formula_format); + let phi = load_formula(formula_path, formula_format, None); Ok(ModelAndFormula::Json { model: game_structure, @@ -468,7 +477,7 @@ fn load( let game_structure = IntermediateLcgs::create(lcgs) .map_err(|err| format!("Invalid LCGS program.\n{}", err))?; - let phi = load_formula(formula_path, formula_format); + let phi = load_formula(formula_path, formula_format, Some(&game_structure)); Ok(ModelAndFormula::Lcgs { model: game_structure, diff --git a/cgaal-engine/src/atl/mod.rs b/cgaal-engine/src/atl/mod.rs index 30d52c2dc..4f105dc7e 100644 --- a/cgaal-engine/src/atl/mod.rs +++ b/cgaal-engine/src/atl/mod.rs @@ -8,7 +8,7 @@ use crate::atl::game_formula::GamePhi; use crate::game_structure::{GameStructure, Player, Proposition}; pub mod game_formula; -mod convert; +pub mod convert; /// Alternating-time Temporal Logic formula #[derive(Hash, Eq, PartialEq, Clone, Debug, Deserialize, Serialize)] diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index f73eb1f72..9687cd946 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -10,13 +10,13 @@ pub mod parser; pub mod span; mod token; -pub fn parse_atl(input: &str) -> Result { +pub fn parse_atl(input: &str, errors: &mut ErrorLog) -> Result { let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, errors); let expr = parser.expr(0).unwrap_or(Expr::new_error()); parser.expect_end(); - if parser.errors.has_errors() { - Err(parser.errors) + if errors.has_errors() { + Err(()) } else { Ok(expr) } diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index 183bcfaf2..c78c7fa3f 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -53,15 +53,15 @@ macro_rules! recover { pub struct Parser<'a> { lexer: Peekable>, - pub errors: ErrorLog, + errors: &'a mut ErrorLog, recovery_tokens: Vec, } impl<'a> Parser<'a> { - pub fn new(lexer: Lexer<'a>) -> Parser { + pub fn new(lexer: Lexer<'a>, errors: &'a mut ErrorLog) -> Parser<'a> { Parser { lexer: lexer.peekable(), - errors: ErrorLog::new(), + errors, recovery_tokens: vec![], } } diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing.rs index 067edb175..1f5367d06 100644 --- a/cgaal-engine/tests/parsing.rs +++ b/cgaal-engine/tests/parsing.rs @@ -1,4 +1,5 @@ use cgaal_engine::parsing::ast::*; +use cgaal_engine::parsing::errors::ErrorLog; use cgaal_engine::parsing::lexer::*; use cgaal_engine::parsing::parser::*; use cgaal_engine::parsing::span::*; @@ -6,15 +7,12 @@ use cgaal_engine::parsing::span::*; #[test] fn basic_expr_001() { let input = "true && false"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); - assert!( - parser.errors.is_empty(), - "ErrorLog is not empty: {:?}", - parser.errors - ); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( expr, Expr::new( @@ -31,15 +29,12 @@ fn basic_expr_001() { #[test] fn basic_expr_002() { let input = "true && false || true"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); - assert!( - parser.errors.is_empty(), - "ErrorLog is not empty: {:?}", - parser.errors - ); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( expr, Expr::new( @@ -64,15 +59,12 @@ fn basic_expr_002() { #[test] fn basic_expr_003() { let input = "foo || true && bar || (true || false)"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); - assert!( - parser.errors.is_empty(), - "ErrorLog is not empty: {:?}", - parser.errors - ); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( expr, Expr::new( @@ -121,15 +113,12 @@ fn basic_expr_003() { #[test] fn atl_expr_001() { let input = "<> F goal"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); - assert!( - parser.errors.is_empty(), - "ErrorLog is not empty: {:?}", - parser.errors - ); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( expr, Expr::new( @@ -157,15 +146,12 @@ fn atl_expr_001() { #[test] fn atl_expr_002() { let input = "[[p1, p2]] G safe"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); let expr = parser.expr(0).expect("Failed to valid parse expression"); parser.expect_end(); - assert!( - parser.errors.is_empty(), - "ErrorLog is not empty: {:?}", - parser.errors - ); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( expr, Expr::new( @@ -193,15 +179,12 @@ fn atl_expr_002() { #[test] fn atl_expr_003() { let input = "<<>> (safe U goal)"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); let expr = parser.expr(0).expect("Error should be recoverable"); parser.expect_end(); - assert!( - parser.errors.is_empty(), - "ErrorLog is not empty: {:?}", - parser.errors - ); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( expr, Expr::new( @@ -227,11 +210,12 @@ fn atl_expr_003() { #[test] fn erroneous_expr_001() { let input = "true && "; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); assert!(parser.expr(0).is_err()); - assert!(parser.errors.has_errors()); - let out = parser.errors.to_string(input); + assert!(errors.has_errors()); + let out = errors.to_string(input); assert_eq!( out, "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected expression term\n" @@ -241,12 +225,13 @@ fn erroneous_expr_001() { #[test] fn erroneous_expr_002() { let input = "foo bar"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); let expr = parser.expr(0).expect("Error should be recoverable"); parser.expect_end(); - assert!(parser.errors.has_errors()); - let out = parser.errors.to_string(input); + assert!(errors.has_errors()); + let out = errors.to_string(input); assert_eq!( out, "\x1b[93m1:5 Error:\x1b[0m Unexpected 'bar', expected EOF\n\ @@ -262,12 +247,13 @@ fn erroneous_expr_002() { #[test] fn erroneous_expr_003() { let input = "(foo false) && true"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); let expr = parser.expr(0).expect("Error should be recoverable"); parser.expect_end(); - assert!(parser.errors.has_errors()); - let out = parser.errors.to_string(input); + assert!(errors.has_errors()); + let out = errors.to_string(input); assert_eq!( out, "\x1b[93m1:6 Error:\x1b[0m Unexpected 'false', expected ')'\n\ @@ -290,11 +276,12 @@ fn erroneous_expr_003() { #[test] fn erroneous_expr_004() { let input = "(true"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); assert!(parser.expr(0).is_err()); - assert!(parser.errors.has_errors()); - let out = parser.errors.to_string(input); + assert!(errors.has_errors()); + let out = errors.to_string(input); assert_eq!( out, "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected ')'\n" @@ -304,11 +291,12 @@ fn erroneous_expr_004() { #[test] fn erroneous_expr_005() { let input = "(foo bar"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); assert!(parser.expr(0).is_err()); - assert!(parser.errors.has_errors()); - let out = parser.errors.to_string(input); + assert!(errors.has_errors()); + let out = errors.to_string(input); assert_eq!( out, "\x1b[93m1:6 Error:\x1b[0m Unexpected 'bar', expected ')'\n\ @@ -320,12 +308,13 @@ fn erroneous_expr_005() { #[test] fn erroneous_expr_006() { let input = " ( << p1 foo goal)"; + let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); - let mut parser = Parser::new(lexer); + let mut parser = Parser::new(lexer, &mut errors); let expr = parser.expr(0).expect("Error should be recoverable"); parser.expect_end(); - assert!(parser.errors.has_errors()); - let out = parser.errors.to_string(input); + assert!(errors.has_errors()); + let out = errors.to_string(input); assert_eq!( out, "\x1b[93m1:10 Error:\x1b[0m Unexpected 'foo', expected ',' or '>>'\n\ diff --git a/lcgs-examples/robot_grid/test.atl b/lcgs-examples/robot_grid/test.atl index 5d49abc99..539eb732b 100644 --- a/lcgs-examples/robot_grid/test.atl +++ b/lcgs-examples/robot_grid/test.atl @@ -1 +1 @@ -<< p1 >> F true \ No newline at end of file +<> F true \ No newline at end of file From 158daa261b23a5f81075c4a1ce027700638f742b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Wed, 6 Sep 2023 15:54:12 +0200 Subject: [PATCH 22/38] Improved styling and unknown character handling --- cgaal-engine/src/parsing/errors.rs | 14 ++++++-------- cgaal-engine/src/parsing/lexer.rs | 15 ++++++++++++--- cgaal-engine/src/parsing/token.rs | 4 ++-- cgaal-engine/tests/parsing.rs | 12 ++++++------ cgaal-engine/tests/partial_strategy_synthesis.rs | 10 ++++++++-- lcgs-examples/robot_grid/test.atl | 2 +- 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/cgaal-engine/src/parsing/errors.rs b/cgaal-engine/src/parsing/errors.rs index 69603cb59..1e49c7fc0 100644 --- a/cgaal-engine/src/parsing/errors.rs +++ b/cgaal-engine/src/parsing/errors.rs @@ -64,7 +64,7 @@ impl ErrorLog { } i += 1; } - writeln!(out, "\x1b[93m{}:{} Error:\x1b[0m {}", line, col, entry.msg).unwrap(); + writeln!(out, "\x1b[31m{}:{} Error:\x1b[0m {}", line, col, entry.msg).unwrap(); // Print the lines with the error, and underline the error span while line_start < span.end && i <= chars.len() { @@ -83,7 +83,7 @@ impl ErrorLog { } } None => { - writeln!(out, "\x1b[93m@ Error:\x1b[0m {}", entry.msg).unwrap(); + writeln!(out, "\x1b[31m@ Error:\x1b[0m {}", entry.msg).unwrap(); } } } @@ -129,11 +129,10 @@ mod tests { Span::new(i, i + 5), "Unknown identifier 'robot'".to_string(), ); - let mut out = String::new(); - log.write_detailed(input, &mut out).unwrap(); + let out = log.to_string(input); assert_eq!( out, - "\x1b[93m3:13 Error:\x1b[0m Unknown identifier 'robot'\n\ + "\x1b[31m3:13 Error:\x1b[0m Unknown identifier 'robot'\n\ | player p1 = robot[x=0, y=1];\n\ | ^^^^^\n" ); @@ -149,11 +148,10 @@ mod tests { let mut log = ErrorLog::new(); let span = Span::new(input.find("123").unwrap(), input.find(";").unwrap()); log.log(span, "RHS of '+' must be integer, found bool".to_string()); - let mut out = String::new(); - log.write_detailed(input, &mut out).unwrap(); + let out = log.to_string(input); assert_eq!( out, - "\x1b[93m2:11 Error:\x1b[0m RHS of '+' must be integer, found bool\n\ + "\x1b[31m2:11 Error:\x1b[0m RHS of '+' must be integer, found bool\n\ | label x = 123 +\n\ | ^^^^^\n\ | true;\n\ diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs index 530573828..63a929eff 100644 --- a/cgaal-engine/src/parsing/lexer.rs +++ b/cgaal-engine/src/parsing/lexer.rs @@ -70,6 +70,15 @@ impl<'a> Lexer<'a> { .unwrap(); self.token(len, TokenKind::Num(val)) } + + fn lex_error(&mut self) -> Token { + let mut len = 1; + while !std::str::from_utf8(&self.input[self.pos..self.pos + len]).is_ok() { + len += 1; + } + let e = std::str::from_utf8(&self.input[self.pos..self.pos + len]).unwrap(); + self.token(len, TokenKind::Err(e.to_string())) + } } impl<'a> Iterator for Lexer<'a> { @@ -109,11 +118,11 @@ impl<'a> Iterator for Lexer<'a> { b'/' => self.token(1, TokenKind::Slash), b'&' => match self.peek(1) { Some(b'&') => self.token(2, TokenKind::AmpAmp), - _ => self.token(1, TokenKind::Err), + _ => self.lex_error(), }, b'|' => match self.peek(1) { Some(b'|') => self.token(2, TokenKind::PipePipe), - _ => self.token(1, TokenKind::Err), + _ => self.lex_error(), }, b'^' => self.token(1, TokenKind::Hat), b'?' => self.token(1, TokenKind::Question), @@ -135,7 +144,7 @@ impl<'a> Iterator for Lexer<'a> { b'\'' => self.token(1, TokenKind::Prime), b'a'..=b'z' | b'A'..=b'Z' => self.lex_alpha(), b'0'..=b'9' => self.lex_num(), - _ => self.token(1, TokenKind::Err), + _ => self.lex_error(), }; Some(tk) } diff --git a/cgaal-engine/src/parsing/token.rs b/cgaal-engine/src/parsing/token.rs index 7038bcc4b..c87ff2e59 100644 --- a/cgaal-engine/src/parsing/token.rs +++ b/cgaal-engine/src/parsing/token.rs @@ -93,7 +93,7 @@ pub enum TokenKind { Word(String), // Utility - Err, + Err(String), } impl Display for TokenKind { @@ -144,7 +144,7 @@ impl Display for TokenKind { TokenKind::False => write!(f, "false"), TokenKind::Num(n) => write!(f, "{n}"), TokenKind::Word(w) => write!(f, "{w}"), - TokenKind::Err => write!(f, "@err"), + TokenKind::Err(e) => write!(f, "{e}"), } } } diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing.rs index 1f5367d06..1cd8c76c8 100644 --- a/cgaal-engine/tests/parsing.rs +++ b/cgaal-engine/tests/parsing.rs @@ -218,7 +218,7 @@ fn erroneous_expr_001() { let out = errors.to_string(input); assert_eq!( out, - "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected expression term\n" + "\x1b[31m@ Error:\x1b[0m Unexpected EOF, expected expression term\n" ); } @@ -234,7 +234,7 @@ fn erroneous_expr_002() { let out = errors.to_string(input); assert_eq!( out, - "\x1b[93m1:5 Error:\x1b[0m Unexpected 'bar', expected EOF\n\ + "\x1b[31m1:5 Error:\x1b[0m Unexpected 'bar', expected EOF\n\ | foo bar\n\ | ^^^\n" ); @@ -256,7 +256,7 @@ fn erroneous_expr_003() { let out = errors.to_string(input); assert_eq!( out, - "\x1b[93m1:6 Error:\x1b[0m Unexpected 'false', expected ')'\n\ + "\x1b[31m1:6 Error:\x1b[0m Unexpected 'false', expected ')'\n\ | (foo false) && true\n\ | ^^^^^\n" ); @@ -284,7 +284,7 @@ fn erroneous_expr_004() { let out = errors.to_string(input); assert_eq!( out, - "\x1b[93m@ Error:\x1b[0m Unexpected EOF, expected ')'\n" + "\x1b[31m@ Error:\x1b[0m Unexpected EOF, expected ')'\n" ); } @@ -299,7 +299,7 @@ fn erroneous_expr_005() { let out = errors.to_string(input); assert_eq!( out, - "\x1b[93m1:6 Error:\x1b[0m Unexpected 'bar', expected ')'\n\ + "\x1b[31m1:6 Error:\x1b[0m Unexpected 'bar', expected ')'\n\ | (foo bar\n\ | ^^^\n" ); @@ -317,7 +317,7 @@ fn erroneous_expr_006() { let out = errors.to_string(input); assert_eq!( out, - "\x1b[93m1:10 Error:\x1b[0m Unexpected 'foo', expected ',' or '>>'\n\ + "\x1b[31m1:10 Error:\x1b[0m Unexpected 'foo', expected ',' or '>>'\n\ | ( << p1 foo goal)\n\ | ^^^\n" ); diff --git a/cgaal-engine/tests/partial_strategy_synthesis.rs b/cgaal-engine/tests/partial_strategy_synthesis.rs index 503c3b8ce..1cb6422fc 100644 --- a/cgaal-engine/tests/partial_strategy_synthesis.rs +++ b/cgaal-engine/tests/partial_strategy_synthesis.rs @@ -8,12 +8,13 @@ use cgaal_engine::{ compute_game_strategy, error::Error, partial::PartialStrategy, WitnessStrategy, }, }, - atl::parse_phi, + atl::convert::convert_expr_to_phi, edg::atledg::{vertex::AtlVertex, AtlDependencyGraph}, game_structure::{ lcgs::{ir::intermediate::IntermediateLcgs, parse::parse_lcgs}, GameStructure, }, + parsing::{errors::ErrorLog, parse_atl}, }; const GAME: &str = " @@ -145,9 +146,14 @@ macro_rules! assert_partial_strat_moves { /// ``` macro_rules! strat_synthesis_test { ($game:expr, $phi:expr, $($rest:tt)*) => { + let mut errors = ErrorLog::new(); let ast = parse_lcgs($game).unwrap(); let game = IntermediateLcgs::create(ast).unwrap(); - let phi = parse_phi($phi).unwrap(); + let phi = parse_atl($phi, &mut errors) + .ok() + .and_then(|expr| convert_expr_to_phi(&expr, &game, &mut errors)) + .ok_or_else(|| format!("{}", errors.to_string($phi))) + .unwrap(); let v0 = AtlVertex::Full { state: game.initial_state_index(), formula: phi.into(), diff --git a/lcgs-examples/robot_grid/test.atl b/lcgs-examples/robot_grid/test.atl index 539eb732b..ed6d360af 100644 --- a/lcgs-examples/robot_grid/test.atl +++ b/lcgs-examples/robot_grid/test.atl @@ -1 +1 @@ -<> F true \ No newline at end of file +(<> F foo bar) && (🤣 || false) \ No newline at end of file From 1570ca63131794ebd96c4d40357a7cbe848c4820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Thu, 7 Sep 2023 09:56:16 +0200 Subject: [PATCH 23/38] Add dot expressions to ATL --- cgaal-engine/src/atl/convert.rs | 47 ++++++++++++++++++++++++++++++ cgaal-engine/src/parsing/ast.rs | 2 ++ cgaal-engine/src/parsing/parser.rs | 15 +++++++++- lcgs-examples/robot_grid/test.atl | 2 +- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/cgaal-engine/src/atl/convert.rs b/cgaal-engine/src/atl/convert.rs index 7e163d2ba..f984a3f87 100644 --- a/cgaal-engine/src/atl/convert.rs +++ b/cgaal-engine/src/atl/convert.rs @@ -45,6 +45,53 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er convert_expr_to_phi(lhs, game, errors)?.into(), convert_expr_to_phi(rhs, game, errors)?.into(), )), + BinaryOpKind::Dot => { + let ExprKind::Ident(owner) = &lhs.kind else { + errors.log(lhs.span.clone(), "Expected player name".to_string()); + return None; + }; + let ExprKind::Ident(prop) = &rhs.kind else { + errors.log(rhs.span.clone(), "Expected proposition label".to_string()); + return None; + }; + match game.get_decl(&Owner::Global.symbol_id(owner)).map(|d| &d.kind) { + Some(DeclKind::Player(_)) => { + let symb = Owner::Player(owner.clone()).symbol_id(prop); + let decl = game.get_decl(&symb); + match decl.map(|d| &d.kind) { + Some(DeclKind::Label(l)) => Some(Phi::Proposition(l.index)), + Some(_) => { + errors.log( + rhs.span.clone(), + format!("Expected proposition label, '{}' is not a label", prop), + ); + None + } + None => { + errors.log( + rhs.span.clone(), + format!("Expected proposition label, '{}' is not defined", prop), + ); + None + } + } + } + Some(_) => { + errors.log( + lhs.span.clone(), + format!("Expected player, '{}' is not a player", owner), + ); + None + } + None => { + errors.log( + lhs.span.clone(), + format!("Expected player, '{}' is not defined", owner), + ); + None + } + } + } BinaryOpKind::Until => { errors.log( span.clone(), diff --git a/cgaal-engine/src/parsing/ast.rs b/cgaal-engine/src/parsing/ast.rs index 96b1d1b25..295c06f3b 100644 --- a/cgaal-engine/src/parsing/ast.rs +++ b/cgaal-engine/src/parsing/ast.rs @@ -44,6 +44,7 @@ pub enum UnaryOpKind { pub enum BinaryOpKind { And, Or, + Dot, // Temporal operators Until, @@ -61,6 +62,7 @@ impl BinaryOpKind { pub fn precedence(&self) -> u8 { match self { + BinaryOpKind::Dot => 3, BinaryOpKind::And => 2, BinaryOpKind::Or => 1, BinaryOpKind::Until => 0, diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index c78c7fa3f..746f8b919 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -156,7 +156,7 @@ impl<'a> Parser<'a> { let tok = self.lexer.next().unwrap(); Ok(Expr::new(tok.span, ExprKind::False)) } - Some(TokenKind::Word(_)) => self.ident(), + Some(TokenKind::Word(_)) => self.owned_ident(), Some(TokenKind::Llangle) => self.enforce_coalition(), Some(TokenKind::Llbracket) => self.despite_coalition(), // Unexpected @@ -182,6 +182,19 @@ impl<'a> Parser<'a> { .map(|(end, expr)| Expr::new(begin + end, ExprKind::Paren(Arc::new(expr)))) } + pub fn owned_ident(&mut self) -> Result { + let lhs = self.ident()?; + if !matches!(self.lexer.peek(), Some(Token { kind: TokenKind::Dot, .. })) { + return Ok(lhs); + } + let _ = self.token(TokenKind::Dot)?; + let rhs = self.ident()?; + Ok(Expr::new( + lhs.span + rhs.span, + ExprKind::Binary(BinaryOpKind::Dot, lhs.into(), rhs.into()), + )) + } + pub fn ident(&mut self) -> Result { match self.lexer.peek() { Some(Token { diff --git a/lcgs-examples/robot_grid/test.atl b/lcgs-examples/robot_grid/test.atl index ed6d360af..913bd6bf7 100644 --- a/lcgs-examples/robot_grid/test.atl +++ b/lcgs-examples/robot_grid/test.atl @@ -1 +1 @@ -(<> F foo bar) && (🤣 || false) \ No newline at end of file +<> F robot.crashed \ No newline at end of file From d33127cb6215c10704c698c84b06e376238973c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Thu, 7 Sep 2023 10:01:35 +0200 Subject: [PATCH 24/38] Add negation to ATL expr --- cgaal-engine/src/parsing/parser.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index 746f8b919..c70da13a5 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -148,6 +148,14 @@ impl<'a> Parser<'a> { pub fn term(&mut self) -> Result { match self.lexer.peek().map(|t| t.kind()) { Some(TokenKind::Lparen) => self.paren(), + Some(TokenKind::Bang) => { + let begin = self.lexer.next().unwrap().span; + let expr = self.term()?; + Ok(Expr::new( + begin + expr.span, + ExprKind::Unary(UnaryOpKind::Not, expr.into()), + )) + } Some(TokenKind::True) => { let tok = self.lexer.next().unwrap(); Ok(Expr::new(tok.span, ExprKind::True)) From ae25f0759711517d7e9990767496ae1b7335f24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Thu, 7 Sep 2023 10:02:50 +0200 Subject: [PATCH 25/38] Add neXt path expression --- cgaal-engine/src/parsing/parser.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index c70da13a5..444cd8213 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -128,6 +128,14 @@ impl<'a> Parser<'a> { ExprKind::Unary(UnaryOpKind::Invariantly, expr.into()), )) } + Some(TokenKind::Word(w)) if w == "X" => { + let begin = self.lexer.next().unwrap().span; + let expr = self.expr(0)?; + Ok(Expr::new( + begin + expr.span, + ExprKind::Unary(UnaryOpKind::Next, expr.into()), + )) + } // Unexpected Some(_) => { let tok = self.lexer.next().unwrap(); From 6df09018fd8e9d52a11df94a6561c3dd981ff85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Thu, 7 Sep 2023 10:09:24 +0200 Subject: [PATCH 26/38] Removed unsupported examples --- cgaal-cli/src/main.rs | 2 +- .../can_2_players_gurantee_atleast_one_of_them_survives.atl | 1 - .../mexican_standoff/can_p1_guarantee_to_survive_FALSE.atl | 1 - json-examples/mexican_standoff/can_p1_suicide_FALSE.atl | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 json-examples/mexican_standoff/can_2_players_gurantee_atleast_one_of_them_survives.atl delete mode 100644 json-examples/mexican_standoff/can_p1_guarantee_to_survive_FALSE.atl delete mode 100644 json-examples/mexican_standoff/can_p1_suicide_FALSE.atl diff --git a/cgaal-cli/src/main.rs b/cgaal-cli/src/main.rs index 7facef0fb..c0f981baf 100644 --- a/cgaal-cli/src/main.rs +++ b/cgaal-cli/src/main.rs @@ -357,7 +357,7 @@ fn load_formula(path: &str, formula_type: FormulaType, game: Option<&Intermediat }), FormulaType::Atl => { let game = game.unwrap_or_else(|| { - eprintln!("Cannot parse ATL formula for non-LCGS models"); + eprintln!("Cannot use ATL formula for non-LCGS models"); exit(1) }); let mut errors = ErrorLog::new(); diff --git a/json-examples/mexican_standoff/can_2_players_gurantee_atleast_one_of_them_survives.atl b/json-examples/mexican_standoff/can_2_players_gurantee_atleast_one_of_them_survives.atl deleted file mode 100644 index c76d8294f..000000000 --- a/json-examples/mexican_standoff/can_2_players_gurantee_atleast_one_of_them_survives.atl +++ /dev/null @@ -1 +0,0 @@ -<<0,1>> G (0 | 1) \ No newline at end of file diff --git a/json-examples/mexican_standoff/can_p1_guarantee_to_survive_FALSE.atl b/json-examples/mexican_standoff/can_p1_guarantee_to_survive_FALSE.atl deleted file mode 100644 index 4aa5defbf..000000000 --- a/json-examples/mexican_standoff/can_p1_guarantee_to_survive_FALSE.atl +++ /dev/null @@ -1 +0,0 @@ -<<0>> G 0 \ No newline at end of file diff --git a/json-examples/mexican_standoff/can_p1_suicide_FALSE.atl b/json-examples/mexican_standoff/can_p1_suicide_FALSE.atl deleted file mode 100644 index 265d0d335..000000000 --- a/json-examples/mexican_standoff/can_p1_suicide_FALSE.atl +++ /dev/null @@ -1 +0,0 @@ -<<0>> F !0 \ No newline at end of file From bec6d79ef82740e77461c719a1b6e4eba1243d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Thu, 7 Sep 2023 10:10:19 +0200 Subject: [PATCH 27/38] Remove test atl file --- lcgs-examples/robot_grid/test.atl | 1 - 1 file changed, 1 deletion(-) delete mode 100644 lcgs-examples/robot_grid/test.atl diff --git a/lcgs-examples/robot_grid/test.atl b/lcgs-examples/robot_grid/test.atl deleted file mode 100644 index 913bd6bf7..000000000 --- a/lcgs-examples/robot_grid/test.atl +++ /dev/null @@ -1 +0,0 @@ -<> F robot.crashed \ No newline at end of file From 78c11d308b0d1f4227cb6bce74752a00a48ae178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Thu, 7 Sep 2023 11:02:29 +0200 Subject: [PATCH 28/38] Fix clippy and fmt --- cgaal-cli/src/main.rs | 5 +- cgaal-engine/src/atl/convert.rs | 209 +++++++++++++++++------------ cgaal-engine/src/atl/mod.rs | 2 +- cgaal-engine/src/parsing/lexer.rs | 2 +- cgaal-engine/src/parsing/mod.rs | 7 +- cgaal-engine/src/parsing/parser.rs | 60 +++++---- 6 files changed, 165 insertions(+), 120 deletions(-) diff --git a/cgaal-cli/src/main.rs b/cgaal-cli/src/main.rs index c0f981baf..f2daddc3d 100644 --- a/cgaal-cli/src/main.rs +++ b/cgaal-cli/src/main.rs @@ -365,7 +365,10 @@ fn load_formula(path: &str, formula_type: FormulaType, game: Option<&Intermediat .ok() .and_then(|expr| convert_expr_to_phi(&expr, game, &mut errors)) .unwrap_or_else(|| { - eprint!("Invalid ATL formula provided:\n{}", errors.to_string(&raw_phi)); + eprint!( + "Invalid ATL formula provided:\n{}", + errors.to_string(&raw_phi) + ); exit(1) }) } diff --git a/cgaal-engine/src/atl/convert.rs b/cgaal-engine/src/atl/convert.rs index f984a3f87..f61c814a2 100644 --- a/cgaal-engine/src/atl/convert.rs +++ b/cgaal-engine/src/atl/convert.rs @@ -6,7 +6,11 @@ use crate::game_structure::Player; use crate::parsing::ast::{BinaryOpKind, Coalition, CoalitionKind, Expr, ExprKind, UnaryOpKind}; use crate::parsing::errors::ErrorLog; -pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut ErrorLog) -> Option { +pub fn convert_expr_to_phi( + expr: &Expr, + game: &IntermediateLcgs, + errors: &mut ErrorLog, +) -> Option { let Expr { span, kind } = expr; match kind { ExprKind::True => Some(Phi::True), @@ -17,11 +21,17 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er match &decl.map(|d| &d.kind) { Some(DeclKind::Label(l)) => Some(Phi::Proposition(l.index)), Some(_) => { - errors.log(span.clone(), format!("Expected proposition label, '{}' is not a label", ident)); + errors.log( + *span, + format!("Expected proposition label, '{}' is not a label", ident), + ); None } None => { - errors.log(span.clone(), format!("Expected proposition label, '{}' is not defined", ident)); + errors.log( + *span, + format!("Expected proposition label, '{}' is not defined", ident), + ); None } } @@ -30,7 +40,7 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er UnaryOpKind::Not => Some(Phi::Not(convert_expr_to_phi(e, game, errors)?.into())), UnaryOpKind::Next | UnaryOpKind::Eventually | UnaryOpKind::Invariantly => { errors.log( - span.clone(), + *span, "Temporal operators are only allowed after a coalition".to_string(), ); None @@ -47,14 +57,17 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er )), BinaryOpKind::Dot => { let ExprKind::Ident(owner) = &lhs.kind else { - errors.log(lhs.span.clone(), "Expected player name".to_string()); + errors.log(lhs.span, "Expected player name".to_string()); return None; }; let ExprKind::Ident(prop) = &rhs.kind else { - errors.log(rhs.span.clone(), "Expected proposition label".to_string()); + errors.log(rhs.span, "Expected proposition label".to_string()); return None; }; - match game.get_decl(&Owner::Global.symbol_id(owner)).map(|d| &d.kind) { + match game + .get_decl(&Owner::Global.symbol_id(owner)) + .map(|d| &d.kind) + { Some(DeclKind::Player(_)) => { let symb = Owner::Player(owner.clone()).symbol_id(prop); let decl = game.get_decl(&symb); @@ -62,15 +75,21 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er Some(DeclKind::Label(l)) => Some(Phi::Proposition(l.index)), Some(_) => { errors.log( - rhs.span.clone(), - format!("Expected proposition label, '{}' is not a label", prop), + rhs.span, + format!( + "Expected proposition label, '{}' is not a label", + prop + ), ); None } None => { errors.log( - rhs.span.clone(), - format!("Expected proposition label, '{}' is not defined", prop), + rhs.span, + format!( + "Expected proposition label, '{}' is not defined", + prop + ), ); None } @@ -78,14 +97,14 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er } Some(_) => { errors.log( - lhs.span.clone(), + lhs.span, format!("Expected player, '{}' is not a player", owner), ); None } None => { errors.log( - lhs.span.clone(), + lhs.span, format!("Expected player, '{}' is not defined", owner), ); None @@ -94,7 +113,7 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er } BinaryOpKind::Until => { errors.log( - span.clone(), + *span, "Temporal operators are only allowed after a coalition".to_string(), ); None @@ -105,97 +124,111 @@ pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &mut Er kind, expr: path_expr, .. - }) => { - match (kind, &path_expr.kind) { - (CoalitionKind::Enforce, ExprKind::Unary(UnaryOpKind::Next, sub_expr)) => { - let phi = convert_expr_to_phi(sub_expr, game, errors)?; - Some(Phi::EnforceNext { - players: convert_players(players, game, errors)?, - formula: phi.into(), - }) - } - (CoalitionKind::Despite, ExprKind::Unary(UnaryOpKind::Next, sub_expr)) => { - let phi = convert_expr_to_phi(sub_expr, game, errors)?; - Some(Phi::DespiteNext { - players: convert_players(players, game, errors)?, - formula: phi.into(), - }) - } - (CoalitionKind::Enforce, ExprKind::Unary(UnaryOpKind::Eventually, sub_expr)) => { - let phi = convert_expr_to_phi(sub_expr, game, errors)?; - Some(Phi::EnforceEventually { - players: convert_players(players, game, errors)?, - formula: phi.into(), - }) - } - (CoalitionKind::Despite, ExprKind::Unary(UnaryOpKind::Eventually, sub_expr)) => { - let phi = convert_expr_to_phi(sub_expr, game, errors)?; - Some(Phi::DespiteEventually { - players: convert_players(players, game, errors)?, - formula: phi.into(), - }) - } - (CoalitionKind::Enforce, ExprKind::Unary(UnaryOpKind::Invariantly, sub_expr)) => { - let phi = convert_expr_to_phi(sub_expr, game, errors)?; - Some(Phi::EnforceInvariant { - players: convert_players(players, game, errors)?, - formula: phi.into(), - }) - } - (CoalitionKind::Despite, ExprKind::Unary(UnaryOpKind::Invariantly, sub_expr)) => { - let phi = convert_expr_to_phi(sub_expr, game, errors)?; - Some(Phi::DespiteInvariant { - players: convert_players(players, game, errors)?, - formula: phi.into(), - }) - } - (CoalitionKind::Enforce, ExprKind::Binary(BinaryOpKind::Until, lhs, rhs)) => { - let lhs_phi = convert_expr_to_phi(lhs, game, errors)?; - let rhs_phi = convert_expr_to_phi(rhs, game, errors)?; - Some(Phi::EnforceUntil { - players: convert_players(players, game, errors)?, - pre: lhs_phi.into(), - until: rhs_phi.into(), - }) - } - (CoalitionKind::Despite, ExprKind::Binary(BinaryOpKind::Until, lhs, rhs)) => { - let lhs_phi = convert_expr_to_phi(lhs, game, errors)?; - let rhs_phi = convert_expr_to_phi(rhs, game, errors)?; - Some(Phi::DespiteUntil { - players: convert_players(players, game, errors)?, - pre: lhs_phi.into(), - until: rhs_phi.into(), - }) - } - _ => { - errors.log(path_expr.span.clone(), "Coalitions must be followed by a path formula".to_string()); - None - } + }) => match (kind, &path_expr.kind) { + (CoalitionKind::Enforce, ExprKind::Unary(UnaryOpKind::Next, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::EnforceNext { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) } - } + (CoalitionKind::Despite, ExprKind::Unary(UnaryOpKind::Next, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::DespiteNext { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Enforce, ExprKind::Unary(UnaryOpKind::Eventually, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::EnforceEventually { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Despite, ExprKind::Unary(UnaryOpKind::Eventually, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::DespiteEventually { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Enforce, ExprKind::Unary(UnaryOpKind::Invariantly, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::EnforceInvariant { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Despite, ExprKind::Unary(UnaryOpKind::Invariantly, sub_expr)) => { + let phi = convert_expr_to_phi(sub_expr, game, errors)?; + Some(Phi::DespiteInvariant { + players: convert_players(players, game, errors)?, + formula: phi.into(), + }) + } + (CoalitionKind::Enforce, ExprKind::Binary(BinaryOpKind::Until, lhs, rhs)) => { + let lhs_phi = convert_expr_to_phi(lhs, game, errors)?; + let rhs_phi = convert_expr_to_phi(rhs, game, errors)?; + Some(Phi::EnforceUntil { + players: convert_players(players, game, errors)?, + pre: lhs_phi.into(), + until: rhs_phi.into(), + }) + } + (CoalitionKind::Despite, ExprKind::Binary(BinaryOpKind::Until, lhs, rhs)) => { + let lhs_phi = convert_expr_to_phi(lhs, game, errors)?; + let rhs_phi = convert_expr_to_phi(rhs, game, errors)?; + Some(Phi::DespiteUntil { + players: convert_players(players, game, errors)?, + pre: lhs_phi.into(), + until: rhs_phi.into(), + }) + } + _ => { + errors.log( + path_expr.span, + "Coalitions must be followed by a path formula".to_string(), + ); + None + } + }, ExprKind::Error => None, } } -fn convert_players(players: &[Expr], game: &IntermediateLcgs, errors: &mut ErrorLog) -> Option> { +fn convert_players( + players: &[Expr], + game: &IntermediateLcgs, + errors: &mut ErrorLog, +) -> Option> { players .iter() .map(|expr| match &expr.kind { - ExprKind::Ident(name) => match game.get_decl(&Owner::Global.symbol_id(name)).map(|d| &d.kind) { + ExprKind::Ident(name) => match game + .get_decl(&Owner::Global.symbol_id(name)) + .map(|d| &d.kind) + { Some(DeclKind::Player(p)) => Some(p.index), Some(_) => { - errors.log(expr.span.clone(), format!("Expected player, '{}' is not a player", name)); + errors.log( + expr.span, + format!("Expected player, '{}' is not a player", name), + ); None } None => { - errors.log(expr.span.clone(), format!("Expected player, '{}' is not defined", name)); + errors.log( + expr.span, + format!("Expected player, '{}' is not defined", name), + ); None } - } + }, _ => { - errors.log(expr.span.clone(), "Expected player name".to_string()); + errors.log(expr.span, "Expected player name".to_string()); None } }) .collect() -} \ No newline at end of file +} diff --git a/cgaal-engine/src/atl/mod.rs b/cgaal-engine/src/atl/mod.rs index 4f105dc7e..fee0240a9 100644 --- a/cgaal-engine/src/atl/mod.rs +++ b/cgaal-engine/src/atl/mod.rs @@ -7,8 +7,8 @@ use joinery::prelude::*; use crate::atl::game_formula::GamePhi; use crate::game_structure::{GameStructure, Player, Proposition}; -pub mod game_formula; pub mod convert; +pub mod game_formula; /// Alternating-time Temporal Logic formula #[derive(Hash, Eq, PartialEq, Clone, Debug, Deserialize, Serialize)] diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs index 63a929eff..8f99f0e6a 100644 --- a/cgaal-engine/src/parsing/lexer.rs +++ b/cgaal-engine/src/parsing/lexer.rs @@ -73,7 +73,7 @@ impl<'a> Lexer<'a> { fn lex_error(&mut self) -> Token { let mut len = 1; - while !std::str::from_utf8(&self.input[self.pos..self.pos + len]).is_ok() { + while std::str::from_utf8(&self.input[self.pos..self.pos + len]).is_err() { len += 1; } let e = std::str::from_utf8(&self.input[self.pos..self.pos + len]).unwrap(); diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 9687cd946..1fcac2ec6 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -10,13 +10,16 @@ pub mod parser; pub mod span; mod token; -pub fn parse_atl(input: &str, errors: &mut ErrorLog) -> Result { +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub struct ParseErrorSeeLog; + +pub fn parse_atl(input: &str, errors: &mut ErrorLog) -> Result { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer, errors); let expr = parser.expr(0).unwrap_or(Expr::new_error()); parser.expect_end(); if errors.has_errors() { - Err(()) + Err(ParseErrorSeeLog) } else { Ok(expr) } diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index 444cd8213..b037548ac 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -23,17 +23,17 @@ macro_rules! recover { *span, format!("Unexpected '{}', expected '{}'", kind, $recover_token), ); - Err(ParseError) + Err(RecoverMode) } _ => { $self .errors .log_msg(format!("Unexpected EOF, expected '{}'", $recover_token)); - Err(ParseError) + Err(RecoverMode) } } } - Err(_) => Err(ParseError), + Err(_) => Err(RecoverMode), }; res.or_else(|_| { // Unhappy path @@ -45,7 +45,7 @@ macro_rules! recover { let tok = $self.lexer.next().unwrap(); Ok((tok.span, $err_val)) } - _ => Err(ParseError), + _ => Err(RecoverMode), } }) }}; @@ -74,10 +74,10 @@ impl<'a> Parser<'a> { .log(tok.span, format!("Unexpected '{}', expected EOF", tok.kind)); } } - for _ in self.lexer.by_ref() {}; + for _ in self.lexer.by_ref() {} } - pub fn expr(&mut self, min_prec: u8) -> Result { + pub fn expr(&mut self, min_prec: u8) -> Result { // Pratt parsing/precedence climbing: https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing let mut lhs = self.term()?; let span_start = lhs.span; @@ -95,7 +95,7 @@ impl<'a> Parser<'a> { } } - pub fn path_expr(&mut self) -> Result { + pub fn path_expr(&mut self) -> Result { match self.lexer.peek().map(|t| t.kind()) { Some(TokenKind::Lparen) => { let begin = self.lexer.next().unwrap().span; @@ -143,17 +143,17 @@ impl<'a> Parser<'a> { tok.span, format!("Unexpected '{}', expected path expression", tok.kind), ); - Err(ParseError) + Err(RecoverMode) } None => { self.errors .log_msg("Unexpected EOF, expected path expression".to_string()); - Err(ParseError) + Err(RecoverMode) } } } - pub fn term(&mut self) -> Result { + pub fn term(&mut self) -> Result { match self.lexer.peek().map(|t| t.kind()) { Some(TokenKind::Lparen) => self.paren(), Some(TokenKind::Bang) => { @@ -182,25 +182,31 @@ impl<'a> Parser<'a> { tok.span, format!("Unexpected '{}', expected expression term", tok.kind), ); - Err(ParseError) + Err(RecoverMode) } None => { self.errors .log_msg("Unexpected EOF, expected expression term".to_string()); - Err(ParseError) + Err(RecoverMode) } } } - pub fn paren(&mut self) -> Result { + pub fn paren(&mut self) -> Result { let begin = self.token(TokenKind::Lparen)?; recover!(self, self.expr(0), TokenKind::Rparen, Expr::new_error()) .map(|(end, expr)| Expr::new(begin + end, ExprKind::Paren(Arc::new(expr)))) } - pub fn owned_ident(&mut self) -> Result { + pub fn owned_ident(&mut self) -> Result { let lhs = self.ident()?; - if !matches!(self.lexer.peek(), Some(Token { kind: TokenKind::Dot, .. })) { + if !matches!( + self.lexer.peek(), + Some(Token { + kind: TokenKind::Dot, + .. + }) + ) { return Ok(lhs); } let _ = self.token(TokenKind::Dot)?; @@ -211,7 +217,7 @@ impl<'a> Parser<'a> { )) } - pub fn ident(&mut self) -> Result { + pub fn ident(&mut self) -> Result { match self.lexer.peek() { Some(Token { kind: TokenKind::Word(_), @@ -225,17 +231,17 @@ impl<'a> Parser<'a> { tok.span, format!("Unexpected '{}', expected identifier", tok.kind), ); - Err(ParseError) + Err(RecoverMode) } None => { self.errors .log_msg("Unexpected EOF, expected identifier".to_string()); - Err(ParseError) + Err(RecoverMode) } } } - pub fn enforce_coalition(&mut self) -> Result { + pub fn enforce_coalition(&mut self) -> Result { let begin = self.token(TokenKind::Llangle)?; let (end, players) = recover!(self, self.coalition_players(), TokenKind::Rrangle, vec![])?; let expr = self.path_expr()?; @@ -250,7 +256,7 @@ impl<'a> Parser<'a> { )) } - pub fn despite_coalition(&mut self) -> Result { + pub fn despite_coalition(&mut self) -> Result { let begin = self.token(TokenKind::Llbracket)?; let (end, players) = recover!(self, self.coalition_players(), TokenKind::Rrbracket, vec![])?; @@ -266,7 +272,7 @@ impl<'a> Parser<'a> { )) } - pub fn coalition_players(&mut self) -> Result, ParseError> { + pub fn coalition_players(&mut self) -> Result, RecoverMode> { let mut players = vec![]; #[allow(clippy::while_let_loop)] loop { @@ -302,7 +308,7 @@ impl<'a> Parser<'a> { self.recovery_tokens.last().unwrap() ), ); - return Err(ParseError); + return Err(RecoverMode); } _ => { self.errors.log_msg(format!( @@ -310,14 +316,14 @@ impl<'a> Parser<'a> { TokenKind::Comma, self.recovery_tokens.last().unwrap() )); - return Err(ParseError); + return Err(RecoverMode); } } } Ok(players) } - fn token(&mut self, kind: TokenKind) -> Result { + fn token(&mut self, kind: TokenKind) -> Result { match self.lexer.next() { Some(tok) if tok.kind == kind => Ok(tok.span), Some(tok) => { @@ -325,12 +331,12 @@ impl<'a> Parser<'a> { tok.span, format!("Unexpected '{}', expected '{}'", tok.kind, kind), ); - Err(ParseError) + Err(RecoverMode) } None => { self.errors .log_msg(format!("Unexpected EOF, expected '{}'", kind)); - Err(ParseError) + Err(RecoverMode) } } } @@ -346,4 +352,4 @@ impl<'a> Parser<'a> { } #[derive(Debug, Eq, PartialEq)] -pub struct ParseError; +pub struct RecoverMode; From 775b31952eb21cceaa3a181c1b4fd837d1bea700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Thu, 7 Sep 2023 11:22:02 +0200 Subject: [PATCH 29/38] Do not expect EOF after unrecovered parsing error --- cgaal-engine/src/parsing/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 1fcac2ec6..a79977c7d 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -16,8 +16,13 @@ pub struct ParseErrorSeeLog; pub fn parse_atl(input: &str, errors: &mut ErrorLog) -> Result { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer, errors); - let expr = parser.expr(0).unwrap_or(Expr::new_error()); - parser.expect_end(); + let expr = parser + .expr(0) + .and_then(|expr| { + parser.expect_end(); + Ok(expr) + }) + .unwrap_or(Expr::new_error()); if errors.has_errors() { Err(ParseErrorSeeLog) } else { From 5e00bf35a6f6b55a1d129cab08fe3c9caa69d4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Fri, 8 Sep 2023 13:17:51 +0200 Subject: [PATCH 30/38] Add batch of atl parsing tests --- cgaal-cli/src/main.rs | 1 - cgaal-engine/src/parsing/mod.rs | 9 +- cgaal-engine/tests/parsing.rs | 89 +++++++++++++++++++ .../tests/partial_strategy_synthesis.rs | 1 - 4 files changed, 92 insertions(+), 8 deletions(-) diff --git a/cgaal-cli/src/main.rs b/cgaal-cli/src/main.rs index f2daddc3d..f2ad02d6c 100644 --- a/cgaal-cli/src/main.rs +++ b/cgaal-cli/src/main.rs @@ -362,7 +362,6 @@ fn load_formula(path: &str, formula_type: FormulaType, game: Option<&Intermediat }); let mut errors = ErrorLog::new(); parse_atl(&raw_phi, &mut errors) - .ok() .and_then(|expr| convert_expr_to_phi(&expr, game, &mut errors)) .unwrap_or_else(|| { eprint!( diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index a79977c7d..9497f47fc 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -10,10 +10,7 @@ pub mod parser; pub mod span; mod token; -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub struct ParseErrorSeeLog; - -pub fn parse_atl(input: &str, errors: &mut ErrorLog) -> Result { +pub fn parse_atl(input: &str, errors: &mut ErrorLog) -> Option { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer, errors); let expr = parser @@ -24,8 +21,8 @@ pub fn parse_atl(input: &str, errors: &mut ErrorLog) -> Result> F p1.attr", + "<> G p1.attr", + "<> X p2.attr", + "[[]] (prop U p1.attr)", + "[[p1, p2]] (prop U p2.attr)", + "prop && <> F p1.attr && !prop", + "[[p1]] F false || [[p2]] G true", + "[[p2]] (<> X p1.attr && !prop U true || false)", + ]; + + let root = parse_lcgs(lcgs_raw).unwrap(); + let game = IntermediateLcgs::create(root).unwrap(); + + for atl_raw in atls { + let mut errors = ErrorLog::new(); + parse_atl(atl_raw, &mut errors) + .and_then(|expr| convert_expr_to_phi(&expr, &game, &mut errors)) + .expect(&format!("For '{}', ErrorLog is not empty: {:?}", atl_raw, errors)); + } +} + +#[test] +fn atl_expr_error_batch() { + let lcgs_raw = "\n\ + player p1 = thing;\n\ + player p2 = thing;\n\ + \n\ + label prop = 1;\n\ + \n\ + template thing\n\ + x : [0..10] init 0;\n\ + x' = x;\n\ + \n\ + label attr = 1;\n\ + \n\ + [wait] 1;\n\ + endtemplate\n"; + + let atls = [ + "!", + "prop &&", + "foo", + "<> F (prop)", + "<> F (global.prop)", + "[[>>", + "()", + "prop || p2", + ]; + + let root = parse_lcgs(lcgs_raw).unwrap(); + let game = IntermediateLcgs::create(root).unwrap(); + + for atl_raw in atls { + let mut errors = ErrorLog::new(); + let is_none = parse_atl(atl_raw, &mut errors) + .and_then(|expr| convert_expr_to_phi(&expr, &game, &mut errors)) + .is_none(); + assert!(is_none && errors.has_errors(), "For '{}', ErrorLog is empty", atl_raw); + } +} \ No newline at end of file diff --git a/cgaal-engine/tests/partial_strategy_synthesis.rs b/cgaal-engine/tests/partial_strategy_synthesis.rs index 1cb6422fc..708a2ee27 100644 --- a/cgaal-engine/tests/partial_strategy_synthesis.rs +++ b/cgaal-engine/tests/partial_strategy_synthesis.rs @@ -150,7 +150,6 @@ macro_rules! strat_synthesis_test { let ast = parse_lcgs($game).unwrap(); let game = IntermediateLcgs::create(ast).unwrap(); let phi = parse_atl($phi, &mut errors) - .ok() .and_then(|expr| convert_expr_to_phi(&expr, &game, &mut errors)) .ok_or_else(|| format!("{}", errors.to_string($phi))) .unwrap(); From 74dcf664e91ca0ad6ef3ff6401c99dd111be6f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Fri, 8 Sep 2023 15:58:28 +0200 Subject: [PATCH 31/38] Add comments to new parser --- cgaal-engine/src/atl/convert.rs | 5 ++++ cgaal-engine/src/atl/game_formula.rs | 4 +-- cgaal-engine/src/atl/mod.rs | 4 +-- cgaal-engine/src/parsing/ast.rs | 32 ++++++++++++++++++++ cgaal-engine/src/parsing/errors.rs | 21 ++++++++----- cgaal-engine/src/parsing/lexer.rs | 11 +++++++ cgaal-engine/src/parsing/mod.rs | 6 ++-- cgaal-engine/src/parsing/parser.rs | 38 +++++++++++++++++++----- cgaal-engine/src/parsing/span.rs | 44 ++++------------------------ cgaal-engine/src/parsing/token.rs | 17 ++++------- cgaal-engine/tests/parsing.rs | 31 ++++++++++++++++++-- 11 files changed, 140 insertions(+), 73 deletions(-) diff --git a/cgaal-engine/src/atl/convert.rs b/cgaal-engine/src/atl/convert.rs index f61c814a2..a82dbcc52 100644 --- a/cgaal-engine/src/atl/convert.rs +++ b/cgaal-engine/src/atl/convert.rs @@ -6,6 +6,9 @@ use crate::game_structure::Player; use crate::parsing::ast::{BinaryOpKind, Coalition, CoalitionKind, Expr, ExprKind, UnaryOpKind}; use crate::parsing::errors::ErrorLog; +/// Convert an ATL expression to a Phi formula. +/// Players and labels must be defined in the game and are compiled to their respective indexes. +/// Returns None if there were errors. See the error log for details. pub fn convert_expr_to_phi( expr: &Expr, game: &IntermediateLcgs, @@ -197,6 +200,8 @@ pub fn convert_expr_to_phi( } } +/// Helper function for converting a list of player names to a list of player indexes. +/// Returns None if there were errors. See the error log for details. fn convert_players( players: &[Expr], game: &IntermediateLcgs, diff --git a/cgaal-engine/src/atl/game_formula.rs b/cgaal-engine/src/atl/game_formula.rs index 5e43cf86d..b95557138 100644 --- a/cgaal-engine/src/atl/game_formula.rs +++ b/cgaal-engine/src/atl/game_formula.rs @@ -11,11 +11,11 @@ use crate::game_structure::GameStructure; /// Given a Phi `formula` (`<> G p1.alive`) and a IntermediateLCGS `game_Structure`, you can /// create a GamePhi with `formula.in_context_of(&game_structure)`. Using this in a print statement /// like -/// ```ignore +/// ```md /// println!("{}", formula.in_context_of(&game_structure)) /// ``` /// will print "`<> G p1.alive`" as opposed to "`<<0>> G 1`", which happens when you just write -/// ```ignore +/// ```md /// println!("{}", formula) /// ``` pub struct GamePhi<'a, G: GameStructure> { diff --git a/cgaal-engine/src/atl/mod.rs b/cgaal-engine/src/atl/mod.rs index fee0240a9..c7240110e 100644 --- a/cgaal-engine/src/atl/mod.rs +++ b/cgaal-engine/src/atl/mod.rs @@ -316,11 +316,11 @@ impl Phi { /// Given a Phi `formula` (`<> G p1.alive`) and a IntermediateLCGS `game_Structure`, you can /// create a GamePhi with `formula.in_context_of(&game_structure)`. Using this in a print statement /// like - /// ```ignore + /// ```md /// println!("{}", formula.in_context_of(&game_structure)) /// ``` /// will print "`<> G p1.alive`" as opposed to "`<<0>> G 1`", which happens when you just write - /// ```ignore + /// ```md /// println!("{}", formula) /// ``` pub fn in_context_of<'a, G: GameStructure>(&'a self, game_structure: &'a G) -> GamePhi<'a, G> { diff --git a/cgaal-engine/src/parsing/ast.rs b/cgaal-engine/src/parsing/ast.rs index 295c06f3b..79354d0d1 100644 --- a/cgaal-engine/src/parsing/ast.rs +++ b/cgaal-engine/src/parsing/ast.rs @@ -2,9 +2,12 @@ use crate::parsing::span::Span; use crate::parsing::token::TokenKind; use std::sync::Arc; +/// An expression. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Expr { + /// The span of the expression in the original input code. pub span: Span, + /// The kind of the expression. pub kind: ExprKind, } @@ -20,37 +23,55 @@ impl Expr { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ExprKind { + /// The true constant True, + /// The false constant False, + /// An expression in parentheses Paren(Arc), + /// An identifier Ident(String), + /// A unary operation Unary(UnaryOpKind, Arc), + /// A binary operation Binary(BinaryOpKind, Arc, Arc), + /// An expression with a coalition Coalition(Coalition), + /// An error Error, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum UnaryOpKind { + /// The `!` operator for negation Not, // Temporal operators + /// The `X` temporal operator (neXt) Next, + /// The `F` temporal operator (Future/Eventually) Eventually, + /// The `G` temporal operator (Globally/Invariant) Invariantly, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum BinaryOpKind { + /// The `&&` operator for logical and (conjunction) And, + /// The `||` operator for logical or (disjunction) Or, + /// The `.` operator for owned symbols Dot, // Temporal operators + /// The `U` temporal operator (Until) Until, } impl BinaryOpKind { + /// Returns the associativity of the operator. + /// E.g. `-` is left-associative so `a - b - c` is parsed as `(a - b) - c`. pub fn associativity(&self) -> Associativity { // All operators are left-associative so far Associativity::LeftToRight @@ -60,6 +81,8 @@ impl BinaryOpKind { self.associativity() == Associativity::RightToLeft } + /// Returns the precedence of the operator. + /// Higher precedence means the operator binds tighter. pub fn precedence(&self) -> u8 { match self { BinaryOpKind::Dot => 3, @@ -82,11 +105,16 @@ impl TryFrom for BinaryOpKind { } } +/// A coalition expression. E.g. `<< p1 >> path_expr` #[derive(Debug, Clone, PartialEq, Eq)] pub struct Coalition { + /// The span of the coalition in the original input code. pub span: Span, + /// The players in the coalition. pub players: Vec, + /// The kind of the coalition. pub kind: CoalitionKind, + /// The path expression following the coalition. pub expr: Arc, } @@ -103,10 +131,14 @@ impl Coalition { #[derive(Debug, Clone, PartialEq, Eq)] pub enum CoalitionKind { + /// The `<< >>` coalition Despite, + /// The `[[ ]]` coalition Enforce, } +/// The associativity of an operator. +/// E.g. `-` is left-associative so `a - b - c` is parsed as `(a - b) - c`. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Associativity { LeftToRight, diff --git a/cgaal-engine/src/parsing/errors.rs b/cgaal-engine/src/parsing/errors.rs index 1e49c7fc0..4d1d5209e 100644 --- a/cgaal-engine/src/parsing/errors.rs +++ b/cgaal-engine/src/parsing/errors.rs @@ -2,6 +2,9 @@ use crate::parsing::span::Span; use std::cmp::{max, min}; use std::fmt::Write; +/// A log of errors that occurred during parsing or semantic analysis. +/// Each error entry has a span that indicates its origin in the original input code. +/// Given the original input code, the error log can be converted to nicely presented error messages. #[derive(Debug, Default)] pub struct ErrorLog { errors: Vec, @@ -36,18 +39,19 @@ impl ErrorLog { self.errors.is_empty() } + /// Converts the error log to a nicely formatted string using the original input code. + /// Example: + /// ```md + /// 3:13 Error: Unknown identifier 'robot' + /// | player p1 = robot[x=0, y=1]; + /// | ^^^^^ + /// ``` + /// where 3:13 indicates line 3, column 13 in the original input code. pub fn to_string(&self, orig_input: &str) -> String { let mut out = String::new(); for entry in &self.errors { match &entry.span { Some(span) => { - // Example: - // """ - // 1:13 Error: Unknown identifier 'robot' - // | player p1 = robot[x=0, y=1]; - // | ^^^^^ - // """ - // Find the line and column of the error let mut i = 0; let mut col = 1; @@ -91,9 +95,12 @@ impl ErrorLog { } } +/// A single error entry in the [ErrorLog]. #[derive(Debug)] pub struct ErrorEntry { + /// The span of the error in the original input code. span: Option, + /// The error message. msg: String, } diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs index 8f99f0e6a..023a5eaf0 100644 --- a/cgaal-engine/src/parsing/lexer.rs +++ b/cgaal-engine/src/parsing/lexer.rs @@ -1,9 +1,13 @@ use crate::parsing::span::Span; use crate::parsing::token::{Token, TokenKind}; +/// A Lexer that converts a byte slice into a stream of tokens. +/// The Lexer is an iterator over tokens. #[derive(Clone, Eq, PartialEq)] pub struct Lexer<'a> { + /// The original input byte slice. input: &'a [u8], + /// The current position in the input, i.e. the number of consumed bytes. pos: usize, } @@ -34,6 +38,8 @@ impl<'a> Lexer<'a> { Token::new(token, span) } + /// Lexes a word, i.e. a sequence of alphanumeric characters and underscores. + /// Typically identifiers and keywords. fn lex_alpha(&mut self) -> Token { let mut len = 1; while self @@ -59,6 +65,7 @@ impl<'a> Lexer<'a> { self.token(len, kind) } + /// Lexes an integer number, i.e. a sequence of digits. fn lex_num(&mut self) -> Token { let mut len = 1; while self.peek(len).map_or(false, |c| c.is_ascii_digit()) { @@ -71,6 +78,8 @@ impl<'a> Lexer<'a> { self.token(len, TokenKind::Num(val)) } + /// Consume bytes until we find a valid utf8 character. + /// This allows us to handle emojis and other non-ascii characters as well. fn lex_error(&mut self) -> Token { let mut len = 1; while std::str::from_utf8(&self.input[self.pos..self.pos + len]).is_err() { @@ -157,6 +166,7 @@ mod tests { #[test] fn lexing_001() { + // Check that the lexer produces the correct tokens with correct spans let input = "==4 /* - x (var01 > 0)"; let lexer = Lexer::new(input.as_bytes()); let mut res = String::new(); @@ -166,6 +176,7 @@ mod tests { #[test] fn lexing_002() { + // Check that the lexer produces the correct tokens with correct spans let input = " !player ->i [..]<< init>>"; let lexer = Lexer::new(input.as_bytes()); let mut res = String::new(); diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 9497f47fc..97b5c6b5c 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -10,14 +10,16 @@ pub mod parser; pub mod span; mod token; +/// Parse an ATL expression. +/// Returns None if there were errors. See the error log for details. pub fn parse_atl(input: &str, errors: &mut ErrorLog) -> Option { let lexer = Lexer::new(input.as_bytes()); let mut parser = Parser::new(lexer, errors); let expr = parser .expr(0) - .and_then(|expr| { + .map(|expr| { parser.expect_end(); - Ok(expr) + expr }) .unwrap_or(Expr::new_error()); if errors.has_errors() { diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index b037548ac..e004ee0b5 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -6,6 +6,12 @@ use crate::parsing::token::{Token, TokenKind}; use std::iter::Peekable; use std::sync::Arc; +/// A helper macro for error recovery. +/// It tries to parse the given expression and then the given recovery token. +/// If it fails to parse expression, it tries to recover by skipping tokens until it finds any recovery token. +/// If the found recovery token is the expected one, it recovers and returns the error value. +/// If the found recovery token is not the expected one, it returns [RecoverMode] as an error such that the caller can try to recover. +/// This macro cannot be a function due to the borrow checker. macro_rules! recover { ($self:expr, $val:expr, $recover_token:expr, $err_val:expr) => {{ $self.recovery_tokens.push($recover_token); @@ -13,12 +19,13 @@ macro_rules! recover { Ok(v) => { match $self.lexer.peek() { Some(Token { kind, .. }) if kind == &$recover_token => { - // Happy path + // Happy path - we parsed the expression and found the recovery token let tok = $self.lexer.next().unwrap(); $self.recovery_tokens.pop(); Ok((tok.span, v)) } Some(Token { kind, span }) => { + // The expression was parsed, but the recovery token was not found $self.errors.log( *span, format!("Unexpected '{}', expected '{}'", kind, $recover_token), @@ -36,12 +43,13 @@ macro_rules! recover { Err(_) => Err(RecoverMode), }; res.or_else(|_| { - // Unhappy path - // Try recover + // Unhappy path - Something went wrong + // Try to recover by skipping tokens until we find a recovery token $self.skip_until_recovery(); $self.recovery_tokens.pop(); match $self.lexer.peek() { Some(Token { kind, .. }) if kind == &$recover_token => { + // Success recovery let tok = $self.lexer.next().unwrap(); Ok((tok.span, $err_val)) } @@ -54,6 +62,7 @@ macro_rules! recover { pub struct Parser<'a> { lexer: Peekable>, errors: &'a mut ErrorLog, + /// A stack of tokens that can be used for error recovery. recovery_tokens: Vec, } @@ -66,6 +75,7 @@ impl<'a> Parser<'a> { } } + /// Parse EOF. If there are any tokens left, log an error. pub fn expect_end(&mut self) { match self.lexer.next() { None => {} @@ -77,12 +87,13 @@ impl<'a> Parser<'a> { for _ in self.lexer.by_ref() {} } + /// Parse an expression. pub fn expr(&mut self, min_prec: u8) -> Result { // Pratt parsing/precedence climbing: https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#climbing let mut lhs = self.term()?; let span_start = lhs.span; loop { - let Some(op): Option = self.lexer.peek().and_then(|t| t.kind().clone().try_into().ok()) else { return Ok(lhs) }; + let Some(op): Option = self.lexer.peek().and_then(|t| t.kind.clone().try_into().ok()) else { return Ok(lhs) }; if op.precedence() < min_prec { return Ok(lhs); } @@ -95,8 +106,10 @@ impl<'a> Parser<'a> { } } + /// Parse a path expression. + /// Path expressions treat `X`, `F`, `G`, `U` as temporal operators instead of identifiers. pub fn path_expr(&mut self) -> Result { - match self.lexer.peek().map(|t| t.kind()) { + match self.lexer.peek().map(|t| &t.kind) { Some(TokenKind::Lparen) => { let begin = self.lexer.next().unwrap().span; let (_, lhs) = recover!( @@ -136,7 +149,7 @@ impl<'a> Parser<'a> { ExprKind::Unary(UnaryOpKind::Next, expr.into()), )) } - // Unexpected + // Unexpected cases Some(_) => { let tok = self.lexer.next().unwrap(); self.errors.log( @@ -153,8 +166,9 @@ impl<'a> Parser<'a> { } } + /// Parse an expression term. pub fn term(&mut self) -> Result { - match self.lexer.peek().map(|t| t.kind()) { + match self.lexer.peek().map(|t| &t.kind) { Some(TokenKind::Lparen) => self.paren(), Some(TokenKind::Bang) => { let begin = self.lexer.next().unwrap().span; @@ -192,12 +206,14 @@ impl<'a> Parser<'a> { } } + /// Parse a parenthesized expression. pub fn paren(&mut self) -> Result { let begin = self.token(TokenKind::Lparen)?; recover!(self, self.expr(0), TokenKind::Rparen, Expr::new_error()) .map(|(end, expr)| Expr::new(begin + end, ExprKind::Paren(Arc::new(expr)))) } + /// Parse an (owned) identifier, e.g. `p1` or `p1.attr`. pub fn owned_ident(&mut self) -> Result { let lhs = self.ident()?; if !matches!( @@ -217,6 +233,7 @@ impl<'a> Parser<'a> { )) } + /// Parse an identifier. pub fn ident(&mut self) -> Result { match self.lexer.peek() { Some(Token { @@ -241,6 +258,7 @@ impl<'a> Parser<'a> { } } + /// Parse an enforce-coalition expression. pub fn enforce_coalition(&mut self) -> Result { let begin = self.token(TokenKind::Llangle)?; let (end, players) = recover!(self, self.coalition_players(), TokenKind::Rrangle, vec![])?; @@ -256,6 +274,7 @@ impl<'a> Parser<'a> { )) } + /// Parse a despite-coalition expression. pub fn despite_coalition(&mut self) -> Result { let begin = self.token(TokenKind::Llbracket)?; let (end, players) = @@ -272,6 +291,7 @@ impl<'a> Parser<'a> { )) } + /// Parse a comma-separated list of players (with the assumption that we are inside a coalition.) pub fn coalition_players(&mut self) -> Result, RecoverMode> { let mut players = vec![]; #[allow(clippy::while_let_loop)] @@ -323,6 +343,7 @@ impl<'a> Parser<'a> { Ok(players) } + /// Parse a token of the given kind. fn token(&mut self, kind: TokenKind) -> Result { match self.lexer.next() { Some(tok) if tok.kind == kind => Ok(tok.span), @@ -341,6 +362,7 @@ impl<'a> Parser<'a> { } } + /// Skip tokens until the next is a recovery token. fn skip_until_recovery(&mut self) { while let Some(tok) = self.lexer.peek() { if self.recovery_tokens.contains(&tok.kind) { @@ -351,5 +373,7 @@ impl<'a> Parser<'a> { } } +/// A marker type for error recovery. If a function returns this, it means that it failed to +/// parse the input and the caller should try to recover. #[derive(Debug, Eq, PartialEq)] pub struct RecoverMode; diff --git a/cgaal-engine/src/parsing/span.rs b/cgaal-engine/src/parsing/span.rs index d70c7394d..fe316376c 100644 --- a/cgaal-engine/src/parsing/span.rs +++ b/cgaal-engine/src/parsing/span.rs @@ -2,8 +2,8 @@ use std::cmp::{max, min}; use std::fmt::{Display, Formatter}; use std::ops::{Add, Range}; -/// A `Span` describes the position of a slice of text in the original program. -/// Usually used to describe what text an AST node was created from. +/// A [Span] describes the position of a slice of text in the original program. +/// Usually used to describe what text a token or an AST node was created from. #[derive(Eq, PartialEq, Debug, Copy, Clone, Default)] pub struct Span { pub begin: usize, @@ -15,12 +15,13 @@ impl Span { Span { begin, end } } - /// Returns and equivalent range + /// Returns an equivalent range pub fn to_range(&self) -> Range { self.begin..self.end } - /// Merge two spans into a new span that contains the original spans and everything in between + /// Merge two spans into a new span that contains the original spans and everything in between. + /// The + operator can also be used for this. pub fn merge(&self, other: Span) -> Span { Span { begin: min(self.begin, other.begin), @@ -54,38 +55,3 @@ impl Add for Span { self.merge(rhs) } } - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LineColumn { - pub line: usize, - pub column: usize, -} - -impl LineColumn { - pub fn new(line: usize, column: usize) -> Self { - LineColumn { line, column } - } - - pub fn from_pos(pos: usize, orig_input: &str) -> Self { - let mut line = 1; - let mut column = 1; - for (i, c) in orig_input.bytes().enumerate() { - if i == pos { - return LineColumn { line, column }; - } - if c == b'\n' { - line += 1; - column = 1; - } else { - column += 1; - } - } - LineColumn { line, column } - } -} - -impl Display for LineColumn { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}", self.line, self.column) - } -} diff --git a/cgaal-engine/src/parsing/token.rs b/cgaal-engine/src/parsing/token.rs index c87ff2e59..ff62d3400 100644 --- a/cgaal-engine/src/parsing/token.rs +++ b/cgaal-engine/src/parsing/token.rs @@ -1,9 +1,12 @@ use crate::parsing::span::Span; use std::fmt::{Debug, Display, Formatter}; +/// A single token in the input. #[derive(Clone, PartialEq, Eq)] pub struct Token { + /// The span of the token in the original input code. pub span: Span, + /// The kind of the token. pub kind: TokenKind, } @@ -11,20 +14,11 @@ impl Token { pub fn new(kind: TokenKind, span: Span) -> Self { Token { kind, span } } - - #[allow(unused)] - pub fn span(&self) -> &Span { - &self.span - } - - #[allow(unused)] - pub fn kind(&self) -> &TokenKind { - &self.kind - } } impl Display for Token { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // Tokens are used print as their kind write!(f, "{}", self.kind) } } @@ -35,6 +29,7 @@ impl Debug for Token { } } +/// The kind of a token. #[derive(Debug, Clone, PartialEq, Eq)] pub enum TokenKind { // Delimiters @@ -92,7 +87,7 @@ pub enum TokenKind { Num(i32), Word(String), - // Utility + // Error Err(String), } diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing.rs index 87afd00fe..3fa61390b 100644 --- a/cgaal-engine/tests/parsing.rs +++ b/cgaal-engine/tests/parsing.rs @@ -10,6 +10,7 @@ use cgaal_engine::parsing::span::*; #[test] fn basic_expr_001() { + // Check true and false let input = "true && false"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -32,6 +33,7 @@ fn basic_expr_001() { #[test] fn basic_expr_002() { + // Check precedence of && over || let input = "true && false || true"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -62,6 +64,7 @@ fn basic_expr_002() { #[test] fn basic_expr_003() { + // Check precedence of && over || and parenthesis let input = "foo || true && bar || (true || false)"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -116,6 +119,7 @@ fn basic_expr_003() { #[test] fn atl_expr_001() { + // Check single player in coalition and eventually operator let input = "<> F goal"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -149,6 +153,7 @@ fn atl_expr_001() { #[test] fn atl_expr_002() { + // Check multiple players in coalition and invariant operator let input = "[[p1, p2]] G safe"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -182,6 +187,7 @@ fn atl_expr_002() { #[test] fn atl_expr_003() { + // Check empty coalition and until operator let input = "<<>> (safe U goal)"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -213,6 +219,7 @@ fn atl_expr_003() { #[test] fn erroneous_expr_001() { + // Check if unexpected EOF is reported correctly let input = "true && "; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -228,6 +235,7 @@ fn erroneous_expr_001() { #[test] fn erroneous_expr_002() { + // Check if error report when EOF is expected let input = "foo bar"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -250,6 +258,7 @@ fn erroneous_expr_002() { #[test] fn erroneous_expr_003() { + // Check if error recovery works on parenthesis let input = "(foo false) && true"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -279,6 +288,7 @@ fn erroneous_expr_003() { #[test] fn erroneous_expr_004() { + // Check if unrecoverable error is detected let input = "(true"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -294,6 +304,7 @@ fn erroneous_expr_004() { #[test] fn erroneous_expr_005() { + // Check error inside unclosed parenthesis is reported correctly let input = "(foo bar"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -311,6 +322,7 @@ fn erroneous_expr_005() { #[test] fn erroneous_expr_006() { + // Check error missing >> inside parenthesis is reported correctly let input = " ( << p1 foo goal)"; let mut errors = ErrorLog::new(); let lexer = Lexer::new(input.as_bytes()); @@ -333,6 +345,7 @@ fn erroneous_expr_006() { #[test] fn atl_expr_batch() { + // Check that no errors are found for valid ATL expressions let lcgs_raw = "\n\ player p1 = thing;\n\ player p2 = thing;\n\ @@ -372,12 +385,16 @@ fn atl_expr_batch() { let mut errors = ErrorLog::new(); parse_atl(atl_raw, &mut errors) .and_then(|expr| convert_expr_to_phi(&expr, &game, &mut errors)) - .expect(&format!("For '{}', ErrorLog is not empty: {:?}", atl_raw, errors)); + .expect(&format!( + "For '{}', ErrorLog is not empty: {:?}", + atl_raw, errors + )); } } #[test] fn atl_expr_error_batch() { + // Check that errors are found for erroneous ATL expressions let lcgs_raw = "\n\ player p1 = thing;\n\ player p2 = thing;\n\ @@ -402,6 +419,10 @@ fn atl_expr_error_batch() { "[[>>", "()", "prop || p2", + "p2.wait", + "p1.attr && p2.attr && p3.attr", + "p1.x > 0", + "p1.x", ]; let root = parse_lcgs(lcgs_raw).unwrap(); @@ -412,6 +433,10 @@ fn atl_expr_error_batch() { let is_none = parse_atl(atl_raw, &mut errors) .and_then(|expr| convert_expr_to_phi(&expr, &game, &mut errors)) .is_none(); - assert!(is_none && errors.has_errors(), "For '{}', ErrorLog is empty", atl_raw); + assert!( + is_none && errors.has_errors(), + "For '{}', ErrorLog is empty", + atl_raw + ); } -} \ No newline at end of file +} From a2c3799704488c52ad9bebb7360abae8e68a789a Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Fri, 8 Sep 2023 22:18:12 +0200 Subject: [PATCH 32/38] Update ATL files to use && and || --- .../fischer/p1_eventually_get_access_if_requested_TRUE.atl | 2 +- ...ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl | 2 +- ...3456_ensure_that_p123456_omniscient_before_10_steps_TRUE.atl | 2 +- .../gossipping_girls/p123456_eventually_omniscient_TRUE.atl | 2 +- .../p123456_omniscient_but_first_after_10_steps_TRUE.atl | 2 +- ...ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl | 2 +- ...12345_ensure_that_p12345_omniscient_before_10_steps_TRUE.atl | 2 +- .../gossipping_girls/p12345_eventually_omniscient_TRUE.atl | 2 +- .../p12345_omniscient_but_first_after_10_steps_TRUE.atl | 2 +- ...ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl | 2 +- ...4_ensure_that_p1234_gets_omniscient_before_10_steps_TRUE.atl | 2 +- .../gossipping_girls/p1234_eventually_omniscient_TRUE.atl | 2 +- .../p1234_omniscient_but_first_after_10_steps_TRUE.atl | 2 +- .../p23456_eventually_omniscient_without_p1.TRUE | 2 +- .../p2345_eventually_omniscient_without_p1.TRUE | 2 +- .../p234_eventually_omniscient_without_p1_TRUE.atl | 2 +- .../matching_pennies/can_they_win_simultaneously_FALSE.atl | 2 +- .../can_2_players_guarantee_atleast_one_of_them_survives.atl | 2 +- .../can_3_players_guarantee_atleast_one_of_them_survives.atl | 2 +- .../can_4_players_guarantee_atleast_one_of_them_survives.atl | 2 +- .../can_5_players_guarantee_atleast_one_of_them_survives.atl | 2 +- .../can_6_players_guarantee_atleast_one_of_them_survives.atl | 2 +- .../peterson/p0_eventually_get_access_if_requested_TRUE.atl | 2 +- .../robot_grid/can_r1_and_r2_swap_with_help_from_r3_FALSE.atl | 2 +- .../robot_grid/can_r1_get_past_r2_when_helped_TRUE.atl | 2 +- lcgs-examples/robot_grid/everyone_starts_home_TRUE.atl | 2 +- .../robot_grid/exist_path_to_targets_with_no_crashes_TRUE.atl | 2 +- lcgs-examples/tic_tac_toe/can_cross_avoid_lose_TRUE.atl | 2 +- lcgs-examples/tic_tac_toe/can_nought_avoid_lose_TRUE.atl | 2 +- 29 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lcgs-examples/fischer/p1_eventually_get_access_if_requested_TRUE.atl b/lcgs-examples/fischer/p1_eventually_get_access_if_requested_TRUE.atl index c1b2a4ff9..c4ccb0fe0 100644 --- a/lcgs-examples/fischer/p1_eventually_get_access_if_requested_TRUE.atl +++ b/lcgs-examples/fischer/p1_eventually_get_access_if_requested_TRUE.atl @@ -1 +1 @@ -<> G (!p1-requested-access-to-CS | <<>> F p1_in_cs) \ No newline at end of file +<> G (!p1-requested-access-to-CS || <<>> F p1_in_cs) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p123456_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl b/lcgs-examples/gossipping_girls/p123456_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl index 7783c3319..3b32d285d 100644 --- a/lcgs-examples/gossipping_girls/p123456_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p123456_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl @@ -1 +1 @@ -<> (less_than_10_steps U p1.omniscient & !p2.omniscient & !p3.omniscient & !p4.omniscient & !p5.omniscient & p6.omniscient) \ No newline at end of file +<> (less_than_10_steps U p1.omniscient && !p2.omniscient && !p3.omniscient && !p4.omniscient && !p5.omniscient && p6.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p123456_ensure_that_p123456_omniscient_before_10_steps_TRUE.atl b/lcgs-examples/gossipping_girls/p123456_ensure_that_p123456_omniscient_before_10_steps_TRUE.atl index dda3f4824..18ac231d5 100644 --- a/lcgs-examples/gossipping_girls/p123456_ensure_that_p123456_omniscient_before_10_steps_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p123456_ensure_that_p123456_omniscient_before_10_steps_TRUE.atl @@ -1 +1 @@ -<> (less_than_10_steps U p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient & p5.omniscient & p6.omniscient) \ No newline at end of file +<> (less_than_10_steps U p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient && p5.omniscient && p6.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p123456_eventually_omniscient_TRUE.atl b/lcgs-examples/gossipping_girls/p123456_eventually_omniscient_TRUE.atl index bff8870b0..49acb1eef 100644 --- a/lcgs-examples/gossipping_girls/p123456_eventually_omniscient_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p123456_eventually_omniscient_TRUE.atl @@ -1 +1 @@ -<> (p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient & p5.omniscient & p6.omniscient) \ No newline at end of file +<> (p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient && p5.omniscient && p6.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p123456_omniscient_but_first_after_10_steps_TRUE.atl b/lcgs-examples/gossipping_girls/p123456_omniscient_but_first_after_10_steps_TRUE.atl index dda3f4824..18ac231d5 100644 --- a/lcgs-examples/gossipping_girls/p123456_omniscient_but_first_after_10_steps_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p123456_omniscient_but_first_after_10_steps_TRUE.atl @@ -1 +1 @@ -<> (less_than_10_steps U p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient & p5.omniscient & p6.omniscient) \ No newline at end of file +<> (less_than_10_steps U p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient && p5.omniscient && p6.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p12345_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl b/lcgs-examples/gossipping_girls/p12345_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl index 708b68f6d..7a0a7c06e 100644 --- a/lcgs-examples/gossipping_girls/p12345_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p12345_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl @@ -1 +1 @@ -<> (less_than_10_steps U p1.omniscient & !p2.omniscient & !p3.omniscient & !p4.omniscient & !p5.omniscient) \ No newline at end of file +<> (less_than_10_steps U p1.omniscient && !p2.omniscient && !p3.omniscient && !p4.omniscient && !p5.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p12345_ensure_that_p12345_omniscient_before_10_steps_TRUE.atl b/lcgs-examples/gossipping_girls/p12345_ensure_that_p12345_omniscient_before_10_steps_TRUE.atl index f10e61111..aa55c359e 100644 --- a/lcgs-examples/gossipping_girls/p12345_ensure_that_p12345_omniscient_before_10_steps_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p12345_ensure_that_p12345_omniscient_before_10_steps_TRUE.atl @@ -1 +1 @@ -<> (less_than_10_steps U p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient & p5.omniscient) \ No newline at end of file +<> (less_than_10_steps U p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient && p5.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p12345_eventually_omniscient_TRUE.atl b/lcgs-examples/gossipping_girls/p12345_eventually_omniscient_TRUE.atl index 35958d40f..4b07386d4 100644 --- a/lcgs-examples/gossipping_girls/p12345_eventually_omniscient_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p12345_eventually_omniscient_TRUE.atl @@ -1 +1 @@ -<> (p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient & p5.omniscient) \ No newline at end of file +<> (p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient && p5.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p12345_omniscient_but_first_after_10_steps_TRUE.atl b/lcgs-examples/gossipping_girls/p12345_omniscient_but_first_after_10_steps_TRUE.atl index f10e61111..aa55c359e 100644 --- a/lcgs-examples/gossipping_girls/p12345_omniscient_but_first_after_10_steps_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p12345_omniscient_but_first_after_10_steps_TRUE.atl @@ -1 +1 @@ -<> (less_than_10_steps U p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient & p5.omniscient) \ No newline at end of file +<> (less_than_10_steps U p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient && p5.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p1234_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl b/lcgs-examples/gossipping_girls/p1234_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl index 6d401383d..054fd0513 100644 --- a/lcgs-examples/gossipping_girls/p1234_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p1234_ensure_that_only_p1_gets_omniscient_before_10_steps_TRUE.atl @@ -1 +1 @@ -<> (less_than_10_steps U p1.omniscient & !p2.omniscient & !p3.omniscient & !p4.omniscient) \ No newline at end of file +<> (less_than_10_steps U p1.omniscient && !p2.omniscient && !p3.omniscient && !p4.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p1234_ensure_that_p1234_gets_omniscient_before_10_steps_TRUE.atl b/lcgs-examples/gossipping_girls/p1234_ensure_that_p1234_gets_omniscient_before_10_steps_TRUE.atl index 252cc156f..a95b7b66c 100644 --- a/lcgs-examples/gossipping_girls/p1234_ensure_that_p1234_gets_omniscient_before_10_steps_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p1234_ensure_that_p1234_gets_omniscient_before_10_steps_TRUE.atl @@ -1 +1 @@ -<> (less_than_10_steps U p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient) \ No newline at end of file +<> (less_than_10_steps U p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p1234_eventually_omniscient_TRUE.atl b/lcgs-examples/gossipping_girls/p1234_eventually_omniscient_TRUE.atl index 8d49af330..17144b028 100644 --- a/lcgs-examples/gossipping_girls/p1234_eventually_omniscient_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p1234_eventually_omniscient_TRUE.atl @@ -1 +1 @@ -<> (p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient) \ No newline at end of file +<> (p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p1234_omniscient_but_first_after_10_steps_TRUE.atl b/lcgs-examples/gossipping_girls/p1234_omniscient_but_first_after_10_steps_TRUE.atl index 8e0d47469..f2e9dea0a 100644 --- a/lcgs-examples/gossipping_girls/p1234_omniscient_but_first_after_10_steps_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p1234_omniscient_but_first_after_10_steps_TRUE.atl @@ -1 +1 @@ -<> F (p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient & !less_than_10_steps) \ No newline at end of file +<> F (p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient && !less_than_10_steps) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p23456_eventually_omniscient_without_p1.TRUE b/lcgs-examples/gossipping_girls/p23456_eventually_omniscient_without_p1.TRUE index 65753b4fd..bc846cae9 100644 --- a/lcgs-examples/gossipping_girls/p23456_eventually_omniscient_without_p1.TRUE +++ b/lcgs-examples/gossipping_girls/p23456_eventually_omniscient_without_p1.TRUE @@ -1 +1 @@ -<> F (!p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient & p5.omniscient & p6.omniscient) \ No newline at end of file +<> F (!p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient && p5.omniscient && p6.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p2345_eventually_omniscient_without_p1.TRUE b/lcgs-examples/gossipping_girls/p2345_eventually_omniscient_without_p1.TRUE index 98cb30ff8..b0b47d000 100644 --- a/lcgs-examples/gossipping_girls/p2345_eventually_omniscient_without_p1.TRUE +++ b/lcgs-examples/gossipping_girls/p2345_eventually_omniscient_without_p1.TRUE @@ -1 +1 @@ -<> F (!p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient & p5.omniscient) \ No newline at end of file +<> F (!p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient && p5.omniscient) \ No newline at end of file diff --git a/lcgs-examples/gossipping_girls/p234_eventually_omniscient_without_p1_TRUE.atl b/lcgs-examples/gossipping_girls/p234_eventually_omniscient_without_p1_TRUE.atl index c69fcb3b4..afcfe3ee7 100644 --- a/lcgs-examples/gossipping_girls/p234_eventually_omniscient_without_p1_TRUE.atl +++ b/lcgs-examples/gossipping_girls/p234_eventually_omniscient_without_p1_TRUE.atl @@ -1 +1 @@ -<> F (!p1.omniscient & p2.omniscient & p3.omniscient & p4.omniscient) \ No newline at end of file +<> F (!p1.omniscient && p2.omniscient && p3.omniscient && p4.omniscient) \ No newline at end of file diff --git a/lcgs-examples/matching_pennies/can_they_win_simultaneously_FALSE.atl b/lcgs-examples/matching_pennies/can_they_win_simultaneously_FALSE.atl index 5ae7ca4a8..645b0c2a7 100644 --- a/lcgs-examples/matching_pennies/can_they_win_simultaneously_FALSE.atl +++ b/lcgs-examples/matching_pennies/can_they_win_simultaneously_FALSE.atl @@ -1 +1 @@ -<> F (odd_won_round & even_won_round) \ No newline at end of file +<> F (odd_won_round && even_won_round) \ No newline at end of file diff --git a/lcgs-examples/mexican_standoff/can_2_players_guarantee_atleast_one_of_them_survives.atl b/lcgs-examples/mexican_standoff/can_2_players_guarantee_atleast_one_of_them_survives.atl index ec21b3a25..e22902496 100644 --- a/lcgs-examples/mexican_standoff/can_2_players_guarantee_atleast_one_of_them_survives.atl +++ b/lcgs-examples/mexican_standoff/can_2_players_guarantee_atleast_one_of_them_survives.atl @@ -1 +1 @@ -<> G (p0.alive | p1.alive) \ No newline at end of file +<> G (p0.alive || p1.alive) \ No newline at end of file diff --git a/lcgs-examples/mexican_standoff/can_3_players_guarantee_atleast_one_of_them_survives.atl b/lcgs-examples/mexican_standoff/can_3_players_guarantee_atleast_one_of_them_survives.atl index 376bb6450..5c172e90b 100644 --- a/lcgs-examples/mexican_standoff/can_3_players_guarantee_atleast_one_of_them_survives.atl +++ b/lcgs-examples/mexican_standoff/can_3_players_guarantee_atleast_one_of_them_survives.atl @@ -1 +1 @@ -<> G (p0.alive | p1.alive | p2.alive) \ No newline at end of file +<> G (p0.alive || p1.alive || p2.alive) \ No newline at end of file diff --git a/lcgs-examples/mexican_standoff/can_4_players_guarantee_atleast_one_of_them_survives.atl b/lcgs-examples/mexican_standoff/can_4_players_guarantee_atleast_one_of_them_survives.atl index c06b45990..c963abe0a 100644 --- a/lcgs-examples/mexican_standoff/can_4_players_guarantee_atleast_one_of_them_survives.atl +++ b/lcgs-examples/mexican_standoff/can_4_players_guarantee_atleast_one_of_them_survives.atl @@ -1 +1 @@ -<> G (p0.alive | p1.alive | p2.alive | p3.alive) \ No newline at end of file +<> G (p0.alive || p1.alive || p2.alive || p3.alive) \ No newline at end of file diff --git a/lcgs-examples/mexican_standoff/can_5_players_guarantee_atleast_one_of_them_survives.atl b/lcgs-examples/mexican_standoff/can_5_players_guarantee_atleast_one_of_them_survives.atl index 5718bf411..2f8f65e2a 100644 --- a/lcgs-examples/mexican_standoff/can_5_players_guarantee_atleast_one_of_them_survives.atl +++ b/lcgs-examples/mexican_standoff/can_5_players_guarantee_atleast_one_of_them_survives.atl @@ -1 +1 @@ -<> G (p0.alive | p1.alive | p2.alive | p3.alive | p4.alive) \ No newline at end of file +<> G (p0.alive || p1.alive || p2.alive || p3.alive || p4.alive) \ No newline at end of file diff --git a/lcgs-examples/mexican_standoff/can_6_players_guarantee_atleast_one_of_them_survives.atl b/lcgs-examples/mexican_standoff/can_6_players_guarantee_atleast_one_of_them_survives.atl index e86e29235..b72ef61ab 100644 --- a/lcgs-examples/mexican_standoff/can_6_players_guarantee_atleast_one_of_them_survives.atl +++ b/lcgs-examples/mexican_standoff/can_6_players_guarantee_atleast_one_of_them_survives.atl @@ -1 +1 @@ -<> G (p0.alive | p1.alive | p2.alive | p3.alive | p4.alive | p5.alive) \ No newline at end of file +<> G (p0.alive || p1.alive || p2.alive || p3.alive || p4.alive || p5.alive) \ No newline at end of file diff --git a/lcgs-examples/peterson/p0_eventually_get_access_if_requested_TRUE.atl b/lcgs-examples/peterson/p0_eventually_get_access_if_requested_TRUE.atl index 360e2b22d..7fa8aa04b 100644 --- a/lcgs-examples/peterson/p0_eventually_get_access_if_requested_TRUE.atl +++ b/lcgs-examples/peterson/p0_eventually_get_access_if_requested_TRUE.atl @@ -1 +1 @@ -<> G (!p0-requested-access-to-CS | <<>> F p0_in_cs) \ No newline at end of file +<> G (!p0-requested-access-to-CS || <<>> F p0_in_cs) \ No newline at end of file diff --git a/lcgs-examples/robot_grid/can_r1_and_r2_swap_with_help_from_r3_FALSE.atl b/lcgs-examples/robot_grid/can_r1_and_r2_swap_with_help_from_r3_FALSE.atl index 3746a45b4..25e1dcf35 100644 --- a/lcgs-examples/robot_grid/can_r1_and_r2_swap_with_help_from_r3_FALSE.atl +++ b/lcgs-examples/robot_grid/can_r1_and_r2_swap_with_help_from_r3_FALSE.atl @@ -1 +1 @@ -<> F (r1.at_target & !r1.crashed & r2.at_target & !r2.crashed) \ No newline at end of file +<> F (r1.at_target && !r1.crashed && r2.at_target && !r2.crashed) \ No newline at end of file diff --git a/lcgs-examples/robot_grid/can_r1_get_past_r2_when_helped_TRUE.atl b/lcgs-examples/robot_grid/can_r1_get_past_r2_when_helped_TRUE.atl index 8616b3111..e56f5bb4a 100644 --- a/lcgs-examples/robot_grid/can_r1_get_past_r2_when_helped_TRUE.atl +++ b/lcgs-examples/robot_grid/can_r1_get_past_r2_when_helped_TRUE.atl @@ -1 +1 @@ -<> F (r1.at_target & !r1.crashed) \ No newline at end of file +<> F (r1.at_target && !r1.crashed) \ No newline at end of file diff --git a/lcgs-examples/robot_grid/everyone_starts_home_TRUE.atl b/lcgs-examples/robot_grid/everyone_starts_home_TRUE.atl index a201a1979..2390496a2 100644 --- a/lcgs-examples/robot_grid/everyone_starts_home_TRUE.atl +++ b/lcgs-examples/robot_grid/everyone_starts_home_TRUE.atl @@ -1 +1 @@ -r1.at_start & r2.at_start & r3.at_start & r4.at_start +r1.at_start && r2.at_start && r3.at_start && r4.at_start diff --git a/lcgs-examples/robot_grid/exist_path_to_targets_with_no_crashes_TRUE.atl b/lcgs-examples/robot_grid/exist_path_to_targets_with_no_crashes_TRUE.atl index af6d31edf..e4da3d255 100644 --- a/lcgs-examples/robot_grid/exist_path_to_targets_with_no_crashes_TRUE.atl +++ b/lcgs-examples/robot_grid/exist_path_to_targets_with_no_crashes_TRUE.atl @@ -1 +1 @@ -<> F ((!any_crashes) & r1.at_target & r2.at_target & r3.at_target & r4.at_target) \ No newline at end of file +<> F ((!any_crashes) && r1.at_target && r2.at_target && r3.at_target && r4.at_target) \ No newline at end of file diff --git a/lcgs-examples/tic_tac_toe/can_cross_avoid_lose_TRUE.atl b/lcgs-examples/tic_tac_toe/can_cross_avoid_lose_TRUE.atl index 087d3af47..b73bb1418 100644 --- a/lcgs-examples/tic_tac_toe/can_cross_avoid_lose_TRUE.atl +++ b/lcgs-examples/tic_tac_toe/can_cross_avoid_lose_TRUE.atl @@ -1 +1 @@ -<> F (game_over & !nought_wins) \ No newline at end of file +<> F (game_over && !nought_wins) \ No newline at end of file diff --git a/lcgs-examples/tic_tac_toe/can_nought_avoid_lose_TRUE.atl b/lcgs-examples/tic_tac_toe/can_nought_avoid_lose_TRUE.atl index 58337328c..3954b7b19 100644 --- a/lcgs-examples/tic_tac_toe/can_nought_avoid_lose_TRUE.atl +++ b/lcgs-examples/tic_tac_toe/can_nought_avoid_lose_TRUE.atl @@ -1 +1 @@ -<> F (game_over & !cross_wins) \ No newline at end of file +<> F (game_over && !cross_wins) \ No newline at end of file From 4a28e5a237f693e421cfd24b42c4558b6d087d73 Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Fri, 8 Sep 2023 22:36:45 +0200 Subject: [PATCH 33/38] fmt with updated rustfmt --- .../search_strategy/linear_programming_search.rs | 4 +++- .../search_strategy/linear_representative_search.rs | 4 +++- cgaal-engine/src/edg/atledg/pmoves.rs | 8 ++++++-- cgaal-engine/src/parsing/parser.rs | 8 +++++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cgaal-engine/src/algorithms/certain_zero/search_strategy/linear_programming_search.rs b/cgaal-engine/src/algorithms/certain_zero/search_strategy/linear_programming_search.rs index 1280fb3f6..f8675a4da 100644 --- a/cgaal-engine/src/algorithms/certain_zero/search_strategy/linear_programming_search.rs +++ b/cgaal-engine/src/algorithms/certain_zero/search_strategy/linear_programming_search.rs @@ -131,7 +131,9 @@ impl LinearProgrammingSearch { let mut symbol_vars: HashMap = HashMap::new(); for state_var in self.game.get_vars() { let decl = self.game.get_decl(&state_var).unwrap(); - let DeclKind::StateVar(var_decl) = &decl.kind else { unreachable!() }; + let DeclKind::StateVar(var_decl) = &decl.kind else { + unreachable!() + }; let range = ( *var_decl.ir_range.start() as f64, *var_decl.ir_range.end() as f64, diff --git a/cgaal-engine/src/algorithms/certain_zero/search_strategy/linear_representative_search.rs b/cgaal-engine/src/algorithms/certain_zero/search_strategy/linear_representative_search.rs index bf203ef96..dbf191fd4 100644 --- a/cgaal-engine/src/algorithms/certain_zero/search_strategy/linear_representative_search.rs +++ b/cgaal-engine/src/algorithms/certain_zero/search_strategy/linear_representative_search.rs @@ -38,7 +38,9 @@ impl LinearRepresentativeSearchBuilder { let mut vars = HashMap::new(); for state_var in self.game.get_vars() { let decl = self.game.get_decl(&state_var).unwrap(); - let DeclKind::StateVar(var_decl) = &decl.kind else { unreachable!() }; + let DeclKind::StateVar(var_decl) = &decl.kind else { + unreachable!() + }; let range = ( *var_decl.ir_range.start() as f64, *var_decl.ir_range.end() as f64, diff --git a/cgaal-engine/src/edg/atledg/pmoves.rs b/cgaal-engine/src/edg/atledg/pmoves.rs index f658b9c71..4f3191d6b 100644 --- a/cgaal-engine/src/edg/atledg/pmoves.rs +++ b/cgaal-engine/src/edg/atledg/pmoves.rs @@ -15,12 +15,16 @@ pub enum PartialMoveChoice { impl PartialMoveChoice { pub fn unwrap_range(&self) -> usize { - let PartialMoveChoice::Range(r) = self else { panic!("PartialMoveChoice was not a range of moves") }; + let PartialMoveChoice::Range(r) = self else { + panic!("PartialMoveChoice was not a range of moves") + }; *r } pub fn unwrap_specific(&self) -> usize { - let PartialMoveChoice::Specific(r) = self else { panic!("PartialMoveChoice was not a specific move") }; + let PartialMoveChoice::Specific(r) = self else { + panic!("PartialMoveChoice was not a specific move") + }; *r } } diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index e004ee0b5..104828f2d 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -93,7 +93,13 @@ impl<'a> Parser<'a> { let mut lhs = self.term()?; let span_start = lhs.span; loop { - let Some(op): Option = self.lexer.peek().and_then(|t| t.kind.clone().try_into().ok()) else { return Ok(lhs) }; + let Some(op): Option = self + .lexer + .peek() + .and_then(|t| t.kind.clone().try_into().ok()) + else { + return Ok(lhs); + }; if op.precedence() < min_prec { return Ok(lhs); } From 726e7475dcfdbc9bf8711ee3abff3303478b51c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Mon, 11 Sep 2023 08:31:11 +0200 Subject: [PATCH 34/38] Improvements to comments and lexer test --- cgaal-engine/src/parsing/ast.rs | 16 ++++++------ cgaal-engine/src/parsing/errors.rs | 18 ++++++------- cgaal-engine/src/parsing/lexer.rs | 41 +++++++++++++++++++++++++----- cgaal-engine/src/parsing/parser.rs | 2 +- cgaal-engine/src/parsing/span.rs | 6 +++++ 5 files changed, 58 insertions(+), 25 deletions(-) diff --git a/cgaal-engine/src/parsing/ast.rs b/cgaal-engine/src/parsing/ast.rs index 79354d0d1..f43eb4d41 100644 --- a/cgaal-engine/src/parsing/ast.rs +++ b/cgaal-engine/src/parsing/ast.rs @@ -105,6 +105,14 @@ impl TryFrom for BinaryOpKind { } } +/// The associativity of an operator. +/// E.g. `-` is left-associative so `a - b - c` is parsed as `(a - b) - c`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Associativity { + LeftToRight, + RightToLeft, +} + /// A coalition expression. E.g. `<< p1 >> path_expr` #[derive(Debug, Clone, PartialEq, Eq)] pub struct Coalition { @@ -136,11 +144,3 @@ pub enum CoalitionKind { /// The `[[ ]]` coalition Enforce, } - -/// The associativity of an operator. -/// E.g. `-` is left-associative so `a - b - c` is parsed as `(a - b) - c`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Associativity { - LeftToRight, - RightToLeft, -} diff --git a/cgaal-engine/src/parsing/errors.rs b/cgaal-engine/src/parsing/errors.rs index 4d1d5209e..4cb43d8cf 100644 --- a/cgaal-engine/src/parsing/errors.rs +++ b/cgaal-engine/src/parsing/errors.rs @@ -3,11 +3,11 @@ use std::cmp::{max, min}; use std::fmt::Write; /// A log of errors that occurred during parsing or semantic analysis. -/// Each error entry has a span that indicates its origin in the original input code. +/// Each [ErrorLogEntry] has a span that indicates its origin in the original input code. /// Given the original input code, the error log can be converted to nicely presented error messages. #[derive(Debug, Default)] pub struct ErrorLog { - errors: Vec, + errors: Vec, } impl ErrorLog { @@ -16,15 +16,15 @@ impl ErrorLog { } pub fn log(&mut self, span: Span, msg: String) { - self.errors.push(ErrorEntry::new(span, msg)); + self.errors.push(ErrorLogEntry::new(span, msg)); } - pub fn log_entry(&mut self, entry: ErrorEntry) { + pub fn log_entry(&mut self, entry: ErrorLogEntry) { self.errors.push(entry); } pub fn log_msg(&mut self, msg: String) { - self.errors.push(ErrorEntry::msg_only(msg)); + self.errors.push(ErrorLogEntry::msg_only(msg)); } pub fn len(&self) -> usize { @@ -97,23 +97,23 @@ impl ErrorLog { /// A single error entry in the [ErrorLog]. #[derive(Debug)] -pub struct ErrorEntry { +pub struct ErrorLogEntry { /// The span of the error in the original input code. span: Option, /// The error message. msg: String, } -impl ErrorEntry { +impl ErrorLogEntry { pub fn new(span: Span, msg: String) -> Self { - ErrorEntry { + ErrorLogEntry { span: Some(span), msg, } } pub fn msg_only(msg: String) -> Self { - ErrorEntry { span: None, msg } + ErrorLogEntry { span: None, msg } } } diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs index 023a5eaf0..65102225e 100644 --- a/cgaal-engine/src/parsing/lexer.rs +++ b/cgaal-engine/src/parsing/lexer.rs @@ -162,16 +162,30 @@ impl<'a> Iterator for Lexer<'a> { #[cfg(test)] mod tests { use crate::parsing::lexer::Lexer; - use std::fmt::Write; + use crate::parsing::token::{Token, TokenKind}; #[test] fn lexing_001() { // Check that the lexer produces the correct tokens with correct spans let input = "==4 /* - x (var01 > 0)"; let lexer = Lexer::new(input.as_bytes()); - let mut res = String::new(); - lexer.for_each(|tk| write!(res, "{tk:?}").unwrap()); - assert_eq!(&res, "'=='(0,2)'4'(2,3)'/'(4,5)'*'(5,6)'-'(7,8)'x'(9,10)'('(11,12)'var01'(12,17)'>'(18,19)'0'(20,21)')'(21,22)") + let tokens = lexer.collect::>(); + assert_eq!( + tokens, + vec![ + Token::new(TokenKind::Eq, (0..2).into()), + Token::new(TokenKind::Num(4), (2..3).into()), + Token::new(TokenKind::Slash, (4..5).into()), + Token::new(TokenKind::Star, (5..6).into()), + Token::new(TokenKind::Minus, (7..8).into()), + Token::new(TokenKind::Word("x".to_string()), (9..10).into()), + Token::new(TokenKind::Lparen, (11..12).into()), + Token::new(TokenKind::Word("var01".to_string()), (12..17).into()), + Token::new(TokenKind::Rangle, (18..19).into()), + Token::new(TokenKind::Num(0), (20..21).into()), + Token::new(TokenKind::Rparen, (21..22).into()), + ] + ); } #[test] @@ -179,8 +193,21 @@ mod tests { // Check that the lexer produces the correct tokens with correct spans let input = " !player ->i [..]<< init>>"; let lexer = Lexer::new(input.as_bytes()); - let mut res = String::new(); - lexer.for_each(|tk| write!(res, "{tk:?}").unwrap()); - assert_eq!(&res, "'!'(2,3)'player'(3,9)'->'(10,12)'i'(12,13)'['(14,15)'..'(15,17)']'(17,18)'<<'(18,20)'init'(21,25)'>>'(25,27)") + let tokens = lexer.collect::>(); + assert_eq!( + tokens, + vec![ + Token::new(TokenKind::Bang, (2..3).into()), + Token::new(TokenKind::KwPlayer, (3..9).into()), + Token::new(TokenKind::Arrow, (10..12).into()), + Token::new(TokenKind::Word("i".to_string()), (12..13).into()), + Token::new(TokenKind::Lbracket, (14..15).into()), + Token::new(TokenKind::DotDot, (15..17).into()), + Token::new(TokenKind::Rbracket, (17..18).into()), + Token::new(TokenKind::Llangle, (18..20).into()), + Token::new(TokenKind::KwInit, (21..25).into()), + Token::new(TokenKind::Rrangle, (25..27).into()), + ] + ); } } diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index e004ee0b5..6fb243cea 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -11,7 +11,7 @@ use std::sync::Arc; /// If it fails to parse expression, it tries to recover by skipping tokens until it finds any recovery token. /// If the found recovery token is the expected one, it recovers and returns the error value. /// If the found recovery token is not the expected one, it returns [RecoverMode] as an error such that the caller can try to recover. -/// This macro cannot be a function due to the borrow checker. +/// This macro cannot be a function due to the borrow checker and the first argument self be a [Parser]. macro_rules! recover { ($self:expr, $val:expr, $recover_token:expr, $err_val:expr) => {{ $self.recovery_tokens.push($recover_token); diff --git a/cgaal-engine/src/parsing/span.rs b/cgaal-engine/src/parsing/span.rs index fe316376c..37060ad01 100644 --- a/cgaal-engine/src/parsing/span.rs +++ b/cgaal-engine/src/parsing/span.rs @@ -48,6 +48,12 @@ impl From> for Span { } } +impl From<(usize, usize)> for Span { + fn from((begin, end): (usize, usize)) -> Self { + Span::new(begin, end) + } +} + impl Add for Span { type Output = Span; From 54f8d853df703cd21ea2e4371ea0efb55ac5cd8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Tue, 12 Sep 2023 09:53:47 +0200 Subject: [PATCH 35/38] Improved some error messages --- cgaal-engine/src/atl/convert.rs | 25 ++++++++++++--------- cgaal-engine/src/game_structure/lcgs/ast.rs | 12 ++++++++++ cgaal-engine/src/parsing/parser.rs | 10 ++++++--- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/cgaal-engine/src/atl/convert.rs b/cgaal-engine/src/atl/convert.rs index a82dbcc52..1b5cbdff3 100644 --- a/cgaal-engine/src/atl/convert.rs +++ b/cgaal-engine/src/atl/convert.rs @@ -23,10 +23,14 @@ pub fn convert_expr_to_phi( let decl = game.get_decl(&Owner::Global.symbol_id(ident)); match &decl.map(|d| &d.kind) { Some(DeclKind::Label(l)) => Some(Phi::Proposition(l.index)), - Some(_) => { + Some(d) => { errors.log( *span, - format!("Expected proposition label, '{}' is not a label", ident), + format!( + "Expected proposition label, '{}' is a {}", + ident, + d.kind_name() + ), ); None } @@ -76,12 +80,13 @@ pub fn convert_expr_to_phi( let decl = game.get_decl(&symb); match decl.map(|d| &d.kind) { Some(DeclKind::Label(l)) => Some(Phi::Proposition(l.index)), - Some(_) => { + Some(d) => { errors.log( rhs.span, format!( - "Expected proposition label, '{}' is not a label", - prop + "Expected proposition label, '{}' is a {}", + prop, + d.kind_name(), ), ); None @@ -91,17 +96,17 @@ pub fn convert_expr_to_phi( rhs.span, format!( "Expected proposition label, '{}' is not defined", - prop + symb, ), ); None } } } - Some(_) => { + Some(d) => { errors.log( lhs.span, - format!("Expected player, '{}' is not a player", owner), + format!("Expected player, '{}' is a {}", owner, d.kind_name()), ); None } @@ -215,10 +220,10 @@ fn convert_players( .map(|d| &d.kind) { Some(DeclKind::Player(p)) => Some(p.index), - Some(_) => { + Some(d) => { errors.log( expr.span, - format!("Expected player, '{}' is not a player", name), + format!("Expected player, '{}' is a {}", name, d.kind_name()), ); None } diff --git a/cgaal-engine/src/game_structure/lcgs/ast.rs b/cgaal-engine/src/game_structure/lcgs/ast.rs index bf9002b05..9f86d3a1d 100644 --- a/cgaal-engine/src/game_structure/lcgs/ast.rs +++ b/cgaal-engine/src/game_structure/lcgs/ast.rs @@ -43,6 +43,18 @@ impl DeclKind { DeclKind::Transition(decl) => &decl.name, } } + + /// Returns the kind of declaration as a string, e.g. "constant", "label", etc. + pub fn kind_name(&self) -> &'static str { + match self { + DeclKind::Const(_) => "constant", + DeclKind::Label(_) => "label", + DeclKind::StateVar(_) => "state variable", + DeclKind::Player(_) => "player", + DeclKind::Template(_) => "template", + DeclKind::Transition(_) => "move", + } + } } /// An identifier. diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index ac858637f..5539806d6 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -160,13 +160,17 @@ impl<'a> Parser<'a> { let tok = self.lexer.next().unwrap(); self.errors.log( tok.span, - format!("Unexpected '{}', expected path expression", tok.kind), + format!( + "Unexpected '{}', expected 'X', 'F', 'G', or '(... U ...)'", + tok.kind + ), ); Err(RecoverMode) } None => { - self.errors - .log_msg("Unexpected EOF, expected path expression".to_string()); + self.errors.log_msg( + "Unexpected EOF, expected 'X', 'F', 'G', or '(... U ...)'".to_string(), + ); Err(RecoverMode) } } From 5b3e3452e3ff7364f5cc20dbb3218412fd10f6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Tue, 12 Sep 2023 10:52:10 +0200 Subject: [PATCH 36/38] Bump version --- cgaal-cli/Cargo.toml | 2 +- cgaal-engine/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cgaal-cli/Cargo.toml b/cgaal-cli/Cargo.toml index 04b323c65..7e71cb389 100644 --- a/cgaal-cli/Cargo.toml +++ b/cgaal-cli/Cargo.toml @@ -26,4 +26,4 @@ tracing-subscriber = "0.2.17" serde_json = "1.0.83" regex = { version = "1", features = ["unicode-case"] } humantime = "2.1.0" -cgaal-engine = { path = "../cgaal-engine", version = "1.0.0" } +cgaal-engine = { path = "../cgaal-engine", version = "1.0.1" } diff --git a/cgaal-engine/Cargo.toml b/cgaal-engine/Cargo.toml index 949678f04..c111e65b9 100644 --- a/cgaal-engine/Cargo.toml +++ b/cgaal-engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cgaal-engine" -version = "1.0.0" +version = "1.0.1" authors = [ "Asger Weirsøe ", "Falke Carlsen ", From dd4bd4ff435725f8b347961c7cd64f99bd0cfad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolaj=20=C3=98sterby=20Jensen?= Date: Tue, 12 Sep 2023 12:47:37 +0200 Subject: [PATCH 37/38] Bump cli version --- cgaal-cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgaal-cli/Cargo.toml b/cgaal-cli/Cargo.toml index 7e71cb389..7eef44956 100644 --- a/cgaal-cli/Cargo.toml +++ b/cgaal-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cgaal" -version = "1.0.0" +version = "1.0.1" authors = [ "d702e20 ", "d802f21 ", From 58bc04a55e9efdd4b73d73befe83c1c19b45d7a1 Mon Sep 17 00:00:00 2001 From: falkecarlsen <11318702+falkecarlsen@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:51:26 +0200 Subject: [PATCH 38/38] add ATL label access pass/fail tests --- cgaal-engine/tests/parsing.rs | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing.rs index 3fa61390b..58ee4e467 100644 --- a/cgaal-engine/tests/parsing.rs +++ b/cgaal-engine/tests/parsing.rs @@ -217,6 +217,50 @@ fn atl_expr_003() { ); } +#[test] +fn atl_expr_004() { + // Check attribute access + let input = "<> F p1.attr"; + let mut errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer, &mut errors); + let expr = parser.expr(0).expect("Failed to valid parse expression"); + parser.expect_end(); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + expr, + Expr::new( + Span::new(0, 16), + ExprKind::Coalition(Coalition::new( + Span::new(0, 6), + vec![Expr::new( + Span::new(2, 4), + ExprKind::Ident("p1".to_string()), + )], + CoalitionKind::Enforce, + Expr::new( + Span::new(7, 16), + ExprKind::Unary( + UnaryOpKind::Eventually, + Expr::new( + Span::new(9, 16), + ExprKind::Binary( + BinaryOpKind::Dot, + Expr::new(Span::new(9, 11), ExprKind::Ident("p1".to_string())) + .into(), + Expr::new(Span::new(12, 16), ExprKind::Ident("attr".to_string())) + .into(), + ), + ) + .into(), + ), + ) + .into(), + )), + ) + ); +} + #[test] fn erroneous_expr_001() { // Check if unexpected EOF is reported correctly @@ -343,6 +387,25 @@ fn erroneous_expr_006() { ); } +#[test] +fn erroneous_expr_007() { + // Check error on subsequent attribute access + let input = "<> G p1.attr1.attr2"; + let mut errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes()); + let mut parser = Parser::new(lexer, &mut errors); + let _expr = parser.expr(0).expect("Error should be recoverable"); + parser.expect_end(); + assert!(errors.has_errors()); + let out = errors.to_string(input); + assert_eq!( + out, + "\u{1b}[31m1:18 Error:\u{1b}[0m Unexpected '.', expected EOF\n\ + | <> G p1.attr1.attr2\n\ + | ^\n" + ); +} + #[test] fn atl_expr_batch() { // Check that no errors are found for valid ATL expressions