hhh 1.0.1

The hhh Binary File Processor
Documentation
// hhh
// Copyright (c) 2023 by Stacy Prowell.  All rights reserved.
// https://gitlab.com/sprowell/hhh

//! Parse expressions.

use trivet::{errors::syntax_error, parser::ParseResult, Parser};

use crate::options::HhhArgs;

/// Parse a primitive expression.  This could be a number or a parenthesized expression.
///
/// On entry the parser is assumed to be pointing at the first character.  On
/// exit any trailing whitespace is consumed.
///
fn parse_primitive_ws(parser: &mut Parser, config: &mut HhhArgs) -> ParseResult<i64> {
    // Look for leading minus sign.
    let is_neg = parser.peek_and_consume('-');

    let value = if parser.peek_and_consume('(') {
        parser.consume_ws();
        let value = parse_top_expr_ws(parser, config)?;
        if !parser.peek_and_consume_ws(')') {
            return Err(syntax_error(parser.loc(), "Missing closing parenthesis."));
        }
        value
    } else if parser.peek_and_consume('$') {
        let name = parser.take_while(|ch| ch.is_alphanumeric() || ch == '_');
        let value = match config.get_variable(&name) {
            Some(bytes) => {
                let len = bytes.len();
                let end = bytes.len().min((usize::BITS / 8) as usize);
                let start = 0;
                let mut newbytes = [0u8; 8];
                newbytes[8 - end..8 - start].copy_from_slice(&bytes[len - end..len - start]);
                Ok(i64::from_be_bytes(newbytes))
            }
            None => Err(syntax_error(
                parser.loc(),
                &format!(
                    "The variable ${} does not have a value at this point.",
                    name
                ),
            )),
        }?;
        parser.consume_ws();
        value
    } else {
        parser.parse_i64_ws()?
    };
    Ok(if is_neg { -value } else { value })
}

/// Parse an expression containing exponentiation or bitwise operators.
///
/// On entry the parser is assumed to be pointing at the first character.  On
/// exit any trailing whitespace is consumed.
///
fn parse_exp_expr_ws(parser: &mut Parser, config: &mut HhhArgs) -> ParseResult<i64> {
    // Parse an initial primitive.
    let mut left = parse_primitive_ws(parser, config)?;
    loop {
        // Look for an operator.
        left = if parser.peek_and_consume_ws('^') {
            left ^ parse_primitive_ws(parser, config)?
        } else if parser.peek_and_consume_ws('|') {
            left | parse_primitive_ws(parser, config)?
        } else if parser.peek_and_consume_ws('&') {
            left & parse_primitive_ws(parser, config)?
        } else if parser.peek_and_consume_str_ws("**") {
            let right = parse_exp_expr_ws(parser, config)?;
            if right > 0x7fff_ffff {
                // Power is too large.
                return Err(syntax_error(
                    parser.loc(),
                    &format!(
                        "Power {} is too large; must be no larger than {}.",
                        right, 0x7fff_ffff
                    ),
                ));
            }
            if right < 0 {
                // Power is too small.
                return Err(syntax_error(parser.loc(), "Powers must be nonnegative."));
            }
            left.saturating_pow(right as u32)
        } else if parser.peek_and_consume_str_ws(">>") {
            left >> parse_primitive_ws(parser, config)?
        } else if parser.peek_and_consume_str_ws("<<") {
            left << parse_primitive_ws(parser, config)?
        } else {
            break;
        }
    }
    Ok(left)
}

/// Parse multiplication, division, and remainder operations.
///
/// On entry the parser is assumed to be pointing at the first character.  On
/// exit any trailing whitespace is consumed.
///
fn parse_product_expr_ws(parser: &mut Parser, config: &mut HhhArgs) -> ParseResult<i64> {
    let mut left = parse_exp_expr_ws(parser, config)?;
    loop {
        left = if parser.peek_and_consume_ws('*') {
            left * parse_exp_expr_ws(parser, config)?
        } else if parser.peek_and_consume_ws('/') {
            left / parse_exp_expr_ws(parser, config)?
        } else if parser.peek_and_consume_ws('%') {
            left % parse_exp_expr_ws(parser, config)?
        } else {
            break;
        }
    }
    Ok(left)
}

/// Parse an expression containing arithmetic and bitwise operators.
///
/// On entry the parser is assumed to be pointing at the first character.  On
/// exit any trailing whitespace is consumed.
///
fn parse_top_expr_ws(parser: &mut Parser, config: &mut HhhArgs) -> ParseResult<i64> {
    let mut left = parse_product_expr_ws(parser, config)?;
    loop {
        left = if parser.peek_and_consume_ws('+') {
            left + parse_product_expr_ws(parser, config)?
        } else if parser.peek_and_consume_ws('-') {
            left - parse_product_expr_ws(parser, config)?
        } else {
            break;
        };
    }
    Ok(left)
}

/// Parse an expression.
pub fn parse_expression_ws(parser: &mut Parser, config: &mut HhhArgs) -> ParseResult<i64> {
    parser.consume_ws();
    parse_top_expr_ws(parser, config)
}

