Browse Source

Merge #40

40: AML r=IsaacWoods a=IsaacWoods

TODO:
- [x] Add attribution note to Bodil's combinators blog post, and general parser documentation
- [x] Investigate representing `Map` as a concrete type and implementing it on `Parser`
- [x] Parse `PkgLength`
- [x] Parse `NameString`
- [x] Parse `TermList`
- [x] Parse `TermObj`
- [x] Parse `DefScope`
- [x] Parse `DefOpRegion`
- [x] Parse `DefField`
- [x] Parse `DefMethod`
- [x] Parse `DefDevice`
- [x] Parse `DefName`
- [x] Parse `DefBuffer`
- [x] Create abstraction and good documentation around hack to avoid recursion limit problem
- [x] Parse `DefPackage`
- [x] Parse `DefProcessor`
- [x] Parse `DefMutex`
- [x] Redo how we represent name objects
- [x] Keep track of scope
- [x] Register stuff in the namespace as we parse it
- [x] Add features to control how much parser debug output is produced

There's also something funky going on with error reporting. Not sure if it's the actual error generation or propagation up the combinators.

Co-authored-by: Isaac Woods <isaacwoods.home@gmail.com>
bors[bot] 5 years ago
parent
commit
2ceea000a4

+ 2 - 2
.travis.yml

@@ -25,8 +25,8 @@ before_script:
 
 script:
   - cargo fmt --all -- --check
-  - cargo build
-  - cargo test
+  - cargo build --all
+  - cargo test --all
 
 notifications:
   email: change

+ 18 - 6
acpi/src/fadt.rs

@@ -1,4 +1,12 @@
-use crate::{sdt::SdtHeader, Acpi, AcpiError, AcpiHandler, GenericAddress, PhysicalMapping};
+use crate::{
+    sdt::SdtHeader,
+    Acpi,
+    AcpiError,
+    AcpiHandler,
+    AmlTable,
+    GenericAddress,
+    PhysicalMapping,
+};
 
 type ExtendedField<T> = crate::sdt::ExtendedField<T, typenum::U2>;
 
@@ -84,15 +92,19 @@ where
     let fadt = &*mapping;
     fadt.header.validate(b"FACP")?;
 
-    unsafe {
-        acpi.dsdt_address = fadt
-            .x_dsdt_address
+    let dsdt_address = unsafe {
+        fadt.x_dsdt_address
             .get(fadt.header.revision())
             .filter(|p| *p != 0)
             .or(Some(fadt.dsdt_address as u64))
             .filter(|p| *p != 0)
-            .map(|p| p as usize);
-    }
+            .map(|p| p as usize)
+    };
+
+    acpi.dsdt = dsdt_address.map(|address| {
+        let dsdt_header = crate::sdt::peek_at_sdt_header(handler, address);
+        AmlTable::new(address, dsdt_header.length())
+    });
 
     Ok(())
 }

+ 0 - 1
acpi/src/hpet.rs

@@ -43,7 +43,6 @@ pub(crate) fn parse_hpet(
     // Make sure the HPET's in system memory
     assert_eq!(hpet.base_address.address_space, 0);
 
-    info!("HPET address: {:#?}", hpet.base_address);
     acpi.hpet = Some(HpetInfo {
         event_timer_block_id: hpet.event_timer_block_id,
         base_address: hpet.base_address.address as usize,

+ 36 - 49
acpi/src/lib.rs

@@ -13,10 +13,10 @@
 //! memory into the virtual address space.
 //!
 //! You should then call one of the entry points, based on how much information you have:
-//!     * Call `parse_rsdp` if you have the physical address of the RSDP
-//!     * Call `parse_rsdt` if you have the physical address of the RSDT / XSDT
-//!     * Call `search_for_rsdp_bios` if you don't have the address of either structure, but **you
-//!     know you're running on BIOS, not UEFI**
+//! * Call `parse_rsdp` if you have the physical address of the RSDP
+//! * Call `parse_rsdt` if you have the physical address of the RSDT / XSDT
+//! * Call `search_for_rsdp_bios` if you don't have the address of either structure, but **you know
+//! you're running on BIOS, not UEFI**
 //!
 //! All of these methods return an instance of `Acpi`. This struct contains all the information
 //! gathered from the static tables, and can be queried to set up hardware etc.
@@ -26,16 +26,11 @@
 #![feature(alloc)]
 #![feature(exclusive_range_pattern)]
 
+extern crate alloc;
 #[cfg_attr(test, macro_use)]
 #[cfg(test)]
 extern crate std;
 
-#[macro_use]
-extern crate log;
-extern crate alloc;
-extern crate bit_field;
-extern crate typenum;
-
 mod fadt;
 pub mod handler;
 mod hpet;
@@ -109,54 +104,46 @@ pub struct Processor {
     pub is_ap: bool,
 }
 
-impl Processor {
-    pub(crate) fn new(
-        processor_uid: u8,
-        local_apic_id: u8,
-        state: ProcessorState,
-        is_ap: bool,
-    ) -> Processor {
-        Processor { processor_uid, local_apic_id, state, is_ap }
+#[derive(Debug)]
+pub struct AmlTable {
+    /// Physical address of the start of the AML stream (excluding the table header).
+    pub address: usize,
+    /// Length (in bytes) of the AML stream.
+    pub length: u32,
+}
+
+impl AmlTable {
+    /// Create an `AmlTable` from the address and length of the table **including the SDT header**.
+    pub(crate) fn new(address: usize, length: u32) -> AmlTable {
+        AmlTable {
+            address: address + mem::size_of::<SdtHeader>(),
+            length: length - mem::size_of::<SdtHeader>() as u32,
+        }
     }
 }
 
 #[derive(Debug)]
 pub struct Acpi {
-    acpi_revision: u8,
-    boot_processor: Option<Processor>,
-    application_processors: Vec<Processor>,
+    pub acpi_revision: u8,
+
+    /// The boot processor. Until you bring up any APs, this is the only processor running code.
+    pub boot_processor: Option<Processor>,
+
+    /// Application processes. These are not brought up until you do so, and must be brought up in
+    /// the order they appear in this list.
+    pub application_processors: Vec<Processor>,
 
     /// ACPI theoretically allows for more than one interrupt model to be supported by the same
     /// hardware. For simplicity and because hardware practically will only support one model, we
     /// just error in cases that the tables detail more than one.
-    interrupt_model: Option<InterruptModel>,
-    hpet: Option<HpetInfo>,
-
-    /// The physical address of the DSDT, if we manage to find it.
-    dsdt_address: Option<usize>,
-
-    /// The physical addresses of the SSDTs, if there are any,
-    ssdt_addresses: Vec<usize>,
-}
+    pub interrupt_model: Option<InterruptModel>,
+    pub hpet: Option<HpetInfo>,
 
-impl Acpi {
-    /// A description of the boot processor. Until you bring any more up, this is the only processor
-    /// running code, even on SMP systems.
-    pub fn boot_processor<'a>(&'a self) -> &'a Option<Processor> {
-        &self.boot_processor
-    }
-
-    /// Descriptions of each of the application processors. These are not brought up until you do
-    /// so. The application processors must be brought up in the order that they appear in this
-    /// list.
-    pub fn application_processors<'a>(&'a self) -> &'a Vec<Processor> {
-        &self.application_processors
-    }
+    /// Info about the DSDT, if we find it.
+    pub dsdt: Option<AmlTable>,
 
-    /// The interrupt model supported by this system.
-    pub fn interrupt_model<'a>(&'a self) -> &'a Option<InterruptModel> {
-        &self.interrupt_model
-    }
+    /// Info about any SSDTs, if there are any.
+    pub ssdts: Vec<AmlTable>,
 }
 
 /// This is the entry point of `acpi` if you have the **physical** address of the RSDP. It maps
@@ -219,8 +206,8 @@ where
         application_processors: Vec::new(),
         interrupt_model: None,
         hpet: None,
-        dsdt_address: None,
-        ssdt_addresses: Vec::with_capacity(0),
+        dsdt: None,
+        ssdts: Vec::with_capacity(0),
     };
 
     let header = sdt::peek_at_sdt_header(handler, physical_address);

+ 6 - 1
acpi/src/madt.rs

@@ -427,7 +427,12 @@ fn parse_apic_model(
                     (false, false) => ProcessorState::Running,
                 };
 
