diff --git a/cgaal-cli/Cargo.toml b/cgaal-cli/Cargo.toml index 73045363..d7febd0f 100644 --- a/cgaal-cli/Cargo.toml +++ b/cgaal-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cgaal" -version = "2.0.0" +version = "2.0.1" authors = [ "Asger Weirsøe ", "Falke Carlsen ", @@ -24,4 +24,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.1" } +cgaal-engine = { path = "../cgaal-engine", version = "1.0.2" } diff --git a/cgaal-engine/Cargo.toml b/cgaal-engine/Cargo.toml index eda3ef0b..91a6f7b4 100644 --- a/cgaal-engine/Cargo.toml +++ b/cgaal-engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cgaal-engine" -version = "1.0.1" +version = "1.0.2" authors = [ "Asger Weirsøe ", "Falke Carlsen ", diff --git a/cgaal-engine/src/atl/convert.rs b/cgaal-engine/src/atl/convert.rs index 1b5cbdff..3cbdd164 100644 --- a/cgaal-engine/src/atl/convert.rs +++ b/cgaal-engine/src/atl/convert.rs @@ -3,32 +3,57 @@ 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::ast::{ + BinaryOpKind, Coalition, CoalitionKind, Expr, ExprKind, Ident, 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 { +pub fn convert_expr_to_phi(expr: &Expr, game: &IntermediateLcgs, errors: &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(ident) => { - let decl = game.get_decl(&Owner::Global.symbol_id(ident)); + ExprKind::OwnedIdent(owner, ident) => { + if let Some(player) = owner { + let symbol = Owner::Global.symbol_id(&player.name); + match game.get_decl(&symbol).map(|d| &d.kind) { + Some(DeclKind::Player(_)) => { + // ok + } + Some(d) => { + errors.log( + player.span, + format!("Expected player, '{}' is a {}", player.name, d.kind_name()), + ); + None? + } + None => { + errors.log( + player.span, + format!("Expected player, '{}' is not defined", player.name), + ); + None? + } + } + } + let symbol = if let Some(owner) = owner { + Owner::Player(owner.name.clone()).symbol_id(&ident.name) + } else { + Owner::Global.symbol_id(&ident.name) + }; + let decl = game.get_decl(&symbol); match &decl.map(|d| &d.kind) { Some(DeclKind::Label(l)) => Some(Phi::Proposition(l.index)), Some(d) => { errors.log( - *span, + ident.span, format!( "Expected proposition label, '{}' is a {}", - ident, + ident.name, d.kind_name() ), ); @@ -36,8 +61,11 @@ pub fn convert_expr_to_phi( } None => { errors.log( - *span, - format!("Expected proposition label, '{}' is not defined", ident), + ident.span, + format!( + "Expected proposition label, '{}' is not defined", + ident.name + ), ); None } @@ -52,6 +80,13 @@ pub fn convert_expr_to_phi( ); None } + UnaryOpKind::Neg => { + errors.log( + *span, + "Arithmetic operators is currently not supported in ATL".to_string(), + ); + None + } }, ExprKind::Binary(op, lhs, rhs) => match op { BinaryOpKind::And => Some(Phi::And( @@ -62,63 +97,6 @@ pub fn convert_expr_to_phi( 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, @@ -126,7 +104,47 @@ pub fn convert_expr_to_phi( ); None } + BinaryOpKind::Xor => { + errors.log( + *span, + "Exclusive OR is currently not supported in ATL".to_string(), + ); + None + } + BinaryOpKind::Implies => { + errors.log( + *span, + "Implication is currently not supported in ATL".to_string(), + ); + None + } + BinaryOpKind::Eq + | BinaryOpKind::Neq + | BinaryOpKind::Gt + | BinaryOpKind::Geq + | BinaryOpKind::Lt + | BinaryOpKind::Leq => { + errors.log( + *span, + "Relational operators are currently not supported in ATL".to_string(), + ); + None + } + BinaryOpKind::Add | BinaryOpKind::Sub | BinaryOpKind::Mul | BinaryOpKind::Div => { + errors.log( + *span, + "Arithmetic operators are currently not supported in ATL".to_string(), + ); + None + } }, + ExprKind::TernaryIf(_, _, _) => { + errors.log( + *span, + "Ternary if expressions are currently not supported in ATL".to_string(), + ); + None + } ExprKind::Coalition(Coalition { players, kind, @@ -201,6 +219,21 @@ pub fn convert_expr_to_phi( None } }, + ExprKind::Num(_) => { + errors.log( + *span, + "Unexpected number. Please use true, false, or label names as propositions." + .to_string(), + ); + None + } + ExprKind::Max(_) | ExprKind::Min(_) => { + errors.log( + *span, + "Max and min expressions are currently not supported in ATL".to_string(), + ); + None + } ExprKind::Error => None, } } @@ -208,36 +241,30 @@ 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], + players: &[Ident], game: &IntermediateLcgs, - errors: &mut ErrorLog, + errors: &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) - { + .map(|ident| { + let symbol = Owner::Global.symbol_id(&ident.name); + match game.get_decl(&symbol).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()), + ident.span, + format!("Expected player, '{}' is a {}", ident.name, d.kind_name()), ); None } None => { errors.log( - expr.span, - format!("Expected player, '{}' is not defined", name), + ident.span, + format!("Expected player, '{}' is not defined", ident.name), ); None } - }, - _ => { - errors.log(expr.span, "Expected player name".to_string()); - None } }) .collect() diff --git a/cgaal-engine/src/parsing/ast.rs b/cgaal-engine/src/parsing/ast.rs index f43eb4d4..15b1b0f0 100644 --- a/cgaal-engine/src/parsing/ast.rs +++ b/cgaal-engine/src/parsing/ast.rs @@ -2,6 +2,148 @@ use crate::parsing::span::Span; use crate::parsing::token::TokenKind; use std::sync::Arc; +/// The root of an LCGS program. +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct LcgsRoot { + pub span: Span, + pub items: Vec, +} + +impl LcgsRoot { + pub fn new(span: Span, items: Vec) -> Self { + LcgsRoot { span, items } + } +} + +/// A declaration. +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Decl { + pub span: Span, + pub ident: Ident, + pub kind: DeclKind, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum DeclKind { + /// A constant declaration + Const(Arc), + /// A state label declaration. Used by ATL formulas + StateLabel(Arc), + /// A state variable declaration. These compose the state of a game. + StateVar(Arc), + /// A player declaration. Can only appear in the global scope. + Player(Arc), + /// A template declaration. Can only appear in the global scope. + Template(Vec), + /// An action declaration. Can only appear in templates. + Action(Arc), + /// An error + Error, +} + +impl Decl { + pub fn new(span: Span, ident: Ident, kind: DeclKind) -> Self { + Decl { span, ident, kind } + } + + pub fn new_error() -> Self { + Decl::new(Span::new(0, 0), Ident::new_error(), DeclKind::Error) + } +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct StateVarDecl { + pub range: RangeClause, + pub init: Arc, + pub update_ident: Ident, + pub update: Arc, +} + +impl StateVarDecl { + pub fn new( + range: RangeClause, + init: Arc, + update_ident: Ident, + update: Arc, + ) -> Self { + StateVarDecl { + range, + init, + update_ident, + update, + } + } +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct RangeClause { + pub span: Span, + pub min: Arc, + pub max: Arc, +} + +impl RangeClause { + pub fn new(span: Span, min: Arc, max: Arc) -> Self { + RangeClause { span, min, max } + } +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct PlayerDecl { + pub template: Ident, + pub relabellings: Vec, +} + +impl PlayerDecl { + pub fn new(template: Ident, relabellings: Vec) -> Self { + PlayerDecl { + template, + relabellings, + } + } +} + +/// A relabelling case, as found in player declarations. +/// Every occurrence of the `from` identifier in the template will be replaced by the `to` expression. +/// If the ident is the name of a declaration or a name with an owner (e.g. `foo` in `p1.foo`), +/// then the expression must be an identifier too. +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct RelabelCase { + pub span: Span, + pub from: Ident, + pub to: Arc, +} + +impl RelabelCase { + pub fn new(span: Span, from: Ident, to: Arc) -> Self { + RelabelCase { span, from, to } + } +} + +/// An identifier. +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Ident { + pub span: Span, + pub name: String, +} + +impl Ident { + pub fn new(span: Span, name: String) -> Self { + Ident { span, name } + } + + pub fn new_error() -> Self { + Ident::new(Span::new(0, 0), String::new()) + } +} + +/// An owned identifier. E.g. `p1.foo` +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct OwnedIdent { + pub owner: Ident, + pub name: Ident, +} + /// An expression. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Expr { @@ -27,14 +169,21 @@ pub enum ExprKind { True, /// The false constant False, + Num(i32), /// An expression in parentheses Paren(Arc), - /// An identifier - Ident(String), + /// An owned identifier + OwnedIdent(Option, Ident), /// A unary operation Unary(UnaryOpKind, Arc), /// A binary operation Binary(BinaryOpKind, Arc, Arc), + /// A ternary if expression + TernaryIf(Arc, Arc, Arc), + /// A max expression + Max(Vec), + /// A min expression + Min(Vec), /// An expression with a coalition Coalition(Coalition), /// An error @@ -43,8 +192,10 @@ pub enum ExprKind { #[derive(Debug, Clone, PartialEq, Eq)] pub enum UnaryOpKind { - /// The `!` operator for negation + /// The `!` operator for logical negation Not, + /// The `-` operator for arithmetic negation + Neg, // Temporal operators /// The `X` temporal operator (neXt) @@ -57,12 +208,39 @@ pub enum UnaryOpKind { #[derive(Debug, Clone, PartialEq, Eq)] pub enum BinaryOpKind { + // Logical operators /// The `&&` operator for logical and (conjunction) And, /// The `||` operator for logical or (disjunction) Or, - /// The `.` operator for owned symbols - Dot, + /// The `^` operator for exclusive or (xor) + Xor, + /// The `->` operator for implication + Implies, + + // Relational operators + /// The `==` operator for equality + Eq, + /// The `!=` operator for inequality + Neq, + /// The `>` operator for greater than + Gt, + /// The `>=` operator for greater than or equal + Geq, + /// The `<` operator for less than + Lt, + /// The `<=` operator for less than or equal + Leq, + + // Arithmetic operators + /// The `+` operator for addition + Add, + /// The `-` operator for subtraction + Sub, + /// The `*` operator for multiplication + Mul, + /// The `/` operator for division + Div, // Temporal operators /// The `U` temporal operator (Until) @@ -85,9 +263,14 @@ impl BinaryOpKind { /// Higher precedence means the operator binds tighter. pub fn precedence(&self) -> u8 { match self { - BinaryOpKind::Dot => 3, - BinaryOpKind::And => 2, - BinaryOpKind::Or => 1, + BinaryOpKind::Mul | BinaryOpKind::Div => 8, + BinaryOpKind::Add | BinaryOpKind::Sub => 7, + BinaryOpKind::Gt | BinaryOpKind::Geq | BinaryOpKind::Lt | BinaryOpKind::Leq => 6, + BinaryOpKind::Eq | BinaryOpKind::Neq => 5, + BinaryOpKind::And => 4, + BinaryOpKind::Or => 3, + BinaryOpKind::Xor => 2, + BinaryOpKind::Implies => 1, BinaryOpKind::Until => 0, } } @@ -100,6 +283,18 @@ impl TryFrom for BinaryOpKind { match value { TokenKind::AmpAmp => Ok(BinaryOpKind::And), TokenKind::PipePipe => Ok(BinaryOpKind::Or), + TokenKind::Hat => Ok(BinaryOpKind::Xor), + TokenKind::Arrow => Ok(BinaryOpKind::Implies), + TokenKind::Eq => Ok(BinaryOpKind::Eq), + TokenKind::Neq => Ok(BinaryOpKind::Neq), + TokenKind::Rangle => Ok(BinaryOpKind::Gt), + TokenKind::Geq => Ok(BinaryOpKind::Geq), + TokenKind::Langle => Ok(BinaryOpKind::Lt), + TokenKind::Leq => Ok(BinaryOpKind::Leq), + TokenKind::Plus => Ok(BinaryOpKind::Add), + TokenKind::Minus => Ok(BinaryOpKind::Sub), + TokenKind::Star => Ok(BinaryOpKind::Mul), + TokenKind::Slash => Ok(BinaryOpKind::Div), _ => Err(()), } } @@ -119,7 +314,7 @@ pub struct Coalition { /// The span of the coalition in the original input code. pub span: Span, /// The players in the coalition. - pub players: Vec, + pub players: Vec, /// The kind of the coalition. pub kind: CoalitionKind, /// The path expression following the coalition. @@ -127,7 +322,7 @@ pub struct Coalition { } impl Coalition { - pub fn new(span: Span, players: Vec, kind: CoalitionKind, expr: Arc) -> Self { + pub fn new(span: Span, players: Vec, kind: CoalitionKind, expr: Arc) -> Self { Coalition { span, players, diff --git a/cgaal-engine/src/parsing/errors.rs b/cgaal-engine/src/parsing/errors.rs index 4cb43d8c..f10c673c 100644 --- a/cgaal-engine/src/parsing/errors.rs +++ b/cgaal-engine/src/parsing/errors.rs @@ -1,13 +1,14 @@ use crate::parsing::span::Span; +use std::cell::RefCell; use std::cmp::{max, min}; use std::fmt::Write; /// A log of errors that occurred during parsing or semantic analysis. /// 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)] +#[derive(Debug, Default, Clone)] pub struct ErrorLog { - errors: Vec, + errors: RefCell>, } impl ErrorLog { @@ -15,28 +16,28 @@ impl ErrorLog { Default::default() } - pub fn log(&mut self, span: Span, msg: String) { - self.errors.push(ErrorLogEntry::new(span, msg)); + pub fn log(&self, span: Span, msg: String) { + self.errors.borrow_mut().push(ErrorLogEntry::new(span, msg)); } - pub fn log_entry(&mut self, entry: ErrorLogEntry) { - self.errors.push(entry); + pub fn log_entry(&self, entry: ErrorLogEntry) { + self.errors.borrow_mut().push(entry); } - pub fn log_msg(&mut self, msg: String) { - self.errors.push(ErrorLogEntry::msg_only(msg)); + pub fn log_msg(&self, msg: String) { + self.errors.borrow_mut().push(ErrorLogEntry::msg_only(msg)); } pub fn len(&self) -> usize { - self.errors.len() + self.errors.borrow().len() } pub fn has_errors(&self) -> bool { - !self.errors.is_empty() + !self.errors.borrow().is_empty() } pub fn is_empty(&self) -> bool { - self.errors.is_empty() + self.errors.borrow().is_empty() } /// Converts the error log to a nicely formatted string using the original input code. @@ -49,7 +50,7 @@ impl ErrorLog { /// 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 { + for entry in self.errors.borrow().iter() { match &entry.span { Some(span) => { // Find the line and column of the error @@ -96,7 +97,7 @@ impl ErrorLog { } /// A single error entry in the [ErrorLog]. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ErrorLogEntry { /// The span of the error in the original input code. span: Option, @@ -130,7 +131,7 @@ mod tests { \nplayer p1 = robot[x=0, y=1];\ \n\ "; - let mut log = ErrorLog::new(); + let log = ErrorLog::new(); let i = input.find("robot").unwrap(); log.log( Span::new(i, i + 5), @@ -152,7 +153,7 @@ mod tests { \nlabel x = 123 +\ \n true;\ "; - let mut log = ErrorLog::new(); + let 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 out = log.to_string(input); diff --git a/cgaal-engine/src/parsing/lexer.rs b/cgaal-engine/src/parsing/lexer.rs index 65102225..f0054cd0 100644 --- a/cgaal-engine/src/parsing/lexer.rs +++ b/cgaal-engine/src/parsing/lexer.rs @@ -1,25 +1,64 @@ +use crate::parsing::errors::ErrorLog; 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, + /// Errors that occurred during lexing. + errors: &'a ErrorLog, } impl<'a> Lexer<'a> { - pub fn new(input: &'a [u8]) -> Self { - Lexer { input, pos: 0 } + pub fn new(input: &'a [u8], errors: &'a ErrorLog) -> Self { + Lexer { + input, + pos: 0, + errors, + } } fn skip_ws(&mut self) { + // Whitespace while self.pos < self.input.len() && self.input[self.pos].is_ascii_whitespace() { self.pos += 1; } + // Comments + if self.peek(0) == Some(b'/') { + let start = self.pos; + if self.peek(1) == Some(b'/') { + // Single line comment + while self.peek(0) != Some(b'\n') && self.pos < self.input.len() { + self.pos += 1; + } + self.skip_ws(); + } else if self.peek(1) == Some(b'*') { + // Multi line comment + let mut depth = 1; + self.pos += 2; + while depth > 0 && self.pos < self.input.len() { + if self.peek(0) == Some(b'/') && self.peek(1) == Some(b'*') { + depth += 1; + self.pos += 2; + } else if self.peek(0) == Some(b'*') && self.peek(1) == Some(b'/') { + depth -= 1; + self.pos += 2; + } else { + self.pos += 1; + } + } + if depth > 0 { + let span = Span::new(start, start + 2); + self.errors + .log(span, "Unclosed multi-line comment".to_string()) + } + self.skip_ws(); + } + } } #[inline] @@ -80,13 +119,13 @@ impl<'a> Lexer<'a> { /// 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 { + fn lex_unsupported(&mut self) -> Token { let mut len = 1; 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(); - self.token(len, TokenKind::Err(e.to_string())) + self.token(len, TokenKind::Unsupported(e.to_string())) } } @@ -127,11 +166,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.lex_error(), + _ => self.lex_unsupported(), }, b'|' => match self.peek(1) { Some(b'|') => self.token(2, TokenKind::PipePipe), - _ => self.lex_error(), + _ => self.lex_unsupported(), }, b'^' => self.token(1, TokenKind::Hat), b'?' => self.token(1, TokenKind::Question), @@ -153,7 +192,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.lex_error(), + _ => self.lex_unsupported(), }; Some(tk) } @@ -161,22 +200,24 @@ impl<'a> Iterator for Lexer<'a> { #[cfg(test)] mod tests { + use crate::parsing::errors::ErrorLog; use crate::parsing::lexer::Lexer; 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 input = "==4 */ - x (var01 > 0)"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); 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::Star, (4..5).into()), + Token::new(TokenKind::Slash, (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()), @@ -186,13 +227,15 @@ mod tests { Token::new(TokenKind::Rparen, (21..22).into()), ] ); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); } #[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 errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); let tokens = lexer.collect::>(); assert_eq!( tokens, @@ -209,5 +252,6 @@ mod tests { Token::new(TokenKind::Rrangle, (25..27).into()), ] ); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); } } diff --git a/cgaal-engine/src/parsing/mod.rs b/cgaal-engine/src/parsing/mod.rs index 97b5c6b5..75b40e45 100644 --- a/cgaal-engine/src/parsing/mod.rs +++ b/cgaal-engine/src/parsing/mod.rs @@ -1,4 +1,4 @@ -use crate::parsing::ast::Expr; +use crate::parsing::ast::{Expr, LcgsRoot}; use crate::parsing::errors::ErrorLog; use crate::parsing::lexer::Lexer; use crate::parsing::parser::Parser; @@ -11,12 +11,12 @@ 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()); +/// Returns None if there were errors. See the [ErrorLog] for details. +pub fn parse_atl(input: &str, errors: &ErrorLog) -> Option { + let lexer = Lexer::new(input.as_bytes(), errors); let mut parser = Parser::new(lexer, errors); let expr = parser - .expr(0) + .expr() .map(|expr| { parser.expect_end(); expr @@ -28,3 +28,19 @@ pub fn parse_atl(input: &str, errors: &mut ErrorLog) -> Option { Some(expr) } } + +/// Parse an LCGS program. +/// Returns None if there were errors. See the [ErrorLog] for details. +pub fn parse_lcgs(input: &str, errors: &ErrorLog) -> Option { + let lexer = Lexer::new(input.as_bytes(), errors); + let mut parser = Parser::new(lexer, errors); + let lcgs = parser.lcgs_root().map(|lcgs| { + parser.expect_end(); + lcgs + }); + if errors.has_errors() { + None + } else { + lcgs.ok() + } +} diff --git a/cgaal-engine/src/parsing/parser.rs b/cgaal-engine/src/parsing/parser.rs index 5539806d..975492c6 100644 --- a/cgaal-engine/src/parsing/parser.rs +++ b/cgaal-engine/src/parsing/parser.rs @@ -1,4 +1,7 @@ -use crate::parsing::ast::{BinaryOpKind, Coalition, CoalitionKind, Expr, ExprKind, UnaryOpKind}; +use crate::parsing::ast::{ + BinaryOpKind, Coalition, CoalitionKind, Decl, DeclKind, Expr, ExprKind, Ident, LcgsRoot, + PlayerDecl, RangeClause, RelabelCase, StateVarDecl, UnaryOpKind, +}; use crate::parsing::errors::ErrorLog; use crate::parsing::lexer::Lexer; use crate::parsing::span::Span; @@ -61,13 +64,13 @@ macro_rules! recover { pub struct Parser<'a> { lexer: Peekable>, - errors: &'a mut ErrorLog, + errors: &'a ErrorLog, /// A stack of tokens that can be used for error recovery. recovery_tokens: Vec, } impl<'a> Parser<'a> { - pub fn new(lexer: Lexer<'a>, errors: &'a mut ErrorLog) -> Parser<'a> { + pub fn new(lexer: Lexer<'a>, errors: &'a ErrorLog) -> Parser<'a> { Parser { lexer: lexer.peekable(), errors, @@ -87,8 +90,337 @@ impl<'a> Parser<'a> { for _ in self.lexer.by_ref() {} } + /// Parse an LCGS program. + pub fn lcgs_root(&mut self) -> Result { + let decls = self.decls(false)?; + let mut span = Span::new(0, 0); + if !decls.is_empty() { + span.begin = decls[0].span.begin; + span.end = decls[decls.len() - 1].span.end; + } + Ok(LcgsRoot::new(span, decls)) + } + + /// Parse a list of declarations, assuming either global or template scope. + pub fn decls(&mut self, in_template: bool) -> Result, RecoverMode> { + let mut decls = vec![]; + loop { + match (self.lexer.peek(), in_template) { + // Const declaration + ( + Some(Token { + kind: TokenKind::KwConst, + .. + }), + _, + ) => { + let (_, decl) = + recover!(self, self.const_decl(), TokenKind::Semi, Decl::new_error())?; + decls.push(decl); + } + // State label declaration + ( + Some(Token { + kind: TokenKind::KwLabel, + .. + }), + _, + ) => { + let (_, decl) = recover!( + self, + self.state_label_decl(), + TokenKind::Semi, + Decl::new_error() + )?; + decls.push(decl); + } + // State variable declaration + ( + Some(Token { + kind: TokenKind::Word(_), + .. + }), + _, + ) => { + let (_, decl) = recover!( + self, + self.state_var_decl(), + TokenKind::Semi, + Decl::new_error() + )?; + decls.push(decl); + } + // Player declaration. Not allowed in templates + ( + Some(Token { + kind: TokenKind::KwPlayer, + .. + }), + false, + ) => { + let (_, decl) = + recover!(self, self.player_decl(), TokenKind::Semi, Decl::new_error())?; + decls.push(decl); + } + // Template declaration. Not allowed in templates + ( + Some(Token { + kind: TokenKind::KwTemplate, + .. + }), + false, + ) => { + decls.push(self.template_decl()?); + } + // Action declaration. Only allowed in templates + ( + Some(Token { + kind: TokenKind::Lbracket, + .. + }), + true, + ) => { + let (_, decl) = + recover!(self, self.action_decl(), TokenKind::Semi, Decl::new_error())?; + decls.push(decl); + } + // Done + ( + Some(Token { + kind: TokenKind::KwEndTemplate, + .. + }), + _, + ) if in_template => { + return Ok(decls); + } + // Done + (None, _) => { + return Ok(decls); + } + // Unexpected + (Some(_), _) => { + self.errors.log( + self.lexer.peek().unwrap().span, + format!( + "Unexpected '{}', expected declaration", + self.lexer.peek().unwrap().kind, + ), + ); + return Err(RecoverMode); + } + } + } + } + + /// Parse a constant declaration. + pub fn const_decl(&mut self) -> Result { + let start = self.token(TokenKind::KwConst)?; + let ident = self.ident()?; + let _ = self.token(TokenKind::Assign)?; + let expr = self.expr()?; + let span = start + expr.span; + let const_decl = DeclKind::Const(Arc::new(expr)); + Ok(Decl::new(span, ident, const_decl)) + } + + /// Parse a state label declaration. + pub fn state_label_decl(&mut self) -> Result { + let start = self.token(TokenKind::KwLabel)?; + let ident = self.ident()?; + let _ = self.token(TokenKind::Assign)?; + let expr = self.expr()?; + let span = start + expr.span; + let state_label_decl = DeclKind::StateLabel(Arc::new(expr)); + Ok(Decl::new(span, ident, state_label_decl)) + } + + fn range_clause(&mut self) -> Result { + let start = self.token(TokenKind::Lbracket)?; + let (end, (min, max)) = recover!( + self, + self.range_clause_inner(), + TokenKind::Rbracket, + (Expr::new_error(), Expr::new_error()) + )?; + Ok(RangeClause::new(start + end, min.into(), max.into())) + } + + fn range_clause_inner(&mut self) -> Result<(Expr, Expr), RecoverMode> { + let min = self.expr()?; + let _ = self.token(TokenKind::DotDot)?; + let max = self.expr()?; + Ok((min, max)) + } + + /// Parse a state label declaration. + pub fn state_var_decl(&mut self) -> Result { + let ident = self.ident()?; + self.token(TokenKind::Colon)?; + let range = self.range_clause()?; + let _ = self.token(TokenKind::KwInit)?; + let init = self.expr()?; + let _ = self.token(TokenKind::Semi)?; + let update_ident = self.ident()?; + let _ = self.token(TokenKind::Prime)?; + let _ = self.token(TokenKind::Assign)?; + let update = self.expr()?; + let span = ident.span + update.span; + let state_var = DeclKind::StateVar(Arc::new(StateVarDecl::new( + range, + init.into(), + update_ident, + update.into(), + ))); + Ok(Decl::new(span, ident, state_var)) + } + + /// Parse a player declaration. + pub fn player_decl(&mut self) -> Result { + let mut span = self.token(TokenKind::KwPlayer)?; + let ident = self.ident()?; + let _ = self.token(TokenKind::Assign)?; + let template = self.ident()?; + let cases = match self.lexer.peek() { + Some(Token { + kind: TokenKind::Lbracket, + .. + }) => { + let (relabel_span, cases) = self.relabelling()?; + span.end = relabel_span.end; + cases + } + _ => { + span.end = template.span.end; + Vec::new() + } + }; + let player = DeclKind::Player(Arc::new(PlayerDecl::new(template, cases))); + Ok(Decl::new(span, ident, player)) + } + + /// Parse a relabelling. + pub fn relabelling(&mut self) -> Result<(Span, Vec), RecoverMode> { + let start = self.token(TokenKind::Lbracket)?; + let (end, cases) = recover!( + self, + self.relabelling_inner(), + TokenKind::Rbracket, + Vec::new() + )?; + Ok((start + end, cases)) + } + + /// Parse the inners of a relabelling. + fn relabelling_inner(&mut self) -> Result, RecoverMode> { + let mut cases = vec![]; + #[allow(clippy::while_let_loop)] + loop { + match self.lexer.peek() { + Some(Token { + kind: TokenKind::Word(_), + .. + }) => { + let ident = self.ident()?; + let _ = self.token(TokenKind::Assign)?; + let expr = self.expr()?; + cases.push(RelabelCase::new(ident.span + expr.span, ident, expr.into())); + } + _ => break, + } + match self.lexer.peek() { + Some(Token { + kind: TokenKind::Comma, + .. + }) => { + self.lexer.next().unwrap(); + } + Some(Token { + kind: TokenKind::Rbracket, + .. + }) => break, + Some(tok) => { + self.errors.log( + tok.span, + format!( + "Unexpected '{}', expected '{}' or '{}'", + tok.kind, + TokenKind::Comma, + self.recovery_tokens.last().unwrap() + ), + ); + return Err(RecoverMode); + } + None => { + self.errors.log_msg(format!( + "Unexpected EOF, expected '{}' or '{}'", + TokenKind::Comma, + self.recovery_tokens.last().unwrap() + )); + return Err(RecoverMode); + } + } + } + Ok(cases) + } + + /// Parse a template declaration. + pub fn template_decl(&mut self) -> Result { + let start = self.token(TokenKind::KwTemplate)?; + let (end, (ident, decls)) = recover!( + self, + self.template_inner(), + TokenKind::KwEndTemplate, + (Ident::new_error(), Vec::new()) + )?; + Ok(Decl::new(start + end, ident, DeclKind::Template(decls))) + } + + /// Parse the inners a template declaration. + fn template_inner(&mut self) -> Result<(Ident, Vec), RecoverMode> { + let ident = self.ident()?; + let decls = self.decls(true)?; + Ok((ident, decls)) + } + + /// Parse an action declaration. + pub fn action_decl(&mut self) -> Result { + let start = self.token(TokenKind::Lbracket)?; + let (_, ident) = recover!(self, self.ident(), TokenKind::Rbracket, Ident::new_error())?; + let cond = self.expr()?; + let span = start + cond.span; + let action = DeclKind::Action(Arc::new(cond)); + Ok(Decl::new(span, ident, action)) + } + + /// Parse an expression. + pub fn expr(&mut self) -> Result { + // The sub-expressions of a ternary cannot be another ternary in order to avoid ambiguity. + // E.g. "a ? b : c ? d : f" is ambiguous and disallowed. + let cond = self.binary_expr(0)?; + match self.lexer.peek() { + Some(Token { + kind: TokenKind::Question, + .. + }) => { + self.lexer.next().unwrap(); + let (_, then) = recover!( + self, + self.binary_expr(0), + TokenKind::Colon, + Expr::new_error() + )?; + let els = self.binary_expr(0)?; + let span = cond.span + els.span; + let kind = ExprKind::TernaryIf(cond.into(), then.into(), els.into()); + Ok(Expr::new(span, kind)) + } + _ => Ok(cond), + } + } + /// Parse an expression. - pub fn expr(&mut self, min_prec: u8) -> Result { + pub fn binary_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; @@ -105,7 +437,7 @@ impl<'a> Parser<'a> { } 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.binary_expr(new_prec)?; let span = span_start + rhs.span; let kind = ExprKind::Binary(op, lhs.into(), rhs.into()); lhs = Expr::new(span, kind); @@ -120,12 +452,11 @@ impl<'a> Parser<'a> { let begin = self.lexer.next().unwrap().span; let (_, lhs) = recover!( self, - self.expr(BinaryOpKind::Until.precedence()), + self.expr(), TokenKind::Word("U".to_string()), Expr::new_error() )?; - let (end, rhs) = - recover!(self, self.expr(0), TokenKind::Rparen, Expr::new_error())?; + let (end, rhs) = recover!(self, self.expr(), TokenKind::Rparen, Expr::new_error())?; Ok(Expr::new( begin + end, ExprKind::Binary(BinaryOpKind::Until, lhs.into(), rhs.into()), @@ -133,7 +464,7 @@ impl<'a> Parser<'a> { } Some(TokenKind::Word(w)) if w == "F" => { let begin = self.lexer.next().unwrap().span; - let expr = self.expr(0)?; + let expr = self.expr()?; Ok(Expr::new( begin + expr.span, ExprKind::Unary(UnaryOpKind::Eventually, expr.into()), @@ -141,7 +472,7 @@ impl<'a> Parser<'a> { } Some(TokenKind::Word(w)) if w == "G" => { let begin = self.lexer.next().unwrap().span; - let expr = self.expr(0)?; + let expr = self.expr()?; Ok(Expr::new( begin + expr.span, ExprKind::Unary(UnaryOpKind::Invariantly, expr.into()), @@ -149,7 +480,7 @@ impl<'a> Parser<'a> { } Some(TokenKind::Word(w)) if w == "X" => { let begin = self.lexer.next().unwrap().span; - let expr = self.expr(0)?; + let expr = self.expr()?; Ok(Expr::new( begin + expr.span, ExprKind::Unary(UnaryOpKind::Next, expr.into()), @@ -188,6 +519,15 @@ impl<'a> Parser<'a> { ExprKind::Unary(UnaryOpKind::Not, expr.into()), )) } + Some(TokenKind::Minus) => { + let begin = self.lexer.next().unwrap().span; + let expr = self.term()?; + Ok(Expr::new( + begin + expr.span, + ExprKind::Unary(UnaryOpKind::Neg, expr.into()), + )) + } + Some(TokenKind::KwMax | TokenKind::KwMin) => self.func_call(), Some(TokenKind::True) => { let tok = self.lexer.next().unwrap(); Ok(Expr::new(tok.span, ExprKind::True)) @@ -196,6 +536,10 @@ impl<'a> Parser<'a> { let tok = self.lexer.next().unwrap(); Ok(Expr::new(tok.span, ExprKind::False)) } + Some(TokenKind::Num(_)) => { + let tok = self.lexer.next().unwrap(); + Ok(Expr::new(tok.span, ExprKind::Num(tok.num().unwrap()))) + } Some(TokenKind::Word(_)) => self.owned_ident(), Some(TokenKind::Llangle) => self.enforce_coalition(), Some(TokenKind::Llbracket) => self.despite_coalition(), @@ -219,10 +563,46 @@ 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()) + recover!(self, self.expr(), TokenKind::Rparen, Expr::new_error()) .map(|(end, expr)| Expr::new(begin + end, ExprKind::Paren(Arc::new(expr)))) } + /// Parse a function call + pub fn func_call(&mut self) -> Result { + match self.lexer.peek() { + Some(Token { + kind: TokenKind::KwMax, + .. + }) => { + let begin = self.lexer.next().unwrap().span; + let _ = self.token(TokenKind::Lparen)?; + let (end, exprs) = recover!(self, self.args(1), TokenKind::Rparen, Vec::new())?; + Ok(Expr::new(begin + end, ExprKind::Max(exprs))) + } + Some(Token { + kind: TokenKind::KwMin, + .. + }) => { + let begin = self.lexer.next().unwrap().span; + let _ = self.token(TokenKind::Lparen)?; + let (end, exprs) = recover!(self, self.args(1), TokenKind::Rparen, Vec::new())?; + Ok(Expr::new(begin + end, ExprKind::Min(exprs))) + } + None => { + self.errors + .log_msg("Unexpected EOF, expected 'max' or 'min'".to_string()); + Err(RecoverMode) + } + Some(Token { kind, span }) => { + self.errors.log( + *span, + format!("Unexpected '{}', expected 'max' or 'min'", kind), + ); + Err(RecoverMode) + } + } + } + /// Parse an (owned) identifier, e.g. `p1` or `p1.attr`. pub fn owned_ident(&mut self) -> Result { let lhs = self.ident()?; @@ -233,25 +613,25 @@ impl<'a> Parser<'a> { .. }) ) { - return Ok(lhs); + return Ok(Expr::new(lhs.span, ExprKind::OwnedIdent(None, 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()), + ExprKind::OwnedIdent(Some(lhs), rhs), )) } /// Parse an identifier. - pub fn ident(&mut self) -> Result { + 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()))) + Ok(Ident::new(tok.span, tok.kind.to_string())) } Some(tok) => { self.errors.log( @@ -268,6 +648,76 @@ impl<'a> Parser<'a> { } } + /// Parse a comma-separated list of expressions. + /// If `min_count` is given, it will try to parse at least that many expressions. + fn args(&mut self, min_count: usize) -> Result, RecoverMode> { + let mut exprs = Vec::new(); + #[allow(clippy::while_let_loop)] + loop { + match self.lexer.peek() { + Some(Token { + // Expr first set + kind: + TokenKind::Lparen + | TokenKind::Bang + | TokenKind::Minus + | TokenKind::True + | TokenKind::False + | TokenKind::Num(_) + | TokenKind::Word(_) + | TokenKind::Llangle + | TokenKind::Llbracket, + .. + }) => { + let expr = self.expr()?; + exprs.push(expr); + } + _ => break, + } + match self.lexer.peek() { + Some(Token { + kind: TokenKind::Comma, + .. + }) => { + self.lexer.next().unwrap(); + } + Some(Token { + kind: TokenKind::Rparen, + .. + }) => break, + Some(tok) => { + self.errors.log( + tok.span, + format!( + "Unexpected '{}', expected '{}' or '{}'", + tok.kind, + TokenKind::Comma, + self.recovery_tokens.last().unwrap() + ), + ); + return Err(RecoverMode); + } + None => { + self.errors.log_msg(format!( + "Unexpected EOF, expected '{}' or '{}'", + TokenKind::Comma, + self.recovery_tokens.last().unwrap() + )); + return Err(RecoverMode); + } + } + } + if exprs.len() < min_count { + self.errors.log_msg(format!( + "Expected at least {} argument(s), got {}", + min_count, + exprs.len() + )); + return Err(RecoverMode); + } + Ok(exprs) + } + /// Parse an enforce-coalition expression. pub fn enforce_coalition(&mut self) -> Result { let begin = self.token(TokenKind::Llangle)?; @@ -302,7 +752,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> { + pub fn coalition_players(&mut self) -> Result, RecoverMode> { let mut players = vec![]; #[allow(clippy::while_let_loop)] loop { @@ -311,9 +761,8 @@ impl<'a> Parser<'a> { kind: TokenKind::Word(_), .. }) => { - let tok = self.lexer.next().unwrap(); - let p = Expr::new(tok.span, ExprKind::Ident(tok.kind.to_string())); - players.push(p); + let ident = self.ident()?; + players.push(ident); } _ => break, } diff --git a/cgaal-engine/src/parsing/token.rs b/cgaal-engine/src/parsing/token.rs index ff62d340..0d01c4b2 100644 --- a/cgaal-engine/src/parsing/token.rs +++ b/cgaal-engine/src/parsing/token.rs @@ -14,6 +14,14 @@ impl Token { pub fn new(kind: TokenKind, span: Span) -> Self { Token { kind, span } } + + /// Returns the value of the Num variant or None if the token is not a Num. + pub fn num(&self) -> Option { + match self.kind { + TokenKind::Num(n) => Some(n), + _ => None, + } + } } impl Display for Token { @@ -88,7 +96,7 @@ pub enum TokenKind { Word(String), // Error - Err(String), + Unsupported(String), } impl Display for TokenKind { @@ -139,7 +147,7 @@ impl Display for TokenKind { TokenKind::False => write!(f, "false"), TokenKind::Num(n) => write!(f, "{n}"), TokenKind::Word(w) => write!(f, "{w}"), - TokenKind::Err(e) => write!(f, "{e}"), + TokenKind::Unsupported(e) => write!(f, "{e}"), } } } diff --git a/cgaal-engine/tests/parsing.rs b/cgaal-engine/tests/parsing_expr.rs similarity index 50% rename from cgaal-engine/tests/parsing.rs rename to cgaal-engine/tests/parsing_expr.rs index 58ee4e46..ee5838b2 100644 --- a/cgaal-engine/tests/parsing.rs +++ b/cgaal-engine/tests/parsing_expr.rs @@ -12,14 +12,14 @@ use cgaal_engine::parsing::span::*; fn basic_expr_001() { // Check true and false let input = "true && false"; - 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"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr(); parser.expect_end(); assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( - expr, + expr.expect("Failed to parse valid expression"), Expr::new( Span::new(0, 13), ExprKind::Binary( @@ -35,14 +35,14 @@ fn basic_expr_001() { 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()); - let mut parser = Parser::new(lexer, &mut errors); - let expr = parser.expr(0).expect("Failed to valid parse expression"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr(); parser.expect_end(); assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( - expr, + expr.expect("Failed to parse valid expression"), Expr::new( Span::new(0, 21), ExprKind::Binary( @@ -65,31 +65,44 @@ 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()); - let mut parser = Parser::new(lexer, &mut errors); - let expr = parser.expr(0).expect("Failed to valid parse expression"); + let input = "foo || true && bar.baz || (true || false)"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr(); parser.expect_end(); assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( - expr, + expr.expect("Failed to parse valid expression"), Expr::new( - Span::new(0, 37), + Span::new(0, 41), ExprKind::Binary( BinaryOpKind::Or, Expr::new( - Span::new(0, 18), + Span::new(0, 22), ExprKind::Binary( BinaryOpKind::Or, - Expr::new(Span::new(0, 3), ExprKind::Ident("foo".to_string())).into(), Expr::new( - Span::new(7, 18), + Span::new(0, 3), + ExprKind::OwnedIdent( + None, + Ident::new(Span::new(0, 3), "foo".to_string()) + ) + ) + .into(), + Expr::new( + Span::new(7, 22), 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() + Expr::new( + Span::new(15, 22), + ExprKind::OwnedIdent( + Some(Ident::new(Span::new(15, 18), "bar".to_string())), + Ident::new(Span::new(19, 22), "baz".to_string()) + ) + ) + .into() ) ) .into() @@ -97,14 +110,14 @@ fn basic_expr_003() { ) .into(), Expr::new( - Span::new(22, 37), + Span::new(26, 41), ExprKind::Paren( Expr::new( - Span::new(23, 36), + Span::new(27, 40), ExprKind::Binary( BinaryOpKind::Or, - Expr::new(Span::new(23, 27), ExprKind::True).into(), - Expr::new(Span::new(31, 36), ExprKind::False).into() + Expr::new(Span::new(27, 31), ExprKind::True).into(), + Expr::new(Span::new(35, 40), ExprKind::False).into() ) ) .into() @@ -117,14 +130,44 @@ fn basic_expr_003() { ); } +#[test] +fn basic_expr_004() { + // Check parsing of argument list + let input = "max(a, b, 5)"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr(); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + expr.expect("Failed to parse valid expression"), + Expr::new( + Span::new(0, 12), + ExprKind::Max(vec![ + Expr::new( + Span::new(4, 5), + ExprKind::OwnedIdent(None, Ident::new(Span::new(4, 5), "a".to_string())) + ) + .into(), + Expr::new( + Span::new(7, 8), + ExprKind::OwnedIdent(None, Ident::new(Span::new(7, 8), "b".to_string())) + ) + .into(), + Expr::new(Span::new(10, 11), ExprKind::Num(5)).into(), + ]) + ) + ) +} + #[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()); - let mut parser = Parser::new(lexer, &mut errors); - let expr = parser.expr(0).expect("Failed to valid parse expression"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr().expect("Failed to valid parse expression"); parser.expect_end(); assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( @@ -133,16 +176,20 @@ fn atl_expr_001() { Span::new(0, 13), ExprKind::Coalition(Coalition::new( Span::new(0, 6), - vec![Expr::new( - Span::new(2, 4), - ExprKind::Ident("p1".to_string()) - )], + vec![Ident::new(Span::new(2, 4), "p1".to_string()).into()], CoalitionKind::Enforce, Expr::new( Span::new(7, 13), ExprKind::Unary( UnaryOpKind::Eventually, - Expr::new(Span::new(9, 13), ExprKind::Ident("goal".to_string())).into() + Expr::new( + Span::new(9, 13), + ExprKind::OwnedIdent( + None, + Ident::new(Span::new(9, 13), "goal".to_string()) + ) + ) + .into() ) ) .into() @@ -155,10 +202,10 @@ fn atl_expr_001() { 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()); - let mut parser = Parser::new(lexer, &mut errors); - let expr = parser.expr(0).expect("Failed to valid parse expression"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr().expect("Failed to valid parse expression"); parser.expect_end(); assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( @@ -168,15 +215,22 @@ fn atl_expr_002() { 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())), + Ident::new(Span::new(2, 4), "p1".to_string()), + Ident::new(Span::new(6, 8), "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() + Expr::new( + Span::new(13, 17), + ExprKind::OwnedIdent( + None, + Ident::new(Span::new(13, 17), "safe".to_string()) + ) + ) + .into() ) ) .into() @@ -189,10 +243,10 @@ fn atl_expr_002() { 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()); - let mut parser = Parser::new(lexer, &mut errors); - let expr = parser.expr(0).expect("Error should be recoverable"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr().expect("Error should be recoverable"); parser.expect_end(); assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( @@ -207,8 +261,22 @@ fn atl_expr_003() { 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() + Expr::new( + Span::new(6, 10), + ExprKind::OwnedIdent( + None, + Ident::new(Span::new(6, 10), "safe".to_string()) + ) + ) + .into(), + Expr::new( + Span::new(13, 17), + ExprKind::OwnedIdent( + None, + Ident::new(Span::new(13, 17), "goal".to_string()) + ) + ) + .into() ) ) .into() @@ -221,10 +289,10 @@ fn atl_expr_003() { 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"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr().expect("Failed to valid parse expression"); parser.expect_end(); assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); assert_eq!( @@ -233,10 +301,7 @@ fn atl_expr_004() { Span::new(0, 16), ExprKind::Coalition(Coalition::new( Span::new(0, 6), - vec![Expr::new( - Span::new(2, 4), - ExprKind::Ident("p1".to_string()), - )], + vec![Ident::new(Span::new(2, 4), "p1".to_string(),)], CoalitionKind::Enforce, Expr::new( Span::new(7, 16), @@ -244,12 +309,9 @@ fn atl_expr_004() { 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(), + ExprKind::OwnedIdent( + Some(Ident::new(Span::new(9, 11), "p1".to_string())), + Ident::new(Span::new(12, 16), "attr".to_string()), ), ) .into(), @@ -261,14 +323,52 @@ fn atl_expr_004() { ); } +#[test] +fn atl_expr_005() { + // Is comma at the end of coalition list accepted? + let input = "<> F safe"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr().expect("Failed to parse valid expression"); + parser.expect_end(); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + expr, + Expr::new( + Span::new(0, 14), + ExprKind::Coalition(Coalition::new( + Span::new(0, 7), + vec![Ident::new(Span::new(2, 4), "p1".to_string())], + CoalitionKind::Enforce, + Expr::new( + Span::new(8, 14), + ExprKind::Unary( + UnaryOpKind::Eventually, + Expr::new( + Span::new(10, 14), + ExprKind::OwnedIdent( + None, + Ident::new(Span::new(10, 14), "safe".to_string()) + ) + ) + .into() + ) + ) + .into() + )) + ) + ); +} + #[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()); - let mut parser = Parser::new(lexer, &mut errors); - assert!(parser.expr(0).is_err()); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + assert!(parser.expr().is_err()); assert!(errors.has_errors()); let out = errors.to_string(input); assert_eq!( @@ -281,10 +381,10 @@ fn erroneous_expr_001() { 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()); - let mut parser = Parser::new(lexer, &mut errors); - let expr = parser.expr(0).expect("Error should be recoverable"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr().expect("Error should be recoverable"); parser.expect_end(); assert!(errors.has_errors()); let out = errors.to_string(input); @@ -296,7 +396,10 @@ fn erroneous_expr_002() { ); assert_eq!( expr, - Expr::new(Span::new(0, 3), ExprKind::Ident("foo".to_string())) + Expr::new( + Span::new(0, 3), + ExprKind::OwnedIdent(None, Ident::new(Span::new(0, 3), "foo".to_string())) + ) ); } @@ -304,10 +407,10 @@ fn erroneous_expr_002() { 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()); - let mut parser = Parser::new(lexer, &mut errors); - let expr = parser.expr(0).expect("Error should be recoverable"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr().expect("Error should be recoverable"); parser.expect_end(); assert!(errors.has_errors()); let out = errors.to_string(input); @@ -334,10 +437,10 @@ fn erroneous_expr_003() { 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()); - let mut parser = Parser::new(lexer, &mut errors); - assert!(parser.expr(0).is_err()); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + assert!(parser.expr().is_err()); assert!(errors.has_errors()); let out = errors.to_string(input); assert_eq!( @@ -350,10 +453,10 @@ fn erroneous_expr_004() { 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()); - let mut parser = Parser::new(lexer, &mut errors); - assert!(parser.expr(0).is_err()); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + assert!(parser.expr().is_err()); assert!(errors.has_errors()); let out = errors.to_string(input); assert_eq!( @@ -368,10 +471,10 @@ fn erroneous_expr_005() { 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()); - let mut parser = Parser::new(lexer, &mut errors); - let expr = parser.expr(0).expect("Error should be recoverable"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let expr = parser.expr().expect("Error should be recoverable"); parser.expect_end(); assert!(errors.has_errors()); let out = errors.to_string(input); @@ -391,10 +494,10 @@ fn erroneous_expr_006() { 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"); + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let _expr = parser.expr().expect("Error should be recoverable"); parser.expect_end(); assert!(errors.has_errors()); let out = errors.to_string(input); @@ -406,6 +509,86 @@ fn erroneous_expr_007() { ); } +#[test] +fn erroneous_expr_008() { + // Check error recovery in function call arguments + let input = "min(2 - 1, 4 . a)"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let _expr = parser.expr().expect("Error should be recoverable"); + parser.expect_end(); + assert!(errors.has_errors()); + let out = errors.to_string(input); + assert_eq!( + out, + "\u{1b}[31m1:14 Error:\u{1b}[0m Unexpected '.', expected ',' or ')'\n\ + | min(2 - 1, 4 . a)\n\ + | ^\n" + ); +} + +#[test] +fn expr_batch() { + let exprs = vec![ + "123 + abc / (2 * 5) - 1", + "999 - 123 - foo.bar", + "-2- ---3--(-!4)", + "foo > 4 && bar < 120 || baz == 0", + "foo23 - (bar || baz) && !!34", + "foo -> bar ^ baz", + "5 > 4 > 3 >= 2 == 2 < 20", + "(((4 + 2) / foo * (baz)) && 4 -> 2)", + "foo > 2 ? bar + 2 : (true - false)", + "a ? (b ? c : d) : (f ? g : h)", + "max(1, 2, 3) + min(1, 2, 3)", + ]; + + for (i, expr) in exprs.iter().enumerate() { + let errors = ErrorLog::new(); + let lexer = Lexer::new(expr.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let is_ok = parser.expr().is_ok(); + parser.expect_end(); + assert!( + is_ok && errors.is_empty(), + "ErrorLog is not empty (expr index {}): {:?}", + i, + errors + ); + } +} + +#[test] +fn expr_erroneous_batch() { + let exprs = vec![ + "123 * 456 *", + "+ 23 - 45", + "123 foo", + "((()(()", + "", + "foo || bar = 0 * 2", + "10!", + "func()", + "foo ? bar", + "a ? b : c ? d ? f : g : h", + "max()", + ]; + + for (i, expr) in exprs.iter().enumerate() { + let errors = ErrorLog::new(); + let lexer = Lexer::new(expr.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let _ = parser.expr(); + parser.expect_end(); + assert!( + !errors.is_empty(), + "ErrorLog is empty. Expected error (expr index {})", + i + ); + } +} + #[test] fn atl_expr_batch() { // Check that no errors are found for valid ATL expressions @@ -445,9 +628,9 @@ fn atl_expr_batch() { 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)) + let errors = ErrorLog::new(); + parse_atl(atl_raw, &errors) + .and_then(|expr| convert_expr_to_phi(&expr, &game, &errors)) .expect(&format!( "For '{}', ErrorLog is not empty: {:?}", atl_raw, errors @@ -456,7 +639,7 @@ fn atl_expr_batch() { } #[test] -fn atl_expr_error_batch() { +fn atl_expr_erroneous_batch() { // Check that errors are found for erroneous ATL expressions let lcgs_raw = "\n\ player p1 = thing;\n\ @@ -492,9 +675,9 @@ fn atl_expr_error_batch() { 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)) + let errors = ErrorLog::new(); + let is_none = parse_atl(atl_raw, &errors) + .and_then(|expr| convert_expr_to_phi(&expr, &game, &errors)) .is_none(); assert!( is_none && errors.has_errors(), diff --git a/cgaal-engine/tests/parsing_lcgs.rs b/cgaal-engine/tests/parsing_lcgs.rs new file mode 100644 index 00000000..ec281165 --- /dev/null +++ b/cgaal-engine/tests/parsing_lcgs.rs @@ -0,0 +1,316 @@ +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::*; + +#[test] +fn const_decl_001() { + let input = "const foo = 1"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let decl = parser + .const_decl() + .expect("Failed to parse valid const decl"); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + decl, + Decl::new( + Span::new(0, 13), + Ident::new(Span::new(6, 9), "foo".into()), + DeclKind::Const(Expr::new(Span::new(12, 13), ExprKind::Num(1),).into()) + ) + ); +} + +#[test] +fn state_label_decl_001() { + let input = "label foo = 1"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let decl = parser + .state_label_decl() + .expect("Failed to parse valid state label decl"); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + decl, + Decl::new( + Span::new(0, 13), + Ident::new(Span::new(6, 9), "foo".into()), + DeclKind::StateLabel(Expr::new(Span::new(12, 13), ExprKind::Num(1),).into()) + ) + ); +} + +#[test] +fn state_var_decl_001() { + let input = "foo : [0..3] init 1; foo' = 2"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let decl = parser + .state_var_decl() + .expect("Failed to parse valid state var decl"); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + decl, + Decl::new( + Span::new(0, 29), + Ident::new(Span::new(0, 3), "foo".into()), + DeclKind::StateVar( + StateVarDecl::new( + RangeClause::new( + Span::new(6, 12), + Expr::new(Span::new(7, 8), ExprKind::Num(0),).into(), + Expr::new(Span::new(10, 11), ExprKind::Num(3),).into(), + ), + Expr::new(Span::new(18, 19), ExprKind::Num(1),).into(), + Ident::new(Span::new(21, 24), "foo".into()), + Expr::new(Span::new(28, 29), ExprKind::Num(2),).into(), + ) + .into() + ) + ) + ); +} + +#[test] +fn player_decl_001() { + let input = "player foo = bar"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let decl = parser + .player_decl() + .expect("Failed to parse valid player decl"); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + decl, + Decl::new( + Span::new(0, 16), + Ident::new(Span::new(7, 10), "foo".into()), + DeclKind::Player( + PlayerDecl::new(Ident::new(Span::new(13, 16), "bar".into()), Vec::new(),).into() + ) + ) + ) +} + +#[test] +fn player_decl_002() { + let input = "player bar = baz [one=1, two=2 || 3]"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let decl = parser + .player_decl() + .expect("Failed to parse valid player decl"); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + decl, + Decl::new( + Span::new(0, 36), + Ident::new(Span::new(7, 10), "bar".into()), + DeclKind::Player( + PlayerDecl::new( + Ident::new(Span::new(13, 16), "baz".into()), + vec![ + RelabelCase::new( + Span::new(18, 23), + Ident::new(Span::new(18, 21), "one".into()), + Expr::new(Span::new(22, 23), ExprKind::Num(1),).into(), + ), + RelabelCase::new( + Span::new(25, 35), + Ident::new(Span::new(25, 28), "two".into()), + Expr::new( + Span::new(29, 35), + ExprKind::Binary( + BinaryOpKind::Or, + Expr::new(Span::new(29, 30), ExprKind::Num(2)).into(), + Expr::new(Span::new(34, 35), ExprKind::Num(3)).into() + ), + ) + .into(), + ), + ] + ) + .into() + ) + ) + ) +} + +#[test] +fn template_decl_001() { + let input = "template foo endtemplate"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let decl = parser + .template_decl() + .expect("Failed to parse valid template decl"); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + decl, + Decl::new( + Span::new(0, 24), + Ident::new(Span::new(9, 12), "foo".into()), + DeclKind::Template(Vec::new()), + ) + ) +} + +#[test] +fn template_decl_002() { + let input = "template foo label bar = 1; [act] 2; endtemplate"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let decl = parser + .template_decl() + .expect("Failed to parse valid template decl"); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + decl, + Decl::new( + Span::new(0, 48), + Ident::new(Span::new(9, 12), "foo".into()), + DeclKind::Template(vec![ + Decl::new( + Span::new(13, 26), + Ident::new(Span::new(19, 22), "bar".into()), + DeclKind::StateLabel(Expr::new(Span::new(25, 26), ExprKind::Num(1),).into()) + ), + Decl::new( + Span::new(28, 35), + Ident::new(Span::new(29, 32), "act".into()), + DeclKind::Action(Expr::new(Span::new(34, 35), ExprKind::Num(2),).into()) + ), + ]), + ) + ) +} + +#[test] +fn action_decl_001() { + let input = "[act] 1"; + let errors = ErrorLog::new(); + let lexer = Lexer::new(input.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let decl = parser + .action_decl() + .expect("Failed to parse valid action decl"); + assert!(errors.is_empty(), "ErrorLog is not empty: {:?}", errors); + assert_eq!( + decl, + Decl::new( + Span::new(0, 7), + Ident::new(Span::new(1, 4), "act".into()), + DeclKind::Action(Expr::new(Span::new(6, 7), ExprKind::Num(1),).into()) + ) + ) +} + +#[test] +fn lcgs_batch() { + let models = vec![ + "// Comment + player p1 = thing; + player p2 = thing; + label prop = 1; + template thing + x : [0..10] init 0; + x' = x; + label attr = 1; + [wait] 1; + endtemplate", + // ---------------------------- + "const N = 3; + player p1 = robot; + player p2 = robot; + label prop = 1; + steps : [0..100] init 0; + steps' = steps || steps; + template robot + /* Multi-line + comment // test + /* nested btw */*/ + x : [0..N] init 0; + x' = x; + [wait] 1; + [left] 1; + [right] 1; + endtemplate", + ]; + + for (i, model) in models.iter().enumerate() { + let errors = ErrorLog::new(); + let lexer = Lexer::new(model.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let _ = parser + .lcgs_root() + .expect(&format!("Failed to parse valid LCGS model (index {})", i)); + parser.expect_end(); + assert!( + errors.is_empty(), + "ErrorLog is not empty (model index {}): {:?}", + i, + errors + ); + } +} + +#[test] +fn lcgs_erroneous_batch() { + let models = vec![ + "player p1 = thing; + label prop = 1 + template thing + x : [0..10] init 0; + x' = x; + label attr = 1; + [wait] 1; + endtemplate", + // ---------------------------- + "const N = 3; + player p1 = robot [5=test]; + label prop = 1; + template robot + [wait] 1; + [left] 1; + endtemplate + steps : [0..100] init 0; + steps' = steps || steps;", + // ---------------------------- + "const N = 3; + player p1 = robot; + label prop = 1; + steps : [0..100] init 0; + template robot + [left] 1; + endtemplate", + // ---------------------------- + "const N = 3; + /* unclosed comment", + // ---------------------------- + "label prop = 1;\ + /* /* nested must be closed */\ + player p1 = robot;", + ]; + + for (i, model) in models.iter().enumerate() { + let errors = ErrorLog::new(); + let lexer = Lexer::new(model.as_bytes(), &errors); + let mut parser = Parser::new(lexer, &errors); + let _ = parser.lcgs_root(); + parser.expect_end(); + assert!( + !errors.is_empty(), + "ErrorLog is empty. Expected error (model index {})", + i + ); + } +} diff --git a/cgaal-engine/tests/partial_strategy_synthesis.rs b/cgaal-engine/tests/partial_strategy_synthesis.rs index 708a2ee2..cd930c6a 100644 --- a/cgaal-engine/tests/partial_strategy_synthesis.rs +++ b/cgaal-engine/tests/partial_strategy_synthesis.rs @@ -146,11 +146,11 @@ macro_rules! assert_partial_strat_moves { /// ``` macro_rules! strat_synthesis_test { ($game:expr, $phi:expr, $($rest:tt)*) => { - let mut errors = ErrorLog::new(); + let errors = ErrorLog::new(); let ast = parse_lcgs($game).unwrap(); let game = IntermediateLcgs::create(ast).unwrap(); - let phi = parse_atl($phi, &mut errors) - .and_then(|expr| convert_expr_to_phi(&expr, &game, &mut errors)) + let phi = parse_atl($phi, &errors) + .and_then(|expr| convert_expr_to_phi(&expr, &game, &errors)) .ok_or_else(|| format!("{}", errors.to_string($phi))) .unwrap(); let v0 = AtlVertex::Full {