#[cfg(test)]
mod test {
    use trivet::{parse_from_string, parser::ParseResult};

    use crate::options::HhhArgs;

    use super::parse_expression_ws;

    #[test]
    fn number_test() -> ParseResult<()> {
        let mut config = HhhArgs::default();
        let mut parser = parse_from_string("0b1110");
        let mut result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 0b1110);

        parser = parse_from_string("0");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 0);

        parser = parse_from_string("1");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 1);

        parser = parse_from_string("-1");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, -1);

        parser = parse_from_string("(0)");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 0);

        parser = parse_from_string("(-1)");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, -1);

        parser = parse_from_string("-(1)");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, -1);

        parser = parse_from_string("65_535");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 65_535);

        parser = parse_from_string("0xffff");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 0xffff);

        parser = parse_from_string("");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());

        Ok(())
    }

    #[test]
    fn add_sub_test() -> ParseResult<()> {
        let mut config = HhhArgs::default();
        let mut parser = parse_from_string("    1    +    1    ");
        let mut result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 2);

        parser = parse_from_string(" 1 - 1 + 2 - 2 + 3 - 3 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 0);
        Ok(())
    }

    #[test]
    fn mul_div_mod_test() -> ParseResult<()> {
        let mut config = HhhArgs::default();
        let mut parser = parse_from_string("    1    *    1    ");
        let mut result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 1);

        parser = parse_from_string(" 1 / 1 * 2 / 2 * 3 / 3 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 1);

        parser = parse_from_string(" 1 / 1 * 2 / 2 * 3 / 3 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 1);

        parser = parse_from_string(" ( 8 / 2 * 3 ) % 11 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 1);

        parser = parse_from_string(" ( 3 % 2 ) * ( 6 % 4 ) ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 2);
        Ok(())
    }

    #[test]
    fn pow_test() -> ParseResult<()> {
        let mut config = HhhArgs::default();
        let mut parser = parse_from_string("    1    **    1    ");
        let mut result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 1);

        parser = parse_from_string(" 1 ** 1 ** 2 ** 3 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 1);

        parser = parse_from_string(" (2 ** 3) ** 4 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 4096);

        parser = parse_from_string(" 2 ** 2 ** 3 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 256);

        parser = parse_from_string(" (2 ** 2) ** 3 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 64);
        Ok(())
    }

    #[test]
    fn shift_test() -> ParseResult<()> {
        let mut config = HhhArgs::default();
        let mut parser = parse_from_string("    1    <<    1    ");
        let mut result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 2);

        parser = parse_from_string(" 1 << 2 << 3 << 4 >> 4 >> 3 >> 2 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 1);

        parser = parse_from_string(" 17 << 2 >> 1 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 34);
        Ok(())
    }

    #[test]
    fn bitwise_test() -> ParseResult<()> {
        let mut config = HhhArgs::default();
        let mut parser = parse_from_string("    1    ^    1    ");
        let mut result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 0);

        parser = parse_from_string(" 0x657463 ^ 0x650460 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 0x007003);

        parser = parse_from_string(" 0x21 | 2 & 3 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 3);
        Ok(())
    }

    #[test]
    fn pemdas_test() -> ParseResult<()> {
        let mut config = HhhArgs::default();
        let mut parser = parse_from_string("    1 + 2 * 3    ");
        let mut result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 7);

        parser = parse_from_string(" 2 * 3 ** 2 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 18);

        parser = parse_from_string(" 2 ** 3 - 1 * 6 ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 2);
        Ok(())
    }

    #[test]
    fn error_test() {
        let mut config = HhhArgs::default();
        let mut parser = parse_from_string("        ");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());

        parser = parse_from_string(" ) ");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());

        parser = parse_from_string(" ( 1 + ) ");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());

        parser = parse_from_string(" 1 ** 2 ** 3 ** 4 ** 5 ");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());

        parser = parse_from_string(" 1 ** -6 ");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());
    }

    #[test]
    fn missing_test() {
        let mut config = HhhArgs::default();
        let mut parser = parse_from_string("    2 *    ");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());
    }

    #[test]
    fn variable_test() -> ParseResult<()> {
        let mut config = HhhArgs::default();
        config.set_variable("one", &[1]);
        config.set_variable("two", &[0, 0, 0, 0, 0, 0, 0, 2]);
        config.set_variable("three", &[0, 0, 0, 3]);
        let mut parser = parse_from_string("    $three    ");
        let mut result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 3);

        parser = parse_from_string(" $one + $two ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 3);

        parser = parse_from_string(" $one + $two * $three ");
        result = parse_expression_ws(&mut parser, &mut config)?;
        assert_eq!(result, 7);

        parser = parse_from_string(" 1 ** $unknown ");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());
        parser = parse_from_string(" ( ");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());
        parser = parse_from_string(" (7 ");
        assert!(parse_expression_ws(&mut parser, &mut config).is_err());
        Ok(())
    }
}