use nom::{
IResult, Parser, character::complete::multispace0, combinator::opt, error::Error as NomError,
};
use crate::parser::ast::{MagicRule, MetaType, Operator, TypeKind, Value};
#[cfg(test)]
use crate::parser::ast::{OffsetSpec, StrengthModifier};
mod getstr;
mod lines;
mod numbers;
mod offset;
mod operator;
mod rule_value;
mod type_and_operator;
mod type_suffix;
mod value;
pub use lines::{
has_continuation, is_comment_line, is_empty_line, is_strength_directive, parse_comment,
parse_message, parse_strength_directive,
};
pub use numbers::parse_number;
#[allow(unused_imports)]
pub use offset::{parse_offset, parse_rule_offset};
pub use operator::parse_operator;
#[allow(unused_imports)]
pub use type_and_operator::{parse_type, parse_type_and_operator};
pub use value::parse_value;
use rule_value::parse_string_family_value;
#[cfg(test)]
use numbers::{parse_decimal_number, parse_hex_number};
#[cfg(test)]
use value::{parse_escape_sequence, parse_hex_bytes, parse_numeric_value, parse_quoted_string};
fn strip_optional_x_operator(input: &str) -> &str {
let trimmed = input.trim_start_matches([' ', '\t']);
if let Some(rest) = trimmed.strip_prefix('x') {
if rest.is_empty() || rest.starts_with([' ', '\t', '\n', '\r']) {
return rest.trim_start_matches([' ', '\t']);
}
}
input
}
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, value_transform)) = parse_type_and_operator(input)?;
if matches!(typ, TypeKind::Meta(_)) && !matches!(typ, TypeKind::Meta(MetaType::Offset)) {
if attached_op.is_some() {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
let input = strip_optional_x_operator(input);
let (input, message) = if input.trim().is_empty() {
(input, String::new())
} else {
parse_message(input)?
};
let rule = MagicRule {
offset,
typ,
op: Operator::AnyValue,
value: Value::Uint(0),
message,
children: vec![],
level,
strength_modifier: None,
value_transform: None,
};
return Ok((input, rule));
}
let (input, separate_op) = opt(parse_operator).parse(input)?;
let (op, value_transform) = match (attached_op, separate_op) {
(Some(Operator::BitwiseAndMask(mask)), Some(separate)) => {
if value_transform.is_some() {
return Err(nom::Err::Error(NomError::new(
input,
nom::error::ErrorKind::Tag,
)));
}
#[allow(clippy::cast_possible_wrap)]
let promoted = crate::parser::ast::ValueTransform {
op: crate::parser::ast::ValueTransformOp::BitAnd,
operand: mask as i64,
};
(separate, Some(promoted))
}
(Some(attached), _) => (attached, value_transform),
(None, Some(separate)) => (separate, value_transform),
(None, None) => (Operator::Equal, value_transform),
};
let is_string_family_type = matches!(
typ,
TypeKind::String { .. }
| TypeKind::String16 { .. }
| TypeKind::PString { .. }
| TypeKind::Regex { .. }
| TypeKind::Search { .. }
);
let (input, value) = if op == Operator::AnyValue {
(input, Value::Uint(0))
} else if matches!(typ, TypeKind::Regex { .. }) {
if input.trim_start().starts_with('"') {
parse_value(input)?
} else {
match getstr::parse_regex_getstr_value(input) {
Ok(ok) => ok,
Err(orig_err) => parse_value(input).map_err(|_| orig_err)?,
}
}
} else if is_string_family_type {
parse_string_family_value(input)?
} 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, value_transform,
};
Ok((input, rule))
}
#[cfg(test)]
mod tests;