Browse Source

Implement DefWhile

This was slightly more involved than lots of the other opcodes (as expected),
as we need to parse part of the stream potentially many times. I thought
we'd have to introduce new versions of a bunch of parsers that don't fully
evaluate the result of a parse, or introduce a new evaluation phase into
existing parsers, but this finds a way around that by allowing the 'extraction'
of the raw AML stream that another parser streams, allowing it to be reparsed.
This doesn't feel amazingly elegant, but it's fairly robust and works well
enough.
Isaac Woods 3 years ago
parent
commit
2185618809
3 changed files with 85 additions and 1 deletions
  1. 1 0
      aml/src/opcode.rs
  2. 20 0
      aml/src/parser.rs
  3. 64 1
      aml/src/type1.rs

+ 1 - 0
aml/src/opcode.rs

@@ -47,6 +47,7 @@ pub const EXT_DEF_THERMAL_ZONE_OP: u8 = 0x85;
  */
 pub const DEF_IF_ELSE_OP: u8 = 0xa0;
 pub const DEF_ELSE_OP: u8 = 0xa1;
+pub const DEF_WHILE_OP: u8 = 0xa2;
 pub const DEF_NOOP_OP: u8 = 0xa3;
 pub const DEF_RETURN_OP: u8 = 0xa4;
 pub const DEF_BREAKPOINT_OP: u8 = 0xcc;

+ 20 - 0
aml/src/parser.rs

@@ -273,6 +273,26 @@ where
     }
 }
 
+/// `extract` observes another parser consuming part of the stream, and returns the result of the parser, and the
+/// section of the stream that was parsed by the parser. This is useful for re-parsing that section of the stream,
+/// which allows the result of a piece of AML to be reevaluated with a new context, for example.
+///
+/// Note that reparsing the stream is not idempotent - the context is changed by this parse.
+pub fn extract<'a, 'c, P, R>(parser: P) -> impl Parser<'a, 'c, (R, &'a [u8])>
+where
+    'c: 'a,
+    P: Parser<'a, 'c, R>,
+{
+    move |input, context: &'c mut AmlContext| {
+        let before = input;
+        let (after, context, result) = parser.parse(input, context)?;
+        let bytes_parsed = before.len() - after.len();
+        let parsed = &before[..bytes_parsed];
+
+        Ok((after, context, (result, parsed)))
+    }
+}
+
 pub struct Or<'a, 'c, P1, P2, R>
 where
     'c: 'a,

+ 64 - 1
aml/src/type1.rs

@@ -3,6 +3,7 @@ use crate::{
     parser::{
         choice,
         comment_scope,
+        extract,
         id,
         take,
         take_to_end_of_pkglength,
@@ -32,7 +33,7 @@ where
     comment_scope(
         DebugVerbosity::AllScopes,
         "Type1Opcode",
-        choice!(def_breakpoint(), def_fatal(), def_if_else(), def_noop(), def_return()),
+        choice!(def_breakpoint(), def_fatal(), def_if_else(), def_noop(), def_return(), def_while()),
     )
 }
 
@@ -102,6 +103,7 @@ where
                             pkg_length().feed(|length| take_to_end_of_pkglength(length))
                         ))
                         .map(|((), else_branch): ((), &[u8])| Ok(else_branch)),
+                    // TODO: can this be `id().map(&[])`?
                     |input, context| -> ParseResult<'a, 'c, &[u8]> {
                         /*
                          * This path parses an DefIfElse that doesn't have an else branch. We simply
@@ -159,3 +161,64 @@ where
         ))
         .discard_result()
 }
+
+fn def_while<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * DefWhile := 0xa2 PkgLength Predicate TermList
+     * Predicate := TermArg => Integer (0 = false, >0 = true)
+     *
+     * Parsing this does something a little unusual - it 'extracts' the predicate when it's first parsed, which
+     * allows us to reevaluate it to see if we should break out of the while yet. This is required, to make sure
+     * we're observing changes to the context between the iterations of the loop.
+     */
+    opcode(opcode::DEF_WHILE_OP)
+        .then(comment_scope(
+            DebugVerbosity::Scopes,
+            "DefWhile",
+            pkg_length()
+                .then(extract(term_arg()))
+                .feed(move |(length, (first_predicate, predicate_stream))| {
+                    take_to_end_of_pkglength(length)
+                        .map(move |body| Ok((first_predicate.clone(), predicate_stream, body)))
+                })
+                .map_with_context(|(first_predicate, predicate_stream, body), mut context| {
+                    if !try_with_context!(context, first_predicate.as_bool()) {
+                        return (Ok(()), context);
+                    }
+
+                    loop {
+                        match term_list(PkgLength::from_raw_length(body, body.len() as u32).unwrap())
+                            .parse(body, context)
+                        {
+                            Ok((_, new_context, result)) => {
+                                context = new_context;
+                            }
+                            // TODO: differentiate real errors and special Propagates (e.g. break, continue, etc.)
+                            Err((_, context, err)) => return (Err(err), context),
+                        }
+
+                        // Reevaluate the predicate to see if we should break out of the loop yet
+                        let predicate =
+                            match comment_scope(DebugVerbosity::AllScopes, "WhilePredicate", term_arg())
+                                .parse(predicate_stream, context)
+                            {
+                                Ok((_, new_context, result)) => {
+                                    context = new_context;
+                                    try_with_context!(context, result.as_bool())
+                                }
+                                Err((_, context, err)) => return (Err(err), context),
+                            };
+
+                        if !predicate {
+                            break;
+                        }
+                    }
+
+                    (Ok(()), context)
+                }),
+        ))
+        .discard_result()
+}