-                let processor = Processor::new(entry.processor_id, entry.apic_id, state, is_ap);
+                let processor = Processor {
+                    processor_uid: entry.processor_id,
+                    local_apic_id: entry.apic_id,
+                    state,
+                    is_ap,
+                };
 
                 if is_ap {
                     acpi.application_processors.push(processor);

+ 1 - 0
acpi/src/rsdp_search.rs

@@ -1,5 +1,6 @@
 use crate::{parse_validated_rsdp, rsdp::Rsdp, Acpi, AcpiError, AcpiHandler};
 use core::{mem, ops::RangeInclusive};
+use log::warn;
 
 /// The pointer to the EBDA (Extended Bios Data Area) start segment pointer
 const EBDA_START_SEGMENT_PTR: usize = 0x40e;

+ 3 - 7
acpi/src/sdt.rs

@@ -1,10 +1,10 @@
-use crate::{fadt::Fadt, hpet::HpetTable, madt::Madt, Acpi, AcpiError, AcpiHandler};
+use crate::{fadt::Fadt, hpet::HpetTable, madt::Madt, Acpi, AcpiError, AcpiHandler, AmlTable};
 use core::{marker::PhantomData, mem, str};
+use log::{trace, warn};
 use typenum::Unsigned;
 
 /// A union that represents a field that is not necessarily present and is only present in a later
 /// ACPI version (represented by the compile time number and type parameter `R`)
-#[allow(dead_code)]
 #[derive(Copy, Clone)]
 #[repr(C, packed)]
 pub union ExtendedField<T: Copy, R: Unsigned> {
@@ -14,10 +14,6 @@ pub union ExtendedField<T: Copy, R: Unsigned> {
 }
 
 impl<T: Copy, R: Unsigned> ExtendedField<T, R> {
-    fn new(field: T) -> Self {
-        ExtendedField { field }
-    }
-
     /// Checks that the given revision is greater than `R` (typenum revision number) and then
     /// returns the field, otherwise returning `None`.
     ///
@@ -202,7 +198,7 @@ where
             handler.unmap_physical_region(madt_mapping);
         }
 
-        "SSDT" => acpi.ssdt_addresses.push(physical_address),
+        "SSDT" => acpi.ssdts.push(AmlTable::new(physical_address, header.length())),
 
         signature => {
             /*

+ 6 - 0
aml_parser/Cargo.toml

@@ -10,3 +10,9 @@ license = "MIT/Apache-2.0"
 edition = "2018"
 
 [dependencies]
+log = "0.4"
+bit_field = "0.9"
+
+[features]
+debug_parser = []
+debug_parser_verbose = []

+ 121 - 0
aml_parser/src/lib.rs

@@ -1 +1,122 @@
+//! `aml_parser` is a pure-Rust AML (ACPI Machine Language) parser, used for parsing the DSDT and
+//! SSDT tables from ACPI. This crate can be used by kernels to gather information about the
+//! hardware, and invoke control methods (this is not yet supported) to query and change the state
+//! of devices in a hardware-independent way.
+//!
+//! ### Using the library
+//! To use the library, you will mostly interact with the `AmlContext` type. You should create an
+//! instance of this type using `AmlContext::new()`, and then pass it tables containing AML
+//! (probably from the `acpi` crate), which you've mapped into the virtual address space. This will
+//! parse the table, populating the namespace with objects encoded by the AML. After this, you may
+//! unmap the memory the table was mapped into - all the information needed will be extracted and
+//! allocated on the heap.
+//!
+//! ### About the parser
+//! The parser is written using a set of custom parser combinators - the code can be confusing on
+//! first reading, but provides an extensible and type-safe way to write parsers. For an easy
+//! introduction to parser combinators and the foundations used for this library, I suggest reading
+//! [Bodil's fantastic blog post](https://bodil.lol/parser-combinators/).
+//!
+//! The actual combinators can be found in `parser.rs`. Various tricks are used to provide a nice
+//! API and work around limitations in the type system, such as the concrete types like
+//! `MapWithContext`, and the `make_parser_concrete` hack macro.
+//!
+//! The actual parsers are then grouped into categories based loosely on the AML grammar sections in
+//! the ACPI spec. Most are written in terms of combinators, but some have to be written in a more
+//! imperitive style, either because they're clearer, or because we haven't yet found good
+//! combinator patterns to express the parse.
+
 #![no_std]
+#![feature(decl_macro, type_ascription, box_syntax)]
+
+extern crate alloc;
+
+#[cfg(test)]
+extern crate std;
+
+#[cfg(test)]
+mod test_utils;
+
+pub(crate) mod name_object;
+pub(crate) mod opcode;
+pub(crate) mod parser;
+pub(crate) mod pkg_length;
+pub(crate) mod term_object;
+pub mod value;
+
+pub use crate::value::AmlValue;
+
+use alloc::collections::BTreeMap;
+use log::error;
+use name_object::AmlName;
+use parser::Parser;
+use pkg_length::PkgLength;
+
+/// AML has a `RevisionOp` operator that returns the "AML interpreter revision". It's not clear
+/// what this is actually used for, but this is ours.
+pub const AML_INTERPRETER_REVISION: u64 = 0;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum AmlError {
+    UnexpectedEndOfStream,
+    UnexpectedByte(u8),
+    InvalidNameSeg([u8; 4]),
+    InvalidFieldFlags,
+    IncompatibleValueConversion,
+    UnterminatedStringConstant,
+    InvalidStringConstant,
+    InvalidRegionSpace(u8),
+    /// Error produced when none of the parsers in a `choice!` could parse the next part of the
+    /// stream.
+    NoParsersCouldParse,
+}
+
+#[derive(Debug)]
+pub struct AmlContext {
+    namespace: BTreeMap<AmlName, AmlValue>,
+    current_scope: AmlName,
+}
+
+impl AmlContext {
+    pub fn new() -> AmlContext {
+        AmlContext { namespace: BTreeMap::new(), current_scope: AmlName::root() }
+    }
+
+    pub fn parse_table(&mut self, stream: &[u8]) -> Result<(), AmlError> {
+        if stream.len() == 0 {
+            return Err(AmlError::UnexpectedEndOfStream);
+        }
+
+        let table_length = PkgLength::from_raw_length(stream, stream.len() as u32) as PkgLength;
+        match term_object::term_list(table_length).parse(stream, self) {
+            Ok(_) => Ok(()),
+            Err((remaining, _context, err)) => {
+                error!("Failed to parse AML stream. Err = {:?}", err);
+                Err(err)
+            }
+        }
+    }
+
+    /// Resolves a given path relative to the current scope (if the given path is not absolute).
+    /// The returned path can be used to index the namespace.
+    pub fn resolve_path(&mut self, path: &AmlName) -> AmlName {
+        // TODO: we should normalize the path by resolving prefix chars etc.
+
+        // If the path is absolute, just return it.
+        if path.is_absolute() {
+            return path.clone();
+        }
+
+        // Otherwise, it's relative to the current scope so append it onto that.
+        let mut new_path = self.current_scope.clone();
+        new_path.0.extend_from_slice(&(path.0));
+        new_path
+    }
+
+    /// Add an `AmlValue` to the namespace. `path` can either be absolute, or relative (in which
+    /// case it's treated as relative to the current scope).
+    pub fn add_to_namespace(&mut self, path: AmlName, value: AmlValue) {
+        let resolved_path = self.resolve_path(&path);
+        self.namespace.insert(resolved_path, value);
+    }
+}

+ 246 - 0
aml_parser/src/name_object.rs

@@ -0,0 +1,246 @@
+use crate::{
+    opcode::{opcode, DUAL_NAME_PREFIX, MULTI_NAME_PREFIX, NULL_NAME, PREFIX_CHAR, ROOT_CHAR},
+    parser::{choice, comment_scope_verbose, consume, n_of, take, Parser},
+    AmlContext,
+    AmlError,
+};
+use alloc::{
+    string::{String, ToString},
+    vec::Vec,
+};
+use core::{fmt, str};
+
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
+pub struct AmlName(pub(crate) Vec<NameComponent>);
+
+impl AmlName {
+    pub fn root() -> AmlName {
+        AmlName(alloc::vec![NameComponent::Root])
+    }
+
+    pub fn from_name_seg(seg: NameSeg) -> AmlName {
+        AmlName(alloc::vec![NameComponent::Segment(seg)])
+    }
+
+    pub fn as_string(&self) -> String {
+        self.0
+            .iter()
+            .fold(String::new(), |name, component| match component {
+                NameComponent::Root => name + "\\",
+                NameComponent::Prefix => name + "^",
+                NameComponent::Segment(seg) => name + seg.as_str() + ".",
+            })
+            .trim_end_matches('.')
+            .to_string()
+    }
+
+    pub fn is_absolute(&self) -> bool {
+        self.0.first() == Some(&NameComponent::Root)
+    }
+}
+
+impl fmt::Display for AmlName {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}", self.as_string())
+    }
+}
+
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
+pub enum NameComponent {
+    Root,
+    Prefix,
+    Segment(NameSeg),
+}
+
+pub fn name_string<'a, 'c>() -> impl Parser<'a, 'c, AmlName>
+where
+    'c: 'a,
+{
+    /*
+     * NameString := <RootChar('\') NamePath> | <PrefixPath NamePath>
+     * PrefixPath := Nothing | <'^' PrefixPath>
+     */
+    let root_name_string = opcode(ROOT_CHAR).then(name_path()).map(|((), ref name_path)| {
+        let mut name = alloc::vec![NameComponent::Root];
+        name.extend_from_slice(name_path);
+        Ok(AmlName(name))
+    });
+
+    // TODO: combinator to select a parser based on a peeked byte?
+    comment_scope_verbose("NameString", move |input: &'a [u8], context| {
+        let first_char = match input.first() {
+            Some(&c) => c,
+            None => return Err((input, context, AmlError::UnexpectedEndOfStream)),
+        };
+
+        // TODO: parse <PrefixPath NamePath> where there are actually PrefixChars
+        match first_char {
+            ROOT_CHAR => root_name_string.parse(input, context),
+            PREFIX_CHAR => unimplemented!(),
+            _ => name_path().map(|path| Ok(AmlName(path))).parse(input, context),
+        }
+    })
+}
+
+pub fn name_path<'a, 'c>() -> impl Parser<'a, 'c, Vec<NameComponent>>
+where
+    'c: 'a,
+{
+    /*
+     * NamePath := NullName | DualNamePath | MultiNamePath | NameSeg
+     */
+    choice!(
+        null_name(),
+        dual_name_path(),
+        multi_name_path(),
+        name_seg().map(|seg| Ok(alloc::vec![NameComponent::Segment(seg)]))
+    )
+}
+
+pub fn null_name<'a, 'c>() -> impl Parser<'a, 'c, Vec<NameComponent>>
+where
+    'c: 'a,
+{
+    /*
+     * NullName := 0x00
+     *
+     * This doesn't actually allocate because the `Vec`'s capacity is zero.
+     */
+    opcode(NULL_NAME).map(|_| Ok(Vec::with_capacity(0)))
+}
+
+pub fn dual_name_path<'a, 'c>() -> impl Parser<'a, 'c, Vec<NameComponent>>
+where
+    'c: 'a,
+{
+    /*
+     * DualNamePath := 0x2e NameSeg NameSeg
+     */
+    opcode(DUAL_NAME_PREFIX).then(name_seg()).then(name_seg()).map(|(((), first), second)| {
+        Ok(alloc::vec![NameComponent::Segment(first), NameComponent::Segment(second)])
+    })
+}
+
+pub fn multi_name_path<'a, 'c>() -> impl Parser<'a, 'c, Vec<NameComponent>>
+where
+    'c: 'a,
+{
+    /*
+     * MultiNamePath := 0x2f ByteData{SegCount} NameSeg(SegCount)
+     */
+    move |input, context| {
+        let (new_input, context, ((), seg_count)) =
+            opcode(MULTI_NAME_PREFIX).then(take()).parse(input, context)?;
+        match n_of(name_seg(), usize::from(seg_count)).parse(new_input, context) {
+            Ok((new_input, context, name_segs)) => Ok((
+                new_input,
+                context,
+                name_segs.iter().map(|&seg| NameComponent::Segment(seg)).collect(),
+            )),
+            // Correct returned input to the one we haven't touched
+            Err((_, context, err)) => Err((input, context, err)),
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct NameSeg([u8; 4]);
+
+impl NameSeg {
+    /// Turn a `NameSeg` into a `&str`. Returns it in a `ParseResult` so it's easy to use from
+    /// inside parsers.
+    pub fn as_str(&self) -> &str {
+        /*
+         * This is safe, because we always check that all the bytes are valid ASCII, so every
+         * `NameSeg` will be valid UTF8.
+         */
+        unsafe { str::from_utf8_unchecked(&self.0) }
+    }
+}
+
+// A list of ASCII codes is pretty much never useful, so we always just show it as a string
+impl fmt::Debug for NameSeg {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self.as_str())
+    }
+}
+
+pub fn name_seg<'a, 'c>() -> impl Parser<'a, 'c, NameSeg>
+where
+    'c: 'a,
+{
+    /*
+     * NameSeg := <LeadNameChar NameChar NameChar NameChar>
+     */
+    // TODO: can we write this better?
+    move |input, context: &'c mut AmlContext| {
+        let (input, context, char_1) = consume(is_lead_name_char).parse(input, context)?;
+        let (input, context, char_2) = consume(is_name_char).parse(input, context)?;
+        let (input, context, char_3) = consume(is_name_char).parse(input, context)?;
+        let (input, context, char_4) = consume(is_name_char).parse(input, context)?;
+        Ok((input, context, NameSeg([char_1, char_2, char_3, char_4])))
+    }
+}
+
+fn is_lead_name_char(byte: u8) -> bool {
+    (byte >= b'A' && byte <= b'Z') || byte == b'_'
+}
+
+fn is_digit_char(byte: u8) -> bool {
+    byte >= b'0' && byte <= b'9'
+}
+
+fn is_name_char(byte: u8) -> bool {
+    is_lead_name_char(byte) || is_digit_char(byte)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{parser::Parser, test_utils::*, AmlContext, AmlError};
+
+    #[test]
+    fn test_name_seg() {
+        let mut context = AmlContext::new();
+
+        check_ok!(
+            name_seg().parse(&[b'A', b'F', b'3', b'Z'], &mut context),
+            NameSeg([b'A', b'F', b'3', b'Z']),
+            &[]
+        );
+        check_ok!(
+            name_seg().parse(&[b'A', b'F', b'3', b'Z', 0xff], &mut context),
+            NameSeg([b'A', b'F', b'3', b'Z']),
+            &[0xff]
+        );
+        check_err!(
+            name_seg().parse(&[0xff, b'E', b'A', b'7'], &mut context),
+            AmlError::UnexpectedByte(0xff),
+            &[0xff, b'E', b'A', b'7']
+        );
+        check_err!(name_seg().parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]);
+    }
+
+    #[test]
+    fn test_name_path() {
+        let mut context = AmlContext::new();
+
+        check_err!(name_path().parse(&[], &mut context), AmlError::NoParsersCouldParse, &[]);
+        check_ok!(name_path().parse(&[0x00], &mut context), alloc::vec![], &[]);
+        check_ok!(name_path().parse(&[0x00, 0x00], &mut context), alloc::vec![], &[0x00]);
+        check_err!(
+            name_path().parse(&[0x2e, b'A'], &mut context),
+            AmlError::NoParsersCouldParse,
+            &[0x2e, b'A']
+        );
+        check_ok!(
+            name_path()
+                .parse(&[0x2e, b'A', b'B', b'C', b'D', b'E', b'_', b'F', b'G'], &mut context),
+            alloc::vec![
+                NameComponent::Segment(NameSeg([b'A', b'B', b'C', b'D'])),
+                NameComponent::Segment(NameSeg([b'E', b'_', b'F', b'G']))
+            ],
+            &[]
+        );
+    }
+}

