aml 0.11.0

Library for parsing AML
Documentation
use crate::{
    opcode::{self, opcode},
    parser::{choice, comment_scope, id, take_to_end_of_pkglength, ParseResult, Parser, Propagate},
    pkg_length::{pkg_length, PkgLength},
    term_object::{term_arg, term_list},
    DebugVerbosity,
};

/// Type 1 opcodes do not return a value and so can't be used in expressions.
pub fn type1_opcode<'a, 'c>() -> impl Parser<'a, 'c, ()>
where
    'c: 'a,
{
    /*
     * Type1Opcode := DefBreak | DefBreakPoint | DefContinue | DefFatal | DefIfElse | DefLoad | DefNoop |
     *                DefNotify | DefRelease | DefReset | DefReturn | DefSignal | DefSleep | DefStall |
     *                DefWhile
     */
    comment_scope(
        DebugVerbosity::AllScopes,
        "Type1Opcode",
        choice!(def_breakpoint(), def_if_else(), def_noop(), def_return()),
    )
}

fn def_breakpoint<'a, 'c>() -> impl Parser<'a, 'c, ()>
where
    'c: 'a,
{
    /*
     * DefBreakPoint := 0xcc
     * TODO: there is no debugger, so this doesn't do anything. If there was, this should stop execution and enter
     * the AML debugger.
     */
    opcode(opcode::DEF_BREAKPOINT_OP)
        .then(comment_scope(DebugVerbosity::AllScopes, "DefBreakPoint", id()))
        .discard_result()
}

fn def_if_else<'a, 'c>() -> impl Parser<'a, 'c, ()>
where
    'c: 'a,
{
    /*
     * DefIfElse := 0xa0 PkgLength Predicate TermList DefElse
     * Predicate := TermArg => Integer (0 = false, >0 = true)
     * DefElse := Nothing | <0xa1 PkgLength TermList>
     */
    opcode(opcode::DEF_IF_ELSE_OP)
        .then(comment_scope(
            DebugVerbosity::Scopes,
            "DefIfElse",
            pkg_length()
                .then(term_arg())
                .feed(|(length, predicate_arg)| {
                    take_to_end_of_pkglength(length)
                        .map(move |then_branch| Ok((predicate_arg.as_bool()?, then_branch)))
                })
                .then(choice!(
                    opcode(opcode::DEF_ELSE_OP)
                        .then(comment_scope(
                            DebugVerbosity::AllScopes,
                            "DefElse",
                            pkg_length().feed(|length| take_to_end_of_pkglength(length))
                        ))
                        .map(|((), else_branch): ((), &[u8])| Ok(else_branch)),
                    |input, context| -> ParseResult<'a, 'c, &[u8]> {
                        /*
                         * This path parses an DefIfElse that doesn't have an else branch. We simply
                         * return an empty slice, so if the predicate is false, we don't execute
                         * anything.
                         */
                        Ok((input, context, &[]))
                    }
                ))
                .map_with_context(|((predicate, then_branch), else_branch), context| {
                    let branch = if predicate { then_branch } else { else_branch };

                    match term_list(PkgLength::from_raw_length(branch, branch.len() as u32).unwrap())
                        .parse(branch, context)
                    {
                        Ok((_, context, result)) => (Ok(result), context),
                        Err((_, context, err)) => (Err(err), context),
                    }
                }),
        ))
        .discard_result()
}

fn def_noop<'a, 'c>() -> impl Parser<'a, 'c, ()>
where
    'c: 'a,
{
    /*
     * DefNoop := 0xa3
     */
    opcode(opcode::DEF_NOOP_OP).then(comment_scope(DebugVerbosity::AllScopes, "DefNoop", id())).discard_result()
}

fn def_return<'a, 'c>() -> impl Parser<'a, 'c, ()>
where
    'c: 'a,
{
    /*
     * DefReturn := 0xa4 ArgObject
     * ArgObject := TermArg => DataRefObject
     */
    opcode(opcode::DEF_RETURN_OP)
        .then(comment_scope(
            DebugVerbosity::Scopes,
            "DefReturn",
            term_arg().map(|return_arg| -> Result<(), Propagate> {
                /*
                 * To return a value, we want to halt execution of the method and propagate the
                 * return value all the way up to the start of the method invocation. To do this,
                 * we emit a special error that is intercepted during method invocation and turned
                 * into a valid result.
                 */
                Err(Propagate::Return(return_arg))
            }),
        ))
        .discard_result()
}