#[cfg(feature = "internal_debug")]
use std::fmt;
use crate::compiler::tokens::Span;
use crate::output::CaptureMode;
use crate::value::Value;
pub const LOOP_FLAG_WITH_LOOP_VAR: u8 = 1;
pub const LOOP_FLAG_RECURSIVE: u8 = 2;
#[cfg(feature = "macros")]
pub const MACRO_CALLER: u8 = 2;
pub type LocalId = u8;
pub const MAX_LOCALS: usize = 50;
#[cfg_attr(feature = "internal_debug", derive(Debug))]
#[cfg_attr(
feature = "unstable_machinery_serde",
derive(serde::Serialize),
serde(tag = "op", content = "arg")
)]
#[derive(Clone)]
pub enum Instruction<'source> {
EmitRaw(&'source str),
StoreLocal(&'source str),
Lookup(&'source str),
GetAttr(&'source str),
SetAttr(&'source str),
GetItem,
Slice,
LoadConst(Value),
BuildMap(usize),
BuildKwargs(usize),
MergeKwargs(usize),
BuildList(Option<usize>),
UnpackList(usize),
UnpackLists(usize),
Add,
Sub,
Mul,
Div,
IntDiv,
Rem,
Pow,
Neg,
Eq,
Ne,
Gt,
Gte,
Lt,
Lte,
Not,
StringConcat,
In,
ApplyFilter(&'source str, Option<u16>, LocalId),
PerformTest(&'source str, Option<u16>, LocalId),
Emit,
PushLoop(u8),
PushWith,
Iterate(u32),
PushDidNotIterate,
PopFrame,
PopLoopFrame,
Jump(u32),
JumpIfFalse(u32),
JumpIfFalseOrPop(u32),
JumpIfTrueOrPop(u32),
PushAutoEscape,
PopAutoEscape,
BeginCapture(CaptureMode),
EndCapture,
CallFunction(&'source str, Option<u16>),
CallMethod(&'source str, Option<u16>),
CallObject(Option<u16>),
DupTop,
DiscardTop,
FastSuper,
FastRecurse,
Swap,
#[cfg(feature = "multi_template")]
CallBlock(&'source str),
#[cfg(feature = "multi_template")]
LoadBlocks,
#[cfg(feature = "multi_template")]
Include(bool),
#[cfg(feature = "multi_template")]
ExportLocals,
#[cfg(feature = "macros")]
BuildMacro(&'source str, u32, u8),
#[cfg(feature = "macros")]
Return,
#[cfg(feature = "macros")]
IsUndefined,
#[cfg(feature = "macros")]
Enclose(&'source str),
#[cfg(feature = "macros")]
GetClosure,
}
#[derive(Copy, Clone)]
struct LineInfo {
first_instruction: u32,
line: u16,
}
#[cfg(feature = "debug")]
#[derive(Copy, Clone)]
struct SpanInfo {
first_instruction: u32,
span: Span,
}
pub struct Instructions<'source> {
pub(crate) instructions: Vec<Instruction<'source>>,
line_infos: Vec<LineInfo>,
#[cfg(feature = "debug")]
span_infos: Vec<SpanInfo>,
name: &'source str,
source: &'source str,
}
pub(crate) static EMPTY_INSTRUCTIONS: Instructions<'static> = Instructions {
instructions: Vec::new(),
line_infos: Vec::new(),
#[cfg(feature = "debug")]
span_infos: Vec::new(),
name: "<unknown>",
source: "",
};
impl<'source> Instructions<'source> {
pub fn new(name: &'source str, source: &'source str) -> Instructions<'source> {
Instructions {
instructions: Vec::with_capacity(256),
line_infos: Vec::with_capacity(128),
#[cfg(feature = "debug")]
span_infos: Vec::with_capacity(128),
name,
source,
}
}
pub fn name(&self) -> &'source str {
self.name
}
pub fn source(&self) -> &'source str {
self.source
}
#[inline(always)]
pub fn get(&self, idx: u32) -> Option<&Instruction<'source>> {
self.instructions.get(idx as usize)
}
pub fn get_mut(&mut self, idx: u32) -> Option<&mut Instruction<'source>> {
self.instructions.get_mut(idx as usize)
}
pub fn add(&mut self, instr: Instruction<'source>) -> u32 {
let rv = self.instructions.len();
self.instructions.push(instr);
rv as u32
}
fn add_line_record(&mut self, instr: u32, line: u16) {
let same_loc = self
.line_infos
.last()
.is_some_and(|last_loc| last_loc.line == line);
if !same_loc {
self.line_infos.push(LineInfo {
first_instruction: instr,
line,
});
}
}
pub fn add_with_line(&mut self, instr: Instruction<'source>, line: u16) -> u32 {
let rv = self.add(instr);
self.add_line_record(rv, line);
#[cfg(feature = "debug")]
{
if self
.span_infos
.last()
.is_some_and(|x| x.span != Span::default())
{
self.span_infos.push(SpanInfo {
first_instruction: rv,
span: Span::default(),
});
}
}
rv
}
pub fn add_with_span(&mut self, instr: Instruction<'source>, span: Span) -> u32 {
let rv = self.add(instr);
#[cfg(feature = "debug")]
{
let same_loc = self
.span_infos
.last()
.is_some_and(|last_loc| last_loc.span == span);
if !same_loc {
self.span_infos.push(SpanInfo {
first_instruction: rv,
span,
});
}
}
self.add_line_record(rv, span.start_line);
rv
}
pub fn get_line(&self, idx: u32) -> Option<usize> {
let loc = match self
.line_infos
.binary_search_by_key(&idx, |x| x.first_instruction)
{
Ok(idx) => &self.line_infos[idx],
Err(0) => return None,
Err(idx) => &self.line_infos[idx - 1],
};
Some(loc.line as usize)
}
pub fn get_span(&self, idx: u32) -> Option<Span> {
#[cfg(feature = "debug")]
{
let loc = match self
.span_infos
.binary_search_by_key(&idx, |x| x.first_instruction)
{
Ok(idx) => &self.span_infos[idx],
Err(0) => return None,
Err(idx) => &self.span_infos[idx - 1],
};
(loc.span != Span::default()).then_some(loc.span)
}
#[cfg(not(feature = "debug"))]
{
let _ = idx;
None
}
}
#[cfg(feature = "debug")]
pub fn get_referenced_names(&self, idx: u32) -> Vec<&'source str> {
let mut rv = Vec::new();
if self.instructions.is_empty() {
return rv;
}
let idx = (idx as usize).min(self.instructions.len() - 1);
for instr in self.instructions[..=idx].iter().rev() {
let name = match instr {
Instruction::Lookup(name)
| Instruction::StoreLocal(name)
| Instruction::CallFunction(name, _) => *name,
Instruction::PushLoop(flags) if flags & LOOP_FLAG_WITH_LOOP_VAR != 0 => "loop",
Instruction::PushLoop(_) | Instruction::PushWith => break,
_ => continue,
};
if !rv.contains(&name) {
rv.push(name);
}
}
rv
}
pub fn len(&self) -> usize {
self.instructions.len()
}
#[allow(unused)]
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
}
#[cfg(feature = "internal_debug")]
impl fmt::Debug for Instructions<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
struct InstructionWrapper<'a>(usize, &'a Instruction<'a>, Option<usize>);
impl fmt::Debug for InstructionWrapper<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
ok!(write!(f, "{:>05} | {:?}", self.0, self.1,));
if let Some(line) = self.2 {
ok!(write!(f, " [line {line}]"));
}
Ok(())
}
}
let mut list = f.debug_list();
let mut last_line = None;
for (idx, instr) in self.instructions.iter().enumerate() {
let line = self.get_line(idx as u32);
list.entry(&InstructionWrapper(
idx,
instr,
if line != last_line { line } else { None },
));
last_line = line;
}
list.finish()
}
}
#[test]
#[cfg(target_pointer_width = "64")]
fn test_sizes() {
assert_eq!(std::mem::size_of::<Instruction>(), 32);
}