+ 101 - 0
aml_parser/src/opcode.rs

@@ -0,0 +1,101 @@
+use crate::{parser::*, AmlContext, AmlError};
+
+pub const NULL_NAME: u8 = 0x00;
+pub const DUAL_NAME_PREFIX: u8 = 0x2E;
+pub const MULTI_NAME_PREFIX: u8 = 0x2F;
+pub const ROOT_CHAR: u8 = b'\\';
+pub const PREFIX_CHAR: u8 = b'^';
+
+pub const RESERVED_FIELD: u8 = 0x00;
+pub const ACCESS_FIELD: u8 = 0x01;
+pub const CONNECT_FIELD: u8 = 0x02;
+pub const EXTENDED_ACCESS_FIELD: u8 = 0x03;
+
+pub const ZERO_OP: u8 = 0x00;
+pub const ONE_OP: u8 = 0x01;
+pub const ONES_OP: u8 = 0xff;
+pub const BYTE_CONST: u8 = 0x0a;
+pub const WORD_CONST: u8 = 0x0b;
+pub const DWORD_CONST: u8 = 0x0c;
+pub const STRING_PREFIX: u8 = 0x0d;
+pub const QWORD_CONST: u8 = 0x0e;
+
+pub const DEF_NAME_OP: u8 = 0x08;
+pub const DEF_SCOPE_OP: u8 = 0x10;
+pub const DEF_BUFFER_OP: u8 = 0x11;
+pub const DEF_PACKAGE_OP: u8 = 0x12;
+pub const DEF_METHOD_OP: u8 = 0x14;
+pub const EXT_DEF_MUTEX_OP: u8 = 0x01;
+pub const EXT_REVISION_OP: u8 = 0x30;
+pub const EXT_DEF_OP_REGION_OP: u8 = 0x80;
+pub const EXT_DEF_FIELD_OP: u8 = 0x81;
+pub const EXT_DEF_DEVICE_OP: u8 = 0x82;
+pub const EXT_DEF_PROCESSOR_OP: u8 = 0x83;
+
+pub const EXT_OPCODE_PREFIX: u8 = 0x5b;
+
+pub(crate) fn opcode<'a, 'c>(opcode: u8) -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    move |input: &'a [u8], context: &'c mut AmlContext| match input.first() {
+        None => Err((input, context, AmlError::UnexpectedEndOfStream)),
+        Some(&byte) if byte == opcode => Ok((&input[1..], context, ())),
+        Some(&byte) => Err((input, context, AmlError::UnexpectedByte(byte))),
+    }
+}
+
+pub(crate) fn ext_opcode<'a, 'c>(ext_opcode: u8) -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    opcode(EXT_OPCODE_PREFIX).then(opcode(ext_opcode)).discard_result()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{test_utils::*, AmlError};
+
+    #[test]
+    fn empty() {
+        let mut context = AmlContext::new();
+        check_err!(
+            opcode(NULL_NAME).parse(&[], &mut context),
+            AmlError::UnexpectedEndOfStream,
+            &[]
+        );
+        check_err!(
+            ext_opcode(EXT_DEF_FIELD_OP).parse(&[], &mut context),
+            AmlError::UnexpectedEndOfStream,
+            &[]
+        );
+    }
+
+    #[test]
+    fn simple_opcodes() {
+        let mut context = AmlContext::new();
+        check_ok!(opcode(DEF_SCOPE_OP).parse(&[DEF_SCOPE_OP], &mut context), (), &[]);
+        check_ok!(
+            opcode(DEF_NAME_OP).parse(&[DEF_NAME_OP, 0x31, 0x55, 0xf3], &mut context),
+            (),
+            &[0x31, 0x55, 0xf3]
+        );
+    }
+
+    #[test]
+    fn extended_opcodes() {
+        let mut context = AmlContext::new();
+        check_err!(
+            ext_opcode(EXT_DEF_FIELD_OP).parse(&[EXT_DEF_FIELD_OP, EXT_DEF_FIELD_OP], &mut context),
+            AmlError::UnexpectedByte(EXT_DEF_FIELD_OP),
+            &[EXT_DEF_FIELD_OP, EXT_DEF_FIELD_OP]
+        );
+        check_ok!(
+            ext_opcode(EXT_DEF_FIELD_OP)
+                .parse(&[EXT_OPCODE_PREFIX, EXT_DEF_FIELD_OP], &mut context),
+            (),
+            &[]
+        );
+    }
+}

+ 489 - 0
aml_parser/src/parser.rs

