use nom::{
IResult, Parser,
branch::alt,
bytes::complete::{tag, take_while},
character::complete::{char, digit1, hex_digit1, multispace0, none_of, one_of},
combinator::{map, opt, recognize},
error::Error as NomError,
multi::many0,
sequence::pair,
};
use crate::parser::ast::{MagicRule, OffsetSpec, Operator, StrengthModifier, TypeKind, Value};
fn parse_decimal_number(input: &str) -> IResult<&str, i64> {
let (input, digits) = digit1(input)?;
if digits.len() > 19 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::MapRes,
)));
}
let number = digits.parse::<i64>().map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::MapRes))
})?;
Ok((input, number))
}
fn parse_unsigned_decimal_number(input: &str) -> IResult<&str, u64> {
let (input, digits) = digit1(input)?;
if digits.len() > 20 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::MapRes,
)));
}
let number = digits.parse::<u64>().map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::MapRes))
})?;
Ok((input, number))
}
fn parse_hex_number(input: &str) -> IResult<&str, i64> {
let (input, _) = tag("0x")(input)?;
let (input, hex_str) = hex_digit1(input)?;
if hex_str.len() > 16 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::MapRes,
)));
}
let number = i64::from_str_radix(hex_str, 16).map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::MapRes))
})?;
Ok((input, number))
}
fn parse_unsigned_hex_number(input: &str) -> IResult<&str, u64> {
let (input, _) = tag("0x")(input)?;
let (input, hex_str) = hex_digit1(input)?;
if hex_str.len() > 16 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::MapRes,
)));
}
let number = u64::from_str_radix(hex_str, 16).map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::MapRes))
})?;
Ok((input, number))
}
fn parse_unsigned_number(input: &str) -> IResult<&str, u64> {
if input.starts_with("0x") {
parse_unsigned_hex_number(input)
} else {
parse_unsigned_decimal_number(input)
}
}
pub fn parse_number(input: &str) -> IResult<&str, i64> {
let (input, sign) = opt(char('-')).parse(input)?;
let is_negative = sign.is_some();
let (input, number) = if input.starts_with("0x") {
parse_hex_number(input)?
} else {
parse_decimal_number(input)?
};
let result = if is_negative {
number.checked_neg().ok_or_else(|| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::MapRes))
})?
} else {
number
};
Ok((input, result))
}
pub fn parse_offset(input: &str) -> IResult<&str, OffsetSpec> {
let (input, _) = multispace0(input)?;
let (input, offset_value) = parse_number(input)?;
let (input, _) = multispace0(input)?;
Ok((input, OffsetSpec::Absolute(offset_value)))
}
pub fn parse_operator(input: &str) -> IResult<&str, Operator> {
let (input, _) = multispace0(input)?;
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("==")(input) {
if remaining.starts_with('=') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::Equal));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("!=")(input) {
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::NotEqual));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("<>")(input) {
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::NotEqual));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("<=")(input) {
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::LessEqual));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>(">=")(input) {
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::GreaterEqual));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("=")(input) {
if remaining.starts_with('=') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::Equal));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("&")(input) {
if remaining.starts_with('&') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::BitwiseAnd));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("^")(input) {
if remaining.starts_with('^') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::BitwiseXor));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("~")(input) {
if remaining.starts_with('~') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::BitwiseNot));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("x")(input) {
if remaining.starts_with(|c: char| c.is_alphanumeric() || c == '_') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::AnyValue));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>("<")(input) {
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::LessThan));
}
if let Ok((remaining, _)) = tag::<&str, &str, nom::error::Error<&str>>(">")(input) {
let (remaining, _) = multispace0(remaining)?;
return Ok((remaining, Operator::GreaterThan));
}
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)))
}
fn parse_hex_byte_with_prefix(input: &str) -> IResult<&str, u8> {
let (input, _) = tag("\\x")(input)?;
let (input, hex_str) = recognize(pair(
one_of("0123456789abcdefABCDEF"),
one_of("0123456789abcdefABCDEF"),
))
.parse(input)?;
let byte_val = u8::from_str_radix(hex_str, 16)
.map_err(|_| nom::Err::Error(NomError::new(input, nom::error::ErrorKind::MapRes)))?;
Ok((input, byte_val))
}
fn parse_hex_bytes_with_prefix(input: &str) -> IResult<&str, Vec<u8>> {
if input.starts_with("\\x") {
many0(parse_hex_byte_with_prefix).parse(input)
} else {
Err(nom::Err::Error(NomError::new(
input,
nom::error::ErrorKind::Tag,
)))
}
}
fn parse_mixed_hex_ascii(input: &str) -> IResult<&str, Vec<u8>> {
if !input.starts_with('\\') {
return Err(nom::Err::Error(NomError::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let mut bytes = Vec::new();
let mut remaining = input;
while !remaining.is_empty() {
if let Ok((new_remaining, escaped_char)) = parse_escape_sequence(remaining) {
bytes.push(escaped_char as u8);
remaining = new_remaining;
} else if let Ok((new_remaining, hex_byte)) = parse_hex_byte_with_prefix(remaining) {
bytes.push(hex_byte);
remaining = new_remaining;
} else if let Ok((new_remaining, ascii_char)) =
none_of::<&str, &str, NomError<&str>>(" \t\n\r")(remaining)
{
bytes.push(ascii_char as u8);
remaining = new_remaining;
} else {
break;
}
}
if bytes.is_empty() {
Err(nom::Err::Error(NomError::new(
input,
nom::error::ErrorKind::Tag,
)))
} else {
Ok((remaining, bytes))
}
}
fn parse_hex_bytes_no_prefix(input: &str) -> IResult<&str, Vec<u8>> {
if input.starts_with("0x") || input.starts_with('-') {
return Err(nom::Err::Error(NomError::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let hex_chars: String = input.chars().take_while(char::is_ascii_hexdigit).collect();
if hex_chars.is_empty() || !hex_chars.len().is_multiple_of(2) {
return Err(nom::Err::Error(NomError::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let has_hex_letters = hex_chars
.chars()
.any(|c| matches!(c, 'a'..='f' | 'A'..='F'));
if !has_hex_letters {
return Err(nom::Err::Error(NomError::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let mut bytes = Vec::with_capacity(hex_chars.len() / 2);
let mut chars = hex_chars.chars();
while let (Some(c1), Some(c2)) = (chars.next(), chars.next()) {
let digit1 = c1
.to_digit(16)
.ok_or_else(|| nom::Err::Error(NomError::new(input, nom::error::ErrorKind::MapRes)))?;
let digit2 = c2
.to_digit(16)
.ok_or_else(|| nom::Err::Error(NomError::new(input, nom::error::ErrorKind::MapRes)))?;
let byte_val = u8::try_from((digit1 << 4) | digit2)
.map_err(|_| nom::Err::Error(NomError::new(input, nom::error::ErrorKind::MapRes)))?;
bytes.push(byte_val);
}
let remaining = &input[hex_chars.len()..];
Ok((remaining, bytes))
}
fn parse_hex_bytes(input: &str) -> IResult<&str, Vec<u8>> {
alt((
parse_mixed_hex_ascii,
parse_hex_bytes_with_prefix,
parse_hex_bytes_no_prefix,
))
.parse(input)
}
fn parse_escape_sequence(input: &str) -> IResult<&str, char> {
let (input, _) = char('\\')(input)?;
if let Ok((remaining, octal_str)) = recognize(pair(
one_of::<&str, &str, NomError<&str>>("0123"),
pair(
one_of::<&str, &str, NomError<&str>>("01234567"),
one_of::<&str, &str, NomError<&str>>("01234567"),
),
))
.parse(input)
&& let Ok(octal_value) = u8::from_str_radix(octal_str, 8)
{
return Ok((remaining, octal_value as char));
}
let (input, escaped_char) = one_of("nrt\\\"'0")(input)?;
let result_char = match escaped_char {
'n' => '\n',
'r' => '\r',
't' => '\t',
'\\' => '\\',
'"' => '"',
'\'' => '\'',
'0' => '\0',
_ => unreachable!("one_of constrains input to known escape characters"),
};
Ok((input, result_char))
}
fn parse_quoted_string(input: &str) -> IResult<&str, String> {
let (input, _) = multispace0(input)?;
let (input, _) = char('"')(input)?;
let mut result = String::new();
let mut remaining = input;
loop {
if let Ok((new_remaining, escaped_char)) = parse_escape_sequence(remaining) {
result.push(escaped_char);
remaining = new_remaining;
continue;
}
if let Ok((new_remaining, regular_char)) =
none_of::<&str, &str, NomError<&str>>("\"\\")(remaining)
{
result.push(regular_char);
remaining = new_remaining;
continue;
}
break;
}
let (remaining, _) = char('"')(remaining)?;
let (remaining, _) = multispace0(remaining)?;
Ok((remaining, result))
}
fn parse_float_value(input: &str) -> IResult<&str, f64> {
let (input, _) = multispace0(input)?;
let (remaining, float_str) = recognize((
opt(char('-')),
digit1,
char('.'),
digit1,
opt((one_of("eE"), opt(one_of("+-")), digit1)),
))
.parse(input)?;
let value: f64 = float_str
.parse()
.map_err(|_| nom::Err::Error(NomError::new(input, nom::error::ErrorKind::MapRes)))?;
if !value.is_finite() {
return Err(nom::Err::Error(NomError::new(
input,
nom::error::ErrorKind::Float,
)));
}
let (remaining, _) = multispace0(remaining)?;
Ok((remaining, value))
}
fn parse_numeric_value(input: &str) -> IResult<&str, Value> {
let (input, _) = multispace0(input)?;
let (input, value) = if input.starts_with('-') {
let (input, number) = parse_number(input)?;
(input, Value::Int(number))
} else {
let (input, number) = parse_unsigned_number(input)?;
(input, Value::Uint(number))
};
let (input, _) = multispace0(input)?;
Ok((input, value))
}
pub fn parse_value(input: &str) -> IResult<&str, Value> {
let (input, _) = multispace0(input)?;
if input.is_empty() {
return Err(nom::Err::Error(NomError::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let (input, value) = alt((
map(parse_quoted_string, Value::String),
map(parse_hex_bytes, Value::Bytes),
map(parse_float_value, Value::Float),
parse_numeric_value,
))
.parse(input)?;
Ok((input, value))
}
pub fn parse_type_and_operator(input: &str) -> IResult<&str, (TypeKind, Option<Operator>)> {
let (input, _) = multispace0(input)?;
let (input, type_name) = crate::parser::types::parse_type_keyword(input)?;
let (input, attached_op) = if let Some(after_amp) = input.strip_prefix('&') {
if after_amp.starts_with("0x") || after_amp.starts_with(|c: char| c.is_ascii_digit()) {
let (rest, mask) = parse_unsigned_number(after_amp).map_err(|_| {
nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::MapRes))
})?;
(rest, Some(Operator::BitwiseAndMask(mask)))
} else if after_amp.starts_with('&') {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
} else {
(after_amp, Some(Operator::BitwiseAnd))
}
} else {
(input, None)
};
let (input, _) = multispace0(input)?;
let type_kind = crate::parser::types::type_keyword_to_kind(type_name);
Ok((input, (type_kind, attached_op)))
}
pub fn parse_type(input: &str) -> IResult<&str, TypeKind> {
let (input, (type_kind, _)) = parse_type_and_operator(input)?;
Ok((input, type_kind))
}
pub fn parse_rule_offset(input: &str) -> IResult<&str, (u32, OffsetSpec)> {
let (input, _) = multispace0(input)?;
let (input, level_chars) = many0(char('>')).parse(input)?;
let level = u32::try_from(level_chars.len()).unwrap_or(0);
let (input, offset_spec) = parse_offset(input)?;
Ok((input, (level, offset_spec)))
}
pub fn parse_message(input: &str) -> IResult<&str, String> {
let (input, _) = multispace0(input)?;
let (input, message_text) = take_while(|c: char| c != '\n' && c != '\r').parse(input)?;
let message = message_text.trim().to_string();
Ok((input, message))
}
pub fn parse_strength_directive(input: &str) -> IResult<&str, StrengthModifier> {
fn clamp_to_i32(n: i64) -> i32 {
let clamped = n.clamp(i64::from(i32::MIN), i64::from(i32::MAX));
i32::try_from(clamped).unwrap()
}
let (input, _) = multispace0(input)?;
let (input, _) = tag("!:strength")(input)?;
let (input, _) = multispace0(input)?;
let (input, modifier) = alt((
map(pair(char('+'), parse_number), |(_, n)| {
StrengthModifier::Add(clamp_to_i32(n))
}),
map(pair(char('-'), parse_decimal_number), |(_, n)| {
StrengthModifier::Subtract(clamp_to_i32(n))
}),
map(pair(char('*'), parse_number), |(_, n)| {
StrengthModifier::Multiply(clamp_to_i32(n))
}),
map(pair(char('/'), parse_number), |(_, n)| {
StrengthModifier::Divide(clamp_to_i32(n))
}),
map(pair(char('='), parse_number), |(_, n)| {
StrengthModifier::Set(clamp_to_i32(n))
}),
map(parse_number, |n| StrengthModifier::Set(clamp_to_i32(n))),
))
.parse(input)?;
Ok((input, modifier))
}
#[must_use]
pub fn is_strength_directive(input: &str) -> bool {
input.trim().starts_with("!:strength")
}
pub fn parse_magic_rule(input: &str) -> IResult<&str, MagicRule> {
let (input, _) = multispace0(input)?;
let (input, (level, offset)) = parse_rule_offset(input)?;
let (input, (typ, attached_op)) = parse_type_and_operator(input)?;
let (input, separate_op) = opt(parse_operator).parse(input)?;
let op = attached_op.or(separate_op).unwrap_or(Operator::Equal);
let (input, value) = if op == Operator::AnyValue {
(input, Value::Uint(0))
} else {
parse_value(input)?
};
let (input, message) = if input.trim().is_empty() {
(input, String::new())
} else {
parse_message(input)?
};
let rule = MagicRule {
offset,
typ,
op,
value,
message,
children: vec![], level,
strength_modifier: None, };
Ok((input, rule))
}
pub fn parse_comment(input: &str) -> IResult<&str, String> {
let (input, _) = multispace0(input)?;
let (input, _) = char('#').parse(input)?;
let (input, comment_text) = take_while(|c: char| c != '\n' && c != '\r').parse(input)?;
let comment = comment_text.trim().to_string();
Ok((input, comment))
}
#[must_use]
pub fn is_empty_line(input: &str) -> bool {
input.trim().is_empty()
}
#[must_use]
pub fn is_comment_line(input: &str) -> bool {
input.trim().starts_with('#')
}
#[must_use]
pub fn has_continuation(input: &str) -> bool {
input.trim_end().ends_with('\\')
}
#[cfg(test)]
mod tests;