use nom::{
IResult, Parser,
character::complete::{char, multispace0, one_of},
error::Error as NomError,
multi::many0,
};
use log::warn;
use crate::parser::ast::{Endianness, IndirectAdjustmentOp, OffsetSpec, TypeKind};
use super::parse_number;
fn pointer_specifier_to_type(spec: char) -> Option<(TypeKind, Endianness)> {
match spec {
'b' => Some((TypeKind::Byte { signed: true }, Endianness::Little)),
'B' => Some((TypeKind::Byte { signed: true }, Endianness::Big)),
's' => Some((
TypeKind::Short {
endian: Endianness::Little,
signed: true,
},
Endianness::Little,
)),
'S' => Some((
TypeKind::Short {
endian: Endianness::Big,
signed: true,
},
Endianness::Big,
)),
'l' => Some((
TypeKind::Long {
endian: Endianness::Little,
signed: true,
},
Endianness::Little,
)),
'L' => Some((
TypeKind::Long {
endian: Endianness::Big,
signed: true,
},
Endianness::Big,
)),
'q' => Some((
TypeKind::Quad {
endian: Endianness::Little,
signed: true,
},
Endianness::Little,
)),
'Q' => Some((
TypeKind::Quad {
endian: Endianness::Big,
signed: true,
},
Endianness::Big,
)),
#[allow(clippy::match_same_arms)]
'i' => Some((
TypeKind::Long {
endian: Endianness::Little,
signed: true,
},
Endianness::Little,
)),
#[allow(clippy::match_same_arms)]
'I' => Some((
TypeKind::Long {
endian: Endianness::Big,
signed: true,
},
Endianness::Big,
)),
_ => None,
}
}
fn parse_indirect_offset(input: &str) -> IResult<&str, OffsetSpec> {
fn parse_operand(input: &str) -> IResult<&str, i64> {
if let Some(rest) = input.strip_prefix('(') {
let (rest, n) = parse_number(rest)?;
let (rest, _) = char(')')(rest)?;
Ok((rest, n))
} else {
parse_number(input)
}
}
fn parse_inside_adjustment(input: &str) -> IResult<&str, Option<(IndirectAdjustmentOp, i64)>> {
if let Some(rest) = input.strip_prefix('+') {
let (rest, n) = parse_operand(rest)?;
Ok((rest, Some((IndirectAdjustmentOp::Add, n))))
} else if input.starts_with('-') {
let (rest, n) = parse_number(input)?;
Ok((rest, Some((IndirectAdjustmentOp::Add, n))))
} else if let Some(rest) = input.strip_prefix('*') {
let (rest, n) = parse_operand(rest)?;
Ok((rest, Some((IndirectAdjustmentOp::Mul, n))))
} else if let Some(rest) = input.strip_prefix('/') {
let (rest, n) = parse_operand(rest)?;
Ok((rest, Some((IndirectAdjustmentOp::Div, n))))
} else if let Some(rest) = input.strip_prefix('%') {
let (rest, n) = parse_operand(rest)?;
Ok((rest, Some((IndirectAdjustmentOp::Mod, n))))
} else if let Some(rest) = input.strip_prefix('&') {
let (rest, n) = parse_operand(rest)?;
Ok((rest, Some((IndirectAdjustmentOp::And, n))))
} else if let Some(rest) = input.strip_prefix('|') {
let (rest, n) = parse_operand(rest)?;
Ok((rest, Some((IndirectAdjustmentOp::Or, n))))
} else if let Some(rest) = input.strip_prefix('^') {
let (rest, n) = parse_operand(rest)?;
Ok((rest, Some((IndirectAdjustmentOp::Xor, n))))
} else {
Ok((input, None))
}
}
fn parse_outside_adjustment(input: &str) -> IResult<&str, Option<i64>> {
if let Some(rest) = input.strip_prefix('+') {
let (rest, n) = parse_number(rest)?;
Ok((rest, Some(n)))
} else if input.starts_with('-') {
let (rest, n) = parse_number(input)?;
Ok((rest, Some(n)))
} else {
Ok((input, None))
}
}
let (input, _) = char('(')(input)?;
let (input, base_relative) = if let Some(rest) = input.strip_prefix('&') {
(rest, true)
} else {
(input, false)
};
let (input, base_offset) = parse_number(input)?;
let (input, sep) = one_of(".,").parse(input)?;
if sep == ',' {
warn!(
"Indirect offset uses ',' as separator (magic(5) requires '.'); \
accepting for GNU `file` typo-tolerance compatibility"
);
}
let (input, spec_char) = one_of("bBsSlLqQiI")(input)?;
let (pointer_type, endian) = pointer_specifier_to_type(spec_char)
.ok_or_else(|| nom::Err::Error(NomError::new(input, nom::error::ErrorKind::OneOf)))?;
let (input, inside) = parse_inside_adjustment(input)?;
let (input, _) = char(')')(input)?;
let (input, adjustment_op, adjustment) = if let Some((op, n)) = inside {
(input, op, n)
} else {
let (input, outside) = parse_outside_adjustment(input)?;
(input, IndirectAdjustmentOp::Add, outside.unwrap_or(0))
};
Ok((
input,
OffsetSpec::Indirect {
base_offset,
base_relative,
pointer_type,
adjustment,
adjustment_op,
result_relative: false,
endian,
},
))
}
pub fn parse_offset(input: &str) -> IResult<&str, OffsetSpec> {
let (input, _) = multispace0(input)?;
if input.starts_with('(') {
let (input, spec) = parse_indirect_offset(input)?;
let (input, _) = multispace0(input)?;
Ok((input, spec))
} else if let Some(rest) = input.strip_prefix('&')
&& rest.starts_with('(')
{
let (rest, mut spec) = parse_indirect_offset(rest)?;
if let OffsetSpec::Indirect {
ref mut result_relative,
..
} = spec
{
*result_relative = true;
}
let (rest, _) = multispace0(rest)?;
Ok((rest, spec))
} else if let Some(rest) = input.strip_prefix('&') {
let (rest, value) = if let Some(after_plus) = rest.strip_prefix('+') {
parse_number(after_plus)?
} else {
parse_number(rest)?
};
let (rest, _) = multispace0(rest)?;
Ok((rest, OffsetSpec::Relative(value)))
} else {
let starts_with_minus = input.starts_with('-');
let (input, offset_value) = parse_number(input)?;
let (input, _) = multispace0(input)?;
if starts_with_minus && offset_value == 0 {
return Ok((input, OffsetSpec::FromEnd(0)));
}
Ok((input, OffsetSpec::Absolute(offset_value)))
}
}
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)))
}