@@ -0,0 +1,489 @@
+use crate::{pkg_length::PkgLength, AmlContext, AmlError};
+use alloc::vec::Vec;
+use core::marker::PhantomData;
+
+pub type ParseResult<'a, 'c, R> =
+    Result<(&'a [u8], &'c mut AmlContext, R), (&'a [u8], &'c mut AmlContext, AmlError)>;
+
+pub trait Parser<'a, 'c, R>: Sized
+where
+    'c: 'a,
+{
+    fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, R>;
+
+    fn map<F, A>(self, map_fn: F) -> Map<'a, 'c, Self, F, R, A>
+    where
+        F: Fn(R) -> Result<A, AmlError>,
+    {
+        Map { parser: self, map_fn, _phantom: PhantomData }
+    }
+
+    fn map_with_context<F, A>(self, map_fn: F) -> MapWithContext<'a, 'c, Self, F, R, A>
+    where
+        F: Fn(R, &'c mut AmlContext) -> (Result<A, AmlError>, &'c mut AmlContext),
+    {
+        MapWithContext { parser: self, map_fn, _phantom: PhantomData }
+    }
+
+    fn discard_result(self) -> DiscardResult<'a, 'c, Self, R> {
+        DiscardResult { parser: self, _phantom: PhantomData }
+    }
+
+    /// Try parsing with `self`. If it fails, try parsing with `other`, returning the result of the
+    /// first of the two parsers to succeed. To `or` multiple parsers ergonomically, see the
+    /// `choice!` macro.
+    fn or<OtherParser>(self, other: OtherParser) -> Or<'a, 'c, Self, OtherParser, R>
+    where
+        OtherParser: Parser<'a, 'c, R>,
+    {
+        Or { p1: self, p2: other, _phantom: PhantomData }
+    }
+
+    fn then<NextParser, NextR>(self, next: NextParser) -> Then<'a, 'c, Self, NextParser, R, NextR>
+    where
+        NextParser: Parser<'a, 'c, NextR>,
+    {
+        Then { p1: self, p2: next, _phantom: PhantomData }
+    }
+
+    /// `feed` takes a function that takes the result of this parser (`self`) and creates another
+    /// parser, which is then used to parse the next part of the stream. This sounds convoluted,
+    /// but is useful for when the next parser's behaviour depends on a property of the result of
+    /// the first (e.g. the first parser might parse a length `n`, and the second parser then
+    /// consumes `n` bytes).
+    fn feed<F, P2, R2>(self, producer_fn: F) -> Feed<'a, 'c, Self, P2, F, R, R2>
+    where
+        P2: Parser<'a, 'c, R2>,
+        F: Fn(R) -> P2,
+    {
+        Feed { parser: self, producer_fn, _phantom: PhantomData }
+    }
+}
+
+impl<'a, 'c, F, R> Parser<'a, 'c, R> for F
+where
+    'c: 'a,
+    F: Fn(&'a [u8], &'c mut AmlContext) -> ParseResult<'a, 'c, R>,
+{
+    fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, R> {
+        self(input, context)
+    }
+}
+
+pub fn take<'a, 'c>() -> impl Parser<'a, 'c, u8>
+where
+    'c: 'a,
+{
+    move |input: &'a [u8], context: &'c mut AmlContext| match input.first() {
+        Some(&byte) => Ok((&input[1..], context, byte)),
+        None => Err((input, context, AmlError::UnexpectedEndOfStream)),
+    }
+}
+
+pub fn take_u16<'a, 'c>() -> impl Parser<'a, 'c, u16>
+where
+    'c: 'a,
+{
+    move |input: &'a [u8], context: &'c mut AmlContext| {
+        if input.len() < 2 {
+            return Err((input, context, AmlError::UnexpectedEndOfStream));
+        }
+
+        Ok((&input[2..], context, input[0] as u16 + ((input[1] as u16) << 8)))
+    }
+}
+
+pub fn take_u32<'a, 'c>() -> impl Parser<'a, 'c, u32>
+where
+    'c: 'a,
+{
+    move |input: &'a [u8], context: &'c mut AmlContext| {
+        if input.len() < 4 {
+            return Err((input, context, AmlError::UnexpectedEndOfStream));
+        }
+
+        Ok((
+            &input[4..],
+            context,
+            input[0] as u32
+                + ((input[1] as u32) << 8)
+                + ((input[2] as u32) << 16)
+                + ((input[3] as u32) << 24),
+        ))
+    }
+}
+
+pub fn take_u64<'a, 'c>() -> impl Parser<'a, 'c, u64>
+where
+    'c: 'a,
+{
+    move |input: &'a [u8], context: &'c mut AmlContext| {
+        if input.len() < 8 {
+            return Err((input, context, AmlError::UnexpectedEndOfStream));
+        }
+
+        Ok((
+            &input[8..],
+            context,
+            input[0] as u64
+                + ((input[1] as u64) << 8)
+                + ((input[2] as u64) << 16)
+                + ((input[3] as u64) << 24)
+                + ((input[4] as u64) << 32)
+                + ((input[5] as u64) << 40)
+                + ((input[6] as u64) << 48)
+                + ((input[7] as u64) << 56),
+        ))
+    }
+}
+
+pub fn take_n<'a, 'c>(n: u32) -> impl Parser<'a, 'c, &'a [u8]>
+where
+    'c: 'a,
+{
+    move |input: &'a [u8], context| {
+        if (input.len() as u32) < n {
+            return Err((input, context, AmlError::UnexpectedEndOfStream));
+        }
+
+        let (result, new_input) = input.split_at(n as usize);
+        Ok((new_input, context, result))
+    }
+}
+
+pub fn take_to_end_of_pkglength<'a, 'c>(length: PkgLength) -> impl Parser<'a, 'c, &'a [u8]>
+where
+    'c: 'a,
+{
+    move |input: &'a [u8], context| {
+        let bytes_to_take = (input.len() as u32) - length.end_offset;
+        take_n(bytes_to_take).parse(input, context)
+    }
+}
+
+// TODO: can we use const generics (e.g. [R; N]) to avoid allocating?
+pub fn n_of<'a, 'c, P, R>(parser: P, n: usize) -> impl Parser<'a, 'c, Vec<R>>
+where
+    'c: 'a,
+    P: Parser<'a, 'c, R>,
+{
+    // TODO: can we write this more nicely?
+    move |mut input, mut context| {
+        let mut results = Vec::with_capacity(n);
+
+        for _ in 0..n {
+            let (new_input, new_context, result) = match parser.parse(input, context) {
+                Ok((input, context, result)) => (input, context, result),
+                Err((_, context, err)) => return Err((input, context, err)),
+            };
+            results.push(result);
+            input = new_input;
+            context = new_context;
+        }
+
+        Ok((input, context, results))
+    }
+}
+
+pub fn consume<'a, 'c, F>(condition: F) -> impl Parser<'a, 'c, u8>
+where
+    'c: 'a,
+    F: Fn(u8) -> bool,
+{
+    move |input: &'a [u8], context: &'c mut AmlContext| match input.first() {
+        Some(&byte) if condition(byte) => Ok((&input[1..], context, byte)),
+        Some(&byte) => Err((input, context, AmlError::UnexpectedByte(byte))),
+        None => Err((input, context, AmlError::UnexpectedEndOfStream)),
+    }
+}
+
+pub fn comment_scope<'a, 'c, P, R>(scope_name: &'a str, parser: P) -> impl Parser<'a, 'c, R>
+where
+    'c: 'a,
+    R: core::fmt::Debug,
+    P: Parser<'a, 'c, R>,
+{
+    move |input, context| {
+        #[cfg(feature = "debug_parser")]
+        trace!("--> {}", scope_name);
+
+        // Return if the parse fails, so we don't print the tail. Makes it easier to debug.
+        let (new_input, context, result) = parser.parse(input, context)?;
+
+        #[cfg(feature = "debug_parser")]
+        trace!("<-- {}", scope_name);
+
+        Ok((new_input, context, result))
+    }
+}
+
+pub fn comment_scope_verbose<'a, 'c, P, R>(scope_name: &'a str, parser: P) -> impl Parser<'a, 'c, R>
+where
+    'c: 'a,
+    R: core::fmt::Debug,
+    P: Parser<'a, 'c, R>,
+{
+    move |input, context| {
+        #[cfg(feature = "debug_parser_verbose")]
+        trace!("--> {}", scope_name);
+
+        // Return if the parse fails, so we don't print the tail. Makes it easier to debug.
+        let (new_input, context, result) = parser.parse(input, context)?;
+
+        #[cfg(feature = "debug_parser_verbose")]
+        trace!("<-- {}", scope_name);
+
+        Ok((new_input, context, result))
+    }
+}
+
+pub struct Or<'a, 'c, P1, P2, R>
+where
+    'c: 'a,
+    P1: Parser<'a, 'c, R>,
+    P2: Parser<'a, 'c, R>,
+{
+    p1: P1,
+    p2: P2,
+    _phantom: PhantomData<(&'a R, &'c ())>,
+}
+
+impl<'a, 'c, P1, P2, R> Parser<'a, 'c, R> for Or<'a, 'c, P1, P2, R>
+where
+    'c: 'a,
+    P1: Parser<'a, 'c, R>,
+    P2: Parser<'a, 'c, R>,
+{
+    fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, R> {
+        let context = match self.p1.parse(input, context) {
+            Ok(parse_result) => return Ok(parse_result),
+            Err((_, context, _)) => context,
+        };
+
+        self.p2.parse(input, context)
+    }
+}
+
+pub struct Map<'a, 'c, P, F, R, A>
+where
+    'c: 'a,
+    P: Parser<'a, 'c, R>,
+    F: Fn(R) -> Result<A, AmlError>,
+{
+    parser: P,
+    map_fn: F,
+    _phantom: PhantomData<(&'a (R, A), &'c ())>,
+}
+
+impl<'a, 'c, P, F, R, A> Parser<'a, 'c, A> for Map<'a, 'c, P, F, R, A>
+where
+    'c: 'a,
+    P: Parser<'a, 'c, R>,
+    F: Fn(R) -> Result<A, AmlError>,
+{
+    fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, A> {
+        match self.parser.parse(input, context) {
+            Ok((new_input, context, result)) => match (self.map_fn)(result) {
+                Ok(result_value) => Ok((new_input, context, result_value)),
+                Err(err) => Err((input, context, err)),
+            },
+            Err(result) => Err(result),
+        }
+    }
+}
+
+pub struct MapWithContext<'a, 'c, P, F, R, A>
+where
+    'c: 'a,
+    P: Parser<'a, 'c, R>,
+    F: Fn(R, &'c mut AmlContext) -> (Result<A, AmlError>, &'c mut AmlContext),
+{
+    parser: P,
+    map_fn: F,
+    _phantom: PhantomData<(&'a (R, A), &'c ())>,
+}
+
+impl<'a, 'c, P, F, R, A> Parser<'a, 'c, A> for MapWithContext<'a, 'c, P, F, R, A>
+where
+    'c: 'a,
+    P: Parser<'a, 'c, R>,
+    F: Fn(R, &'c mut AmlContext) -> (Result<A, AmlError>, &'c mut AmlContext),
+{
+    fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, A> {
+        match self.parser.parse(input, context) {
+            Ok((new_input, context, result)) => match (self.map_fn)(result, context) {
+                (Ok(result_value), context) => Ok((new_input, context, result_value)),
+                (Err(err), context) => Err((input, context, err)),
+            },
+            Err(result) => Err(result),
+        }
+    }
+}
+
+pub struct DiscardResult<'a, 'c, P, R>
+where
+    'c: 'a,
+    P: Parser<'a, 'c, R>,
+{
+    parser: P,
+    _phantom: PhantomData<(&'a R, &'c ())>,
+}
+
+impl<'a, 'c, P, R> Parser<'a, 'c, ()> for DiscardResult<'a, 'c, P, R>
+where
+    'c: 'a,
+    P: Parser<'a, 'c, R>,
+{
+    fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, ()> {
+        self.parser
+            .parse(input, context)
+            .map(|(new_input, new_context, _)| (new_input, new_context, ()))
+    }
+}
+
+pub struct Then<'a, 'c, P1, P2, R1, R2>
+where
+    'c: 'a,
+    P1: Parser<'a, 'c, R1>,
+    P2: Parser<'a, 'c, R2>,
+{
+    p1: P1,
+    p2: P2,
+    _phantom: PhantomData<(&'a (R1, R2), &'c ())>,
+}
+
+impl<'a, 'c, P1, P2, R1, R2> Parser<'a, 'c, (R1, R2)> for Then<'a, 'c, P1, P2, R1, R2>
+where
+    'c: 'a,
+    P1: Parser<'a, 'c, R1>,
+    P2: Parser<'a, 'c, R2>,
+{
+    fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, (R1, R2)> {
+        self.p1.parse(input, context).and_then(|(next_input, context, result_a)| {
+            self.p2.parse(next_input, context).map(|(final_input, context, result_b)| {
+                (final_input, context, (result_a, result_b))
+            })
+        })
+    }
+}
+
+pub struct Feed<'a, 'c, P1, P2, F, R1, R2>
+where
+    'c: 'a,
+    P1: Parser<'a, 'c, R1>,
+    P2: Parser<'a, 'c, R2>,
+    F: Fn(R1) -> P2,
+{
+    parser: P1,
+    producer_fn: F,
+    _phantom: PhantomData<(&'a (R1, R2), &'c ())>,
+}
+
+impl<'a, 'c, P1, P2, F, R1, R2> Parser<'a, 'c, R2> for Feed<'a, 'c, P1, P2, F, R1, R2>
+where
+    'c: 'a,
+    P1: Parser<'a, 'c, R1>,
+    P2: Parser<'a, 'c, R2>,
+    F: Fn(R1) -> P2,
+{
+    fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, R2> {
+        let (input, context, first_result) = self.parser.parse(input, context)?;
+
+        // We can now produce the second parser, and parse using that.
+        let second_parser = (self.producer_fn)(first_result);
+        second_parser.parse(input, context)
+    }
+}
+
+pub(crate) fn emit_no_parsers_could_parse<'a, 'c, R>() -> impl Parser<'a, 'c, R>
+where
+    'c: 'a,
+{
+    |input: &'a [u8], context| Err((input, context, AmlError::NoParsersCouldParse))
+}
+
+/// Takes a number of parsers, and tries to apply each one to the input in order. Returns the
+/// result of the first one that succeeds, or fails if all of them fail.
+pub macro choice {
+    ($first_parser: expr) => {
+        $first_parser
+        .or(emit_no_parsers_could_parse())
+    },
+
+    ($first_parser: expr, $($other_parser: expr),*) => {
+        $first_parser
+        $(
+            .or($other_parser)
+         )*
+        .or(emit_no_parsers_could_parse())
+    }
+}
+
+/// This encapsulates an unfortunate hack we sometimes need to use, where the type checker gets
+/// caught in an infinite loop of parser types. This occurs when an object can indirectly contain
+/// itself, and so the parser type will contain its own type. This works by breaking the cycle of
+/// `impl Parser` chains that build up, by effectively creating a "concrete" closure type.
+///
+/// You can try using this hack if you are writing a parser and end up with an error of the form:
+/// `error[E0275]: overflow evaluating the requirement 'impl Parser<{a type}>'
+///     help: consider adding a a '#![recursion_limit="128"] attribute to your crate`
+/// Note: Increasing the recursion limit will not fix the issue, as the cycle will just continue
+/// until you either hit the new recursion limit or `rustc` overflows its stack.
+pub macro make_parser_concrete($parser: expr) {
+    |input, context| ($parser).parse(input, context)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test_utils::*;
+
+    #[test]
+    fn test_take_n() {
+        let mut context = AmlContext::new();
+        check_err!(take_n(1).parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]);
+        check_err!(
+            take_n(2).parse(&[0xf5], &mut context),
+            AmlError::UnexpectedEndOfStream,
+            &[0xf5]
+        );
+
+        check_ok!(take_n(1).parse(&[0xff], &mut context), &[0xff], &[]);
+        check_ok!(take_n(1).parse(&[0xff, 0xf8], &mut context), &[0xff], &[0xf8]);
+        check_ok!(take_n(2).parse(&[0xff, 0xf8], &mut context), &[0xff, 0xf8], &[]);
+    }
+
+    #[test]
+    fn test_take_ux() {
+        let mut context = AmlContext::new();
+        check_err!(
+            take_u16().parse(&[0x34], &mut context),
+            AmlError::UnexpectedEndOfStream,
+            &[0x34]
+        );
+        check_ok!(take_u16().parse(&[0x34, 0x12], &mut context), 0x1234, &[]);
+
+        check_err!(
+            take_u32().parse(&[0x34, 0x12], &mut context),
+            AmlError::UnexpectedEndOfStream,
+            &[0x34, 0x12]
+        );
+        check_ok!(
+            take_u32().parse(&[0x34, 0x12, 0xf4, 0xc3, 0x3e], &mut context),
+            0xc3f41234,
+            &[0x3e]
+        );
+
+        check_err!(
+            take_u64().parse(&[0x34], &mut context),
+            AmlError::UnexpectedEndOfStream,
+            &[0x34]
+        );
+        check_ok!(
+            take_u64()
+                .parse(&[0x34, 0x12, 0x35, 0x76, 0xd4, 0x43, 0xa3, 0xb6, 0xff, 0x00], &mut context),
+            0xb6a343d476351234,
+            &[0xff, 0x00]
+        );
+    }
+}

+ 139 - 0
aml_parser/src/pkg_length.rs

@@ -0,0 +1,139 @@
+use crate::{
+    parser::{take, take_n, Parser},
+    AmlContext,
+    AmlError,
+};
+use bit_field::BitField;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub struct PkgLength {
+    pub raw_length: u32,
+    /// The distance from the end of the structure this `PkgLength` refers to, and the end of the
+    /// stream.
+    pub end_offset: u32,
+}
+
+impl PkgLength {
+    pub fn from_raw_length(stream: &[u8], raw_length: u32) -> PkgLength {
+        PkgLength { raw_length, end_offset: stream.len() as u32 - raw_length }
+    }
+
+    /// Returns `true` if the given stream is still within the structure this `PkgLength` refers
+    /// to.
+    pub fn still_parsing(&self, stream: &[u8]) -> bool {
+        stream.len() as u32 > self.end_offset
+    }
+}
+
+pub fn pkg_length<'a, 'c>() -> impl Parser<'a, 'c, PkgLength>
+where
+    'c: 'a,
+{
+    move |input: &'a [u8], context: &'c mut AmlContext| {
+        let (new_input, context, raw_length) = raw_pkg_length().parse(input, context)?;
+
+        /*
+         * NOTE: we use the original input here, because `raw_length` includes the length of the
+         * `PkgLength`.
+         */
+        Ok((new_input, context, PkgLength::from_raw_length(input, raw_length)))
+    }
+}
+
+/// Parses a `PkgLength` and returns the *raw length*. If you want an instance of `PkgLength`, use
+/// `pkg_length` instead.
+pub fn raw_pkg_length<'a, 'c>() -> impl Parser<'a, 'c, u32>
+where
+    'c: 'a,
+{
+    /*
+     * PkgLength := PkgLeadByte |
+     * <PkgLeadByte ByteData> |
+     * <PkgLeadByte ByteData ByteData> |
+     * <PkgLeadByte ByteData ByteData ByteData>
+     *
+     * The length encoded by the PkgLength includes the number of bytes used to encode it.
+     */
+    move |input: &'a [u8], context: &'c mut AmlContext| {
+        let (new_input, context, lead_byte) = take().parse(input, context)?;
+        let byte_count = lead_byte.get_bits(6..8);
+
+        if byte_count == 0 {
+            let length = u32::from(lead_byte.get_bits(0..6));
+            return Ok((new_input, context, length));
+        }
+
+        let (new_input, context, length): (&[u8], &mut AmlContext, u32) =
+            match take_n(byte_count as u32).parse(new_input, context) {
+                Ok((new_input, context, bytes)) => {
+                    let initial_length = u32::from(lead_byte.get_bits(0..4));
+                    (
+                        new_input,
+                        context,
+                        bytes.iter().enumerate().fold(initial_length, |length, (i, &byte)| {
+                            length + (u32::from(byte) << (4 + i * 8))
+                        }),
+                    )
+                }
+
+                /*
+                 * The stream was too short. We return an error, making sure to return the
+                 * *original* stream (that we haven't consumed any of).
+                 */
+                Err((_, context, _)) => {
+                    return Err((input, context, AmlError::UnexpectedEndOfStream))
+                }
+            };
+
+        Ok((new_input, context, length))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{test_utils::*, AmlError};
+
+    fn test_correct_pkglength(stream: &[u8], expected_raw_length: u32, expected_leftover: &[u8]) {
+        let mut context = AmlContext::new();
+        check_ok!(
+            pkg_length().parse(stream, &mut context),
+            PkgLength::from_raw_length(stream, expected_raw_length),
+            &expected_leftover
+        );
+    }
+
+    #[test]
+    fn test_raw_pkg_length() {
+        let mut context = AmlContext::new();
+        check_ok!(raw_pkg_length().parse(&[0b01000101, 0x14], &mut context), 325, &[]);
+        check_ok!(raw_pkg_length().parse(&[0b01000111, 0x14, 0x46], &mut context), 327, &[0x46]);
+        check_ok!(raw_pkg_length().parse(&[0b10000111, 0x14, 0x46], &mut context), 287047, &[]);
+    }
+
+    #[test]
+    fn test_pkg_length() {
+        let mut context = AmlContext::new();
+        check_err!(pkg_length().parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]);
+        test_correct_pkglength(&[0x00], 0, &[]);
+        test_correct_pkglength(
+            &[0x05, 0xf5, 0x7f, 0x3e, 0x54, 0x03],
+            5,
+            &[0xf5, 0x7f, 0x3e, 0x54, 0x03],
+        );
+        check_err!(
+            pkg_length().parse(&[0b11000000, 0xff, 0x4f], &mut context),
+            AmlError::UnexpectedEndOfStream,
+            &[0b11000000, 0xff, 0x4f]
+        );
+    }
+
+    #[test]
+    #[should_panic]
+    fn not_enough_stream() {
+        /*
+         * TODO: Ideally, this shouldn't panic the parser, but return a `UnexpectedEndOfStream`.
+         */
+        test_correct_pkglength(&[0x05, 0xf5], 5, &[0xf5]);
+    }
+}

+ 633 - 0
aml_parser/src/term_object.rs

@@ -0,0 +1,633 @@
+use crate::{
+    name_object::{name_seg, name_string, AmlName},
+    opcode::{self, ext_opcode, opcode},
+    parser::{
+        choice,
+        comment_scope,
+        comment_scope_verbose,
+        make_parser_concrete,
+        take,
+        take_to_end_of_pkglength,
+        take_u16,
+        take_u32,
+        take_u64,
+        ParseResult,
+        Parser,
+    },
+    pkg_length::{pkg_length, PkgLength},
+    value::{AmlValue, FieldFlags, MethodFlags, RegionSpace},
+    AmlContext,
+    AmlError,
+};
+use alloc::{string::String, vec::Vec};
+use core::str;
+
+/// `TermList`s are usually found within explicit-length objects (so they have a `PkgLength`
+/// elsewhere in the structure), so this takes a number of bytes to parse.
+pub fn term_list<'a, 'c>(list_length: PkgLength) -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * TermList := Nothing | <TermObj TermList>
+     */
+    move |mut input: &'a [u8], mut context: &'c mut AmlContext| {
+        while list_length.still_parsing(input) {
+            let (new_input, new_context, ()) = term_object().parse(input, context)?;
+            input = new_input;
+            context = new_context;
+        }
+
+        Ok((input, context, ()))
+    }
+}
+
+pub fn term_object<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * TermObj := NamespaceModifierObj | NamedObj | Type1Opcode | Type2Opcode
+     */
+    comment_scope_verbose("TermObj", choice!(namespace_modifier(), named_obj()))
+}
+
+pub fn namespace_modifier<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * NamespaceModifierObj := DefAlias | DefName | DefScope
+     */
+    choice!(def_name(), def_scope())
+}
+
+pub fn named_obj<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * NamedObj := DefBankField | DefCreateBitField | DefCreateByteField | DefCreateDWordField |
+     *             DefCreateField | DefCreateQWordField | DefCreateWordField | DefDataRegion |
+     *             DefExternal | DefOpRegion | DefPowerRes | DefProcessor | DefThermalZone |
+     *             DefMethod | DefMutex
+     *
+     * XXX: DefMethod and DefMutex (at least) are not included in any rule in the AML grammar,
+     * but are defined in the NamedObj section so we assume they're part of NamedObj
+     */
+    comment_scope_verbose(
+        "NamedObj",
+        choice!(
+            def_op_region(),
+            def_field(),
+            def_method(),
+            def_device(),
+            def_processor(),
+            def_mutex()
+        ),
+    )
+}
+
+pub fn def_name<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * DefName := 0x08 NameString DataRefObject
+     */
+    opcode(opcode::DEF_NAME_OP)
+        .then(comment_scope(
+            "DefName",
+            name_string().then(data_ref_object()).map_with_context(
+                |(name, data_ref_object), context| {
+                    context.add_to_namespace(name, AmlValue::Name(box data_ref_object));
+                    (Ok(()), context)
+                },
+            ),
+        ))
+        .discard_result()
+}
+
+pub fn def_scope<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * DefScope := 0x10 PkgLength NameString TermList
+     */
+    opcode(opcode::DEF_SCOPE_OP)
+        .then(comment_scope(
+            "DefScope",
+            pkg_length()
+                .then(name_string())
+                .map_with_context(|(length, name), context| {
+                    let previous_scope = context.current_scope.clone();
+                    context.current_scope = context.resolve_path(&name);
+                    (Ok((length, name, previous_scope)), context)
+                })
+                .feed(|(pkg_length, name, previous_scope)| {
+                    term_list(pkg_length).map(move |_| Ok((name.clone(), previous_scope.clone())))
+                })
+                .map_with_context(|(name, previous_scope), context| {
+                    context.current_scope = previous_scope;
+                    (Ok(()), context)
+                }),
+        ))
+        .discard_result()
+}
+
+pub fn def_op_region<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * DefOpRegion := ExtOpPrefix 0x80 NameString RegionSpace RegionOffset RegionLen
+     * RegionSpace := ByteData (where 0x00      = SystemMemory
+     *                                0x01      = SystemIO
+     *                                0x02      = PciConfig
+     *                                0x03      = EmbeddedControl
+     *                                0x04      = SMBus
+     *                                0x05      = SystemCMOS
+     *                                0x06      = PciBarTarget
+     *                                0x07      = IPMI
+     *                                0x08      = GeneralPurposeIO
+     *                                0x09      = GenericSerialBus
+     *                                0x80-0xff = OEM Defined)
+     * ByteData := 0x00 - 0xff
+     * RegionOffset := TermArg => Integer
+     * RegionLen := TermArg => Integer
+     */
+    ext_opcode(opcode::EXT_DEF_OP_REGION_OP)
+        .then(comment_scope(
+            "DefOpRegion",
+            name_string().then(take()).then(term_arg()).then(term_arg()).map_with_context(
+                |(((name, space), offset), length), context| {
+                    let region = match space {
+                        0x00 => RegionSpace::SystemMemory,
+                        0x01 => RegionSpace::SystemIo,
+                        0x02 => RegionSpace::PciConfig,
+                        0x03 => RegionSpace::EmbeddedControl,
+                        0x04 => RegionSpace::SMBus,
+                        0x05 => RegionSpace::SystemCmos,
+                        0x06 => RegionSpace::PciBarTarget,
+                        0x07 => RegionSpace::IPMI,
+                        0x08 => RegionSpace::GeneralPurposeIo,
+                        0x09 => RegionSpace::GenericSerialBus,
+                        space @ 0x80..=0xff => RegionSpace::OemDefined(space),
+                        byte => return (Err(AmlError::InvalidRegionSpace(byte)), context),
+                    };
+                    let offset = match offset.as_integer() {
+                        Ok(offset) => offset,
+                        Err(err) => return (Err(err), context),
+                    };
+                    let length = match length.as_integer() {
+                        Ok(length) => length,
+                        Err(err) => return (Err(err), context),
+                    };
+
+                    context.add_to_namespace(name, AmlValue::OpRegion { region, offset, length });
+                    (Ok(()), context)
+                },
+            ),
+        ))
+        .discard_result()
+}
+
+pub fn def_field<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * DefField = ExtOpPrefix 0x81 PkgLength NameString FieldFlags FieldList
+     * FieldFlags := ByteData
+     */
+    ext_opcode(opcode::EXT_DEF_FIELD_OP)
+        .then(comment_scope(
+            "DefField",
+            pkg_length().then(name_string()).then(take()).feed(
+                |((list_length, region_name), flags)| {
+                    move |mut input: &'a [u8],
+                          mut context: &'c mut AmlContext|
+                          -> ParseResult<'a, 'c, ()> {
+                        /*
+                         * FieldList := Nothing | <FieldElement FieldList>
+                         */
+                        // TODO: can this pattern be expressed as a combinator
+                        let mut current_offset = 0;
+                        while list_length.still_parsing(input) {
+                            let (new_input, new_context, field_length) = field_element(
+                                region_name.clone(),
+                                FieldFlags::new(flags),
+                                current_offset,
+                            )
+                            .parse(input, context)?;
+                            input = new_input;
+                            context = new_context;
+                            current_offset += field_length;
+                        }
+
+                        Ok((input, context, ()))
+                    }
+                },
+            ),
+        ))
+        .discard_result()
+}
+
+/// Parses a `FieldElement`. Takes the current offset within the field list, and returns the length
+/// of the field element parsed.
+pub fn field_element<'a, 'c>(
+    region_name: AmlName,
+    flags: FieldFlags,
+    current_offset: u64,
+) -> impl Parser<'a, 'c, u64>
+where
+    'c: 'a,
+{
+    /*
+     * FieldElement := NamedField | ReservedField | AccessField | ExtendedAccessField |
+     *                 ConnectField
+     * NamedField := NameSeg PkgLength
+     * ReservedField := 0x00 PkgLength
+     * AccessField := 0x01 AccessType AccessAttrib
+     * ConnectField := <0x02 NameString> | <0x02 BufferData>
+     * ExtendedAccessField := 0x03 AccessType ExtendedAccessAttrib AccessLength
+     *
+     * AccessType := ByteData
+     * AccessAttrib := ByteData
+     *
+     * XXX: The spec says a ConnectField can be <0x02 BufferData>, but BufferData isn't an AML
+     * object (it seems to be defined in ASL). We treat BufferData as if it was encoded like
+     * DefBuffer, and this seems to work so far.
+     */
+    // TODO: parse ConnectField and ExtendedAccessField
+
+    /*
+     * Reserved fields shouldn't actually be added to the namespace; they seem to show gaps in
+     * the operation region that aren't used for anything.
+     */
+    let reserved_field = opcode(opcode::RESERVED_FIELD)
+        .then(pkg_length())
+        .map(|((), length)| Ok(length.raw_length as u64));
+
+    // TODO: work out what to do with an access field
+    // let access_field = opcode(opcode::ACCESS_FIELD)
+    //     .then(take())
+    //     .then(take())
+    //     .map_with_context(|(((), access_type), access_attrib), context| (Ok(    , context));
+
+    let named_field =
+        name_seg().then(pkg_length()).map_with_context(move |(name_seg, length), context| {
+            context.add_to_namespace(
+                AmlName::from_name_seg(name_seg),
+                AmlValue::Field {
+                    region: region_name.clone(),
+                    flags,
+                    offset: current_offset,
+                    length: length.raw_length as u64,
+                },
+            );
+
+            (Ok(length.raw_length as u64), context)
+        });
+
+    choice!(reserved_field, named_field)
+}
+
+pub fn def_method<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * DefMethod := 0x14 PkgLength NameString MethodFlags TermList
+     * MethodFlags := ByteData (where bits 0-2: ArgCount (0 to 7)
+     *                                bit 3: SerializeFlag (0 = Not Serialized, 1 = Serialized)
+     *                                bits 4-7: SyncLevel (0x00 to 0x0f))
+     */
+    opcode(opcode::DEF_METHOD_OP)
+        .then(comment_scope(
+            "DefMethod",
+            pkg_length()
+                .then(name_string())
+                .then(take())
+                .feed(|((length, name), flags)| {
+                    take_to_end_of_pkglength(length)
+                        .map(move |code| Ok((name.clone(), flags, code)))
+                })
+                .map_with_context(|(name, flags, code), context| {
+                    context.add_to_namespace(
+                        name,
+                        AmlValue::Method { flags: MethodFlags::new(flags), code: code.to_vec() },
+                    );
+                    (Ok(()), context)
+                }),
+        ))
+        .discard_result()
+}
+
+pub fn def_device<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * DefDevice := ExtOpPrefix 0x82 PkgLength NameString TermList
+     */
+    ext_opcode(opcode::EXT_DEF_DEVICE_OP)
+        .then(comment_scope(
+            "DefDevice",
+            pkg_length()
+                .then(name_string())
+                .map_with_context(|(length, name), context| {
+                    context.add_to_namespace(name.clone(), AmlValue::Device);
+
+                    let previous_scope = context.current_scope.clone();
+                    context.current_scope = context.resolve_path(&name);
+
+                    (Ok((length, previous_scope)), context)
+                })
+                .feed(|(length, previous_scope)| {
+                    term_list(length).map(move |_| Ok(previous_scope.clone()))
+                })
+                .map_with_context(|previous_scope, context| {
+                    context.current_scope = previous_scope;
+                    (Ok(()), context)
+                }),
+        ))
+        .discard_result()
+}
+
+pub fn def_processor<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * DefProcessor := ExtOpPrefix 0x83 PkgLength NameString ProcID PblkAddress PblkLen TermList
+     * ProcID := ByteData
+     * PblkAddress := DWordData
+     * PblkLen := ByteData
+     */
+    ext_opcode(opcode::EXT_DEF_PROCESSOR_OP)
+        .then(comment_scope(
+            "DefProcessor",
+            pkg_length()
+                .then(name_string())
+                .then(take())
+                .then(take_u32())
+                .then(take())
+                .feed(|((((pkg_length, name), proc_id), pblk_address), pblk_len)| {
+                    term_list(pkg_length)
+                        .map(move |_| Ok((name.clone(), proc_id, pblk_address, pblk_len)))
+                })
+                .map_with_context(|(name, id, pblk_address, pblk_len), context| {
+                    context
+                        .add_to_namespace(name, AmlValue::Processor { id, pblk_address, pblk_len });
+                    (Ok(()), context)
+                }),
+        ))
+        .discard_result()
+}
+
+pub fn def_mutex<'a, 'c>() -> impl Parser<'a, 'c, ()>
+where
+    'c: 'a,
+{
+    /*
+     * DefMutex := ExtOpPrefix 0x01 NameString SyncFlags
+     * SyncFlags := ByteData (where bits 0-3: SyncLevel
+     *                              bits 4-7: Reserved)
+     */
+    ext_opcode(opcode::EXT_DEF_MUTEX_OP)
+        .then(comment_scope(
+            "DefMutex",
+            name_string().then(take()).map_with_context(|(name, sync_level), context| {
+                context.add_to_namespace(name, AmlValue::Mutex { sync_level });
+                (Ok(()), context)
+            }),
+        ))
+        .discard_result()
+}
+
+pub fn def_buffer<'a, 'c>() -> impl Parser<'a, 'c, AmlValue>
+where
+    'c: 'a,
+{
+    /*
+     * DefBuffer := 0x11 PkgLength BufferSize ByteList
+     * BufferSize := TermArg => Integer
+     *
+     * XXX: The spec says that zero-length buffers (e.g. the PkgLength is 0) are illegal, but
+     * we've encountered them in QEMU-generated tables, so we return an empty buffer in these
+     * cases.
+     */
+    opcode(opcode::DEF_BUFFER_OP)
+        .then(comment_scope(
+            "DefBuffer",
+            pkg_length().then(term_arg()).feed(|(pkg_length, buffer_size)| {
+                take_to_end_of_pkglength(pkg_length)
+                    .map(move |bytes| Ok((bytes.to_vec(), buffer_size.as_integer()?)))
+            }),
+        ))
+        .map(|((), (bytes, buffer_size))| Ok(AmlValue::Buffer { bytes, size: buffer_size }))
+}
+
+pub fn def_package<'a, 'c>() -> impl Parser<'a, 'c, AmlValue>
+where
+    'c: 'a,
+{
+    /*
+     * DefPackage := 0x12 PkgLength NumElements PackageElementList
+     * NumElements := ByteData
+     * PackageElementList := Nothing | <PackageElement PackageElementList>
+     * PackageElement := DataRefObject | NameString
+     */
+    opcode(opcode::DEF_PACKAGE_OP)
+        .then(comment_scope(
+            "DefPackage",
+            pkg_length().then(take()).feed(|(pkg_length, num_elements)| {
+                move |mut input, mut context| {
+                    let mut package_contents = Vec::new();
+
+                    while pkg_length.still_parsing(input) {
+                        let (new_input, new_context, value) =
+                            package_element().parse(input, context)?;
+                        input = new_input;
+                        context = new_context;
+
+                        package_contents.push(value);
+                    }
+
+                    assert_eq!(package_contents.len(), num_elements as usize);
+                    Ok((input, context, AmlValue::Package(package_contents)))
+                }
+            }),
+        ))
+        .map(|((), package)| Ok(package))
+}
+
+pub fn package_element<'a, 'c>() -> impl Parser<'a, 'c, AmlValue>
+where
+    'c: 'a,
+{
+    choice!(data_ref_object(), name_string().map(|string| Ok(AmlValue::String(string.as_string()))))
+}
+
+pub fn term_arg<'a, 'c>() -> impl Parser<'a, 'c, AmlValue>
+where
+    'c: 'a,
+{
+    /*
+     * TermArg := Type2Opcode | DataObject | ArgObj | LocalObj
+     */
+    // TODO: this doesn't yet parse Term2Opcode, ArgObj, or LocalObj
+    comment_scope_verbose("TermArg", choice!(data_object()))
+}
+
+pub fn data_ref_object<'a, 'c>() -> impl Parser<'a, 'c, AmlValue>
+where
+    'c: 'a,
+{
+    /*
+     * DataRefObject := DataObject | ObjectReference | DDBHandle
+     */
+    comment_scope_verbose("DataRefObject", choice!(data_object()))
+}
+
+pub fn data_object<'a, 'c>() -> impl Parser<'a, 'c, AmlValue>
+where
+    'c: 'a,
+{
+    /*
+     * DataObject := DefPackage | DefVarPackage | ComputationalData
+     *
+     * The order of the parsers are important here, as DefPackage and DefVarPackage can be
+     * accidently parsed as ComputationalDatas.
+     */
+    // TODO: this doesn't yet parse DefVarPackage
+    comment_scope_verbose("DataObject", choice!(def_package(), computational_data()))
+}
+
+pub fn computational_data<'a, 'c>() -> impl Parser<'a, 'c, AmlValue>
+where
+    'c: 'a,
+{
+    /*
+     * ComputationalData := ByteConst | WordConst | DWordConst | QWordConst | String |
+     *                      ConstObj | RevisionOp | DefBuffer
+     * ByteConst := 0x0a ByteData
+     * WordConst := 0x0b WordData
+     * DWordConst := 0x0c DWordData
+     * QWordConst := 0x0e QWordData
+     * String := 0x0d AsciiCharList NullChar
+     * ConstObj := ZeroOp(0x00) | OneOp(0x01) | OnesOp(0xff)
+     * RevisionOp := ExtOpPrefix(0x5b) 0x30
+     */
+    let const_parser = |input: &'a [u8], context: &'c mut AmlContext| {
+        let string_parser = |input: &'a [u8], context| -> ParseResult<'a, 'c, AmlValue> {
+            /*
+             * Using `position` isn't very efficient here, but is probably fine because the
+             * strings are usually quite short.
+             */
+            let nul_position = match input.iter().position(|&c| c == b'\0') {
+                Some(position) => position,
+                None => return Err((input, context, AmlError::UnterminatedStringConstant)),
+            };
+
+            let string = String::from(match str::from_utf8(&input[0..nul_position]) {
+                Ok(string) => string,
+                Err(_) => return Err((input, context, AmlError::InvalidStringConstant)),
+            });
+
+            Ok((&input[(nul_position + 1)..], context, AmlValue::String(string)))
+        };
+
+        let (new_input, context, op) = take().parse(input, context)?;
+        match op {
+            opcode::BYTE_CONST => {
+                take().map(|value| Ok(AmlValue::Integer(value as u64))).parse(new_input, context)
+            }
+            opcode::WORD_CONST => take_u16()
+                .map(|value| Ok(AmlValue::Integer(value as u64)))
+                .parse(new_input, context),
+            opcode::DWORD_CONST => take_u32()
+                .map(|value| Ok(AmlValue::Integer(value as u64)))
+                .parse(new_input, context),
+            opcode::QWORD_CONST => {
+                take_u64().map(|value| Ok(AmlValue::Integer(value))).parse(new_input, context)
+            }
+            opcode::STRING_PREFIX => string_parser.parse(new_input, context),
+            opcode::ZERO_OP => Ok((new_input, context, AmlValue::Integer(0))),
+            opcode::ONE_OP => Ok((new_input, context, AmlValue::Integer(1))),
+            opcode::ONES_OP => Ok((new_input, context, AmlValue::Integer(u64::max_value()))),
+
+            _ => Err((input, context, AmlError::UnexpectedByte(op))),
+        }
+    };
+
+    comment_scope_verbose(
+        "ComputationalData",
+        choice!(
+            ext_opcode(opcode::EXT_REVISION_OP)
+                .map(|_| Ok(AmlValue::Integer(crate::AML_INTERPRETER_REVISION))),
+            const_parser,
+            make_parser_concrete!(def_buffer())
+        ),
+    )
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::test_utils::*;
+
+    #[test]
+    fn test_computational_data() {
+        let mut context = AmlContext::new();
+        check_ok!(
+            computational_data().parse(&[0x00, 0x34, 0x12], &mut context),
+            AmlValue::Integer(0),
+            &[0x34, 0x12]
+        );
+        check_ok!(
+            computational_data().parse(&[0x01, 0x18, 0xf3], &mut context),
+            AmlValue::Integer(1),
+            &[0x18, 0xf3]
+        );
+        check_ok!(
+            computational_data().parse(&[0xff, 0x98, 0xc3], &mut context),
+            AmlValue::Integer(u64::max_value()),
+            &[0x98, 0xc3]
+        );
+        check_ok!(
+            computational_data().parse(&[0x5b, 0x30], &mut context),
+            AmlValue::Integer(crate::AML_INTERPRETER_REVISION),
+            &[]
+        );
+        check_ok!(
+            computational_data().parse(&[0x0a, 0xf3, 0x35], &mut context),
+            AmlValue::Integer(0xf3),
+            &[0x35]
+        );
+        check_ok!(
+            computational_data().parse(&[0x0b, 0xf3, 0x35], &mut context),
+            AmlValue::Integer(0x35f3),
+            &[]
+        );
+        check_ok!(
+            computational_data().parse(&[0x0c, 0xf3, 0x35, 0x12, 0x65, 0xff, 0x00], &mut context),
+            AmlValue::Integer(0x651235f3),
+            &[0xff, 0x00]
+        );
+        check_ok!(
+            computational_data()
+                .parse(&[0x0e, 0xf3, 0x35, 0x12, 0x65, 0xff, 0x00, 0x67, 0xde, 0x28], &mut context),
+            AmlValue::Integer(0xde6700ff651235f3),
+            &[0x28]
+        );
+        check_ok!(
+            computational_data()
+                .parse(&[0x0d, b'A', b'B', b'C', b'D', b'\0', 0xff, 0xf5], &mut context),
+            AmlValue::String(String::from("ABCD")),
+            &[0xff, 0xf5]
+        );
+    }
+}

