use crate::ast::{BinaryOperator, Expression, Node, UnaryOperator};
use crate::parser::lex::{name, qualified_name, ws_and_comments};
use crate::parser::node_from_to;
use crate::parser::Input;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::map;
use nom::sequence::{delimited, preceded};
use nom::IResult;
use nom::Parser;
fn numeric_literal_text(input: Input<'_>) -> IResult<Input<'_>, String> {
let (input, _) = ws_and_comments(input)?;
let frag = input.fragment();
let mut i = 0usize;
if matches!(frag.first(), Some(b'+' | b'-')) {
i += 1;
}
let digit_start = i;
while i < frag.len() && frag[i].is_ascii_digit() {
i += 1;
}
if i == digit_start {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Digit,
)));
}
if i < frag.len() && frag[i] == b'.' {
i += 1;
while i < frag.len() && frag[i].is_ascii_digit() {
i += 1;
}
}
if i < frag.len() && matches!(frag[i], b'e' | b'E') {
i += 1;
if i < frag.len() && matches!(frag[i], b'+' | b'-') {
i += 1;
}
let exp_start = i;
while i < frag.len() && frag[i].is_ascii_digit() {
i += 1;
}
if i == exp_start {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Digit,
)));
}
}
let text = String::from_utf8_lossy(&frag[..i]).to_string();
let (input, _) = nom::bytes::complete::take(i).parse(input)?;
Ok((input, text))
}
fn classify_numeric_literal(text: &str) -> Expression {
let normalized = text.trim();
if normalized.contains('.') || normalized.chars().skip(1).any(|c| c == 'e' || c == 'E') {
Expression::LiteralReal(normalized.to_string())
} else {
Expression::LiteralInteger(normalized.parse().unwrap_or(0))
}
}
fn literal_integer(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, text) = numeric_literal_text(input)?;
if text.contains('.') || text.chars().skip(1).any(|c| c == 'e' || c == 'E') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Digit,
)));
}
Ok((
input,
node_from_to(start, input, classify_numeric_literal(&text)),
))
}
fn literal_real(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, text) = numeric_literal_text(input)?;
if !text.contains('.') && !text.chars().skip(1).any(|c| c == 'e' || c == 'E') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Digit,
)));
}
Ok((
input,
node_from_to(start, input, classify_numeric_literal(&text)),
))
}
fn literal_string(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, _) = tag(&b"\""[..]).parse(input)?;
let frag = input.fragment();
let mut i = 0;
while i < frag.len() {
if frag[i] == b'\\' && i + 1 < frag.len() {
i += 2;
continue;
}
if frag[i] == b'"' {
let s = String::from_utf8_lossy(&frag[..i]).replace("\\\"", "\"");
let (input, _) = nom::bytes::complete::take(i + 1).parse(input)?;
return Ok((
input,
node_from_to(start, input, Expression::LiteralString(s)),
));
}
i += 1;
}
let s = String::from_utf8_lossy(frag).replace("\\\"", "\"");
let (input, _) = nom::bytes::complete::take(frag.len()).parse(input)?;
Ok((
input,
node_from_to(start, input, Expression::LiteralString(s)),
))
}
fn literal_boolean(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, v) = alt((
map(tag(&b"true"[..]), |_| true),
map(tag(&b"false"[..]), |_| false),
))
.parse(input)?;
Ok((
input,
node_from_to(start, input, Expression::LiteralBoolean(v)),
))
}
fn feature_ref_primary(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, n) = qualified_name(input)?;
Ok((input, node_from_to(start, input, Expression::FeatureRef(n))))
}
fn metadata_ref_primary(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, _) = tag(&b"@"[..]).parse(input)?;
let (input, _) = ws_and_comments(input)?;
let (input, n) = qualified_name(input)?;
Ok((
input,
node_from_to(start, input, Expression::FeatureRef(format!("@{}", n))),
))
}
fn constructor_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, _) = tag(&b"new"[..]).parse(input)?;
let (input, _) = ws_and_comments(input)?;
let (input, type_name) = qualified_name(input)?;
let current = node_from_to(
start,
input,
Expression::FeatureRef(format!("new {type_name}")),
);
postfix(input, start, current)
}
fn literal_only(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let (input, _) = ws_and_comments(input)?;
alt((
literal_boolean,
literal_real,
literal_integer,
literal_string,
))
.parse(input)
}
fn quoted_unit_string(input: Input<'_>) -> IResult<Input<'_>, String> {
let quote = *input.fragment().first().ok_or_else(|| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Tag))
})?;
if quote != b'\'' && quote != b'"' {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let (input, _) = nom::bytes::complete::take(1usize).parse(input)?;
let frag = input.fragment();
let mut i = 0usize;
while i < frag.len() {
if frag[i] == quote {
let s = String::from_utf8_lossy(&frag[..i]).to_string();
let (input, _) = nom::bytes::complete::take(i + 1).parse(input)?;
return Ok((input, s));
}
if frag[i] == b'\\' && i + 1 < frag.len() {
i += 2;
continue;
}
i += 1;
}
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)))
}
fn unit_name_in_brackets(input: Input<'_>) -> IResult<Input<'_>, String> {
let (input, _) = ws_and_comments(input)?;
if matches!(input.fragment().first(), Some(b'"' | b'\'')) {
return quoted_unit_string(input);
}
let frag = input.fragment();
let mut i = 0usize;
while i < frag.len() {
let c = frag[i];
if c == b']' {
break;
}
if c.is_ascii_whitespace() {
break;
}
if c.is_ascii_alphanumeric() || matches!(c, b'_' | b'/' | b'-' | b'^' | b'.' | b'*' | b':')
{
i += 1;
continue;
}
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::AlphaNumeric,
)));
}
if i == 0 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::AlphaNumeric,
)));
}
let s = String::from_utf8_lossy(&frag[..i]).trim().to_string();
let (input, _) = nom::bytes::complete::take(i).parse(input)?;
Ok((input, s))
}
fn literal_with_unit(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, value_node) = literal_only(input)?;
let (input, _) = ws_and_comments(input)?;
if !input.fragment().starts_with(b"[") {
return Ok((input, value_node));
}
let (input, _) = tag(&b"["[..]).parse(input)?;
let (input, _) = ws_and_comments(input)?;
let (input, unit_name) = unit_name_in_brackets.parse(input)?;
let (input, _) = ws_and_comments(input)?;
let (input, _) = tag(&b"]"[..]).parse(input)?;
let unit = Node::new(
crate::ast::Span::dummy(),
Expression::Bracket(Box::new(Node::new(
crate::ast::Span::dummy(),
Expression::FeatureRef(unit_name),
))),
);
let expr = Expression::LiteralWithUnit {
value: Box::new(value_node),
unit: Box::new(unit),
};
Ok((input, node_from_to(start, input, expr)))
}
fn parenthesized(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, _) = tag(&b"("[..]).parse(input)?;
let (input, _) = ws_and_comments(input)?;
let (input, first) = expression(input)?;
let (input, _) = ws_and_comments(input)?;
if input.fragment().starts_with(b")") {
let (input, _) = tag(&b")"[..]).parse(input)?;
return Ok((input, node_from_to(start, input, first.value)));
}
let (input, _) = tag(&b","[..]).parse(input)?;
let mut elements = vec![first];
let mut input = input;
loop {
let (next, _) = ws_and_comments(input)?;
if next.fragment().starts_with(b")") {
let (input, _) = tag(&b")"[..]).parse(next)?;
return Ok((
input,
node_from_to(start, input, Expression::Tuple(elements)),
));
}
let (next, expr) = expression(next)?;
elements.push(expr);
let (next, _) = ws_and_comments(next)?;
if next.fragment().starts_with(b")") {
let (input, _) = tag(&b")"[..]).parse(next)?;
return Ok((
input,
node_from_to(start, input, Expression::Tuple(elements)),
));
}
if next.fragment().starts_with(b",") {
let (next, _) = tag(&b","[..]).parse(next)?;
input = next;
continue;
}
return Err(nom::Err::Error(nom::error::Error::new(
next,
nom::error::ErrorKind::Tag,
)));
}
}
fn null_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, _) = alt((
map(tag(&b"null"[..]), |_| ()),
map(
delimited(tag(&b"("[..]), ws_and_comments, tag(&b")"[..])),
|_| (),
),
))
.parse(input)?;
Ok((input, node_from_to(start, input, Expression::Null)))
}
fn select_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, base) = feature_ref_primary(input)?;
let (input, _) = tag(&b".?"[..]).parse(input)?;
let (input, selector) = preceded(ws_and_comments, name).parse(input)?;
Ok((
input,
node_from_to(
start,
input,
Expression::MemberAccess(Box::new(base), format!("?{selector}")),
),
))
}
fn collect_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, base) = feature_ref_primary(input)?;
let (input, _) = tag(&b".**"[..]).parse(input)?;
let (input, selector) = preceded(ws_and_comments, name).parse(input)?;
Ok((
input,
node_from_to(
start,
input,
Expression::MemberAccess(Box::new(base), format!("**{selector}")),
),
))
}
fn sequence_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, _) = tag(&b"("[..]).parse(input)?;
let (input, first) = preceded(ws_and_comments, expression).parse(input)?;
let (input, rest) = nom::multi::many0(preceded(
preceded(ws_and_comments, tag(&b","[..])),
preceded(ws_and_comments, expression),
))
.parse(input)?;
let (input, _) = preceded(ws_and_comments, tag(&b")"[..])).parse(input)?;
let mut args = vec![first];
args.extend(rest);
Ok((
input,
node_from_to(
start,
input,
Expression::Invocation {
callee: Box::new(node_from_to(start, start, Expression::Null)),
args,
},
),
))
}
fn primary(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let (input, _) = ws_and_comments(input)?;
alt((
literal_with_unit,
literal_only,
null_expression,
metadata_ref_primary,
constructor_expression,
collect_expression,
select_expression,
feature_ref_primary,
parenthesized,
sequence_expression,
))
.parse(input)
}
fn postfix<'a>(
input: Input<'a>,
start: Input<'a>,
current: Node<Expression>,
) -> IResult<Input<'a>, Node<Expression>> {
let (input, _) = ws_and_comments(input)?;
if input.fragment().starts_with(b"(") {
let (input, _) = tag(&b"("[..]).parse(input)?;
let (mut input, _) = ws_and_comments(input)?;
let mut args = Vec::new();
if input.fragment().starts_with(b")") {
let (next, _) = tag(&b")"[..]).parse(input)?;
let expr = Expression::Invocation {
callee: Box::new(current),
args,
};
return postfix(next, start, node_from_to(start, next, expr));
}
loop {
let (next, arg) = expression(input)?;
args.push(arg);
let (next, _) = ws_and_comments(next)?;
if next.fragment().starts_with(b")") {
let (next, _) = tag(&b")"[..]).parse(next)?;
let expr = Expression::Invocation {
callee: Box::new(current),
args,
};
return postfix(next, start, node_from_to(start, next, expr));
}
let (next, _) = tag(&b","[..]).parse(next)?;
let (next, _) = ws_and_comments(next)?;
input = next;
}
}
if input.fragment().starts_with(b"#") {
let (input, _) = tag(&b"#"[..]).parse(input)?;
let (input, _) = preceded(ws_and_comments, tag(&b"("[..])).parse(input)?;
let (input, index_node) = preceded(ws_and_comments, expression).parse(input)?;
let (input, _) = preceded(ws_and_comments, tag(&b")"[..])).parse(input)?;
let expr = Expression::Index {
base: Box::new(current),
index: Box::new(index_node),
};
return postfix(input, start, node_from_to(start, input, expr));
}
if input.fragment().starts_with(b"::") {
let (input, _) = tag(&b"::"[..]).parse(input)?;
let (input, _) = ws_and_comments(input)?;
let (input, member) = name(input)?;
let expr = Expression::MemberAccess(Box::new(current), member);
return postfix(input, start, node_from_to(start, input, expr));
}
if input.fragment().starts_with(b".") {
let (input, _) = tag(&b"."[..]).parse(input)?;
let (input, _) = ws_and_comments(input)?;
let (input, member) = name(input)?;
let expr = Expression::MemberAccess(Box::new(current), member);
return postfix(input, start, node_from_to(start, input, expr));
}
Ok((input, current))
}
fn logical_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
let (input, _) = ws_and_comments(input)?;
alt((
map(tag(&b"and"[..]), |_| "&&".to_string()),
map(tag(&b"or"[..]), |_| "||".to_string()),
map(tag(&b"xor"[..]), |_| "xor".to_string()),
map(tag(&b"&&"[..]), |_| "&&".to_string()),
map(tag(&b"||"[..]), |_| "||".to_string()),
))
.parse(input)
}
fn implies_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
preceded(ws_and_comments, tag(&b"implies"[..]))
.map(|_| "implies".to_string())
.parse(input)
}
fn equality_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
let (input, _) = ws_and_comments(input)?;
alt((
map(tag(&b"==="[..]), |_| "===".to_string()),
map(tag(&b"!=="[..]), |_| "!==".to_string()),
map(tag(&b"=="[..]), |_| "==".to_string()),
map(tag(&b"!="[..]), |_| "!=".to_string()),
))
.parse(input)
}
fn comparison_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
let (input, _) = ws_and_comments(input)?;
alt((
map(tag(&b">="[..]), |_| ">=".to_string()),
map(tag(&b"<="[..]), |_| "<=".to_string()),
map(tag(&b">"[..]), |_| ">".to_string()),
map(tag(&b"<"[..]), |_| "<".to_string()),
map(tag(&b".."[..]), |_| "..".to_string()),
))
.parse(input)
}
fn additive_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
let (input, _) = ws_and_comments(input)?;
alt((
map(tag(&b"+"[..]), |_| "+".to_string()),
map(tag(&b"-"[..]), |_| "-".to_string()),
map(tag(&b"|"[..]), |_| "|".to_string()),
map(tag(&b"&"[..]), |_| "&".to_string()),
))
.parse(input)
}
fn multiplicative_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
let (input, _) = ws_and_comments(input)?;
alt((
map(tag(&b"**"[..]), |_| "**".to_string()),
map(tag(&b"*"[..]), |_| "*".to_string()),
map(tag(&b"/"[..]), |_| "/".to_string()),
map(tag(&b"%"[..]), |_| "%".to_string()),
map(tag(&b"^"[..]), |_| "^".to_string()),
))
.parse(input)
}
fn binary_chain_with<'a, P, N>(
mut input: Input<'a>,
start: Input<'a>,
mut left: Node<Expression>,
mut op_parser: P,
mut next_parser: N,
) -> IResult<Input<'a>, Node<Expression>>
where
P: Parser<Input<'a>, Output = String, Error = nom::error::Error<Input<'a>>>,
N: Parser<Input<'a>, Output = Node<Expression>, Error = nom::error::Error<Input<'a>>>,
{
loop {
let Ok((next_input, op)) = op_parser.parse(input) else {
return Ok((input, left));
};
let (next_input, right) = next_parser.parse(next_input)?;
left = node_from_to(
start,
next_input,
Expression::BinaryOp {
op: BinaryOperator::from_token(&op),
left: Box::new(left),
right: Box::new(right),
},
);
input = next_input;
}
}
fn unary_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
let (input, _) = ws_and_comments(input)?;
alt((
map(tag(&b"not"[..]), |_| "not".to_string()),
map(tag(&b"~"[..]), |_| "~".to_string()),
map(tag(&b"+"[..]), |_| "+".to_string()),
map(tag(&b"-"[..]), |_| "-".to_string()),
))
.parse(input)
}
fn unary_and_primary(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, prefixes) = nom::multi::many0(unary_op_token).parse(input)?;
let primary_start = input;
let (input, primary_node) = primary(input)?;
let (input, after_postfix) = postfix(input, primary_start, primary_node)?;
let mut expr = after_postfix;
for op in prefixes.into_iter().rev() {
expr = node_from_to(
start,
input,
Expression::UnaryOp {
op: UnaryOperator::from_token(&op),
operand: Box::new(expr),
},
);
}
Ok((input, expr))
}
fn multiplicative_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, left) = unary_and_primary(input)?;
binary_chain_with(
input,
start,
left,
multiplicative_op_token,
unary_and_primary,
)
}
fn additive_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, left) = multiplicative_expression(input)?;
binary_chain_with(
input,
start,
left,
additive_op_token,
multiplicative_expression,
)
}
fn comparison_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, left) = additive_expression(input)?;
binary_chain_with(input, start, left, comparison_op_token, additive_expression)
}
fn equality_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, left) = comparison_expression(input)?;
binary_chain_with(input, start, left, equality_op_token, comparison_expression)
}
fn logical_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, left) = equality_expression(input)?;
binary_chain_with(input, start, left, logical_op_token, equality_expression)
}
pub(crate) fn expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, left) = logical_expression(input)?;
binary_chain_with(input, start, left, implies_op_token, logical_expression)
}
pub(crate) fn path_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, first) = crate::parser::lex::qualified_name(input)?;
let mut expr = Expression::FeatureRef(first);
let mut rest = input;
loop {
let (next, _) = ws_and_comments(rest)?;
if !next.fragment().starts_with(b".") {
break;
}
let (next, _) = tag(&b"."[..]).parse(next)?;
let (next, _) = ws_and_comments(next)?;
let (next, member) = name(next)?;
expr =
Expression::MemberAccess(Box::new(Node::new(crate::ast::Span::dummy(), expr)), member);
rest = next;
}
Ok((rest, node_from_to(start, rest, expr)))
}
#[cfg(test)]
mod tests {
use super::*;
use nom_locate::LocatedSpan;
fn span_input(text: &str) -> Input<'_> {
LocatedSpan::new(text.as_bytes())
}
#[test]
fn expression_parses_implies_lower_than_or() {
let input = span_input("a or b implies c");
let (_, node) = expression(input).expect("expression");
match &node.value {
Expression::BinaryOp { op, left, right } => {
assert_eq!(op, &BinaryOperator::Implies);
match &left.value {
Expression::BinaryOp { op, .. } => assert_eq!(op, &BinaryOperator::Or),
other => panic!("expected or on lhs, got {other:?}"),
}
assert!(matches!(&right.value, Expression::FeatureRef(s) if s == "c"));
}
other => panic!("expected implies, got {other:?}"),
}
}
}