Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite ATL parsing for better parse errors and more flexibility #216

Merged
merged 42 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
305f73f
Add parsing methods that logs errors
NicEastvillage Aug 17, 2022
f445d66
Add state to ATL parser
NicEastvillage Aug 21, 2022
960cc2b
Merge branch 'main' into parse-errors
NicEastvillage Jun 5, 2023
dc5b8cd
Small fixes
NicEastvillage Jun 5, 2023
6adc1ba
Merge branch 'main' into parse-errors
NicEastvillage Aug 20, 2023
267dfa2
Beginning of homemade Lexer
NicEastvillage Aug 20, 2023
f75d7fc
Lexer utils cleanup and test
NicEastvillage Aug 20, 2023
fa0423e
Add remaing tokens and lexing thereof
NicEastvillage Aug 23, 2023
5fbe324
Add nice error formatting
NicEastvillage Aug 25, 2023
25797a3
Fix parse error introduced in latest merge
NicEastvillage Aug 25, 2023
8a3eea4
Allow parse_or_sync to be nested
NicEastvillage Aug 25, 2023
3a6fd91
Created AstAtl with spans
NicEastvillage Aug 25, 2023
cbccb70
Parser with errors attempt
NicEastvillage Aug 27, 2023
dbbe066
Add Parser, almost done for ATL
NicEastvillage Aug 31, 2023
6c1114d
Working ATL parser
NicEastvillage Sep 1, 2023
ab32955
Keep parsing to EOF
NicEastvillage Sep 1, 2023
84ff548
Add simple parser recovery
NicEastvillage Sep 1, 2023
e74ee1e
Working parser
NicEastvillage Sep 5, 2023
b2d955c
Clean up parser
NicEastvillage Sep 5, 2023
4af0b2f
Add conversion to phi
NicEastvillage Sep 6, 2023
5121d6d
Also convert expr idents to propositions in conversion
NicEastvillage Sep 6, 2023
d3eafbf
Remove accidental 4
NicEastvillage Sep 6, 2023
f0c436a
Use new ATL parsing in cli
NicEastvillage Sep 6, 2023
158daa2
Improved styling and unknown character handling
NicEastvillage Sep 6, 2023
1570ca6
Add dot expressions to ATL
NicEastvillage Sep 7, 2023
d33127c
Add negation to ATL expr
NicEastvillage Sep 7, 2023
ae25f07
Add neXt path expression
NicEastvillage Sep 7, 2023
6df0901
Removed unsupported examples
NicEastvillage Sep 7, 2023
bec6d79
Remove test atl file
NicEastvillage Sep 7, 2023
78c11d3
Fix clippy and fmt
NicEastvillage Sep 7, 2023
775b319
Do not expect EOF after unrecovered parsing error
NicEastvillage Sep 7, 2023
5e00bf3
Add batch of atl parsing tests
NicEastvillage Sep 8, 2023
74dcf66
Add comments to new parser
NicEastvillage Sep 8, 2023
a2c3799
Update ATL files to use && and ||
NicEastvillage Sep 8, 2023
4a28e5a
fmt with updated rustfmt
NicEastvillage Sep 8, 2023
726e747
Improvements to comments and lexer test
NicEastvillage Sep 11, 2023
9bf4ff6
Merge branch 'parse-errors' of https://github.com/d702e20/CGAAL into …
NicEastvillage Sep 12, 2023
54f8d85
Improved some error messages
NicEastvillage Sep 12, 2023
5b3e345
Bump version
NicEastvillage Sep 12, 2023
dd4bd4f
Bump cli version
NicEastvillage Sep 12, 2023
58bc04a
add ATL label access pass/fail tests
falkecarlsen Sep 12, 2023
18805c4
Merge branch 'main' into parse-errors
falkecarlsen Sep 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cgaal-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cgaal"
version = "1.0.0"
version = "1.0.1"
authors = [
"d702e20 <[email protected]>",
"d802f21 <[email protected]>",
Expand All @@ -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" }
32 changes: 20 additions & 12 deletions cgaal-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ 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::convert::convert_expr_to_phi;
use cgaal_engine::atl::Phi;
use cgaal_engine::edg::atledg::vertex::AtlVertex;
use cgaal_engine::edg::atledg::AtlDependencyGraph;
use cgaal_engine::edg::ExtendedDependencyGraph;
Expand All @@ -27,6 +28,8 @@ 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;

Expand Down Expand Up @@ -335,11 +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<A: AtlExpressionParser>(
path: &str,
formula_type: FormulaType,
expr_parser: &A,
) -> 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);
Expand All @@ -357,11 +356,20 @@ fn load_formula<A: AtlExpressionParser>(
exit(1);
}),
FormulaType::Atl => {
let result = cgaal_engine::atl::parse_phi(expr_parser, &raw_phi);
result.unwrap_or_else(|err| {
eprintln!("Invalid ATL formula provided:\n\n{}", err);
let game = game.unwrap_or_else(|| {
eprintln!("Cannot use ATL formula for non-LCGS models");
exit(1)
})
});
let mut errors = ErrorLog::new();
parse_atl(&raw_phi, &mut errors)
.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)
})
}
}
}
Expand Down Expand Up @@ -457,7 +465,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, None);

