use super::output::OutputWriter;
use crate::runtime::*;
use crate::{
lang::{Expression, Identifier, Sequence},
CapturedVar, Ranty, RantyValue,
};
use fnv::FnvBuildHasher;
use quickscope::ScopeMap;
use std::rc::Rc;
type CallStackVector<I> = SmallVec<[StackFrame<I>; super::CALL_STACK_INLINE_COUNT]>;
pub struct CallStack<I> {
frames: CallStackVector<I>,
locals: ScopeMap<InternalString, RantyVar, FnvBuildHasher>,
}
impl<I> Default for CallStack<I> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<I> CallStack<I> {
#[inline]
pub(crate) fn new() -> Self {
Self {
frames: Default::default(),
locals: Default::default(),
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.frames.len()
}
#[inline]
pub fn pop_frame(&mut self) -> Option<StackFrame<I>> {
if let Some(frame) = self.frames.pop() {
if frame.has_scope {
self.locals.pop_layer();
}
return Some(frame);
}
None
}
#[inline]
pub fn push_frame(&mut self, frame: StackFrame<I>) {
if frame.has_scope {
self.locals.push_layer();
}
self.frames.push(frame);
}
#[inline]
pub fn top_mut(&mut self) -> Option<&mut StackFrame<I>> {
self.frames.last_mut()
}
#[inline]
pub fn parent_mut(&mut self, depth: usize) -> Option<&mut StackFrame<I>> {
self.frames.iter_mut().rev().nth(depth)
}
#[inline]
pub fn parent(&self, depth: usize) -> Option<&StackFrame<I>> {
self.frames.iter().rev().nth(depth)
}
#[inline]
pub fn top(&self) -> Option<&StackFrame<I>> {
self.frames.last()
}
pub fn gen_stack_trace(&self) -> String {
let mut trace = String::new();
let mut last_frame_info: Option<(String, usize)> = None;
for frame in self.frames.iter().rev() {
let current_frame_string = frame.to_string();
if let Some((last_frame_string, count)) = last_frame_info.take() {
if current_frame_string == last_frame_string {
last_frame_info = Some((last_frame_string, count + 1));
} else {
match count {
1 => trace.push_str(&format!("-> {}\n", last_frame_string)),
_ => trace
.push_str(&format!("-> {} ({} frames)\n", last_frame_string, count)),
}
last_frame_info = Some((current_frame_string, 1));
}
} else {
last_frame_info = Some((current_frame_string, 1));
}
}
if let Some((last_frame_string, count)) = last_frame_info.take() {
match count {
1 => trace.push_str(&format!("-> {}", last_frame_string)),
_ => trace.push_str(&format!("-> {} ({} frames)", last_frame_string, count)),
}
}
trace
}
#[inline]
pub fn set_var_value(
&mut self,
context: &mut Ranty,
id: &str,
access: VarAccessMode,
val: RantyValue,
) -> RuntimeResult<()> {
match access {
VarAccessMode::Local => {
if let Some(var) = self.locals.get_mut(id) {
if !var.write(val) {
runtime_error!(
RuntimeErrorType::InvalidAccess,
"cannot reassign local constant '{}'",
id
);
}
return Ok(());
}
}
VarAccessMode::Descope(n) => {
if let Some(var) = self.locals.get_parent_mut(id, n) {
if !var.write(val) {
runtime_error!(
RuntimeErrorType::InvalidAccess,
"cannot reassign local constant '{}'",
id
);
}
return Ok(());
}
}
VarAccessMode::ExplicitGlobal => {}
}
if context.has_global(id) {
if !context.set_global(id, val) {
runtime_error!(
RuntimeErrorType::InvalidAccess,
"cannot reassign global constant '{}'",
id
);
}
return Ok(());
}
runtime_error!(
RuntimeErrorType::InvalidAccess,
"variable '{}' not found",
id
);
}
#[inline]
pub fn get_var_value(
&self,
context: &Ranty,
id: &str,
access: VarAccessMode,
prefer_function: bool,
) -> RuntimeResult<RantyValue> {
macro_rules! percolating_func_lookup {
($value_iter:expr) => {
if let Some(mut vars) = $value_iter {
if let Some(mut var) = vars.next() {
if !var.value_ref().is_callable() {
if let Some(func_var) = vars
.find(|v| v.value_ref().is_callable())
.or_else(|| context.get_global_var(id).filter(|v| v.value_ref().is_callable()))
{
var = func_var;
}
}
return Ok(var.value_cloned())
}
}
}
}
match access {
VarAccessMode::Local => {
if prefer_function {
percolating_func_lookup!(self.locals.get_all(id));
} else if let Some(var) = self.locals.get(id) {
return Ok(var.value_cloned());
}
}
VarAccessMode::Descope(n) => {
if prefer_function {
percolating_func_lookup!(self.locals.get_parents(id, n));
} else if let Some(var) = self.locals.get_parent(id, n) {
return Ok(var.value_cloned());
}
}
VarAccessMode::ExplicitGlobal => {}
}
if let Some(val) = context.get_global(id) {
return Ok(val);
}
Err(RuntimeError {
error_type: RuntimeErrorType::InvalidAccess,
description: Some(format!(
"{} '{}' not found",
if prefer_function {
"function"
} else {
"variable"
},
id
)),
stack_trace: None,
})
}
pub fn get_var_mut<'a>(
&'a mut self,
context: &'a mut Ranty,
id: &str,
access: VarAccessMode,
) -> RuntimeResult<&'a mut RantyVar> {
match access {
VarAccessMode::Local => {
if let Some(var) = self.locals.get_mut(id) {
return Ok(var);
}
}
VarAccessMode::Descope(n) => {
if let Some(var) = self.locals.get_parent_mut(id, n) {
return Ok(var);
}
}
VarAccessMode::ExplicitGlobal => {}
}
if let Some(var) = context.get_global_var_mut(id) {
return Ok(var);
}
Err(RuntimeError {
error_type: RuntimeErrorType::InvalidAccess,
description: Some(format!("variable '{}' not found", id)),
stack_trace: None,
})
}
#[inline]
pub fn get_var_cloned(&self, context: &Ranty, id: &str, access: VarAccessMode) -> Option<RantyVar> {
match access {
VarAccessMode::Local => {
if let Some(var) = self.locals.get(id) {
return Some(var.clone());
}
}
VarAccessMode::Descope(n) => {
if let Some(var) = self.locals.get_parent(id, n) {
return Some(var.clone());
}
}
VarAccessMode::ExplicitGlobal => {}
}
context.get_global_var(id).cloned()
}
#[inline]
pub fn get_visible_var_clones(
&self,
context: &Ranty,
id: &str,
access: VarAccessMode,
) -> Vec<RantyVar> {
let mut vars = match access {
VarAccessMode::Local => self
.locals
.get_all(id)
.map(|vars| vars.cloned().collect())
.unwrap_or_default(),
VarAccessMode::Descope(n) => self
.locals
.get_parents(id, n)
.map(|vars| vars.cloned().collect())
.unwrap_or_default(),
VarAccessMode::ExplicitGlobal => vec![],
};
if let Some(global) = context.get_global_var(id) {
vars.push(global.clone());
}
vars
}
pub(crate) fn capture_visible_vars(&mut self) -> Vec<CapturedVar> {
self.locals
.iter_mut()
.map(|(name, var)| {
var.make_by_ref();
CapturedVar {
name: Identifier::new(name.clone()),
var: var.clone(),
}
})
.collect()
}
pub fn def_local_var(&mut self, id: &str, var: RantyVar) -> RuntimeResult<()> {
self.locals.define(InternalString::from(id), var);
Ok(())
}
pub fn def_var(
&mut self,
context: &mut Ranty,
id: &str,
access: VarAccessMode,
variable: RantyVar,
) -> RuntimeResult<()> {
let access = if context.options().top_level_defs_are_globals {
match access {
VarAccessMode::Local if self.locals.depth() <= 2 => VarAccessMode::ExplicitGlobal,
VarAccessMode::Descope(n) if self.locals.depth().saturating_sub(n) <= 2 => {
VarAccessMode::ExplicitGlobal
}
other => other,
}
} else {
access
};
match access {
VarAccessMode::Local => {
if let Some(v) = self.locals.get(id) {
if v.is_const() && self.locals.depth_of(id) == Some(0) {
runtime_error!(
RuntimeErrorType::InvalidAccess,
"attempted to redefine local constant '{}'",
id
);
}
}
self.locals.define(InternalString::from(id), variable);
return Ok(());
}
VarAccessMode::Descope(descope_count) => {
if let Some((v, vd)) = self.locals.get_parent_depth(id, descope_count) {
if v.is_const() && vd == descope_count {
runtime_error!(
RuntimeErrorType::InvalidAccess,
"attempted to redefine parent constant '{}'",
id
);
}
}
self.locals
.define_parent(InternalString::from(id), variable, descope_count);
return Ok(());
}
VarAccessMode::ExplicitGlobal => {}
}
if context.has_global(id) && context.get_global_var(id).is_some_and(|v| v.is_const()) {
runtime_error!(
RuntimeErrorType::InvalidAccess,
"attempted to redefine global constant '{}'",
id
);
}
context.set_global_var(id, variable);
Ok(())
}
#[inline]
pub fn def_var_value(
&mut self,
context: &mut Ranty,
id: &str,
access: VarAccessMode,
val: RantyValue,
is_const: bool,
) -> RuntimeResult<()> {
self.def_var(
context,
id,
access,
if is_const {
RantyVar::ByValConst(val)
} else {
RantyVar::ByVal(val)
},
)
}
#[inline]
pub fn taste_for_first(&self, target_flavor: StackFrameFlavor) -> Option<usize> {
for (frame_index, frame) in self.frames.iter().rev().enumerate() {
if frame.flavor > target_flavor {
return None;
} else if frame.flavor == target_flavor {
return Some(frame_index);
}
}
None
}
#[inline]
pub fn taste_for(&self, target_flavor: StackFrameFlavor) -> Option<usize> {
for (frame_index, frame) in self.frames.iter().rev().enumerate() {
if frame.flavor == target_flavor {
return Some(frame_index);
}
}
None
}
}
pub struct StackFrame<I> {
sequence: Option<Rc<Sequence>>,
has_scope: bool,
pc: usize,
output: OutputWriter,
intents: Vec<I>,
debug_cursor: (usize, usize),
origin: Rc<RantyProgramInfo>,
flavor: StackFrameFlavor,
}
impl<I> StackFrame<I> {
#[inline]
pub(crate) fn new(sequence: Rc<Sequence>, prev_output: Option<&OutputWriter>) -> Self {
Self {
origin: Rc::clone(&sequence.origin),
sequence: Some(sequence),
output: OutputWriter::new(prev_output),
has_scope: true,
pc: 0,
intents: Default::default(),
debug_cursor: (0, 0),
flavor: Default::default(),
}
}
#[inline]
pub(crate) fn with_extended_config(
sequence: Option<Rc<Sequence>>,
prev_output: Option<&OutputWriter>,
origin: Rc<RantyProgramInfo>,
has_scope: bool,
debug_pos: (usize, usize),
flavor: StackFrameFlavor,
) -> Self {
Self {
origin,
sequence,
output: OutputWriter::new(prev_output),
has_scope,
pc: 0,
intents: Default::default(),
debug_cursor: debug_pos,
flavor,
}
}
#[inline]
pub(crate) fn without_scope(self) -> Self {
let mut frame = self;
frame.has_scope = false;
frame
}
#[inline]
pub(crate) fn has_scope(self, has_scope: bool) -> Self {
let mut frame = self;
frame.has_scope = has_scope;
frame
}
#[inline(always)]
pub(crate) fn with_flavor(self, flavor: StackFrameFlavor) -> Self {
let mut frame = self;
frame.flavor = flavor;
frame
}
}
impl<I> StackFrame<I> {
#[inline]
pub(crate) fn seq_next(&mut self) -> Option<Rc<Expression>> {
if self.is_done() {
return None;
}
let next = self
.sequence
.as_ref()
.and_then(|seq| seq.get(self.pc).map(Rc::clone));
self.pc += 1;
next
}
#[inline(always)]
pub fn pc(&self) -> usize {
self.pc
}
#[inline(always)]
pub fn flavor(&self) -> StackFrameFlavor {
self.flavor
}
#[inline(always)]
pub fn output(&self) -> &OutputWriter {
&self.output
}
#[inline(always)]
pub fn output_mut(&mut self) -> &mut OutputWriter {
&mut self.output
}
#[inline]
pub fn render_and_reset_output(&mut self) -> RantyValue {
let mut other = OutputWriter::new(Some(&self.output));
std::mem::swap(&mut self.output, &mut other);
other.render_value()
}
#[inline]
pub fn render_and_reset_modifier_input(&mut self) -> RantyValue {
let mut other = OutputWriter::new(Some(&self.output));
std::mem::swap(&mut self.output, &mut other);
other.render_modifier_input()
}
#[inline(always)]
pub fn origin(&self) -> &Rc<RantyProgramInfo> {
&self.origin
}
#[inline(always)]
pub fn debug_cursor(&self) -> (usize, usize) {
self.debug_cursor
}
#[inline]
pub fn origin_name(&self) -> &str {
self.origin
.path
.as_deref()
.unwrap_or_else(|| self.origin.name.as_deref().unwrap_or(DEFAULT_PROGRAM_NAME))
}
#[inline(always)]
pub fn push_intent(&mut self, intent: I) {
self.intents.push(intent);
}
#[inline(always)]
pub(crate) fn pop_intent(&mut self) -> Option<I> {
self.intents.pop()
}
#[inline]
pub fn set_debug_info(&mut self, info: &DebugInfo) {
match info {
DebugInfo::Location { line, col } => self.debug_cursor = (*line, *col),
}
}
}
impl<I> StackFrame<I> {
#[inline(always)]
fn is_done(&self) -> bool {
self.sequence.is_none() || self.pc >= self.sequence.as_ref().unwrap().len()
}
#[inline]
pub fn write_frag(&mut self, frag: &str) {
self.output.write_frag(frag);
}
#[inline]
pub fn write_ws(&mut self, ws: &str) {
self.output.write_ws(ws);
}
#[inline]
pub fn write<T: IntoRanty>(&mut self, val: T) {
self.output.write_value(val.into_ranty());
}
#[inline]
pub fn into_output(self) -> RantyValue {
self.output.render_value()
}
}
impl<I> Display for StackFrame<I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[{}:{}:{}] in {}",
self.origin_name(),
self.debug_cursor.0,
self.debug_cursor.1,
self.sequence
.as_ref()
.and_then(|seq| seq.name().map(|name| name.as_str()))
.unwrap_or_else(|| self.flavor.name()),
)
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub enum StackFrameFlavor {
Original,
NativeCall,
BlockElement,
RepeaterElement,
FunctionBody,
DynamicKeyExpression,
ArgumentExpression,
}
impl Default for StackFrameFlavor {
fn default() -> Self {
Self::Original
}
}
impl StackFrameFlavor {
fn name(&self) -> &'static str {
match self {
Self::Original => "sequence",
Self::NativeCall => "native call",
Self::BlockElement => "block element",
Self::RepeaterElement => "repeater element",
Self::FunctionBody => "function body",
Self::DynamicKeyExpression => "dynamic key",
Self::ArgumentExpression => "argument",
}
}
}