use std::num::NonZeroU16;
use super::constants::{SECTION_ALIGN, STEP_SIZE};
use super::effects::EffectOp;
use super::nav::Nav;
use super::node_type_ir::NodeTypeIR;
pub type StepAddr = u16;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
pub struct StepId(pub NonZeroU16);
impl StepId {
#[inline]
pub fn new(n: u16) -> Self {
Self(NonZeroU16::new(n).expect("StepId cannot be 0"))
}
#[inline]
pub fn get(self) -> u16 {
self.0.get()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Opcode {
Match8 = 0x0,
Match16 = 0x1,
Match24 = 0x2,
Match32 = 0x3,
Match48 = 0x4,
Match64 = 0x5,
Call = 0x6,
Return = 0x7,
Trampoline = 0x8,
}
impl Opcode {
pub fn from_u8(v: u8) -> Self {
match v {
0x0 => Self::Match8,
0x1 => Self::Match16,
0x2 => Self::Match24,
0x3 => Self::Match32,
0x4 => Self::Match48,
0x5 => Self::Match64,
0x6 => Self::Call,
0x7 => Self::Return,
0x8 => Self::Trampoline,
_ => panic!("invalid opcode: {v}"),
}
}
pub fn size(self) -> usize {
match self {
Self::Match8 => 8,
Self::Match16 => 16,
Self::Match24 => 24,
Self::Match32 => 32,
Self::Match48 => 48,
Self::Match64 => 64,
Self::Call => 8,
Self::Return => 8,
Self::Trampoline => 8,
}
}
pub fn step_count(self) -> u16 {
(self.size() / STEP_SIZE) as u16
}
pub fn is_match(self) -> bool {
matches!(
self,
Self::Match8
| Self::Match16
| Self::Match24
| Self::Match32
| Self::Match48
| Self::Match64
)
}
pub fn is_extended_match(self) -> bool {
matches!(
self,
Self::Match16 | Self::Match24 | Self::Match32 | Self::Match48 | Self::Match64
)
}
pub fn payload_slots(self) -> usize {
match self {
Self::Match16 => 4,
Self::Match24 => 8,
Self::Match32 => 12,
Self::Match48 => 20,
Self::Match64 => 28,
_ => 0,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Match<'a> {
bytes: &'a [u8],
pub segment: u8,
pub nav: Nav,
pub node_type: NodeTypeIR,
pub node_field: Option<NonZeroU16>,
is_match8: bool,
match8_next: u16,
pre_count: u8,
neg_count: u8,
post_count: u8,
succ_count: u8,
has_predicate: bool,
}
impl<'a> Match<'a> {
#[inline]
pub fn from_bytes(bytes: &'a [u8]) -> Self {
debug_assert!(bytes.len() >= 8, "Match instruction too short");
let type_id_byte = bytes[0];
let segment = (type_id_byte >> 6) & 0x3;
let node_kind = (type_id_byte >> 4) & 0x3;
let opcode = Opcode::from_u8(type_id_byte & 0xF);
debug_assert!(segment == 0, "non-zero segment not yet supported");
debug_assert!(opcode.is_match(), "expected Match opcode");
let nav = Nav::from_byte(bytes[1]);
let node_type_val = u16::from_le_bytes([bytes[2], bytes[3]]);
let node_type = NodeTypeIR::from_bytes(node_kind, node_type_val);
let node_field = NonZeroU16::new(u16::from_le_bytes([bytes[4], bytes[5]]));
let (is_match8, match8_next, pre_count, neg_count, post_count, succ_count, has_predicate) =
if opcode == Opcode::Match8 {
let next = u16::from_le_bytes([bytes[6], bytes[7]]);
(true, next, 0, 0, 0, if next == 0 { 0 } else { 1 }, false)
} else {
let counts = u16::from_le_bytes([bytes[6], bytes[7]]);
(
false,
0,
((counts >> 13) & 0x7) as u8,
((counts >> 10) & 0x7) as u8,
((counts >> 7) & 0x7) as u8,
((counts >> 2) & 0x1F) as u8,
(counts >> 1) & 0x1 != 0,
)
};
Self {
bytes,
segment,
nav,
node_type,
node_field,
is_match8,
match8_next,
pre_count,
neg_count,
post_count,
succ_count,
has_predicate,
}
}
#[inline]
pub fn is_terminal(&self) -> bool {
self.succ_count == 0
}
#[inline]
pub fn is_epsilon(&self) -> bool {
self.nav == Nav::Epsilon
}
#[inline]
pub fn is_match8(&self) -> bool {
self.is_match8
}
#[inline]
pub fn succ_count(&self) -> usize {
self.succ_count as usize
}
#[inline]
pub fn successor(&self, idx: usize) -> StepId {
debug_assert!(
idx < self.succ_count as usize,
"successor index out of bounds"
);
if self.is_match8 {
debug_assert!(idx == 0);
debug_assert!(self.match8_next != 0, "terminal has no successors");
StepId::new(self.match8_next)
} else {
let offset = self.succ_offset() + idx * 2;
StepId::new(u16::from_le_bytes([
self.bytes[offset],
self.bytes[offset + 1],
]))
}
}
#[inline]
pub fn pre_effects(&self) -> impl Iterator<Item = EffectOp> + '_ {
let start = 8; (0..self.pre_count as usize).map(move |i| {
let offset = start + i * 2;
EffectOp::from_bytes([self.bytes[offset], self.bytes[offset + 1]])
})
}
#[inline]
pub fn neg_fields(&self) -> impl Iterator<Item = u16> + '_ {
let start = 8 + (self.pre_count as usize) * 2;
(0..self.neg_count as usize).map(move |i| {
let offset = start + i * 2;
u16::from_le_bytes([self.bytes[offset], self.bytes[offset + 1]])
})
}
#[inline]
pub fn post_effects(&self) -> impl Iterator<Item = EffectOp> + '_ {
let start = 8 + (self.pre_count as usize + self.neg_count as usize) * 2;
(0..self.post_count as usize).map(move |i| {
let offset = start + i * 2;
EffectOp::from_bytes([self.bytes[offset], self.bytes[offset + 1]])
})
}
#[inline]
pub fn successors(&self) -> impl Iterator<Item = StepId> + '_ {
(0..self.succ_count as usize).map(move |i| self.successor(i))
}
#[inline]
pub fn has_predicate(&self) -> bool {
self.has_predicate
}
pub fn predicate(&self) -> Option<(u8, bool, u16)> {
if !self.has_predicate {
return None;
}
let effects_size =
(self.pre_count as usize + self.neg_count as usize + self.post_count as usize) * 2;
let offset = 8 + effects_size;
let op_and_flags = u16::from_le_bytes([self.bytes[offset], self.bytes[offset + 1]]);
let op = (op_and_flags & 0xFF) as u8;
let is_regex = (op_and_flags >> 8) & 0x1 != 0;
let value_ref = u16::from_le_bytes([self.bytes[offset + 2], self.bytes[offset + 3]]);
Some((op, is_regex, value_ref))
}
#[inline]
fn succ_offset(&self) -> usize {
let effects_size =
(self.pre_count as usize + self.neg_count as usize + self.post_count as usize) * 2;
let predicate_size = if self.has_predicate { 4 } else { 0 };
8 + effects_size + predicate_size
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Call {
pub segment: u8,
pub nav: Nav,
pub node_field: Option<NonZeroU16>,
pub next: StepId,
pub target: StepId,
}
impl Call {
pub fn new(nav: Nav, node_field: Option<NonZeroU16>, next: StepId, target: StepId) -> Self {
Self {
segment: 0,
nav,
node_field,
next,
target,
}
}
pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self {
let type_id_byte = bytes[0];
let segment = (type_id_byte >> 6) & 0x3;
let opcode = Opcode::from_u8(type_id_byte & 0xF);
assert!(
segment == 0,
"non-zero segment not yet supported: {segment}"
);
assert_eq!(opcode, Opcode::Call, "expected Call opcode");
Self {
segment,
nav: Nav::from_byte(bytes[1]),
node_field: NonZeroU16::new(u16::from_le_bytes([bytes[2], bytes[3]])),
next: StepId::new(u16::from_le_bytes([bytes[4], bytes[5]])),
target: StepId::new(u16::from_le_bytes([bytes[6], bytes[7]])),
}
}
pub fn to_bytes(&self) -> [u8; 8] {
let mut bytes = [0u8; 8];
bytes[0] = (self.segment << 6) | (Opcode::Call as u8);
bytes[1] = self.nav.to_byte();
bytes[2..4].copy_from_slice(&self.node_field.map_or(0, |v| v.get()).to_le_bytes());
bytes[4..6].copy_from_slice(&self.next.get().to_le_bytes());
bytes[6..8].copy_from_slice(&self.target.get().to_le_bytes());
bytes
}
pub fn nav(&self) -> Nav {
self.nav
}
pub fn node_field(&self) -> Option<NonZeroU16> {
self.node_field
}
pub fn next(&self) -> StepId {
self.next
}
pub fn target(&self) -> StepId {
self.target
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Return {
pub segment: u8,
}
impl Return {
pub fn new() -> Self {
Self { segment: 0 }
}
pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self {
let type_id_byte = bytes[0];
let segment = (type_id_byte >> 6) & 0x3;
let opcode = Opcode::from_u8(type_id_byte & 0xF);
assert!(
segment == 0,
"non-zero segment not yet supported: {segment}"
);
assert_eq!(opcode, Opcode::Return, "expected Return opcode");
Self { segment }
}
pub fn to_bytes(&self) -> [u8; 8] {
let mut bytes = [0u8; 8];
bytes[0] = (self.segment << 6) | (Opcode::Return as u8);
bytes
}
}
impl Default for Return {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Trampoline {
pub segment: u8,
pub next: StepId,
}
impl Trampoline {
pub fn new(next: StepId) -> Self {
Self { segment: 0, next }
}
pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self {
let type_id_byte = bytes[0];
let segment = (type_id_byte >> 6) & 0x3;
let opcode = Opcode::from_u8(type_id_byte & 0xF);
assert!(
segment == 0,
"non-zero segment not yet supported: {segment}"
);
assert_eq!(opcode, Opcode::Trampoline, "expected Trampoline opcode");
Self {
segment,
next: StepId::new(u16::from_le_bytes([bytes[2], bytes[3]])),
}
}
pub fn to_bytes(&self) -> [u8; 8] {
let mut bytes = [0u8; 8];
bytes[0] = (self.segment << 6) | (Opcode::Trampoline as u8);
bytes[2..4].copy_from_slice(&self.next.get().to_le_bytes());
bytes
}
pub fn next(&self) -> StepId {
self.next
}
}
pub fn select_match_opcode(slots_needed: usize) -> Option<Opcode> {
if slots_needed == 0 {
return Some(Opcode::Match8);
}
match slots_needed {
1..=4 => Some(Opcode::Match16),
5..=8 => Some(Opcode::Match24),
9..=12 => Some(Opcode::Match32),
13..=20 => Some(Opcode::Match48),
21..=28 => Some(Opcode::Match64),
_ => None, }
}
#[inline]
pub fn align_to_section(size: usize) -> usize {
(size + SECTION_ALIGN - 1) & !(SECTION_ALIGN - 1)
}