feat!(argus-parser): complete parser

This changes the API for `ExprBuilder`, but that is OK.
This commit is contained in:
Anand Balakrishnan 2023-10-03 16:08:22 -07:00
parent 17042a2544
commit 2319668e2b
No known key found for this signature in database
9 changed files with 545 additions and 146 deletions

View file

@ -1,7 +1,305 @@
//! # Argus logic syntax
use std::time::Duration;
use argus_core::ExprBuilder;
mod lexer;
mod parser;
use chumsky::prelude::Rich;
pub use lexer::{lexer, Error as LexError, Span, Token};
pub use parser::{parser, Expr, Interval};
pub fn parse_str(src: &str) -> Result<argus_core::Expr, Vec<Rich<'_, String>>> {
use chumsky::prelude::{Input, Parser};
let (tokens, lex_errors) = lexer().parse(src.clone()).into_output_errors();
let (parsed, parse_errors) = if let Some(tokens) = &tokens {
parser()
.parse(tokens.as_slice().spanned((src.len()..src.len()).into()))
.into_output_errors()
} else {
(None, Vec::new())
};
let (expr, expr_errors) = if let Some((ast, span)) = parsed {
let mut expr_builder = ExprBuilder::new();
let result = ast_to_expr(&ast, span, &mut expr_builder);
match result {
Ok(expr) => (Some(expr), vec![]),
Err(err) => (None, vec![err]),
}
} else {
(None, vec![])
};
let errors: Vec<_> = lex_errors
.into_iter()
.map(|e| e.map_token(|c| c.to_string()))
.chain(parse_errors.into_iter().map(|e| e.map_token(|tok| tok.to_string())))
.chain(expr_errors.into_iter().map(|e| e.map_token(|tok| tok.to_string())))
.map(|e| e.into_owned())
.collect();
if !errors.is_empty() {
Err(errors)
} else {
expr.ok_or(errors)
}
}
fn interval_convert(interval: &Interval<'_>) -> argus_core::Interval {
use core::ops::Bound;
let a = if let Some(a) = &interval.a {
match &a.0 {
Expr::UInt(value) => Bound::Included(Duration::from_secs(*value)),
Expr::Float(value) => Bound::Included(Duration::from_secs_f64(*value)),
_ => unreachable!("must be valid numeric literal."),
}
} else {
Bound::Unbounded
};
let b = if let Some(b) = &interval.b {
match &b.0 {
Expr::UInt(value) => Bound::Included(Duration::from_secs(*value)),
Expr::Float(value) => Bound::Included(Duration::from_secs_f64(*value)),
_ => unreachable!("must be valid numeric literal."),
}
} else {
Bound::Unbounded
};
argus_core::Interval::new(a, b)
}
/// Convert a parsed [`Expr`] into an [Argus `Expr`](argus_core::Expr)
fn ast_to_expr<'tokens, 'src: 'tokens>(
ast: &Expr<'src>,
span: lexer::Span,
ctx: &mut ExprBuilder,
) -> Result<argus_core::Expr, Rich<'tokens, Token<'src>, lexer::Span>> {
match ast {
Expr::Error => unreachable!("Errors should have been caught by parser"),
Expr::Bool(value) => Ok(ctx.bool_const(*value).into()),
Expr::Int(value) => Ok(ctx.int_const(*value).into()),
Expr::UInt(value) => Ok(ctx.uint_const(*value).into()),
Expr::Float(value) => Ok(ctx.float_const(*value).into()),
Expr::Var { name, kind } => match kind {
parser::Type::Unknown => Err(Rich::custom(span, "All variables must have defined type by now.")),
parser::Type::Bool => ctx
.bool_var(name.to_string())
.map(|var| var.into())
.map_err(|err| Rich::custom(span, err.to_string())),
parser::Type::UInt => ctx
.uint_var(name.to_string())
.map(|var| var.into())
.map_err(|err| Rich::custom(span, err.to_string())),
parser::Type::Int => ctx
.int_var(name.to_string())
.map(|var| var.into())
.map_err(|err| Rich::custom(span, err.to_string())),
parser::Type::Float => ctx
.float_var(name.to_string())
.map(|var| var.into())
.map_err(|err| Rich::custom(span, err.to_string())),
},
Expr::Unary { op, interval, arg } => {
let arg = ast_to_expr(&arg.0, arg.1, ctx)?;
let interval = interval.as_ref().map(|(i, span)| (interval_convert(i), span));
match op {
parser::UnaryOps::Neg => {
assert!(interval.is_none());
let argus_core::Expr::Num(arg) = arg else {
unreachable!("- must have numeric expression argument");
};
Ok(ctx.make_neg(Box::new(arg)).into())
}
parser::UnaryOps::Not => {
assert!(interval.is_none());
let argus_core::Expr::Bool(arg) = arg else {
unreachable!("`Not` must have boolean expression argument");
};
Ok(ctx.make_not(Box::new(arg)).into())
}
parser::UnaryOps::Next => {
use core::ops::Bound;
let argus_core::Expr::Bool(arg) = arg else {
unreachable!("`Next` must have boolean expression argument");
};
match interval {
Some((interval, ispan)) => {
let steps: usize = match (interval.start, interval.end) {
(Bound::Included(start), Bound::Included(end)) => (end - start).as_secs() as usize,
_ => {
return Err(Rich::custom(
*ispan,
"Oracle operation (X[..]) cannot have unbounded intervals",
))
}
};
Ok(ctx.make_oracle(steps, Box::new(arg)).into())
}
None => Ok(ctx.make_next(Box::new(arg)).into()),
}
}
parser::UnaryOps::Always => {
let argus_core::Expr::Bool(arg) = arg else {
unreachable!("`Always` must have boolean expression argument");
};
match interval {
Some((interval, _)) => Ok(ctx.make_timed_always(interval, Box::new(arg)).into()),
None => Ok(ctx.make_always(Box::new(arg)).into()),
}
}
parser::UnaryOps::Eventually => {
let argus_core::Expr::Bool(arg) = arg else {
unreachable!("`Eventually` must have boolean expression argument");
};
match interval {
Some((interval, _)) => Ok(ctx.make_timed_eventually(interval, Box::new(arg)).into()),
None => Ok(ctx.make_eventually(Box::new(arg)).into()),
}
}
}
}
Expr::Binary {
op,
interval,
args: (lhs, rhs),
} => {
let lhs = ast_to_expr(&lhs.0, lhs.1, ctx)?;
let rhs = ast_to_expr(&rhs.0, rhs.1, ctx)?;
let interval = interval.as_ref().map(|(i, span)| (interval_convert(i), span));
match op {
parser::BinaryOps::Add => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("`Add` must have numeric expression arguments");
};
ctx.make_add([lhs, rhs])
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Sub => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("`Sub` must have numeric expression arguments");
};
Ok(ctx.make_sub(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Mul => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("`Mul` must have numeric expression arguments");
};
ctx.make_mul([lhs, rhs])
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Div => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("`Div` must have numeric expression arguments");
};
Ok(ctx.make_div(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Lt => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_lt(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Le => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_le(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Gt => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_gt(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Ge => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_ge(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Eq => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_eq(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Neq => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_neq(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::And => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`And` must have boolean expression arguments");
};
ctx.make_and([lhs, rhs])
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Or => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Or` must have boolean expression arguments");
};
ctx.make_or([lhs, rhs])
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Implies => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Implies` must have boolean expression arguments");
};
ctx.make_implies(Box::new(lhs), Box::new(rhs))
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Equiv => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Equiv` must have boolean expression arguments");
};
ctx.make_equiv(Box::new(lhs), Box::new(rhs))
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Xor => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Xor` must have boolean expression arguments");
};
ctx.make_xor(Box::new(lhs), Box::new(rhs))
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Until => {
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Until` must have boolean expression arguments");
};
match interval {
Some((interval, _)) => Ok(ctx.make_timed_until(interval, Box::new(lhs), Box::new(rhs)).into()),
None => Ok(ctx.make_until(Box::new(lhs), Box::new(rhs)).into()),
}
}
}
}
}
}

View file

@ -37,8 +37,8 @@ impl Type {
#[derive(Debug, Clone, PartialEq)]
pub struct Interval<'src> {
a: Box<Spanned<Expr<'src>>>,
b: Box<Spanned<Expr<'src>>>,
pub a: Option<Box<Spanned<Expr<'src>>>>,
pub b: Option<Box<Spanned<Expr<'src>>>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -134,38 +134,40 @@ impl<'src> Expr<'src> {
}
/// Make untyped (`Type::Unknown`) expressions into the given type.
/// Returns a boolean flag to denote successful transformation or not.
fn make_typed(&mut self, ty: Type) -> bool {
match self {
Expr::Var { name: _, kind } => {
*kind = ty;
true
}
_ => false,
fn make_typed(mut self, ty: Type) -> Self {
if let Expr::Var { name: _, kind } = &mut self {
*kind = ty;
}
self
}
fn unary_op(op: UnaryOps, arg: Box<Spanned<Self>>, interval: Option<Spanned<Interval<'src>>>) -> Self {
let mut arg = arg;
(*arg).0.make_typed(op.default_type());
fn unary_op(op: UnaryOps, arg: Spanned<Self>, interval: Option<Spanned<Interval<'src>>>) -> Self {
let arg = Box::new((arg.0.make_typed(op.default_type()), arg.1));
Self::Unary { op, interval, arg }
}
fn binary_op(
op: BinaryOps,
args: (Box<Spanned<Self>>, Box<Spanned<Self>>),
args: (Spanned<Self>, Spanned<Self>),
interval: Option<Spanned<Interval<'src>>>,
) -> Self {
let mut args = args;
let lhs = &mut (*args.0).0;
let rhs = &mut (*args.1).0;
let (lhs, lspan) = args.0;
let (rhs, rspan) = args.1;
let common_type = lhs.get_type().get_common_cast(rhs.get_type());
lhs.make_typed(common_type);
rhs.make_typed(common_type);
let common_type = if Type::Unknown == common_type {
op.default_type()
} else {
common_type
};
let lhs = Box::new((lhs.make_typed(common_type), lspan));
let rhs = Box::new((rhs.make_typed(common_type), rspan));
Self::Binary { op, interval, args }
Self::Binary {
op,
interval,
args: (lhs, rhs),
}
}
}
@ -179,7 +181,7 @@ pub type Error<'tokens, 'src> = extra::Err<Rich<'tokens, Token<'src>, Span>>;
// to understand.
type ParserInput<'tokens, 'src> = SpannedInput<Token<'src>, Span, &'tokens [(Token<'src>, Span)]>;
pub fn num_expr_parser<'tokens, 'src: 'tokens>(
fn num_expr_parser<'tokens, 'src: 'tokens>(
) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, Spanned<Expr<'src>>, Error<'tokens, 'src>> + Clone {
recursive(|num_expr| {
let var = select! { Token::Ident(name) => Expr::Var{ name, kind: Type::default()} }.labelled("variable");
@ -210,7 +212,7 @@ pub fn num_expr_parser<'tokens, 'src: 'tokens>(
.repeated()
.foldr(num_atom, |op, rhs| {
let span = op.1.start..rhs.1.end;
(Expr::unary_op(op.0, Box::new(rhs), None), span.into())
(Expr::unary_op(op.0, rhs, None), span.into())
});
// Product ops (multiply and divide) have equal precedence
@ -223,7 +225,7 @@ pub fn num_expr_parser<'tokens, 'src: 'tokens>(
.clone()
.foldl(op.then(neg_op).repeated(), |a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::binary_op(op, (Box::new(a), Box::new(b)), None), span.into())
(Expr::binary_op(op, (a, b), None), span.into())
})
.boxed()
};
@ -238,7 +240,7 @@ pub fn num_expr_parser<'tokens, 'src: 'tokens>(
.clone()
.foldl(op.then(product_op).repeated(), |a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::binary_op(op, (Box::new(a), Box::new(b)), None), span.into())
(Expr::binary_op(op, (a, b), None), span.into())
})
.boxed()
};
@ -251,7 +253,6 @@ pub fn parser<'tokens, 'src: 'tokens>(
) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, Spanned<Expr<'src>>, Error<'tokens, 'src>> + Clone {
let interval = {
let num_literal = select! {
Token::Int(val) => Expr::Int(val),
Token::UInt(val) => Expr::UInt(val),
Token::Float(val) => Expr::Float(val),
}
@ -259,17 +260,17 @@ pub fn parser<'tokens, 'src: 'tokens>(
let sep = just(Token::Comma).or(just(Token::DotDot));
num_literal
.or_not()
.then_ignore(sep)
.then(num_literal)
.then(num_literal.or_not())
.delimited_by(just(Token::LBracket), just(Token::RBracket))
.map(|(a, b)| {
let span = a.1.start..b.1.end;
.map_with_span(|(a, b), span| {
(
Interval {
a: Box::new(a),
b: Box::new(b),
a: a.map(Box::new),
b: b.map(Box::new),
},
span.into(),
span,
)
})
.boxed()
@ -300,7 +301,7 @@ pub fn parser<'tokens, 'src: 'tokens>(
.then(op.then(num_expr))
.map(|(a, (op, b))| {
let span = a.1.start..b.1.end;
(Expr::binary_op(op, (Box::new(a), Box::new(b)), None), span.into())
(Expr::binary_op(op, (a, b), None), span.into())
})
.boxed()
}
@ -324,7 +325,7 @@ pub fn parser<'tokens, 'src: 'tokens>(
.repeated()
.foldr(atom, |op, rhs| {
let span = op.1.start..rhs.1.end;
(Expr::unary_op(op.0, Box::new(rhs), None), span.into())
(Expr::unary_op(op.0, rhs, None), span.into())
})
.boxed();
@ -339,7 +340,7 @@ pub fn parser<'tokens, 'src: 'tokens>(
.repeated()
.foldr(not_op, |(op, interval), rhs| {
let span = op.1.start..rhs.1.end;
(Expr::unary_op(op.0, Box::new(rhs), interval), span.into())
(Expr::unary_op(op.0, rhs, interval), span.into())
})
.boxed()
};
@ -351,10 +352,7 @@ pub fn parser<'tokens, 'src: 'tokens>(
.foldr(unary_temporal_op, |(lhs, (op, interval)), rhs| {
let span = lhs.1.start..rhs.1.end;
assert_eq!(op, BinaryOps::Until);
(
Expr::binary_op(op, (Box::new(lhs), Box::new(rhs)), interval),
span.into(),
)
(Expr::binary_op(op, (lhs, rhs), interval), span.into())
})
.boxed();
@ -364,7 +362,7 @@ pub fn parser<'tokens, 'src: 'tokens>(
.clone()
.foldl(op.then(binary_temporal_op).repeated(), |a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::binary_op(op, (Box::new(a), Box::new(b)), None), span.into())
(Expr::binary_op(op, (a, b), None), span.into())
})
.boxed()
};
@ -375,7 +373,7 @@ pub fn parser<'tokens, 'src: 'tokens>(
.clone()
.foldl(op.then(and_op).repeated(), |a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::binary_op(op, (Box::new(a), Box::new(b)), None), span.into())
(Expr::binary_op(op, (a, b), None), span.into())
})
.boxed()
};
@ -386,7 +384,7 @@ pub fn parser<'tokens, 'src: 'tokens>(
.clone()
.foldl(op.then(or_op).repeated(), |a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::binary_op(op, (Box::new(a), Box::new(b)), None), span.into())
(Expr::binary_op(op, (a, b), None), span.into())
})
.boxed()
};
@ -399,11 +397,14 @@ pub fn parser<'tokens, 'src: 'tokens>(
.clone()
.foldl(op.then(xor_op).repeated(), |a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::binary_op(op, (Box::new(a), Box::new(b)), None), span.into())
(Expr::binary_op(op, (a, b), None), span.into())
})
.boxed()
};
implies_equiv_op.labelled("boolean expression").as_context()
implies_equiv_op
.map(|(expr, span)| (expr.make_typed(Type::Bool), span))
.labelled("boolean expression")
.as_context()
})
}

1
argus-parser/src/util.rs Normal file
View file

@ -0,0 +1 @@