Ok(ModelAndFormula::Json {
model: game_structure,
Expand All @@ -471,7 +479,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, Some(&game_structure));

Ok(ModelAndFormula::Lcgs {
model: game_structure,
Expand Down
2 changes: 1 addition & 1 deletion cgaal-engine/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cgaal-engine"
version = "1.0.0"
version = "1.0.1"
authors = [
"Asger Weirsøe <[email protected]>",
"Falke Carlsen <[email protected]>",
Expand Down
244 changes: 244 additions & 0 deletions cgaal-engine/src/atl/convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
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;

/// 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,
errors: &mut ErrorLog,
) -> Option<Phi> {
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(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(d) => {
errors.log(
*span,
format!(
"Expected proposition label, '{}' is a {}",
ident,
d.kind_name()
),
);
None
}
None => {
errors.log(
*span,
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 => {
errors.log(
*span,
"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::Dot => {
let ExprKind::Ident(owner) = &lhs.kind else {
errors.log(lhs.span, "Expected player name".to_string());
return None;
};
let ExprKind::Ident(prop) = &rhs.kind else {
errors.log(rhs.span, "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(d) => {
errors.log(
rhs.span,
format!(
"Expected proposition label, '{}' is a {}",
prop,
d.kind_name(),
),
);
None
}
None => {
errors.log(
rhs.span,
format!(
"Expected proposition label, '{}' is not defined",
symb,
),
);
None
}
}
}
Some(d) => {
errors.log(
lhs.span,
format!("Expected player, '{}' is a {}", owner, d.kind_name()),
);
None
}
None => {
errors.log(
lhs.span,
format!("Expected player, '{}' is not defined", owner),
);
None
}
}
}
BinaryOpKind::Until => {
errors.log(
*span,
"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,
"Coalitions must be followed by a path formula".to_string(),
);
None
}
},
ExprKind::Error => None,
}
}

/// 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,
errors: &mut ErrorLog,
) -> Option<Vec<Player>> {
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(d) => {
errors.log(
expr.span,
format!("Expected player, '{}' is a {}", name, d.kind_name()),
);
None
}
None => {
errors.log(
expr.span,
format!("Expected player, '{}' is not defined", name),
);
None
}
},
_ => {
errors.log(expr.span, "Expected player name".to_string());
None
}
})
.collect()
}
4 changes: 2 additions & 2 deletions cgaal-engine/src/atl/game_formula.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ use crate::game_structure::GameStructure;
/// Given a Phi `formula` (`<<p1>> 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 "`<<p1>> 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> {
Expand Down
7 changes: 3 additions & 4 deletions cgaal-engine/src/atl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 convert;
pub mod game_formula;
pub mod parser;

/// Alternating-time Temporal Logic formula
#[derive(Hash, Eq, PartialEq, Clone, Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -317,11 +316,11 @@ impl Phi {
/// Given a Phi `formula` (`<<p1>> 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 "`<<p1>> 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> {
Expand Down
Loading
Loading