use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{none_of, one_of},
combinator::{cut, map, opt},
multi::{count, many0, many1, many_m_n},
sequence::{delimited, pair, preceded, terminated},
IResult, Parser,
};
use std::fmt;
use super::{ws0, ParseResult, SyntaxError, SyntaxErrorKind};
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Expression {
Number(String),
BigInt(String),
String(StringLiteral),
Identifier(String),
UnaryOperation {
operator: UnaryOperator,
operand: Box<Expression>,
},
BinaryOperation {
operator: BinaryOperator,
left: Box<Expression>,
right: Box<Expression>,
},
}
impl fmt::Display for Expression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Number(number) => write!(f, "{}", number),
Self::BigInt(bigint) => write!(f, "{}n", bigint),
Self::String(string) => write!(f, "\"{}\"", string.value),
Self::Identifier(name) => write!(f, "{}", name),
Self::UnaryOperation { operator, operand } => match operator {
UnaryOperator::Try => write!(f, "({}?)", operand),
_ => write!(f, "({}{})", operator, operand),
},
Self::BinaryOperation {
operator,
left,
right,
} => write!(f, "({} {} {})", left, operator, right),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct StringLiteral {
pub value: String,
pub interpolations: Vec<(usize, Box<Expression>)>,
}
#[derive(Debug, PartialEq, Clone)]
enum StringPart {
Literal(String),
Interpolation(Box<Expression>),
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum UnaryOperator {
Negate,
LogicalNot,
BitwiseNot,
UnaryPlus,
UnaryMinus,
Try,
}
impl fmt::Display for UnaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Negate => write!(f, "!"),
Self::LogicalNot => write!(f, "!"),
Self::BitwiseNot => write!(f, "~"),
Self::UnaryPlus => write!(f, "+"),
Self::UnaryMinus => write!(f, "-"),
Self::Try => write!(f, "?"),
}
}
}
impl std::str::FromStr for UnaryOperator {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"!" => Ok(Self::Negate),
"~" => Ok(Self::BitwiseNot),
"+" => Ok(Self::UnaryPlus),
"-" => Ok(Self::UnaryMinus),
"?" => Ok(Self::Try),
_ => Err(()),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Associativity {
LeftToRight,
RightToLeft,
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum BinaryOperator {
Exponent,
Multiply,
Divide,
Modulo,
Add,
Subtract,
LeftShift,
RightShift,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Equal,
NotEqual,
BitwiseAnd,
BitwiseXor,
BitwiseOr,
LogicalAnd,
LogicalOr,
Pipe,
}
impl BinaryOperator {
pub fn precedence(&self) -> u8 {
match self {
Self::Exponent => 13,
Self::Multiply | Self::Divide | Self::Modulo => 12,
Self::Add | Self::Subtract => 11,
Self::LeftShift | Self::RightShift => 10,
Self::LessThan
| Self::LessThanOrEqual
| Self::GreaterThan
| Self::GreaterThanOrEqual => 9,
Self::Equal | Self::NotEqual => 8,
Self::BitwiseAnd => 7,
Self::BitwiseXor => 6,
Self::BitwiseOr => 5,
Self::LogicalAnd => 4,
Self::LogicalOr => 3,
Self::Pipe => 2,
}
}
pub fn associativity(&self) -> Associativity {
match self {
Self::Exponent => Associativity::RightToLeft,
Self::Multiply
| Self::Divide
| Self::Modulo
| Self::Add
| Self::Subtract
| Self::LeftShift
| Self::RightShift
| Self::LessThan
| Self::LessThanOrEqual
| Self::GreaterThan
| Self::GreaterThanOrEqual
| Self::Equal
| Self::NotEqual
| Self::BitwiseAnd
| Self::BitwiseXor
| Self::BitwiseOr
| Self::LogicalAnd
| Self::LogicalOr
| Self::Pipe => Associativity::LeftToRight,
}
}
}
impl std::str::FromStr for BinaryOperator {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"**" => Ok(Self::Exponent),
"*" => Ok(Self::Multiply),
"/" => Ok(Self::Divide),
"%" => Ok(Self::Modulo),
"+" => Ok(Self::Add),
"-" => Ok(Self::Subtract),
"<<" => Ok(Self::LeftShift),
">>" => Ok(Self::RightShift),
"<" => Ok(Self::LessThan),
"<=" => Ok(Self::LessThanOrEqual),
">" => Ok(Self::GreaterThan),
">=" => Ok(Self::GreaterThanOrEqual),
"==" => Ok(Self::Equal),
"!=" => Ok(Self::NotEqual),
"&" => Ok(Self::BitwiseAnd),
"^" => Ok(Self::BitwiseXor),
"|" => Ok(Self::BitwiseOr),
"&&" => Ok(Self::LogicalAnd),
"||" => Ok(Self::LogicalOr),
"|>" => Ok(Self::Pipe),
_ => Err(()),
}
}
}
impl fmt::Display for BinaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Exponent => write!(f, "**"),
Self::Multiply => write!(f, "*"),
Self::Divide => write!(f, "/"),
Self::Modulo => write!(f, "%"),
Self::Add => write!(f, "+"),
Self::Subtract => write!(f, "-"),
Self::LeftShift => write!(f, "<<"),
Self::RightShift => write!(f, ">>"),
Self::LessThan => write!(f, "<"),
Self::LessThanOrEqual => write!(f, "<="),
Self::GreaterThan => write!(f, ">"),
Self::GreaterThanOrEqual => write!(f, ">="),
Self::Equal => write!(f, "=="),
Self::NotEqual => write!(f, "!="),
Self::BitwiseAnd => write!(f, "&"),
Self::BitwiseXor => write!(f, "^"),
Self::BitwiseOr => write!(f, "|"),
Self::LogicalAnd => write!(f, "&&"),
Self::LogicalOr => write!(f, "||"),
Self::Pipe => write!(f, "|>"),
}
}
}
fn non_zero_digit(input: &str) -> ParseResult<char> {
one_of("123456789")(input)
}
fn digit(input: &str) -> ParseResult<char> {
one_of("0123456789")(input)
}
fn hex_digit(input: &str) -> ParseResult<char> {
let result: IResult<&str, char> = one_of("0123456789abcdefABCDEF").parse(input);
match result {
Ok((input, c)) => Ok((input, c)),
Err(_) => Err(nom::Err::Error(SyntaxError {
input,
error: SyntaxErrorKind::InvalidHexDigit,
})),
}
}
fn delimited_digit(input: &str) -> ParseResult<char> {
if input.starts_with("__") {
return Err(nom::Err::Failure(SyntaxError {
input,
error: SyntaxErrorKind::ConsecutiveUnderscoreInNumericLiteral,
}));
}
alt((digit, preceded(tag("_"), digit))).parse(input)
}
fn whole_number(input: &str) -> ParseResult<String> {
if input.starts_with('_') {
return Err(nom::Err::Failure(SyntaxError {
input,
error: SyntaxErrorKind::NumericLiteralStartsWithUnderscore,
}));
} else if input.is_empty() {
return Err(nom::Err::Failure(SyntaxError {
input,
error: SyntaxErrorKind::EmptyInput,
}));
}
let (input, start) = non_zero_digit(input)?;
let (input, digits) = many0(delimited_digit).parse(input)?;
let number = std::iter::once(start).chain(digits).collect::<String>();
Ok((input, number))
}
fn decimal_number(input: &str) -> ParseResult<String> {
let (input, mut number) = whole_number(input)?;
let (input, decimal) = opt(preceded(tag("."), many1(delimited_digit))).parse(input)?;
if input.starts_with('_') {
return Err(nom::Err::Failure(SyntaxError {
input,
error: SyntaxErrorKind::NumericLiteralEndsWithUnderscore,
}));
}
if let Some(decimal) = decimal {
number.extend(std::iter::once('.').chain(decimal));
}
Ok((input, number))
}
fn scientific_notation(input: &str) -> ParseResult<String> {
let (input, mut number) = decimal_number(input)?;
let (input, sign) = preceded(alt((tag("e"), tag("E"))), opt(one_of("+-"))).parse(input)?;
let (input, exponent) = many1(delimited_digit).parse(input)?;
if input.starts_with('_') {
return Err(nom::Err::Failure(SyntaxError {
input,
error: SyntaxErrorKind::NumericLiteralEndsWithUnderscore,
}));
}
match sign {
Some('-') => number.push_str("e-"),
Some('+') => number.push('e'),
None => number.push('e'),
_ => unreachable!(),
};
number.extend(exponent);
Ok((input, number))
}
fn parse_number(input: &str) -> ParseResult<Expression> {
map(alt((scientific_notation, decimal_number)), |number| {
Expression::Number(number)
})
.parse(input)
}
fn parse_bigint(input: &str) -> ParseResult<Expression> {
if input.starts_with('n') {
return Err(nom::Err::Failure(SyntaxError {
input,
error: SyntaxErrorKind::EmptyBigIntLiteral,
}));
}
map(
terminated(alt((scientific_notation, whole_number)), tag("n")),
Expression::BigInt,
)
.parse(input)
}
fn parse_escaped_char(input: &str) -> ParseResult<char> {
let (input, result) = alt((
tag("\\\\"), tag("\\\""), tag("\\n"), tag("\\r"), tag("\\t"), tag("\\0"), tag("{{"), tag("}}"), ))
.parse(input)?;
match result {
"\\\\" => Ok((input, '\\')),
"\\\"" => Ok((input, '"')),
"\\n" => Ok((input, '\n')),
"\\r" => Ok((input, '\r')),
"\\t" => Ok((input, '\t')),
"\\0" => Ok((input, '\0')),
"{{" => Ok((input, '{')),
"}}" => Ok((input, '}')),
_ => unreachable!(),
}
}
fn parse_escaped_code_point(input: &str) -> ParseResult<char> {
let (input, value) = alt((
preceded(
tag("\\u"),
cut(delimited(tag("{"), many_m_n(2, 6, hex_digit), tag("}"))),
),
preceded(tag("\\x"), cut(count(hex_digit, 2))),
))
.parse(input)?;
let value = value.iter().collect::<String>();
let code_point = u32::from_str_radix(&value, 16).unwrap();
match std::char::from_u32(code_point) {
Some(c) => Ok((input, c)),
None => Err(nom::Err::Failure(SyntaxError {
input,
error: SyntaxErrorKind::InvalidUnicodeCodePoint(code_point),
})),
}
}
fn parse_regular_char(input: &str) -> ParseResult<char> {
none_of("\\\"{}").parse(input)
}
fn parse_interpolation(input: &str) -> ParseResult<StringPart> {
map(
delimited(tag("{"), alt((parse_number, parse_bigint)), tag("}")),
|inter| StringPart::Interpolation(inter.into()),
)
.parse(input)
}
fn parse_string(input: &str) -> ParseResult<Expression> {
let (input, parsed) = delimited(
tag("\""),
many0(alt((
map(
many1(alt((
parse_regular_char,
parse_escaped_char,
parse_escaped_code_point,
))),
|f| StringPart::Literal(f.iter().collect()),
),
parse_interpolation,
))),
tag("\""),
)
.parse(input)?;
let mut string = StringLiteral {
value: String::new(),
interpolations: vec![],
};
for part in parsed {
match part {
StringPart::Literal(literal) => string.value.push_str(&literal),
StringPart::Interpolation(interpolation) => {
string
.interpolations
.push((string.value.len(), interpolation));
}
}
}
Ok((input, Expression::String(string)))
}
const ALPHA_WITH_UNDERSCORE: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
const ALPHANUMERIC_WITH_UNDERSCORE: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
pub(crate) fn parse_identifier(input: &str) -> ParseResult<String> {
map(
pair(
one_of(ALPHA_WITH_UNDERSCORE),
many0(one_of(ALPHANUMERIC_WITH_UNDERSCORE)),
),
|(first, rest)| std::iter::once(first).chain(rest).collect(),
)
.parse(input)
}
pub(crate) fn parse_binary_operator(input: &str) -> ParseResult<BinaryOperator> {
map(
ws0(alt((
tag("|>"),
tag("**"),
tag("*"),
tag("/"),
tag("%"),
tag("+"),
tag("-"),
tag("<<"),
tag(">>"),
tag("<="),
tag("<"),
tag(">="),
tag(">"),
tag("=="),
tag("!="),
tag("&&"),
tag("||"),
tag("&"),
tag("^"),
tag("|"),
))),
|op| op.parse().unwrap(),
)
.parse(input)
}
pub(crate) fn parse_unary_operator(input: &str) -> ParseResult<UnaryOperator> {
map(ws0(alt((tag("!"), tag("~"), tag("+"), tag("-")))), |op| {
op.parse().unwrap()
})
.parse(input)
}
pub(crate) fn parse_term(input: &str) -> ParseResult<Expression> {
map(
(
opt(parse_unary_operator),
alt((
map(parse_identifier, Expression::Identifier),
parse_string,
parse_bigint,
parse_number,
)),
opt(tag("?")),
),
|(prefix, expr, postfix)| {
let mut expression = expr;
if postfix.is_some() {
expression = Expression::UnaryOperation {
operator: UnaryOperator::Try,
operand: Box::new(expression),
};
}
if let Some(op) = prefix {
expression = Expression::UnaryOperation {
operator: op,
operand: Box::new(expression),
};
}
expression
},
)
.parse(input)
}
pub(crate) fn parse_expression(input: &str) -> ParseResult<Expression> {
let (input, left) = parse_term(input)?;
let mut output: Vec<Expression> = vec![left];
let mut operators = Vec::<BinaryOperator>::new();
let mut remaining = input;
loop {
let (input, opt_right) =
opt(pair(ws0(parse_binary_operator), ws0(parse_term))).parse(remaining)?;
remaining = input;
let (op, right) = match opt_right {
Some((op, right)) => (op, right),
None => break,
};
while let Some(&top) = operators.last() {
if top.precedence() > op.precedence()
|| (top.precedence() == op.precedence()
&& op.associativity() == Associativity::LeftToRight)
{
let right = output.pop().unwrap();
let left = output.pop().unwrap();
output.push(Expression::BinaryOperation {
operator: operators.pop().unwrap(),
left: Box::new(left),
right: Box::new(right),
});
} else {
break;
}
}
output.push(right);
operators.push(op);
}
while let Some(op) = operators.pop() {
let right = output.pop().unwrap();
let left = output.pop().unwrap();
output.push(Expression::BinaryOperation {
operator: op,
left: Box::new(left),
right: Box::new(right),
});
}
Ok((remaining, output.pop().unwrap()))
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! test_expression {
($name:ident, $input:expr, $expected:expr) => {
#[test]
fn $name() {
let (rest, result) = parse_expression($input).unwrap();
assert_eq!(rest, "");
assert_eq!(result, $expected);
}
};
}
macro_rules! test_error_kind {
($parser:ident, $name:ident, $input:expr, $kind:pat) => {
#[test]
fn $name() {
let result = $parser($input);
assert!(result.is_err());
let err = result.unwrap_err();
match err {
nom::Err::Failure(SyntaxError { error, .. }) => assert!(matches!(error, $kind)),
_ => panic!("Unexpected error: {:?}", err),
}
}
};
($name:ident, $input:expr, $kind:pat) => {
test_error_kind!(parse_expression, $name, $input, $kind);
};
}
test_expression!(number_simple, "42", Expression::Number("42".into()));
test_expression!(number_pi, "3.14159", Expression::Number("3.14159".into()));
test_expression!(
number_pi_underscore,
"3.141_59",
Expression::Number("3.14159".into())
);
test_expression!(
number_billion,
"1000000000",
Expression::Number("1000000000".into())
);
test_expression!(
number_billion_with_underline,
"1_000_000_000",
Expression::Number("1000000000".into())
);
test_error_kind!(parse_number, number_empty, "", SyntaxErrorKind::EmptyInput);
test_error_kind!(
parse_number,
number_underscore_at_start,
"_42",
SyntaxErrorKind::NumericLiteralStartsWithUnderscore
);
test_error_kind!(
parse_number,
number_underscore_at_end,
"42_",
SyntaxErrorKind::NumericLiteralEndsWithUnderscore
);
test_error_kind!(
parse_number,
number_decimal_underscore_at_end,
"42.0_",
SyntaxErrorKind::NumericLiteralEndsWithUnderscore
);
test_error_kind!(
parse_number,
number_exponent_underscore_at_end,
"42e10_",
SyntaxErrorKind::NumericLiteralEndsWithUnderscore
);
test_error_kind!(
parse_number,
number_consecutive_underscores,
"1__000",
SyntaxErrorKind::ConsecutiveUnderscoreInNumericLiteral
);
test_expression!(
number_scientific_notation,
"1e6",
Expression::Number("1e6".into())
);
test_expression!(
number_scientific_notation_uppercase,
"1E6",
Expression::Number("1e6".into())
);
test_expression!(
number_scientific_notation_positive,
"1e+6",
Expression::Number("1e6".into())
);
test_expression!(
number_scientific_notation_negative,
"1e-6",
Expression::Number("1e-6".into())
);
test_expression!(
number_scientific_notation_decimal,
"1.23e4",
Expression::Number("1.23e4".into())
);
test_expression!(
number_scientific_notation_decimal_positive,
"1.23e+4",
Expression::Number("1.23e4".into())
);
test_expression!(
number_scientific_notation_decimal_negative,
"1.23e-4",
Expression::Number("1.23e-4".into())
);
test_expression!(bigint_simple, "42n", Expression::BigInt("42".into()));
test_expression!(
bigint_billion,
"1000000000n",
Expression::BigInt("1000000000".into())
);
test_expression!(
bigint_billion_with_underline,
"1_000_000_000n",
Expression::BigInt("1000000000".into())
);
test_expression!(
bigint_scientific_notation,
"1e6n",
Expression::BigInt("1e6".into())
);
test_expression!(
bigint_scientific_notation_uppercase,
"1E6n",
Expression::BigInt("1e6".into())
);
test_expression!(
bigint_scientific_notation_positive,
"1e+6n",
Expression::BigInt("1e6".into())
);
test_expression!(
bigint_scientific_notation_negative,
"100e-2n",
Expression::BigInt("100e-2".into())
);
test_expression!(
bigint_scientific_notation_decimal,
"1.23e4n",
Expression::BigInt("1.23e4".into())
);
test_error_kind!(
parse_bigint,
bigint_empty,
"n",
SyntaxErrorKind::EmptyBigIntLiteral
);
test_error_kind!(
parse_bigint,
bigint_underscore_at_end,
"42_n",
SyntaxErrorKind::NumericLiteralEndsWithUnderscore
);
test_error_kind!(
parse_bigint,
bigint_consecutive_underscores,
"1__000n",
SyntaxErrorKind::ConsecutiveUnderscoreInNumericLiteral
);
test_expression!(
string_empty,
r#""""#,
Expression::String(StringLiteral {
value: "".into(),
interpolations: vec![]
})
);
test_expression!(
string_simple,
r#""Hello, World!""#,
Expression::String(StringLiteral {
value: "Hello, World!".into(),
interpolations: vec![]
})
);
test_expression!(
string_escaped_chars,
r#""\\\"\n\r\t{{}}""#,
Expression::String(StringLiteral {
value: "\\\"\n\r\t{}".into(),
interpolations: vec![]
})
);
test_expression!(
string_unicode_code,
r#""\x41\u{41}\u{0041}\u{000041}""#,
Expression::String(StringLiteral {
value: "AAAA".into(),
interpolations: vec![]
})
);
test_expression!(
string_unicode_code_emoji,
r#""\u{2603}\u{1F600}""#,
Expression::String(StringLiteral {
value: "☃😀".into(),
interpolations: vec![]
})
);
test_error_kind!(
string_invalid_unicode_code_point,
r#""\u{110000}""#,
SyntaxErrorKind::InvalidUnicodeCodePoint(0x110000)
);
test_error_kind!(
string_missing_unicode_code_point_opening_brace,
r#""\u41}""#,
SyntaxErrorKind::NomError(nom::error::ErrorKind::Tag)
);
test_error_kind!(
string_missing_unicode_code_point_closing_brace,
r#""\u{41""#,
SyntaxErrorKind::NomError(nom::error::ErrorKind::Tag)
);
test_error_kind!(
string_invalid_hex_digit,
r#""\xGG""#,
SyntaxErrorKind::InvalidHexDigit
);
test_error_kind!(
string_invalid_unicode_code_point_hex,
r#""\u{GG}""#,
SyntaxErrorKind::InvalidHexDigit
);
test_expression!(
string_interpolation,
r#""{42}""#,
Expression::String(StringLiteral {
value: "".into(),
interpolations: vec![(0, Box::new(Expression::Number("42".into())))]
})
);
test_expression!(
string_interpolation_with_text,
r#""Hello, {42}!""#,
Expression::String(StringLiteral {
value: "Hello, !".into(),
interpolations: vec![(7, Box::new(Expression::Number("42".into())))]
})
);
test_expression!(
string_newline,
r#""
""#,
Expression::String(StringLiteral {
value: "\n".into(),
interpolations: vec![]
})
);
test_expression!(
identifier_simple,
"foo",
Expression::Identifier("foo".into())
);
test_expression!(
identifier_with_underscore,
"_foo",
Expression::Identifier("_foo".into())
);
test_expression!(
identifier_with_numbers,
"foo123",
Expression::Identifier("foo123".into())
);
test_expression!(
identifier_with_underscore_and_numbers,
"_foo_123",
Expression::Identifier("_foo_123".into())
);
test_expression!(
identifier_all_uppercase,
"FOO",
Expression::Identifier("FOO".into())
);
test_expression!(
identifier_mixed_case,
"FooBar",
Expression::Identifier("FooBar".into())
);
test_expression!(
identifier_single_character,
"f",
Expression::Identifier("f".into())
);
test_expression!(
identifier_with_multiple_underscores,
"foo_bar_baz",
Expression::Identifier("foo_bar_baz".into())
);
test_expression!(
identifier_with_leading_underscore_and_numbers,
"_123foo",
Expression::Identifier("_123foo".into())
);
test_expression!(
identifier_underscore_number,
"_42",
Expression::Identifier("_42".into())
);
test_expression!(
bigint_underscore_number_n,
"_42n",
Expression::Identifier("_42n".into())
);
macro_rules! test_binary_expression {
($input:expr, $expected:expr) => {
let (input, result) = parse_expression($input).unwrap();
assert_eq!(input, "");
assert_eq!(dbg!(result).to_string(), $expected);
};
}
#[test]
fn test_binary_expression() {
test_binary_expression!("1 + 2 * 3", "(1 + (2 * 3))");
test_binary_expression!("2 * 3 + 4", "((2 * 3) + 4)");
test_binary_expression!("2 ** 3 * 4", "((2 ** 3) * 4)");
test_binary_expression!("2 * 3 ** 4", "(2 * (3 ** 4))");
test_binary_expression!("1 + 2 + 3", "((1 + 2) + 3)"); test_binary_expression!("2 ** 3 ** 2", "(2 ** (3 ** 2))"); test_binary_expression!("10 / 5 / 2", "((10 / 5) / 2)");
test_binary_expression!("-2 * 3", "((-2) * 3)");
test_binary_expression!("!1 && 2", "((!1) && 2)");
test_binary_expression!("~1 & 2", "((~1) & 2)");
test_binary_expression!("1 | 2 & 3", "(1 | (2 & 3))");
test_binary_expression!("1 & 2 ^ 3", "((1 & 2) ^ 3)");
test_binary_expression!("1 | 2 | 3", "((1 | 2) | 3)");
test_binary_expression!("1 || 2 && 3", "(1 || (2 && 3))");
test_binary_expression!("1 && 2 || 3 && 4", "((1 && 2) || (3 && 4))");
test_binary_expression!("1 < 2 == 3 > 4", "((1 < 2) == (3 > 4))");
test_binary_expression!("1 == 2 != 3 == 4", "(((1 == 2) != 3) == 4)");
test_binary_expression!("1 << 2 + 3", "(1 << (2 + 3))");
test_binary_expression!("1 + 2 >> 3", "((1 + 2) >> 3)");
test_binary_expression!("1 |> 2 |> 3", "((1 |> 2) |> 3)");
test_binary_expression!("1 + 2 * 3 ** 4", "(1 + (2 * (3 ** 4)))");
test_binary_expression!(
"1 || 2 && 3 | 4 ^ 5 & 6",
"(1 || (2 && (3 | (4 ^ (5 & 6)))))"
);
test_binary_expression!(
"1 + 2 * 3 < 4 && 5 == 6",
"(((1 + (2 * 3)) < 4) && (5 == 6))"
);
}
}