dbin 0.1.1

Regex-like utility for parsing and rendering binary data
Documentation
use crate::Context;
use crate::Data;
use crate::Expr;
use crate::Key;
use crate::Spec;
use std::collections::HashMap;

impl Spec {
    pub fn parse(&self, bytes: &[u8]) -> Result<Data, String> {
        let mut ctx = Context::new(bytes);
        parse(&mut ctx, self)
    }
}

fn parse(ctx: &mut Context, spec: &Spec) -> Result<Data, String> {
    match spec {
        Spec::Int {
            little_endian,
            signed,
            nbytes,
            expected,
        } => {
            let bytes = ctx.read(*nbytes)?;
            let i = getint(*little_endian, *signed, bytes);
            match expected {
                Some(expected) => {
                    if i == *expected {
                        Ok(Data::Int(i))
                    } else {
                        Err(format!("Expected {} but got {}", expected, i))
                    }
                }
                None => Ok(Data::Int(i)),
            }
        }
        Spec::Array { size, member } => {
            let size = evalsize(ctx, size)?;
            let mut ret = Vec::new();
            for _ in 0..size {
                ret.push(parse(ctx, member)?);
            }
            Ok(Data::Array(ret))
        }
        Spec::Enum(pairs) => {
            let mut ret = Err("Empty enum".to_owned());
            let save_point = ctx.store();
            for (key, spec) in pairs {
                ret = parse(ctx, spec);
                if let Ok(data) = ret {
                    return Ok(Data::Enum(key.clone(), data.into()));
                } else {
                    ctx.restore(save_point);
                }
            }
            ret
        }
        Spec::Struct(pairs) => {
            let mut ret = Vec::new();
            for spec in pairs {
                let data = parse(ctx, spec)?;
                ret.push(data);
            }
            Ok(Data::Struct(ret))
        }
        Spec::Scope { args, body } => {
            let mut map = HashMap::new();
            for (key, valexpr) in args {
                let val = eval(ctx, valexpr)?;
                map.insert(key.clone(), val);
            }
            ctx.push_stack(map);
            let ret = parse(ctx, body);
            ctx.pop_stack();
            ret
        }
        Spec::Store(key, body) => {
            let data = parse(ctx, body)?;
            let val = match &data {
                Data::Int(i) => Key::Int(*i),
                Data::String(s) => Key::String(s.clone()),
                _ => {
                    return Err(format!("Could not convert {:?} into key value", data));
                }
            };
            ctx.setvar(key.clone(), val);
            Ok(data)
        }
    }
}

fn eval(ctx: &mut Context, expr: &Expr) -> Result<Key, String> {
    match expr {
        Expr::Int(i) => Ok(Key::Int(*i)),
        Expr::String(s) => Ok(Key::String(s.clone())),
        Expr::Var(name) => match ctx.getvar(name.clone()) {
            Some(val) => Ok(val.clone()),
            None => Err(format!("Variable {:?} not found", name)),
        },
        Expr::Add(parts) => {
            let mut ret = 0;
            for part in parts {
                ret += evalint(ctx, part)?;
            }
            Ok(Key::Int(ret))
        }
    }
}

fn evalint(ctx: &mut Context, expr: &Expr) -> Result<i64, String> {
    let key = eval(ctx, expr)?;
    match key {
        Key::Int(i) => Ok(i),
        Key::String(s) => Err(format!("Expected int but got {:?}", s)),
    }
}

fn evalsize(ctx: &mut Context, expr: &Expr) -> Result<usize, String> {
    let key = eval(ctx, expr)?;
    match key {
        Key::Int(i) => Ok(i as usize),
        Key::String(s) => Err(format!("Expected size but got {:?}", s)),
    }
}

fn getint(little_endian: bool, signed: bool, bytes: &[u8]) -> i64 {
    if signed {
        sint(little_endian, bytes)
    } else {
        uint(little_endian, bytes) as i64
    }
}

fn uint(little_endian: bool, bytes: &[u8]) -> u64 {
    let mut ret: u64 = 0;
    if little_endian {
        for byte in bytes.iter().rev() {
            ret <<= 8;
            ret += (*byte) as u64;
        }
    } else {
        for byte in bytes {
            ret <<= 8;
            ret += (*byte) as u64;
        }
    }
    ret
}

fn sint(little_endian: bool, bytes: &[u8]) -> i64 {
    let mut bytes = bytes.to_vec();
    let byte = if little_endian {
        *bytes.last_mut().unwrap() as i8
    } else {
        bytes[0] as i8
    };
    let minus = if byte < 0 {
        for byte in &mut bytes {
            *byte = !*byte;
        }
        true
    } else {
        false
    };
    let ui = uint(little_endian, &bytes);
    if minus {
        -(ui.wrapping_add(1) as i64)
    } else {
        ui as i64
    }
}

#[cfg(test)]
mod tests {

    fn le_sint(bytes: &[u8]) -> i64 {
        super::sint(true, bytes)
    }

    fn le_uint(bytes: &[u8]) -> u64 {
        super::uint(true, bytes)
    }

    fn be_sint(bytes: &[u8]) -> i64 {
        super::sint(false, bytes)
    }

    fn be_uint(bytes: &[u8]) -> u64 {
        super::uint(false, bytes)
    }

    #[test]
    fn int_helpers() {
        assert_eq!(le_uint(&890u64.to_le_bytes()), 890);
        assert_eq!(le_uint(&678u64.to_le_bytes()), 678);
        assert_eq!(le_sint(&123i64.to_le_bytes()), 123);
        assert_eq!(le_sint(&(-456i64).to_le_bytes()), -456);
        assert_eq!(le_sint(&(-0i64).to_le_bytes()), -0);
        assert_eq!(le_sint(&(-1i64).to_le_bytes()), -1);
        assert_eq!(be_uint(&890u64.to_be_bytes()), 890);
        assert_eq!(be_uint(&678u64.to_be_bytes()), 678);
        assert_eq!(be_sint(&123i64.to_be_bytes()), 123);
        assert_eq!(be_sint(&(-456i64).to_be_bytes()), -456);
        assert_eq!(be_sint(&(-0i64).to_be_bytes()), -0);
        assert_eq!(be_sint(&(-1i64).to_be_bytes()), -1);

        assert_eq!(le_uint(&890u32.to_le_bytes()), 890);
        assert_eq!(le_uint(&678u32.to_le_bytes()), 678);
        assert_eq!(le_sint(&123i32.to_le_bytes()), 123);
        assert_eq!(le_sint(&(-456i32).to_le_bytes()), -456);
        assert_eq!(le_sint(&(-0i32).to_le_bytes()), -0);
        assert_eq!(le_sint(&(-1i32).to_le_bytes()), -1);
        assert_eq!(be_uint(&890u32.to_be_bytes()), 890);
        assert_eq!(be_uint(&678u32.to_be_bytes()), 678);
        assert_eq!(be_sint(&123i32.to_be_bytes()), 123);
        assert_eq!(be_sint(&(-456i32).to_be_bytes()), -456);
        assert_eq!(be_sint(&(-0i32).to_be_bytes()), -0);
        assert_eq!(be_sint(&(-1i32).to_be_bytes()), -1);
    }
}