use urge_core::{
ast::{node, AstNode, Expr, Literal, Token},
symbol::{ParadigmSet, SemanticClass},
};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
pub struct Parser {
tokens: Vec<Token>,
pos: usize,
}
#[cfg(feature = "alloc")]
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Parser { tokens, pos: 0 }
}
pub fn parse(&mut self) -> Option<AstNode> {
self.parse_expr(0)
}
fn peek(&self) -> Option<&Token> {
self.tokens.get(self.pos)
}
fn consume(&mut self) -> Option<Token> {
if self.pos < self.tokens.len() {
let t = self.tokens[self.pos].clone();
self.pos += 1;
Some(t)
} else {
None
}
}
fn infix_bp(class: SemanticClass) -> Option<(u8, u8)> {
match class {
SemanticClass::Biconditional => Some((1, 2)),
SemanticClass::Implication => Some((3, 4)),
SemanticClass::Disjunction | SemanticClass::FuzzyOr => Some((5, 6)),
SemanticClass::Conjunction | SemanticClass::FuzzyAnd => Some((7, 8)),
SemanticClass::Until | SemanticClass::Release | SemanticClass::WeakUntil => {
Some((9, 10))
}
SemanticClass::Equals
| SemanticClass::NotEquals
| SemanticClass::LessThan
| SemanticClass::LessOrEqual
| SemanticClass::GreaterThan
| SemanticClass::GreaterOrEqual => Some((11, 12)),
SemanticClass::ExclusiveOr => Some((13, 14)),
_ => None,
}
}
fn parse_expr(&mut self, min_bp: u8) -> Option<AstNode> {
let token = self.peek()?.clone();
let mut lhs = match token.class {
SemanticClass::Verum => {
self.consume();
node(Expr::Lit(Literal::Bool(true)))
}
SemanticClass::Falsum => {
self.consume();
node(Expr::Lit(Literal::Bool(false)))
}
SemanticClass::NumericLiteral => {
self.consume();
let val: i64 = token.raw.as_str().parse().unwrap_or(0);
node(Expr::Lit(Literal::Integer(val)))
}
SemanticClass::BooleanLiteral => {
self.consume();
let b = token.raw.as_str() == "true";
node(Expr::Lit(Literal::Bool(b)))
}
SemanticClass::Negation => {
self.consume();
let operand = self.parse_expr(20)?;
let mut ps = ParadigmSet::empty();
for &p in SemanticClass::Negation.paradigms() {
ps.insert(p);
}
node(Expr::Unary {
op: SemanticClass::Negation,
operand,
paradigms: ps,
})
}
SemanticClass::Necessity | SemanticClass::Possibility => {
let op = token.class;
self.consume();
let operand = self.parse_expr(20)?;
let mut ps = ParadigmSet::empty();
for &p in op.paradigms() {
ps.insert(p);
}
node(Expr::Unary {
op,
operand,
paradigms: ps,
})
}
SemanticClass::Globally | SemanticClass::Finally | SemanticClass::Next => {
let op = token.class;
self.consume();
let body = self.parse_expr(20)?;
let mut ps = ParadigmSet::empty();
ps.insert(urge_core::engine::Paradigm::Temporal);
node(Expr::TemporalConstraint {
op,
body,
bound_ns: None,
paradigms: ps,
})
}
SemanticClass::Obligatory | SemanticClass::Permitted | SemanticClass::Forbidden => {
let modality = token.class;
self.consume();
let body = self.parse_expr(20)?;
let mut ps = ParadigmSet::empty();
ps.insert(urge_core::engine::Paradigm::Deontic);
node(Expr::Unary {
op: modality,
operand: body,
paradigms: ps,
})
}
SemanticClass::Identifier => {
self.consume();
let mut ps = ParadigmSet::empty();
ps.insert(urge_core::engine::Paradigm::Boolean);
let mut name = heapless::String::new();
for c in token.raw.chars().take(32) {
let _ = name.push(c);
}
node(Expr::Var {
name,
paradigms: ps,
})
}
_ => {
self.consume();
node(Expr::Lit(Literal::Bool(false)))
}
};
while let Some(op_token) = self.peek().cloned() {
if let Some((l_bp, r_bp)) = Self::infix_bp(op_token.class) {
if l_bp < min_bp {
break;
}
self.consume();
if matches!(
op_token.class,
SemanticClass::Until | SemanticClass::Release | SemanticClass::WeakUntil
) {
let right = self.parse_expr(r_bp)?;
let mut ps = ParadigmSet::empty();
ps.insert(urge_core::engine::Paradigm::Temporal);
lhs = node(Expr::Binary {
op: op_token.class,
left: lhs,
right,
paradigms: ps,
});
} else {
let right = self.parse_expr(r_bp)?;
let mut ps = lhs.paradigms();
for &p in op_token.class.paradigms() {
ps.insert(p);
}
lhs = node(Expr::Binary {
op: op_token.class,
left: lhs,
right,
paradigms: ps,
});
}
} else {
break;
}
}
Some(lhs)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tokenizer::Tokenizer;
#[test]
#[cfg(feature = "alloc")]
fn parse_simple_conjunction() {
let t = Tokenizer::new();
let tokens = t.tokenize("a and b");
let mut parser = Parser::new(tokens);
let ast = parser.parse();
assert!(ast.is_some());
assert!(matches!(
ast.unwrap().as_ref(),
Expr::Binary {
op: SemanticClass::Conjunction,
..
}
));
}
#[test]
#[cfg(feature = "alloc")]
fn parse_deontic_obligation() {
let t = Tokenizer::new();
let tokens = t.tokenize("must obtain_consent");
let mut parser = Parser::new(tokens);
let ast = parser.parse();
assert!(ast.is_some());
assert!(matches!(
ast.unwrap().as_ref(),
Expr::Unary {
op: SemanticClass::Obligatory,
..
}
));
}
#[test]
#[cfg(feature = "alloc")]
fn parse_temporal_globally() {
let t = Tokenizer::new();
let tokens = t.tokenize("always consent_valid");
let mut parser = Parser::new(tokens);
let ast = parser.parse();
assert!(ast.is_some());
assert!(matches!(
ast.unwrap().as_ref(),
Expr::TemporalConstraint {
op: SemanticClass::Globally,
..
}
));
}
}