use std::collections::BTreeMap;
use std::num::NonZeroU16;
use crate::analyze::type_check::TypeId;
use plotnik_bytecode::{
Call, EffectOp, EffectOpcode, Nav, Opcode, PredicateOp, Return, StepAddr, StepId, Trampoline,
select_match_opcode,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum NodeTypeIR {
#[default]
Any,
Named(Option<NonZeroU16>),
Anonymous(Option<NonZeroU16>),
}
impl NodeTypeIR {
pub fn to_bytes(self) -> (u8, u16) {
match self {
Self::Any => (0b00, 0),
Self::Named(opt) => (0b01, opt.map(|n| n.get()).unwrap_or(0)),
Self::Anonymous(opt) => (0b10, opt.map(|n| n.get()).unwrap_or(0)),
}
}
pub fn from_bytes(node_kind: u8, node_type: u16) -> Self {
match node_kind {
0b00 => Self::Any,
0b01 => Self::Named(NonZeroU16::new(node_type)),
0b10 => Self::Anonymous(NonZeroU16::new(node_type)),
_ => panic!("invalid node_kind: {node_kind}"),
}
}
pub fn type_id(&self) -> Option<NonZeroU16> {
match self {
Self::Any => None,
Self::Named(opt) | Self::Anonymous(opt) => *opt,
}
}
pub fn is_any(&self) -> bool {
matches!(self, Self::Any)
}
pub fn is_named(&self) -> bool {
matches!(self, Self::Named(_))
}
pub fn is_anonymous(&self) -> bool {
matches!(self, Self::Anonymous(_))
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Label(pub u32);
impl Label {
#[inline]
pub fn resolve(self, map: &BTreeMap<Label, StepAddr>) -> StepAddr {
*map.get(&self).expect("label not in layout")
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MemberRef {
Absolute(u16),
Deferred {
field_name: plotnik_core::Symbol,
field_type: TypeId,
},
DeferredByIndex {
parent_type: TypeId,
relative_index: u16,
},
}
impl MemberRef {
pub fn absolute(index: u16) -> Self {
Self::Absolute(index)
}
pub fn deferred(field_name: plotnik_core::Symbol, field_type: TypeId) -> Self {
Self::Deferred {
field_name,
field_type,
}
}
pub fn deferred_by_index(parent_type: TypeId, relative_index: u16) -> Self {
Self::DeferredByIndex {
parent_type,
relative_index,
}
}
pub fn resolve<F, G>(self, lookup_member: F, get_member_base: G) -> u16
where
F: Fn(plotnik_core::Symbol, TypeId) -> Option<u16>,
G: Fn(TypeId) -> Option<u16>,
{
match self {
Self::Absolute(n) => n,
Self::Deferred {
field_name,
field_type,
} => lookup_member(field_name, field_type)
.expect("deferred member reference must resolve"),
Self::DeferredByIndex {
parent_type,
relative_index,
} => {
get_member_base(parent_type).expect("deferred member base must resolve")
+ relative_index
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EffectIR {
pub opcode: EffectOpcode,
pub payload: usize,
pub member_ref: Option<MemberRef>,
}
impl EffectIR {
pub fn simple(opcode: EffectOpcode, payload: usize) -> Self {
Self {
opcode,
payload,
member_ref: None,
}
}
pub fn with_member(opcode: EffectOpcode, member_ref: MemberRef) -> Self {
Self {
opcode,
payload: 0,
member_ref: Some(member_ref),
}
}
pub fn node() -> Self {
Self::simple(EffectOpcode::Node, 0)
}
pub fn text() -> Self {
Self::simple(EffectOpcode::Text, 0)
}
pub fn null() -> Self {
Self::simple(EffectOpcode::Null, 0)
}
pub fn push() -> Self {
Self::simple(EffectOpcode::Push, 0)
}
pub fn start_arr() -> Self {
Self::simple(EffectOpcode::Arr, 0)
}
pub fn end_arr() -> Self {
Self::simple(EffectOpcode::EndArr, 0)
}
pub fn start_obj() -> Self {
Self::simple(EffectOpcode::Obj, 0)
}
pub fn end_obj() -> Self {
Self::simple(EffectOpcode::EndObj, 0)
}
pub fn start_enum() -> Self {
Self::simple(EffectOpcode::Enum, 0)
}
pub fn end_enum() -> Self {
Self::simple(EffectOpcode::EndEnum, 0)
}
pub fn suppress_begin() -> Self {
Self::simple(EffectOpcode::SuppressBegin, 0)
}
pub fn suppress_end() -> Self {
Self::simple(EffectOpcode::SuppressEnd, 0)
}
pub fn resolve<F, G>(&self, lookup_member: F, get_member_base: G) -> EffectOp
where
F: Fn(plotnik_core::Symbol, TypeId) -> Option<u16>,
G: Fn(TypeId) -> Option<u16>,
{
let payload = if let Some(member_ref) = self.member_ref {
member_ref.resolve(&lookup_member, &get_member_base) as usize
} else {
self.payload
};
EffectOp::new(self.opcode, payload)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PredicateValueIR {
String(plotnik_bytecode::StringId),
Regex(plotnik_bytecode::StringId),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PredicateIR {
pub op: PredicateOp,
pub value: PredicateValueIR,
}
impl PredicateIR {
pub fn string(op: PredicateOp, value: plotnik_bytecode::StringId) -> Self {
Self {
op,
value: PredicateValueIR::String(value),
}
}
pub fn regex(op: PredicateOp, pattern_id: plotnik_bytecode::StringId) -> Self {
Self {
op,
value: PredicateValueIR::Regex(pattern_id),
}
}
pub fn op_byte(&self) -> u8 {
self.op.to_byte()
}
}
#[derive(Clone, Debug)]
pub enum InstructionIR {
Match(MatchIR),
Call(CallIR),
Return(ReturnIR),
Trampoline(TrampolineIR),
}
impl InstructionIR {
#[inline]
pub fn label(&self) -> Label {
match self {
Self::Match(m) => m.label,
Self::Call(c) => c.label,
Self::Return(r) => r.label,
Self::Trampoline(t) => t.label,
}
}
pub fn size(&self) -> usize {
match self {
Self::Match(m) => m.size(),
Self::Call(_) | Self::Return(_) | Self::Trampoline(_) => 8,
}
}
pub fn successors(&self) -> Vec<Label> {
match self {
Self::Match(m) => m.successors.clone(),
Self::Call(c) => vec![c.next],
Self::Return(_) => vec![],
Self::Trampoline(t) => vec![t.next],
}
}
pub fn resolve<F, G, R>(
&self,
map: &BTreeMap<Label, StepAddr>,
lookup_member: F,
get_member_base: G,
lookup_regex: R,
) -> Vec<u8>
where
F: Fn(plotnik_core::Symbol, TypeId) -> Option<u16>,
G: Fn(TypeId) -> Option<u16>,
R: Fn(plotnik_bytecode::StringId) -> Option<u16>,
{
match self {
Self::Match(m) => m.resolve(map, lookup_member, get_member_base, lookup_regex),
Self::Call(c) => c.resolve(map).to_vec(),
Self::Return(r) => r.resolve().to_vec(),
Self::Trampoline(t) => t.resolve(map).to_vec(),
}
}
}
#[derive(Clone, Debug)]
pub struct MatchIR {
pub label: Label,
pub nav: Nav,
pub node_type: NodeTypeIR,
pub node_field: Option<NonZeroU16>,
pub pre_effects: Vec<EffectIR>,
pub neg_fields: Vec<u16>,
pub post_effects: Vec<EffectIR>,
pub predicate: Option<PredicateIR>,
pub successors: Vec<Label>,
}
impl MatchIR {
pub fn terminal(label: Label) -> Self {
Self {
label,
nav: Nav::Epsilon,
node_type: NodeTypeIR::Any,
node_field: None,
pre_effects: vec![],
neg_fields: vec![],
post_effects: vec![],
predicate: None,
successors: vec![],
}
}
pub fn at(label: Label) -> Self {
Self::terminal(label)
}
pub fn epsilon(label: Label, next: Label) -> Self {
Self::at(label).next(next)
}
pub fn nav(mut self, nav: Nav) -> Self {
self.nav = nav;
self
}
pub fn node_type(mut self, t: NodeTypeIR) -> Self {
self.node_type = t;
self
}
pub fn node_field(mut self, f: impl Into<Option<NonZeroU16>>) -> Self {
self.node_field = f.into();
self
}
pub fn neg_field(mut self, f: u16) -> Self {
self.neg_fields.push(f);
self
}
pub fn pre_effect(mut self, e: EffectIR) -> Self {
self.pre_effects.push(e);
self
}
pub fn post_effect(mut self, e: EffectIR) -> Self {
self.post_effects.push(e);
self
}
pub fn neg_fields(mut self, fields: impl IntoIterator<Item = u16>) -> Self {
self.neg_fields.extend(fields);
self
}
pub fn pre_effects(mut self, effects: impl IntoIterator<Item = EffectIR>) -> Self {
self.pre_effects.extend(effects);
self
}
pub fn post_effects(mut self, effects: impl IntoIterator<Item = EffectIR>) -> Self {
self.post_effects.extend(effects);
self
}
pub fn predicate(mut self, p: PredicateIR) -> Self {
self.predicate = Some(p);
self
}
pub fn next(mut self, s: Label) -> Self {
self.successors = vec![s];
self
}
pub fn next_many(mut self, s: Vec<Label>) -> Self {
self.successors = s;
self
}
pub fn size(&self) -> usize {
let can_use_match8 = self.pre_effects.is_empty()
&& self.neg_fields.is_empty()
&& self.post_effects.is_empty()
&& self.predicate.is_none()
&& self.successors.len() <= 1;
if can_use_match8 {
return 8;
}
let predicate_slots = if self.predicate.is_some() { 2 } else { 0 };
let slots = self.pre_effects.len()
+ self.neg_fields.len()
+ self.post_effects.len()
+ predicate_slots
+ self.successors.len();
select_match_opcode(slots).map(|op| op.size()).unwrap_or(64)
}
pub fn resolve<F, G, R>(
&self,
map: &BTreeMap<Label, StepAddr>,
lookup_member: F,
get_member_base: G,
lookup_regex: R,
) -> Vec<u8>
where
F: Fn(plotnik_core::Symbol, TypeId) -> Option<u16>,
G: Fn(TypeId) -> Option<u16>,
R: Fn(plotnik_bytecode::StringId) -> Option<u16>,
{
let can_use_match8 = self.pre_effects.is_empty()
&& self.neg_fields.is_empty()
&& self.post_effects.is_empty()
&& self.predicate.is_none()
&& self.successors.len() <= 1;
let opcode = if can_use_match8 {
Opcode::Match8
} else {
let predicate_slots = if self.predicate.is_some() { 2 } else { 0 };
let slots_needed = self.pre_effects.len()
+ self.neg_fields.len()
+ self.post_effects.len()
+ predicate_slots
+ self.successors.len();
select_match_opcode(slots_needed).expect("instruction too large")
};
let size = opcode.size();
let mut bytes = vec![0u8; size];
let (node_kind, node_type_val) = self.node_type.to_bytes();
bytes[0] = (node_kind << 4) | (opcode as u8); bytes[1] = self.nav.to_byte();
bytes[2..4].copy_from_slice(&node_type_val.to_le_bytes());
let node_field_val = self.node_field.map(|n| n.get()).unwrap_or(0);
bytes[4..6].copy_from_slice(&node_field_val.to_le_bytes());
if opcode == Opcode::Match8 {
let next = self
.successors
.first()
.map(|&l| l.resolve(map))
.unwrap_or(0);
bytes[6..8].copy_from_slice(&next.to_le_bytes());
} else {
let pre_count = self.pre_effects.len();
let neg_count = self.neg_fields.len();
let post_count = self.post_effects.len();
let succ_count = self.successors.len();
let has_predicate = self.predicate.is_some();
assert!(
pre_count <= 7,
"pre_effects overflow: {pre_count} > 7 (lowering should have cascaded)"
);
assert!(
neg_count <= 7,
"neg_fields overflow: {neg_count} > 7 (lowering should have cascaded)"
);
assert!(
post_count <= 7,
"post_effects overflow: {post_count} > 7 (lowering should have cascaded)"
);
assert!(
succ_count <= 31,
"successors overflow: {succ_count} > 31 (lowering should have cascaded)"
);
let counts = ((pre_count as u16) << 13)
| ((neg_count as u16) << 10)
| ((post_count as u16) << 7)
| ((succ_count as u16) << 2)
| ((has_predicate as u16) << 1);
bytes[6..8].copy_from_slice(&counts.to_le_bytes());
let mut offset = 8;
for effect in &self.pre_effects {
let resolved = effect.resolve(&lookup_member, &get_member_base);
bytes[offset..offset + 2].copy_from_slice(&resolved.to_bytes());
offset += 2;
}
for &field in &self.neg_fields {
bytes[offset..offset + 2].copy_from_slice(&field.to_le_bytes());
offset += 2;
}
for effect in &self.post_effects {
let resolved = effect.resolve(&lookup_member, &get_member_base);
bytes[offset..offset + 2].copy_from_slice(&resolved.to_bytes());
offset += 2;
}
if let Some(pred) = &self.predicate {
let is_regex = matches!(pred.value, PredicateValueIR::Regex(_));
let op_and_flags = (pred.op_byte() as u16) | ((is_regex as u16) << 8);
bytes[offset..offset + 2].copy_from_slice(&op_and_flags.to_le_bytes());
offset += 2;
let value_ref = match &pred.value {
PredicateValueIR::String(string_id) => string_id.get(),
PredicateValueIR::Regex(string_id) => {
lookup_regex(*string_id).expect("regex predicate must be interned")
}
};
bytes[offset..offset + 2].copy_from_slice(&value_ref.to_le_bytes());
offset += 2;
}
for &label in &self.successors {
let addr = label.resolve(map);
bytes[offset..offset + 2].copy_from_slice(&addr.to_le_bytes());
offset += 2;
}
}
bytes
}
#[inline]
pub fn is_epsilon(&self) -> bool {
self.nav == Nav::Epsilon
}
}
impl From<MatchIR> for InstructionIR {
fn from(m: MatchIR) -> Self {
Self::Match(m)
}
}
#[derive(Clone, Debug)]
pub struct CallIR {
pub label: Label,
pub nav: Nav,
pub node_field: Option<NonZeroU16>,
pub next: Label,
pub target: Label,
}
impl CallIR {
pub fn new(label: Label, target: Label, next: Label) -> Self {
Self {
label,
nav: Nav::Stay,
node_field: None,
next,
target,
}
}
pub fn nav(mut self, nav: Nav) -> Self {
self.nav = nav;
self
}
pub fn node_field(mut self, f: impl Into<Option<NonZeroU16>>) -> Self {
self.node_field = f.into();
self
}
pub fn resolve(&self, map: &BTreeMap<Label, StepAddr>) -> [u8; 8] {
Call::new(
self.nav,
self.node_field,
StepId::new(self.next.resolve(map)),
StepId::new(self.target.resolve(map)),
)
.to_bytes()
}
}
impl From<CallIR> for InstructionIR {
fn from(c: CallIR) -> Self {
Self::Call(c)
}
}
#[derive(Clone, Debug)]
pub struct ReturnIR {
pub label: Label,
}
impl ReturnIR {
pub fn new(label: Label) -> Self {
Self { label }
}
pub fn resolve(&self) -> [u8; 8] {
Return::new().to_bytes()
}
}
impl From<ReturnIR> for InstructionIR {
fn from(r: ReturnIR) -> Self {
Self::Return(r)
}
}
#[derive(Clone, Debug)]
pub struct TrampolineIR {
pub label: Label,
pub next: Label,
}
impl TrampolineIR {
pub fn new(label: Label, next: Label) -> Self {
Self { label, next }
}
pub fn resolve(&self, map: &BTreeMap<Label, StepAddr>) -> [u8; 8] {
Trampoline::new(StepId::new(self.next.resolve(map))).to_bytes()
}
}
impl From<TrampolineIR> for InstructionIR {
fn from(t: TrampolineIR) -> Self {
Self::Trampoline(t)
}
}
#[derive(Clone, Debug)]
pub struct LayoutResult {
pub(crate) label_to_step: BTreeMap<Label, StepAddr>,
pub(crate) total_steps: u16,
}
impl LayoutResult {
pub fn new(label_to_step: BTreeMap<Label, StepAddr>, total_steps: u16) -> Self {
Self {
label_to_step,
total_steps,
}
}
pub fn empty() -> Self {
Self {
label_to_step: BTreeMap::new(),
total_steps: 0,
}
}
pub fn label_to_step(&self) -> &BTreeMap<Label, StepAddr> {
&self.label_to_step
}
pub fn total_steps(&self) -> u16 {
self.total_steps
}
}