+ 21 - 0
aml_parser/src/test_utils.rs

@@ -0,0 +1,21 @@
+pub(crate) macro check_err($parse: expr, $error: pat, $remains: expr) {
+    match $parse {
+        Ok(result) => panic!("Expected Err, got {:#?}", result),
+        Err((remains, _, $error)) if *remains == *$remains => (),
+        Err((remains, _, $error)) => {
+            panic!("Correct error, incorrect stream returned: {:x?}", remains)
+        }
+        Err((_, _, err)) => panic!("Got wrong error: {:?}", err),
+    }
+}
+
+pub(crate) macro check_ok($parse: expr, $expected: expr, $remains: expr) {
+    match $parse {
+        Ok((remains, _, ref result)) if remains == *$remains && result == &$expected => (),
+        Ok((remains, _, ref result)) if result == &$expected => {
+            panic!("Correct result, incorrect slice returned: {:x?}", remains)
+        }
+        Ok(result) => panic!("Successfully parsed Ok, but it was wrong: {:#?}", result),
+        Err((_, _, err)) => panic!("Expected Ok, got {:#?}", err),
+    }
+}

+ 135 - 0
aml_parser/src/value.rs

@@ -0,0 +1,135 @@
+use crate::{name_object::AmlName, AmlError};
+use alloc::{boxed::Box, string::String, vec::Vec};
+use bit_field::BitField;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum RegionSpace {
+    SystemMemory,
+    SystemIo,
+    PciConfig,
+    EmbeddedControl,
+    SMBus,
+    SystemCmos,
+    PciBarTarget,
+    IPMI,
+    GeneralPurposeIo,
+    GenericSerialBus,
+    OemDefined(u8),
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum FieldAccessType {
+    Any,
+    Byte,
+    Word,
+    DWord,
+    QWord,
+    Buffer,
+    Reserved,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum FieldUpdateRule {
+    Preserve,
+    WriteAsOnes,
+    WriteAsZeros,
+}
+
+// TODO: custom debug impl
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub struct FieldFlags(u8);
+
+impl FieldFlags {
+    pub fn new(value: u8) -> FieldFlags {
+        FieldFlags(value)
+    }
+
+    pub fn access_type(&self) -> Result<FieldAccessType, AmlError> {
+        match self.0.get_bits(0..4) {
+            0 => Ok(FieldAccessType::Any),
+            1 => Ok(FieldAccessType::Byte),
+            2 => Ok(FieldAccessType::Word),
+            3 => Ok(FieldAccessType::DWord),
+            4 => Ok(FieldAccessType::QWord),
+            5 => Ok(FieldAccessType::Buffer),
+            _ => Err(AmlError::InvalidFieldFlags),
+        }
+    }
+
+    pub fn lock_rule(&self) -> bool {
+        self.0.get_bit(4)
+    }
+
+    pub fn field_update_rule(&self) -> Result<FieldUpdateRule, AmlError> {
+        match self.0.get_bits(5..7) {
+            0 => Ok(FieldUpdateRule::Preserve),
+            1 => Ok(FieldUpdateRule::WriteAsOnes),
+            2 => Ok(FieldUpdateRule::WriteAsZeros),
+            _ => Err(AmlError::InvalidFieldFlags),
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub struct MethodFlags(u8);
+
+impl MethodFlags {
+    pub fn new(value: u8) -> MethodFlags {
+        MethodFlags(value)
+    }
+
+    pub fn arg_count(&self) -> u8 {
+        self.0.get_bits(0..3)
+    }
+
+    pub fn serialize(&self) -> bool {
+        self.0.get_bit(3)
+    }
+
+    pub fn sync_level(&self) -> u8 {
+        self.0.get_bits(4..8)
+    }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub enum AmlValue {
+    Integer(u64),
+    String(String),
+    Name(Box<AmlValue>),
+    OpRegion { region: RegionSpace, offset: u64, length: u64 },
+    Field { region: AmlName, flags: FieldFlags, offset: u64, length: u64 },
+    Device,
+    Method { flags: MethodFlags, code: Vec<u8> },
+    Buffer { bytes: Vec<u8>, size: u64 },
+    Processor { id: u8, pblk_address: u32, pblk_len: u8 },
+    Mutex { sync_level: u8 },
+    Package(Vec<AmlValue>),
+}
+
+impl AmlValue {
+    pub fn as_integer(&self) -> Result<u64, AmlError> {
+        match self {
+            AmlValue::Integer(value) => Ok(*value),
+
+            AmlValue::Buffer { size, ref bytes } => {
+                /*
+                 * "The first 8 bytes of the buffer are converted to an integer, taking the first
+                 * byte as the least significant byte of the integer. A zero-length buffer is
+                 * illegal." - §19.6.140
+                 *
+                 * XXX: We return `0` for zero-length buffers because they literally occur in
+                 * the reference implementation.
+                 */
+                let bytes = if bytes.len() > 8 { &bytes[0..8] } else { bytes };
+
+                Ok(bytes.iter().rev().fold(0: u64, |mut i, &popped| {
+                    i <<= 8;
+                    i += popped as u64;
+                    i
+                }))
+            }
+
+            _ => Err(AmlError::IncompatibleValueConversion),
+        }
+    }
+}