Skip to main content

flowsurface_indicator_engine/
runtime.rs

1use crate::CompiledIndicator;
2use crate::frame::{
3    FrameOutput, HorizontalLevelOutput, IndicatorFrame, MarkerOutput, OhlcOutput, PlotOutput,
4    RangeOutput, RegionOutput, RegionUpdate, SegmentOutput, SeriesInputValue,
5};
6use crate::hir::{
7    BarStateField, BinaryOperator, ExprId, ExpressionKind, SourceInputSlot, StateSlot,
8    StatementKind,
9};
10use crate::intrinsic::{IntrinsicError, IntrinsicEvalContext, IntrinsicState, WarmupSpec};
11use crate::scene::Color;
12use crate::types::{ArrayValue, IndicatorConfig, InputType, InputValue, Value, ValueType};
13use std::collections::{BTreeMap, VecDeque};
14use std::mem::size_of;
15use std::sync::Arc;
16use thiserror::Error;
17
18const MAX_COLLECTION_NESTING: usize = 256;
19
20#[derive(Debug, Clone, PartialEq)]
21struct IndexedHistory {
22    first_index: usize,
23    capacity: usize,
24    values: VecDeque<Value>,
25    resource_collections: usize,
26    resource_heap_bytes: usize,
27}
28
29impl IndexedHistory {
30    fn new(capacity: usize) -> Self {
31        Self {
32            first_index: 0,
33            capacity: capacity.max(1),
34            values: VecDeque::new(),
35            resource_collections: 0,
36            resource_heap_bytes: 0,
37        }
38    }
39
40    fn get(&self, index: usize) -> Option<&Value> {
41        let offset = index.checked_sub(self.first_index)?;
42        self.values.get(offset)
43    }
44
45    fn set(&mut self, index: usize, value: Value) {
46        let value_usage = value.resource_usage();
47        if self.values.is_empty() {
48            self.first_index = index;
49            self.values.push_back(value);
50            self.add_usage(value_usage);
51            return;
52        }
53
54        if index < self.first_index {
55            return;
56        }
57
58        let end_index = self.first_index.saturating_add(self.values.len());
59        if index == end_index {
60            self.values.push_back(value);
61            self.add_usage(value_usage);
62        } else if let Some(existing) = self.values.get_mut(index - self.first_index) {
63            let previous_usage = existing.resource_usage();
64            *existing = value;
65            self.subtract_usage(previous_usage);
66            self.add_usage(value_usage);
67        } else {
68            return;
69        }
70
71        while self.values.len() > self.capacity {
72            if let Some(evicted) = self.values.pop_front() {
73                self.subtract_usage(evicted.resource_usage());
74            }
75            self.first_index = self.first_index.saturating_add(1);
76        }
77    }
78
79    fn evict_before(&mut self, index: usize) {
80        if index <= self.first_index {
81            return;
82        }
83
84        let remove = index
85            .saturating_sub(self.first_index)
86            .min(self.values.len());
87        let removed_usage = self
88            .values
89            .drain(..remove)
90            .map(|value| value.resource_usage())
91            .fold(
92                (0usize, 0usize),
93                |(collections, bytes), (value_collections, value_bytes)| {
94                    (
95                        collections.saturating_add(value_collections),
96                        bytes.saturating_add(value_bytes),
97                    )
98                },
99            );
100        self.subtract_usage(removed_usage);
101        self.first_index = self.first_index.saturating_add(remove);
102    }
103
104    fn resource_usage(&self) -> (usize, usize) {
105        (self.resource_collections, self.resource_heap_bytes)
106    }
107
108    fn container_heap_bytes(&self) -> usize {
109        size_of::<VecDeque<Value>>()
110            .saturating_add(self.values.capacity().saturating_mul(size_of::<Value>()))
111    }
112
113    fn add_usage(&mut self, (collections, bytes): (usize, usize)) {
114        self.resource_collections = self.resource_collections.saturating_add(collections);
115        self.resource_heap_bytes = self.resource_heap_bytes.saturating_add(bytes);
116    }
117
118    fn subtract_usage(&mut self, (collections, bytes): (usize, usize)) {
119        self.resource_collections = self.resource_collections.saturating_sub(collections);
120        self.resource_heap_bytes = self.resource_heap_bytes.saturating_sub(bytes);
121    }
122}
123
124#[derive(Debug, Clone, PartialEq)]
125pub struct InstancePlan {
126    pub inputs: Vec<Value>,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub struct RuntimeLimits {
131    /// Weighted work units per frame, including scans and copies.
132    pub max_evaluated_nodes: usize,
133    pub max_call_depth: usize,
134    pub max_heap_bytes: usize,
135    pub max_collection_elements: usize,
136    /// Maximum rolling-window length accepted by stateful TA intrinsics.
137    pub max_window_length: usize,
138    pub max_output_items: usize,
139    pub max_string_bytes: usize,
140}
141
142impl Default for RuntimeLimits {
143    fn default() -> Self {
144        Self {
145            max_evaluated_nodes: 1_000_000,
146            max_call_depth: 128,
147            max_heap_bytes: 64 * 1024 * 1024,
148            max_collection_elements: 100_000,
149            max_window_length: 5_000,
150            max_output_items: 1_000_000,
151            max_string_bytes: 1 << 20,
152        }
153    }
154}
155
156impl RuntimeLimits {
157    pub fn untrusted() -> Self {
158        Self {
159            max_evaluated_nodes: 250_000,
160            max_call_depth: 64,
161            max_heap_bytes: 16 * 1024 * 1024,
162            max_collection_elements: 25_000,
163            max_window_length: 5_000,
164            max_output_items: 4_096,
165            max_string_bytes: 64 * 1024,
166        }
167    }
168}
169
170impl InstancePlan {
171    pub(crate) fn from_compiled(
172        compiled: &CompiledIndicator,
173        config: &IndicatorConfig,
174    ) -> Result<Self, PlanningError> {
175        let mut inputs = vec![Value::Na; compiled.program.locals];
176        for (slot, name) in &compiled.program.inputs {
177            let Some(descriptor) = compiled
178                .descriptor
179                .inputs
180                .iter()
181                .find(|input| &input.id == name)
182            else {
183                return Err(PlanningError::MissingInput(name.clone()));
184            };
185            let configured = config.value(descriptor);
186            if !descriptor.accepts(&configured) {
187                return Err(PlanningError::InvalidInput(name.clone()));
188            }
189            let configured = match (descriptor.value_type, configured) {
190                (InputType::Float, InputValue::Int(value)) => Value::Float(value as f64),
191                (_, configured) => configured.into_value(),
192            };
193            inputs[slot.0] = configured;
194        }
195        Ok(Self { inputs })
196    }
197}
198
199#[derive(Debug, Error, Clone, PartialEq, Eq)]
200pub enum PlanningError {
201    #[error("input descriptor is missing for {0}")]
202    MissingInput(String),
203    #[error("input {0} has a value with the wrong type")]
204    InvalidInput(String),
205    #[error("history analysis work limit exceeded")]
206    HistoryAnalysisWorkLimitExceeded,
207    #[error("history analysis depth limit exceeded")]
208    HistoryAnalysisDepthLimitExceeded,
209}
210
211#[derive(Debug, Error, Clone, PartialEq)]
212pub enum RuntimeError {
213    #[error("unknown expression {0:?}")]
214    UnknownExpression(ExprId),
215    #[error("unknown intrinsic {0:?}")]
216    UnknownIntrinsic(crate::hir::IntrinsicId),
217    #[error("invalid value for {operation}")]
218    InvalidValue { operation: &'static str },
219    #[error("output {output} field {field} expects {expected}, got {actual}")]
220    InvalidOutput {
221        output: String,
222        field: &'static str,
223        expected: &'static str,
224        actual: &'static str,
225    },
226    #[error("intrinsic error: {0}")]
227    Intrinsic(#[from] IntrinsicError),
228    #[error("execution limit exceeded")]
229    ExecutionLimitExceeded,
230    #[error("resource limit exceeded: {0}")]
231    ResourceLimitExceeded(&'static str),
232    #[error("state slot {0:?} is out of range")]
233    UnknownState(StateSlot),
234}
235
236#[derive(Debug, Clone, PartialEq)]
237struct SeriesHistory {
238    values: Vec<IndexedHistory>,
239}
240
241impl SeriesHistory {
242    fn new(count: usize, capacity: usize) -> Self {
243        Self {
244            values: (0..count).map(|_| IndexedHistory::new(capacity)).collect(),
245        }
246    }
247
248    fn get(&self, slot: usize, index: usize) -> Option<&Value> {
249        self.values.get(slot)?.get(index)
250    }
251
252    fn set(&mut self, slot: usize, index: usize, value: Value) {
253        if let Some(values) = self.values.get_mut(slot) {
254            values.set(index, value);
255        }
256    }
257}
258
259#[derive(Debug, Clone, PartialEq)]
260struct CommittedState {
261    index: Option<usize>,
262    states: Arc<Vec<StateValue>>,
263    function_states: Arc<BTreeMap<crate::hir::CallSiteId, StateValue>>,
264    history: SeriesHistory,
265    intrinsics: Arc<BTreeMap<crate::hir::CallSiteId, IntrinsicState>>,
266}
267
268#[derive(Debug, Clone, PartialEq)]
269struct WorkingState {
270    states: Arc<Vec<StateValue>>,
271    function_states: Arc<BTreeMap<crate::hir::CallSiteId, StateValue>>,
272    current_series: Vec<Option<Value>>,
273    intrinsics: Arc<BTreeMap<crate::hir::CallSiteId, IntrinsicState>>,
274}
275
276#[derive(Debug, Clone, PartialEq)]
277enum StateValue {
278    Uninitialized,
279    Value(Value),
280}
281
282impl StateValue {
283    fn value(&self) -> Option<&Value> {
284        match self {
285            Self::Uninitialized => None,
286            Self::Value(value) => Some(value),
287        }
288    }
289
290    fn is_initialized(&self) -> bool {
291        matches!(self, Self::Value(_))
292    }
293}
294
295impl WorkingState {
296    fn from_committed(committed: &CommittedState, series_count: usize) -> Self {
297        Self {
298            states: Arc::clone(&committed.states),
299            function_states: Arc::clone(&committed.function_states),
300            current_series: vec![None; series_count],
301            intrinsics: Arc::clone(&committed.intrinsics),
302        }
303    }
304
305    fn reset_from_committed(&mut self, committed: &CommittedState, series_count: usize) {
306        self.states = Arc::clone(&committed.states);
307        self.function_states = Arc::clone(&committed.function_states);
308        self.intrinsics = Arc::clone(&committed.intrinsics);
309        if self.current_series.len() != series_count {
310            self.current_series.clear();
311            self.current_series.resize(series_count, None);
312        } else {
313            self.current_series.fill(None);
314        }
315    }
316
317    fn clear_current_series(&mut self) {
318        self.current_series.fill(None);
319    }
320}
321
322struct OutputBuffer {
323    max_items: usize,
324    items: usize,
325    plots: Vec<PlotOutput>,
326    markers: Vec<MarkerOutput>,
327    segments: Vec<SegmentOutput>,
328    levels: Vec<HorizontalLevelOutput>,
329    ohlc: Vec<OhlcOutput>,
330    ranges: Vec<RangeOutput>,
331    regions: Vec<RegionOutput>,
332    region_updates: Vec<RegionUpdate>,
333}
334
335impl OutputBuffer {
336    fn new(max_items: usize) -> Self {
337        Self {
338            max_items,
339            items: 0,
340            plots: Vec::new(),
341            markers: Vec::new(),
342            segments: Vec::new(),
343            levels: Vec::new(),
344            ohlc: Vec::new(),
345            ranges: Vec::new(),
346            regions: Vec::new(),
347            region_updates: Vec::new(),
348        }
349    }
350
351    fn reserve(&mut self) -> Result<(), RuntimeError> {
352        if self.items >= self.max_items {
353            return Err(RuntimeError::ResourceLimitExceeded("output items"));
354        }
355        self.items += 1;
356        Ok(())
357    }
358
359    fn push_plot(&mut self, value: PlotOutput) -> Result<(), RuntimeError> {
360        self.reserve()?;
361        self.plots.push(value);
362        Ok(())
363    }
364
365    fn push_marker(&mut self, value: MarkerOutput) -> Result<(), RuntimeError> {
366        self.reserve()?;
367        self.markers.push(value);
368        Ok(())
369    }
370
371    fn push_segment(&mut self, value: SegmentOutput) -> Result<(), RuntimeError> {
372        self.reserve()?;
373        self.segments.push(value);
374        Ok(())
375    }
376
377    fn push_level(&mut self, value: HorizontalLevelOutput) -> Result<(), RuntimeError> {
378        self.reserve()?;
379        self.levels.push(value);
380        Ok(())
381    }
382
383    fn push_ohlc(&mut self, value: OhlcOutput) -> Result<(), RuntimeError> {
384        self.reserve()?;
385        self.ohlc.push(value);
386        Ok(())
387    }
388
389    fn push_range(&mut self, value: RangeOutput) -> Result<(), RuntimeError> {
390        self.reserve()?;
391        self.ranges.push(value);
392        Ok(())
393    }
394
395    fn push_region(&mut self, value: RegionOutput) -> Result<(), RuntimeError> {
396        if let Some(existing) = self
397            .regions
398            .iter_mut()
399            .find(|region| region.id == value.id && region.key == value.key)
400        {
401            *existing = value;
402            return Ok(());
403        }
404        self.reserve()?;
405        self.regions.push(value);
406        Ok(())
407    }
408
409    fn push_region_update(&mut self, value: RegionUpdate) -> Result<(), RuntimeError> {
410        let (id, key) = match &value {
411            RegionUpdate::Upsert(region) => (&region.id, region.key),
412            RegionUpdate::Remove { id, key } => (id, *key),
413        };
414        if let Some(existing) = self.region_updates.iter_mut().find(|update| {
415            let (existing_id, existing_key) = match update {
416                RegionUpdate::Upsert(region) => (&region.id, region.key),
417                RegionUpdate::Remove { id, key } => (id, *key),
418            };
419            existing_id == id && existing_key == key
420        }) {
421            *existing = value;
422            return Ok(());
423        }
424        self.reserve()?;
425        self.region_updates.push(value);
426        Ok(())
427    }
428
429    fn resource_usage(&self) -> (usize, usize) {
430        let mut bytes = size_of::<Self>()
431            .saturating_add(
432                self.plots
433                    .capacity()
434                    .saturating_mul(size_of::<PlotOutput>()),
435            )
436            .saturating_add(
437                self.markers
438                    .capacity()
439                    .saturating_mul(size_of::<MarkerOutput>()),
440            )
441            .saturating_add(
442                self.segments
443                    .capacity()
444                    .saturating_mul(size_of::<SegmentOutput>()),
445            )
446            .saturating_add(
447                self.levels
448                    .capacity()
449                    .saturating_mul(size_of::<HorizontalLevelOutput>()),
450            )
451            .saturating_add(self.ohlc.capacity().saturating_mul(size_of::<OhlcOutput>()))
452            .saturating_add(
453                self.ranges
454                    .capacity()
455                    .saturating_mul(size_of::<RangeOutput>()),
456            )
457            .saturating_add(
458                self.regions
459                    .capacity()
460                    .saturating_mul(size_of::<RegionOutput>()),
461            );
462        bytes = bytes.saturating_add(
463            self.region_updates
464                .capacity()
465                .saturating_mul(size_of::<RegionUpdate>()),
466        );
467        for plot in &self.plots {
468            bytes = bytes
469                .saturating_add(plot.id.capacity())
470                .saturating_add(plot.value.resource_usage().1);
471        }
472        for marker in &self.markers {
473            bytes = bytes
474                .saturating_add(marker.id.capacity())
475                .saturating_add(marker.text.as_ref().map_or(0, String::capacity));
476        }
477        for segment in &self.segments {
478            bytes = bytes
479                .saturating_add(segment.id.capacity())
480                .saturating_add(segment.text.as_ref().map_or(0, String::capacity));
481        }
482        for level in &self.levels {
483            bytes = bytes.saturating_add(level.id.capacity());
484        }
485        for ohlc in &self.ohlc {
486            bytes = bytes.saturating_add(ohlc.id.capacity());
487        }
488        for range in &self.ranges {
489            bytes = bytes.saturating_add(range.id.capacity());
490        }
491        for region in &self.regions {
492            bytes = bytes
493                .saturating_add(region.id.capacity())
494                .saturating_add(
495                    region
496                        .border_color_slot
497                        .as_ref()
498                        .map_or(0, String::capacity),
499                )
500                .saturating_add(region.fill_color_slot.as_ref().map_or(0, String::capacity));
501        }
502        for update in &self.region_updates {
503            match update {
504                RegionUpdate::Upsert(region) => {
505                    bytes = bytes
506                        .saturating_add(region.id.capacity())
507                        .saturating_add(
508                            region
509                                .border_color_slot
510                                .as_ref()
511                                .map_or(0, String::capacity),
512                        )
513                        .saturating_add(
514                            region.fill_color_slot.as_ref().map_or(0, String::capacity),
515                        );
516                }
517                RegionUpdate::Remove { id, .. } => {
518                    bytes = bytes.saturating_add(id.capacity());
519                }
520            }
521        }
522        (self.items, bytes)
523    }
524
525    fn has_string_over(&self, limit: usize) -> bool {
526        self.plots
527            .iter()
528            .any(|plot| plot.id.capacity() > limit || plot.value.has_string_over(limit))
529            || self.markers.iter().any(|marker| {
530                marker.id.capacity() > limit
531                    || marker
532                        .text
533                        .as_ref()
534                        .is_some_and(|text| text.capacity() > limit)
535            })
536            || self.segments.iter().any(|segment| {
537                segment.id.capacity() > limit
538                    || segment
539                        .text
540                        .as_ref()
541                        .is_some_and(|text| text.capacity() > limit)
542            })
543            || self.levels.iter().any(|level| level.id.capacity() > limit)
544            || self.ohlc.iter().any(|ohlc| ohlc.id.capacity() > limit)
545            || self.ranges.iter().any(|range| range.id.capacity() > limit)
546            || self.regions.iter().any(|region| {
547                region.id.capacity() > limit
548                    || region
549                        .border_color_slot
550                        .as_ref()
551                        .is_some_and(|slot| slot.capacity() > limit)
552                    || region
553                        .fill_color_slot
554                        .as_ref()
555                        .is_some_and(|slot| slot.capacity() > limit)
556            })
557            || self.region_updates.iter().any(|update| match update {
558                RegionUpdate::Upsert(region) => {
559                    region.id.capacity() > limit
560                        || region
561                            .border_color_slot
562                            .as_ref()
563                            .is_some_and(|slot| slot.capacity() > limit)
564                        || region
565                            .fill_color_slot
566                            .as_ref()
567                            .is_some_and(|slot| slot.capacity() > limit)
568                }
569                RegionUpdate::Remove { id, .. } => id.capacity() > limit,
570            })
571    }
572
573    fn finish(self, index: usize, time: i64) -> FrameOutput {
574        FrameOutput {
575            index,
576            time,
577            plots: self.plots.into(),
578            markers: self.markers.into(),
579            segments: self.segments.into(),
580            levels: self.levels.into(),
581            ohlc: self.ohlc.into(),
582            ranges: self.ranges.into(),
583            regions: self.regions.into(),
584            region_updates: self.region_updates.into(),
585        }
586    }
587}
588
589pub struct IndicatorInstance {
590    compiled: Arc<CompiledIndicator>,
591    plan: InstancePlan,
592    committed: CommittedState,
593    working: WorkingState,
594    locals: Vec<Value>,
595    current_bar_index: usize,
596    current_frame: Option<IndicatorFrame>,
597    executed_work: usize,
598    previewing: bool,
599    limits: RuntimeLimits,
600    break_requested: bool,
601    continue_requested: bool,
602    return_requested: bool,
603    return_value: Option<Value>,
604    call_depth: usize,
605    call_site_stack: Vec<crate::hir::CallSiteId>,
606    max_bars_back: usize,
607    quarantined: Option<RuntimeError>,
608    resource_check_dirty: bool,
609}
610
611impl std::fmt::Debug for IndicatorInstance {
612    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
613        formatter
614            .debug_struct("IndicatorInstance")
615            .field("indicator", &self.compiled.descriptor.id)
616            .field("bar_index", &self.current_bar_index)
617            .finish_non_exhaustive()
618    }
619}
620
621impl IndicatorInstance {
622    /// Instantiate a shared compiled program without copying its HIR or registry.
623    pub fn with_limits(
624        compiled: Arc<CompiledIndicator>,
625        config: IndicatorConfig,
626        limits: RuntimeLimits,
627    ) -> Result<Self, PlanningError> {
628        let plan = InstancePlan::from_compiled(&compiled, &config)?;
629        compiled.validate_history_analysis(&config)?;
630        let committed = CommittedState {
631            index: None,
632            states: Arc::new(vec![StateValue::Uninitialized; compiled.program.states]),
633            function_states: Arc::new(BTreeMap::new()),
634            history: SeriesHistory::new(
635                compiled.program.series,
636                compiled.descriptor.max_bars_back.saturating_add(1),
637            ),
638            intrinsics: Arc::new(BTreeMap::new()),
639        };
640        Ok(Self {
641            working: WorkingState::from_committed(&committed, compiled.program.series),
642            locals: vec![Value::Na; compiled.program.locals],
643            current_bar_index: 0,
644            current_frame: None,
645            executed_work: 0,
646            previewing: false,
647            limits,
648            break_requested: false,
649            continue_requested: false,
650            return_requested: false,
651            return_value: None,
652            call_depth: 0,
653            call_site_stack: Vec::new(),
654            max_bars_back: compiled.descriptor.max_bars_back,
655            quarantined: None,
656            resource_check_dirty: false,
657            compiled,
658            plan,
659            committed,
660        })
661    }
662
663    fn evaluate_frame(
664        &mut self,
665        frame: IndicatorFrame,
666        previewing: bool,
667    ) -> Result<FrameOutput, RuntimeError> {
668        if let Some(error) = &self.quarantined {
669            return Err(error.clone());
670        }
671        if !previewing
672            && self
673                .committed
674                .index
675                .is_some_and(|index| frame.index <= index)
676        {
677            return Err(RuntimeError::InvalidValue {
678                operation: "closed frame order",
679            });
680        }
681
682        let index = frame.index;
683        let time = frame.time;
684        self.current_bar_index = index;
685        self.previewing = previewing;
686        self.current_frame = Some(frame.clone());
687        self.locals.fill(Value::Na);
688        self.executed_work = 0;
689        self.break_requested = false;
690        self.continue_requested = false;
691        self.return_requested = false;
692        self.return_value = None;
693        self.working
694            .reset_from_committed(&self.committed, self.compiled.program.series);
695        self.resource_check_dirty = false;
696
697        let mut output = OutputBuffer::new(self.limits.max_output_items);
698        let result = (|| {
699            self.charge_by(self.compiled.program.expressions.len())?;
700            self.seed_frame_series(&frame);
701            self.enforce_resource_limits(false)?;
702            let entry_point = self
703                .compiled
704                .program
705                .blocks
706                .get(self.compiled.program.entry_point.0)
707                .map(|block| block.statements.clone())
708                .unwrap_or_default();
709            for statement_id in entry_point {
710                self.execute_statement(statement_id, &mut output)?;
711            }
712            self.complete_outputs(&mut output)?;
713            self.enforce_output_limits(&output)?;
714            self.enforce_resource_limits(false)?;
715            self.resource_check_dirty = false;
716            if !previewing {
717                self.commit(index)?;
718                self.enforce_resource_limits(true)?;
719            }
720            Ok(output.finish(index, time))
721        })();
722
723        if let Err(error) = &result
724            && matches!(
725                error,
726                RuntimeError::ExecutionLimitExceeded | RuntimeError::ResourceLimitExceeded(_)
727            )
728        {
729            self.quarantined = Some(error.clone());
730        }
731        self.working.clear_current_series();
732        self.current_frame = None;
733        result
734    }
735
736    /// Evaluate and commit one closed chart frame.
737    pub fn evaluate_closed(&mut self, frame: IndicatorFrame) -> Result<FrameOutput, RuntimeError> {
738        self.evaluate_frame(frame, false)
739    }
740
741    /// Preview the current open frame from the previous closed state.
742    pub fn preview_open(&mut self, frame: IndicatorFrame) -> Result<FrameOutput, RuntimeError> {
743        self.evaluate_frame(frame, true)
744    }
745
746    pub fn committed_index(&self) -> Option<usize> {
747        self.committed.index
748    }
749
750    /// Discard all committed values and bind a new input configuration.
751    ///
752    /// The caller must recalculate the loaded closed frames before requesting
753    /// an open preview.
754    pub fn reset(&mut self, config: IndicatorConfig) -> Result<(), PlanningError> {
755        let replacement = Self::with_limits(Arc::clone(&self.compiled), config, self.limits)?;
756        *self = replacement;
757        Ok(())
758    }
759
760    fn seed_frame_series(&mut self, frame: &IndicatorFrame) {
761        for expression in &self.compiled.program.expressions {
762            let Some(slot) = expression.series_slot else {
763                continue;
764            };
765            let value = match &expression.kind {
766                ExpressionKind::LoadFrame(field) => Some(frame.primary_value(*field)),
767                _ => None,
768            };
769            let value = match &expression.kind {
770                ExpressionKind::LoadSeriesInput(slot) => {
771                    Some(self.series_input_value(*slot, frame))
772                }
773                _ => value,
774            };
775            if let Some(value) = value
776                && let Some(current) = self.working.current_series.get_mut(slot.0)
777            {
778                *current = Some(value);
779            }
780        }
781    }
782
783    fn series_input_value(&self, slot: SourceInputSlot, frame: &IndicatorFrame) -> Value {
784        let Some(descriptor) = self.compiled.descriptor.series_inputs.get(slot.0) else {
785            return Value::Na;
786        };
787        match frame.series_input(slot.0) {
788            SeriesInputValue::Default => frame.primary_value(descriptor.default_field),
789            SeriesInputValue::Base(source) => source.value(frame).map_or(Value::Na, Value::Float),
790            SeriesInputValue::Value(Some(value)) if value.is_finite() => Value::Float(value),
791            SeriesInputValue::Value(Some(_)) => Value::Na,
792            SeriesInputValue::Value(None) => Value::Na,
793        }
794    }
795
796    fn complete_outputs(&mut self, output: &mut OutputBuffer) -> Result<(), RuntimeError> {
797        self.charge_by(
798            self.compiled.program.statements.len().saturating_add(
799                self.compiled
800                    .program
801                    .plots
802                    .len()
803                    .saturating_mul(output.plots.len()),
804            ),
805        )?;
806        for plot in &self.compiled.program.plots {
807            if !output.plots.iter().any(|output| output.id == plot.id) {
808                output.push_plot(PlotOutput {
809                    id: plot.id.clone(),
810                    value: Value::Na,
811                    color: None,
812                })?;
813            }
814        }
815
816        for statement in &self.compiled.program.statements {
817            match &statement.kind {
818                StatementKind::Marker { id, .. }
819                    if !output.markers.iter().any(|output| output.id == *id) =>
820                {
821                    output.push_marker(MarkerOutput {
822                        id: id.clone(),
823                        value: None,
824                        color: None,
825                        text: None,
826                    })?;
827                }
828                StatementKind::HorizontalLevel { id, .. }
829                    if !output.levels.iter().any(|output| output.id == *id) =>
830                {
831                    output.push_level(HorizontalLevelOutput {
832                        id: id.clone(),
833                        value: None,
834                        color: None,
835                    })?;
836                }
837                StatementKind::OhlcPlot { plot, .. }
838                    if self.compiled.program.plots.get(plot.0).is_some_and(|plot| {
839                        !output.ohlc.iter().any(|output| output.id == plot.id)
840                    }) =>
841                {
842                    if let Some(plot) = self.compiled.program.plots.get(plot.0) {
843                        output.push_ohlc(OhlcOutput {
844                            id: plot.id.clone(),
845                            open: None,
846                            high: None,
847                            low: None,
848                            close: None,
849                            color: None,
850                        })?;
851                    }
852                }
853                StatementKind::RangePlot { plot, .. }
854                    if self.compiled.program.plots.get(plot.0).is_some_and(|plot| {
855                        !output.ranges.iter().any(|output| output.id == plot.id)
856                    }) =>
857                {
858                    if let Some(plot) = self.compiled.program.plots.get(plot.0) {
859                        output.push_range(RangeOutput {
860                            id: plot.id.clone(),
861                            upper: None,
862                            lower: None,
863                            color: None,
864                        })?;
865                    }
866                }
867                _ => {}
868            }
869        }
870        Ok(())
871    }
872
873    fn checkpoint(&mut self, output: &OutputBuffer) -> Result<(), RuntimeError> {
874        self.enforce_output_limits(output)?;
875        if self.resource_check_dirty {
876            self.enforce_resource_limits(false)?;
877            self.resource_check_dirty = false;
878        }
879        Ok(())
880    }
881
882    fn output_value(
883        &mut self,
884        expression: ExprId,
885        output: &str,
886        field: &'static str,
887        expected: ValueType,
888    ) -> Result<Value, RuntimeError> {
889        let value = self.evaluate_expression(expression)?;
890        let valid = value.is_na()
891            || matches!(
892                (expected, &value),
893                (ValueType::Float, Value::Int(_) | Value::Float(_))
894                    | (ValueType::Int, Value::Int(_))
895                    | (ValueType::Bool, Value::Bool(_))
896                    | (ValueType::Color, Value::Color(_))
897                    | (
898                        ValueType::Any,
899                        Value::Int(_) | Value::Float(_) | Value::Bool(_)
900                    )
901            );
902        if valid {
903            return Ok(value);
904        }
905        let expected = match expected {
906            ValueType::Float => "number",
907            ValueType::Int => "integer",
908            ValueType::Bool => "boolean",
909            ValueType::Color => "color",
910            _ => "number or boolean",
911        };
912        Err(RuntimeError::InvalidOutput {
913            output: output.to_owned(),
914            field,
915            expected,
916            actual: value.kind(),
917        })
918    }
919
920    fn output_visibility(
921        &mut self,
922        expression: Option<ExprId>,
923        output: &str,
924        field: &'static str,
925        default: bool,
926    ) -> Result<bool, RuntimeError> {
927        expression
928            .map(|expression| self.output_value(expression, output, field, ValueType::Bool))
929            .transpose()
930            .map(|value| value.map_or(default, |value| value.is_truthy()))
931    }
932
933    fn evaluate_color(
934        &mut self,
935        expression: Option<ExprId>,
936        output: &str,
937        field: &'static str,
938    ) -> Result<Option<Color>, RuntimeError> {
939        expression
940            .map(|expression| self.output_value(expression, output, field, ValueType::Color))
941            .transpose()
942            .map(|value| match value {
943                Some(Value::Color(color)) => Some(Color(color)),
944                _ => None,
945            })
946    }
947
948    fn execute_statement(
949        &mut self,
950        statement_id: crate::hir::StatementId,
951        output: &mut OutputBuffer,
952    ) -> Result<(), RuntimeError> {
953        let statement = self
954            .compiled
955            .program
956            .statements
957            .get(statement_id.0)
958            .ok_or(RuntimeError::UnknownExpression(ExprId(statement_id.0)))?
959            .clone();
960        self.charge()?;
961        match statement.kind {
962            StatementKind::Binding { target, expression } => {
963                let value = self.evaluate_expression(expression)?;
964                self.locals[target.0] = value;
965            }
966            StatementKind::LocalUpdate { target, expression } => {
967                if !self.try_push_array(target, expression)? {
968                    let value = self.evaluate_expression(expression)?;
969                    if let Some(local) = self.locals.get_mut(target.0) {
970                        *local = value;
971                    }
972                }
973            }
974            StatementKind::Destructure {
975                targets,
976                expression,
977            } => {
978                let value = self.evaluate_expression(expression)?;
979                let values = match &value {
980                    Value::Tuple(values) => values.as_slice(),
981                    Value::Array(values) => values.as_slice(),
982                    _ => &[],
983                };
984                for (index, target) in targets.into_iter().enumerate() {
985                    let value = values.get(index);
986                    self.charge_by(1usize.saturating_add(value.map_or(0, Value::copy_work)))?;
987                    if let Some(local) = self.locals.get_mut(target.0) {
988                        *local = value.cloned().unwrap_or(Value::Na);
989                    }
990                }
991            }
992            StatementKind::StateDeclaration {
993                target, initial, ..
994            } => {
995                if !self.state_initialized(target) {
996                    let value = self.evaluate_expression(initial)?;
997                    if !value.is_na() {
998                        self.set_state_value(target, value)?;
999                    }
1000                }
1001            }
1002            StatementKind::StateUpdate { target, expression } => {
1003                let value = self.evaluate_expression(expression)?;
1004                self.set_state_value(target, value)?;
1005            }
1006            StatementKind::Plot {
1007                plot,
1008                expression,
1009                color,
1010            } => {
1011                let Some(descriptor) = self.compiled.program.plots.get(plot.0) else {
1012                    return Ok(());
1013                };
1014                let id = descriptor.id.clone();
1015                let expected = if descriptor.value_type == crate::scene::PlotValueType::Boolean {
1016                    ValueType::Bool
1017                } else {
1018                    ValueType::Float
1019                };
1020                let value = self.output_value(expression, &id, "value", expected)?;
1021                let color = self.evaluate_color(color, &id, "color")?;
1022                output.push_plot(PlotOutput { id, value, color })?;
1023            }
1024            StatementKind::Marker {
1025                id,
1026                expression,
1027                visible,
1028                color,
1029                text,
1030            } => {
1031                let value = self.output_value(expression, &id, "value", ValueType::Any)?;
1032                let visible = self.output_visibility(visible, &id, "visible", true)?;
1033                let value = if !visible {
1034                    None
1035                } else if matches!(value, Value::Bool(true)) {
1036                    self.current_frame
1037                        .as_ref()
1038                        .and_then(|frame| frame.chart.close)
1039                } else if matches!(value, Value::Bool(false)) {
1040                    None
1041                } else {
1042                    value.as_float()
1043                };
1044                output.push_marker(MarkerOutput {
1045                    id: id.clone(),
1046                    value,
1047                    color: self.evaluate_color(color, &id, "color")?,
1048                    text,
1049                })?;
1050            }
1051            StatementKind::Segment {
1052                id,
1053                start_value,
1054                end_value,
1055                start_index,
1056                end_index,
1057                visible,
1058                color,
1059                text,
1060            } => {
1061                let visible = self.output_visibility(visible, &id, "visible", true)?;
1062                if !visible {
1063                    return Ok(());
1064                }
1065                let Some(start_index) = self
1066                    .output_value(start_index, &id, "start_index", ValueType::Int)?
1067                    .as_int()
1068                    .and_then(|value| usize::try_from(value).ok())
1069                else {
1070                    return Ok(());
1071                };
1072                let Some(end_index) = self
1073                    .output_value(end_index, &id, "end_index", ValueType::Int)?
1074                    .as_int()
1075                    .and_then(|value| usize::try_from(value).ok())
1076                else {
1077                    return Ok(());
1078                };
1079                let (Some(start_value), Some(end_value)) = (
1080                    self.output_value(start_value, &id, "start_value", ValueType::Float)?
1081                        .as_float(),
1082                    self.output_value(end_value, &id, "end_value", ValueType::Float)?
1083                        .as_float(),
1084                ) else {
1085                    return Ok(());
1086                };
1087                output.push_segment(SegmentOutput {
1088                    id: id.clone(),
1089                    start_index,
1090                    start_value: start_value.is_finite().then_some(start_value),
1091                    end_index,
1092                    end_value: end_value.is_finite().then_some(end_value),
1093                    color: self.evaluate_color(color, &id, "color")?,
1094                    text,
1095                })?;
1096            }
1097            StatementKind::Region {
1098                id,
1099                key,
1100                upper,
1101                lower,
1102                left_index,
1103                right_index,
1104                visible,
1105                border_color,
1106                fill_color,
1107                border_color_slot,
1108                fill_color_slot,
1109                style_by,
1110                true_color_slot,
1111                false_color_slot,
1112                extend_right,
1113                persistent,
1114                width,
1115            } => {
1116                let visible = self.output_visibility(visible, &id, "visible", true)?;
1117                if !visible {
1118                    return Ok(());
1119                }
1120                let Some(key) = self.output_value(key, &id, "key", ValueType::Int)?.as_int() else {
1121                    return Ok(());
1122                };
1123                let Some(left_index) = self
1124                    .output_value(left_index, &id, "left_index", ValueType::Int)?
1125                    .as_int()
1126                    .and_then(|value| usize::try_from(value).ok())
1127                else {
1128                    return Ok(());
1129                };
1130                let Some(right_index) = self
1131                    .output_value(right_index, &id, "right_index", ValueType::Int)?
1132                    .as_int()
1133                    .and_then(|value| usize::try_from(value).ok())
1134                else {
1135                    return Ok(());
1136                };
1137                let (Some(upper), Some(lower)) = (
1138                    self.output_value(upper, &id, "upper", ValueType::Float)?
1139                        .as_float(),
1140                    self.output_value(lower, &id, "lower", ValueType::Float)?
1141                        .as_float(),
1142                ) else {
1143                    return Ok(());
1144                };
1145                let (border_color_slot, fill_color_slot) = if let Some(style_by) = style_by {
1146                    let selected = if self
1147                        .output_value(style_by, &id, "style_by", ValueType::Bool)?
1148                        .is_truthy()
1149                    {
1150                        true_color_slot
1151                    } else {
1152                        false_color_slot
1153                    };
1154                    (selected.clone(), selected)
1155                } else {
1156                    (border_color_slot, fill_color_slot)
1157                };
1158                let region = RegionOutput {
1159                    id: id.clone(),
1160                    key,
1161                    left_index,
1162                    right_index,
1163                    upper,
1164                    lower,
1165                    border_color: self.evaluate_color(border_color, &id, "border_color")?,
1166                    fill_color: self.evaluate_color(fill_color, &id, "fill_color")?,
1167                    border_color_slot,
1168                    fill_color_slot,
1169                    extend_right: self.output_visibility(
1170                        extend_right,
1171                        &id,
1172                        "extend_right",
1173                        false,
1174                    )?,
1175                    width,
1176                };
1177                if persistent {
1178                    self.charge_by(output.region_updates.len())?;
1179                    output.push_region_update(RegionUpdate::Upsert(region))?;
1180                } else {
1181                    self.charge_by(output.regions.len())?;
1182                    output.push_region(region)?;
1183                }
1184            }
1185            StatementKind::RegionRemove { id, key } => {
1186                let Some(key) = self.output_value(key, &id, "key", ValueType::Int)?.as_int() else {
1187                    return Ok(());
1188                };
1189                self.charge_by(output.region_updates.len())?;
1190                output.push_region_update(RegionUpdate::Remove { id, key })?;
1191            }
1192            StatementKind::HorizontalLevel {
1193                id,
1194                expression,
1195                color,
1196            } => {
1197                output.push_level(crate::frame::HorizontalLevelOutput {
1198                    id: id.clone(),
1199                    value: self
1200                        .output_value(expression, &id, "value", ValueType::Float)?
1201                        .as_float(),
1202                    color: self.evaluate_color(color, &id, "color")?,
1203                })?;
1204            }
1205            StatementKind::OhlcPlot {
1206                plot,
1207                open,
1208                high,
1209                low,
1210                close,
1211                color,
1212            } => {
1213                let Some(id) = self
1214                    .compiled
1215                    .program
1216                    .plots
1217                    .get(plot.0)
1218                    .map(|plot| plot.id.clone())
1219                else {
1220                    return Ok(());
1221                };
1222                output.push_ohlc(OhlcOutput {
1223                    id: id.clone(),
1224                    open: self
1225                        .output_value(open, &id, "open", ValueType::Float)?
1226                        .as_float(),
1227                    high: self
1228                        .output_value(high, &id, "high", ValueType::Float)?
1229                        .as_float(),
1230                    low: self
1231                        .output_value(low, &id, "low", ValueType::Float)?
1232                        .as_float(),
1233                    close: self
1234                        .output_value(close, &id, "close", ValueType::Float)?
1235                        .as_float(),
1236                    color: self.evaluate_color(color, &id, "color")?,
1237                })?;
1238            }
1239            StatementKind::RangePlot {
1240                plot,
1241                upper,
1242                lower,
1243                color,
1244            } => {
1245                let Some(id) = self
1246                    .compiled
1247                    .program
1248                    .plots
1249                    .get(plot.0)
1250                    .map(|plot| plot.id.clone())
1251                else {
1252                    return Ok(());
1253                };
1254                output.push_range(RangeOutput {
1255                    id: id.clone(),
1256                    upper: self
1257                        .output_value(upper, &id, "upper", ValueType::Float)?
1258                        .as_float(),
1259                    lower: self
1260                        .output_value(lower, &id, "lower", ValueType::Float)?
1261                        .as_float(),
1262                    color: self.evaluate_color(color, &id, "color")?,
1263                })?;
1264            }
1265            StatementKind::Expression(expression) => {
1266                let _ = self.evaluate_expression(expression)?;
1267            }
1268            StatementKind::If {
1269                condition,
1270                then_branch,
1271                else_branch,
1272            } => {
1273                let branch = if self.evaluate_expression(condition)?.is_truthy() {
1274                    then_branch
1275                } else {
1276                    else_branch
1277                };
1278                for statement in branch {
1279                    self.execute_statement(statement, output)?;
1280                    if self.return_requested || self.break_requested || self.continue_requested {
1281                        break;
1282                    }
1283                }
1284                self.checkpoint(output)?;
1285            }
1286            StatementKind::While { condition, body } => {
1287                let mut iterations = 0usize;
1288                loop {
1289                    if iterations >= self.limits.max_evaluated_nodes {
1290                        return Err(RuntimeError::ExecutionLimitExceeded);
1291                    }
1292                    if !self.evaluate_expression(condition)?.is_truthy() {
1293                        break;
1294                    }
1295                    for statement in &body {
1296                        self.execute_statement(*statement, output)?;
1297                        if self.return_requested || self.break_requested || self.continue_requested
1298                        {
1299                            break;
1300                        }
1301                    }
1302                    self.checkpoint(output)?;
1303                    if self.return_requested {
1304                        break;
1305                    }
1306                    if self.break_requested {
1307                        self.break_requested = false;
1308                        break;
1309                    }
1310                    if self.continue_requested {
1311                        self.continue_requested = false;
1312                    }
1313                    iterations = iterations
1314                        .checked_add(1)
1315                        .ok_or(RuntimeError::ExecutionLimitExceeded)?;
1316                }
1317            }
1318            StatementKind::ForRange {
1319                target,
1320                start,
1321                end,
1322                body,
1323            } => {
1324                let mut index = self.evaluate_expression(start)?.as_int().unwrap_or(0);
1325                let limit = self.evaluate_expression(end)?.as_int().unwrap_or(-1);
1326                let mut iterations = 0usize;
1327                while index <= limit {
1328                    if iterations >= self.limits.max_evaluated_nodes {
1329                        return Err(RuntimeError::ExecutionLimitExceeded);
1330                    }
1331                    if let Some(value) = self.locals.get_mut(target.0) {
1332                        *value = Value::Int(index);
1333                    }
1334                    for statement in &body {
1335                        self.execute_statement(*statement, output)?;
1336                        if self.return_requested || self.break_requested || self.continue_requested
1337                        {
1338                            break;
1339                        }
1340                    }
1341                    self.checkpoint(output)?;
1342                    if self.return_requested {
1343                        break;
1344                    }
1345                    if self.break_requested {
1346                        self.break_requested = false;
1347                        break;
1348                    }
1349                    self.continue_requested = false;
1350                    index = index
1351                        .checked_add(1)
1352                        .ok_or(RuntimeError::ExecutionLimitExceeded)?;
1353                    iterations = iterations
1354                        .checked_add(1)
1355                        .ok_or(RuntimeError::ExecutionLimitExceeded)?;
1356                }
1357            }
1358            StatementKind::ForEach {
1359                target,
1360                collection,
1361                body,
1362            } => {
1363                let (array, tuple) = match self.evaluate_expression(collection)? {
1364                    Value::Array(values) => (Some(values), Vec::new()),
1365                    Value::Tuple(values) => (None, values),
1366                    _ => (None, Vec::new()),
1367                };
1368                let values = array
1369                    .iter()
1370                    .flat_map(|values| values.as_slice().iter())
1371                    .chain(tuple.iter());
1372                for value in values {
1373                    self.charge_by(1usize.saturating_add(value.copy_work()))?;
1374                    if let Some(local) = self.locals.get_mut(target.0) {
1375                        *local = value.clone();
1376                    }
1377                    for statement in &body {
1378                        self.execute_statement(*statement, output)?;
1379                        if self.return_requested || self.break_requested || self.continue_requested
1380                        {
1381                            break;
1382                        }
1383                    }
1384                    self.checkpoint(output)?;
1385                    if self.return_requested {
1386                        break;
1387                    }
1388                    if self.break_requested {
1389                        self.break_requested = false;
1390                        break;
1391                    }
1392                    self.continue_requested = false;
1393                }
1394            }
1395            StatementKind::Break => self.break_requested = true,
1396            StatementKind::Continue => self.continue_requested = true,
1397            StatementKind::Return(expression) => {
1398                self.return_value = expression
1399                    .map(|expression| self.evaluate_expression(expression))
1400                    .transpose()?;
1401                self.return_requested = true;
1402            }
1403        }
1404        Ok(())
1405    }
1406
1407    fn try_push_array(
1408        &mut self,
1409        target: crate::hir::LocalSlot,
1410        expression: ExprId,
1411    ) -> Result<bool, RuntimeError> {
1412        let Some(ExpressionKind::IntrinsicCall {
1413            intrinsic,
1414            arguments,
1415            ..
1416        }) = self
1417            .compiled
1418            .program
1419            .expressions
1420            .get(expression.0)
1421            .map(|expression| expression.kind.clone())
1422        else {
1423            return Ok(false);
1424        };
1425        if self
1426            .compiled
1427            .registry
1428            .get(intrinsic)
1429            .is_none_or(|definition| definition.name != "array.push")
1430            || arguments.len() != 2
1431            || !matches!(
1432                self.compiled
1433                    .program
1434                    .expressions
1435                    .get(arguments[0].0)
1436                .map(|expression| &expression.kind),
1437                Some(ExpressionKind::LoadLocal(source)) if *source == target
1438            )
1439            || !matches!(self.locals.get(target.0), Some(Value::Array(_)))
1440        {
1441            return Ok(false);
1442        }
1443        self.charge_by(2)?;
1444        let value = self.evaluate_expression(arguments[1])?;
1445        if value.nesting_depth() >= MAX_COLLECTION_NESTING {
1446            return Err(RuntimeError::ResourceLimitExceeded("collection nesting"));
1447        }
1448        let copy_work = match &self.locals[target.0] {
1449            Value::Array(values) if Arc::strong_count(values) > 1 => {
1450                values.as_slice().iter().fold(values.len(), |work, value| {
1451                    work.saturating_add(value.copy_work())
1452                })
1453            }
1454            _ => 0,
1455        };
1456        self.charge_by(copy_work)?;
1457        let Some(Value::Array(values)) = self.locals.get_mut(target.0) else {
1458            return Ok(false);
1459        };
1460        let aliased = Arc::strong_count(values) > 1;
1461        Arc::make_mut(values).push(value);
1462        let (collections, heap_bytes) = self.locals[target.0].resource_usage();
1463        if collections > self.limits.max_collection_elements {
1464            return Err(RuntimeError::ResourceLimitExceeded("collection elements"));
1465        }
1466        if heap_bytes > self.limits.max_heap_bytes {
1467            return Err(RuntimeError::ResourceLimitExceeded("heap bytes"));
1468        }
1469        if aliased {
1470            self.resource_check_dirty = true;
1471        }
1472        Ok(true)
1473    }
1474
1475    fn evaluate_expression(&mut self, expression_id: ExprId) -> Result<Value, RuntimeError> {
1476        self.charge()?;
1477        let expression = self
1478            .compiled
1479            .program
1480            .expressions
1481            .get(expression_id.0)
1482            .ok_or(RuntimeError::UnknownExpression(expression_id))?
1483            .clone();
1484        let copy_work = match &expression.kind {
1485            ExpressionKind::Constant(id) => self.compiled.program.constants.get(id.0),
1486            ExpressionKind::LoadLocal(slot) => self.locals.get(slot.0),
1487            ExpressionKind::LoadInput(slot) => self.plan.inputs.get(slot.0),
1488            ExpressionKind::LoadState(slot) => self.state_value(*slot),
1489            _ => None,
1490        }
1491        .map_or(0, Value::copy_work);
1492        self.charge_by(copy_work)?;
1493        let value = match expression.kind {
1494            ExpressionKind::Constant(constant) => self
1495                .compiled
1496                .program
1497                .constants
1498                .get(constant.0)
1499                .cloned()
1500                .ok_or(RuntimeError::UnknownExpression(expression_id))?,
1501            ExpressionKind::LoadLocal(slot) => {
1502                self.locals.get(slot.0).cloned().unwrap_or(Value::Na)
1503            }
1504            ExpressionKind::LoadInput(slot) => {
1505                self.plan.inputs.get(slot.0).cloned().unwrap_or(Value::Na)
1506            }
1507            ExpressionKind::LoadSeriesInput(slot) => self
1508                .current_frame
1509                .as_ref()
1510                .map_or(Value::Na, |frame| self.series_input_value(slot, frame)),
1511            ExpressionKind::LoadState(slot) => self.state_value(slot).cloned().unwrap_or(Value::Na),
1512            ExpressionKind::LoadBarIndex => Value::Int(self.current_bar_index as i64),
1513            ExpressionKind::LoadBarState(field) => Value::Bool(match field {
1514                BarStateField::IsFirst => self.current_bar_index == 0,
1515                BarStateField::IsLast => self.previewing,
1516                BarStateField::IsConfirmed => !self.previewing,
1517                BarStateField::IsRealtime => self.previewing,
1518            }),
1519            ExpressionKind::Tuple(expressions) => Value::Tuple(
1520                expressions
1521                    .iter()
1522                    .map(|expression| self.evaluate_expression(*expression))
1523                    .collect::<Result<Vec<_>, _>>()?,
1524            ),
1525            ExpressionKind::Array(expressions) => Value::Array(Arc::new(ArrayValue::new(
1526                expressions
1527                    .iter()
1528                    .map(|expression| self.evaluate_expression(*expression))
1529                    .collect::<Result<Vec<_>, _>>()?,
1530            ))),
1531            ExpressionKind::Record(fields) => {
1532                let mut values = BTreeMap::new();
1533                for (name, expression) in fields {
1534                    values.insert(name, self.evaluate_expression(expression)?);
1535                }
1536                Value::Record(values)
1537            }
1538            ExpressionKind::Switch {
1539                operand,
1540                branches,
1541                fallback,
1542            } => {
1543                let operand = self.evaluate_expression(operand)?;
1544
1545                let mut selected = None;
1546                for (condition, result) in branches {
1547                    let condition = self.evaluate_expression(condition)?;
1548                    self.charge_by(
1549                        operand
1550                            .comparison_work()
1551                            .saturating_add(condition.comparison_work())
1552                            .saturating_mul(2),
1553                    )?;
1554                    if operand.equals(&condition).is_some_and(|equal| equal) {
1555                        selected = Some(self.evaluate_expression(result)?);
1556                        break;
1557                    }
1558                }
1559                if let Some(value) = selected {
1560                    value
1561                } else if let Some(fallback) = fallback {
1562                    self.evaluate_expression(fallback)?
1563                } else {
1564                    Value::Na
1565                }
1566            }
1567            ExpressionKind::LoadSeries(slot) => self
1568                .working
1569                .current_series
1570                .get(slot.0)
1571                .and_then(Clone::clone)
1572                .unwrap_or(Value::Na),
1573            ExpressionKind::LoadFrame(field) => self
1574                .current_frame
1575                .as_ref()
1576                .map(|frame| frame.primary_value(field))
1577                .unwrap_or(Value::Na),
1578            ExpressionKind::RecordField { source, field } => {
1579                let source = self.evaluate_expression(source)?;
1580                match source {
1581                    Value::Record(values) => {
1582                        self.charge_by(values.get(&field).map_or(0, Value::copy_work))?;
1583                        values.get(&field).cloned().unwrap_or(Value::Na)
1584                    }
1585                    _ => Value::Na,
1586                }
1587            }
1588            ExpressionKind::History { source, offset } => {
1589                let offset = self.evaluate_expression(offset)?.as_int().unwrap_or(-1);
1590                if offset < 0 {
1591                    Value::Na
1592                } else if offset == 0 {
1593                    self.evaluate_expression(source)?
1594                } else {
1595                    let offset = offset as usize;
1596                    if offset > self.current_bar_index {
1597                        Value::Na
1598                    } else {
1599                        let target = self.current_bar_index - offset;
1600                        let slot = self.compiled.program.expressions[source.0].series_slot;
1601                        let work = slot
1602                            .and_then(|slot| self.committed.history.get(slot.0, target))
1603                            .map_or(0, Value::copy_work);
1604                        self.charge_by(work)?;
1605                        slot.and_then(|slot| self.committed.history.get(slot.0, target))
1606                            .cloned()
1607                            .unwrap_or(Value::Na)
1608                    }
1609                }
1610            }
1611            ExpressionKind::CollectionIndex { source, index } => {
1612                let source = self.evaluate_expression(source)?;
1613                let index = self.evaluate_expression(index)?.as_int();
1614                match (source, index.and_then(|index| usize::try_from(index).ok())) {
1615                    (Value::Tuple(values), Some(index)) => {
1616                        self.charge_by(values.get(index).map_or(0, Value::copy_work))?;
1617                        values.get(index).cloned().unwrap_or(Value::Na)
1618                    }
1619                    (Value::Array(values), Some(index)) => {
1620                        self.charge_by(values.get(index).map_or(0, Value::copy_work))?;
1621                        values.get(index).cloned().unwrap_or(Value::Na)
1622                    }
1623                    _ => Value::Na,
1624                }
1625            }
1626            ExpressionKind::Unary { operator, operand } => {
1627                let operand = self.evaluate_expression(operand)?;
1628                operand.unary(operator).ok_or(RuntimeError::InvalidValue {
1629                    operation: "unary expression",
1630                })?
1631            }
1632            ExpressionKind::Binary {
1633                operator,
1634                left,
1635                right,
1636            } => {
1637                let left = self.evaluate_expression(left)?;
1638                let short_circuit = matches!(
1639                    (&left, operator),
1640                    (Value::Bool(false), BinaryOperator::And)
1641                        | (Value::Bool(true), BinaryOperator::Or)
1642                );
1643                if short_circuit {
1644                    left
1645                } else {
1646                    let right = self.evaluate_expression(right)?;
1647                    if matches!(operator, BinaryOperator::Equal | BinaryOperator::NotEqual) {
1648                        self.charge_by(
1649                            left.comparison_work()
1650                                .saturating_add(right.comparison_work())
1651                                .saturating_mul(2),
1652                        )?;
1653                    }
1654                    left.binary(operator, right)
1655                }
1656            }
1657            ExpressionKind::Conditional {
1658                condition,
1659                when_true,
1660                when_false,
1661            } => {
1662                if self.evaluate_expression(condition)?.is_truthy() {
1663                    self.evaluate_expression(when_true)?
1664                } else {
1665                    self.evaluate_expression(when_false)?
1666                }
1667            }
1668            ExpressionKind::Coalesce { source, fallback } => {
1669                let source = self.evaluate_expression(source)?;
1670                if source.is_na() {
1671                    self.evaluate_expression(fallback)?
1672                } else {
1673                    source
1674                }
1675            }
1676            ExpressionKind::IntrinsicCall {
1677                intrinsic,
1678                call_site,
1679                arguments,
1680            } => {
1681                if self
1682                    .compiled
1683                    .registry
1684                    .get(intrinsic)
1685                    .is_some_and(|definition| definition.name == "record.get")
1686                    && arguments.len() == 2
1687                    && let ExpressionKind::LoadLocal(slot) =
1688                        self.compiled.program.expressions[arguments[0].0].kind
1689                {
1690                    self.charge()?;
1691                    let key = self.evaluate_expression(arguments[1])?;
1692                    let Value::String(key) = key else {
1693                        return Err(RuntimeError::Intrinsic(IntrinsicError::InvalidArguments(
1694                            "record.get expects a string key",
1695                        )));
1696                    };
1697                    let value = match self.locals.get(slot.0) {
1698                        Some(Value::Record(values)) => values.get(&key),
1699                        _ => None,
1700                    };
1701                    let work = value.map_or(0, Value::copy_work);
1702                    self.charge_by(work)?;
1703                    let value = match self.locals.get(slot.0) {
1704                        Some(Value::Record(values)) => {
1705                            values.get(&key).cloned().unwrap_or(Value::Na)
1706                        }
1707                        _ => Value::Na,
1708                    };
1709                    self.enforce_value_limits(&value)?;
1710                    if let Some(slot) = expression.series_slot {
1711                        self.working.current_series[slot.0] = Some(value.clone());
1712                    }
1713                    return Ok(value);
1714                }
1715                let values = arguments
1716                    .iter()
1717                    .map(|argument| self.evaluate_expression(*argument))
1718                    .collect::<Result<Vec<_>, _>>()?;
1719                let (stateful, evaluator, fixed_window) = self
1720                    .compiled
1721                    .registry
1722                    .get(intrinsic)
1723                    .map(|definition| {
1724                        let fixed_argument = |index: usize| {
1725                            arguments.get(index).is_some_and(|argument| {
1726                                self.compiled.program.expressions[argument.0].is_fixed
1727                            })
1728                        };
1729                        let fixed_window = match &definition.signature.warmup {
1730                            Some(WarmupSpec::Window { argument, .. }) => fixed_argument(*argument),
1731                            Some(WarmupSpec::Centered {
1732                                left_argument,
1733                                right_argument,
1734                            }) => fixed_argument(*left_argument) && fixed_argument(*right_argument),
1735                            None => false,
1736                        };
1737                        (
1738                            definition.effects.stateful,
1739                            definition.evaluator,
1740                            fixed_window,
1741                        )
1742                    })
1743                    .ok_or(RuntimeError::UnknownIntrinsic(intrinsic))?;
1744                if stateful {
1745                    self.resource_check_dirty = true;
1746                }
1747                let state_call_site = self.state_call_site(call_site);
1748                if Arc::strong_count(&self.working.intrinsics) > 1 {
1749                    let work = self
1750                        .working
1751                        .intrinsics
1752                        .values()
1753                        .fold(0usize, |work, state| work.saturating_add(state.copy_work()));
1754                    self.charge_by(work)?;
1755                }
1756                let (result, state_heap_exceeded) = {
1757                    let mut context = IntrinsicEvalContext {
1758                        bar_index: self.current_bar_index,
1759                        bar_time: self.current_frame.as_ref().map_or(0, |frame| frame.time),
1760                        max_heap_bytes: self.limits.max_heap_bytes,
1761                        max_window_length: self.limits.max_window_length,
1762                        fixed_window,
1763                        max_collection_elements: self.limits.max_collection_elements,
1764                        states: Arc::make_mut(&mut self.working.intrinsics),
1765                        executed_work: &mut self.executed_work,
1766                        max_work: self.limits.max_evaluated_nodes,
1767                    };
1768                    let result = evaluator(&mut context, state_call_site, &values);
1769                    context
1770                        .charge(context.states.len())
1771                        .map_err(|_| RuntimeError::ExecutionLimitExceeded)?;
1772                    let state_heap_exceeded = context
1773                        .states
1774                        .values()
1775                        .map(IntrinsicState::heap_bytes)
1776                        .fold(0usize, usize::saturating_add)
1777                        > context.max_heap_bytes;
1778                    (result, state_heap_exceeded)
1779                };
1780                if state_heap_exceeded {
1781                    return Err(RuntimeError::ResourceLimitExceeded("heap bytes"));
1782                }
1783                match result {
1784                    Ok(value) => value,
1785                    Err(crate::intrinsic::IntrinsicError::ExecutionLimitExceeded) => {
1786                        return Err(RuntimeError::ExecutionLimitExceeded);
1787                    }
1788                    Err(crate::intrinsic::IntrinsicError::LimitExceeded(resource)) => {
1789                        return Err(RuntimeError::ResourceLimitExceeded(resource));
1790                    }
1791                    Err(error) => return Err(RuntimeError::Intrinsic(error)),
1792                }
1793            }
1794            ExpressionKind::FunctionCall {
1795                function,
1796                call_site,
1797                arguments,
1798                argument_slots,
1799                ..
1800            } => {
1801                if self.call_depth >= self.limits.max_call_depth {
1802                    return Err(RuntimeError::ExecutionLimitExceeded);
1803                }
1804                let function = self
1805                    .compiled
1806                    .program
1807                    .functions
1808                    .get(function.0)
1809                    .cloned()
1810                    .ok_or(RuntimeError::InvalidValue {
1811                        operation: "function call",
1812                    })?;
1813                let values = arguments
1814                    .iter()
1815                    .map(|argument| self.evaluate_expression(*argument))
1816                    .collect::<Result<Vec<_>, _>>()?;
1817                self.charge_by(self.locals.iter().fold(self.locals.len(), |work, value| {
1818                    work.saturating_add(value.copy_work())
1819                }))?;
1820                let saved_locals = self.locals.clone();
1821                let saved_return_requested = self.return_requested;
1822                let saved_return_value = self.return_value.take();
1823                self.return_requested = false;
1824                self.call_depth += 1;
1825                self.call_site_stack.push(call_site);
1826                for slot in &function.local_slots {
1827                    if let Some(value) = self.locals.get_mut(slot.0) {
1828                        *value = Value::Na;
1829                    }
1830                }
1831                let mut provided = vec![false; function.parameters.len()];
1832                for (parameter_index, value) in argument_slots.into_iter().zip(values) {
1833                    let Some(slot) = function.parameters.get(parameter_index) else {
1834                        continue;
1835                    };
1836                    if let Some(local) = self.locals.get_mut(slot.0) {
1837                        *local = value;
1838                        provided[parameter_index] = true;
1839                    }
1840                }
1841                let mut nested_output = OutputBuffer::new(self.limits.max_output_items);
1842                let execution = (|| {
1843                    for (parameter_index, default) in function.parameter_defaults.iter().enumerate()
1844                    {
1845                        if provided.get(parameter_index).copied().unwrap_or(false) {
1846                            continue;
1847                        }
1848                        let Some(default) = default else {
1849                            continue;
1850                        };
1851                        let value = self.evaluate_expression(*default)?;
1852                        if let Some(slot) = function.parameters.get(parameter_index)
1853                            && let Some(local) = self.locals.get_mut(slot.0)
1854                        {
1855                            *local = value;
1856                        }
1857                    }
1858                    for statement in function.body {
1859                        self.execute_statement(statement, &mut nested_output)?;
1860                        if self.return_requested {
1861                            break;
1862                        }
1863                    }
1864                    self.checkpoint(&nested_output)?;
1865                    Ok::<(), RuntimeError>(())
1866                })();
1867                self.call_depth = self.call_depth.saturating_sub(1);
1868                self.call_site_stack.pop();
1869                let result = self.return_value.take().unwrap_or(Value::Na);
1870                self.locals = saved_locals;
1871                self.return_requested = saved_return_requested;
1872                self.return_value = saved_return_value;
1873                execution?;
1874                result
1875            }
1876        };
1877        let value = value.normalize_nonfinite();
1878        self.enforce_value_limits(&value)?;
1879        if let Some(slot) = expression.series_slot
1880            && let Some(current) = self.working.current_series.get_mut(slot.0)
1881        {
1882            *current = Some(value.clone());
1883        }
1884        Ok(value)
1885    }
1886
1887    fn state_value(&self, slot: StateSlot) -> Option<&Value> {
1888        let call_site = self
1889            .compiled
1890            .program
1891            .state_call_sites
1892            .get(slot.0)
1893            .copied()
1894            .unwrap_or(crate::hir::CallSiteId(slot.0));
1895        if self.state_owner(slot).is_some() && !self.call_site_stack.is_empty() {
1896            return self
1897                .working
1898                .function_states
1899                .get(&self.state_call_site(call_site))
1900                .and_then(StateValue::value);
1901        }
1902        self.working.states.get(slot.0).and_then(StateValue::value)
1903    }
1904
1905    fn state_initialized(&self, slot: StateSlot) -> bool {
1906        let call_site = self
1907            .compiled
1908            .program
1909            .state_call_sites
1910            .get(slot.0)
1911            .copied()
1912            .unwrap_or(crate::hir::CallSiteId(slot.0));
1913        if self.state_owner(slot).is_some() && !self.call_site_stack.is_empty() {
1914            return self
1915                .working
1916                .function_states
1917                .get(&self.state_call_site(call_site))
1918                .is_some_and(StateValue::is_initialized);
1919        }
1920        self.working
1921            .states
1922            .get(slot.0)
1923            .is_some_and(StateValue::is_initialized)
1924    }
1925
1926    fn set_state_value(&mut self, slot: StateSlot, value: Value) -> Result<(), RuntimeError> {
1927        let call_site = self
1928            .compiled
1929            .program
1930            .state_call_sites
1931            .get(slot.0)
1932            .copied()
1933            .unwrap_or(crate::hir::CallSiteId(slot.0));
1934        self.resource_check_dirty = true;
1935        if self.state_owner(slot).is_some() && !self.call_site_stack.is_empty() {
1936            let state_call_site = self.state_call_site(call_site);
1937            if Arc::strong_count(&self.working.function_states) > 1 {
1938                let work = self
1939                    .working
1940                    .function_states
1941                    .values()
1942                    .filter_map(StateValue::value)
1943                    .fold(self.working.function_states.len(), |work, value| {
1944                        work.saturating_add(value.copy_work())
1945                    });
1946                self.charge_by(work)?;
1947            }
1948            Arc::make_mut(&mut self.working.function_states)
1949                .insert(state_call_site, StateValue::Value(value));
1950            return Ok(());
1951        }
1952        if Arc::strong_count(&self.working.states) > 1 {
1953            let work = self
1954                .working
1955                .states
1956                .iter()
1957                .filter_map(StateValue::value)
1958                .fold(self.working.states.len(), |work, value| {
1959                    work.saturating_add(value.copy_work())
1960                });
1961            self.charge_by(work)?;
1962        }
1963        if let Some(state) = Arc::make_mut(&mut self.working.states).get_mut(slot.0) {
1964            *state = StateValue::Value(value);
1965        }
1966        Ok(())
1967    }
1968
1969    fn state_owner(&self, slot: StateSlot) -> Option<crate::hir::FunctionId> {
1970        self.compiled
1971            .program
1972            .state_functions
1973            .get(slot.0)
1974            .copied()
1975            .flatten()
1976    }
1977
1978    fn state_call_site(&self, call_site: crate::hir::CallSiteId) -> crate::hir::CallSiteId {
1979        if self.call_site_stack.is_empty() {
1980            return call_site;
1981        }
1982        let mut value = call_site.0.wrapping_add(1);
1983        for parent in &self.call_site_stack {
1984            value = value
1985                .wrapping_mul(1_000_003)
1986                .wrapping_add(parent.0.wrapping_add(1));
1987        }
1988        crate::hir::CallSiteId(value)
1989    }
1990
1991    fn charge(&mut self) -> Result<(), RuntimeError> {
1992        self.charge_by(1)
1993    }
1994
1995    fn charge_by(&mut self, work: usize) -> Result<(), RuntimeError> {
1996        if charge_work(
1997            &mut self.executed_work,
1998            self.limits.max_evaluated_nodes,
1999            work,
2000        ) {
2001            Ok(())
2002        } else {
2003            Err(RuntimeError::ExecutionLimitExceeded)
2004        }
2005    }
2006
2007    fn enforce_resource_limits(&mut self, include_history: bool) -> Result<(), RuntimeError> {
2008        let work = self
2009            .plan
2010            .inputs
2011            .iter()
2012            .chain(self.locals.iter())
2013            .chain(self.committed.states.iter().filter_map(StateValue::value))
2014            .chain(self.working.states.iter().filter_map(StateValue::value))
2015            .chain(
2016                self.committed
2017                    .function_states
2018                    .values()
2019                    .filter_map(StateValue::value),
2020            )
2021            .chain(
2022                self.working
2023                    .function_states
2024                    .values()
2025                    .filter_map(StateValue::value),
2026            )
2027            .chain(self.working.current_series.iter().flatten())
2028            .fold(
2029                self.committed
2030                    .intrinsics
2031                    .len()
2032                    .saturating_add(self.working.intrinsics.len()),
2033                |work, value| work.saturating_add(value.scan_work().saturating_mul(2)),
2034            );
2035        self.charge_by(work.saturating_add(if include_history {
2036            self.committed.history.values.len().saturating_mul(2)
2037        } else {
2038            0
2039        }))?;
2040        match self.resource_limit_exceeded(include_history) {
2041            Some(resource) => Err(RuntimeError::ResourceLimitExceeded(resource)),
2042            None => Ok(()),
2043        }
2044    }
2045
2046    fn enforce_output_limits(&mut self, output: &OutputBuffer) -> Result<(), RuntimeError> {
2047        self.charge_by(output.items.saturating_mul(2))?;
2048        if output.has_string_over(self.limits.max_string_bytes) {
2049            return Err(RuntimeError::ResourceLimitExceeded("string bytes"));
2050        }
2051        let (_, heap_bytes) = output.resource_usage();
2052        if heap_bytes > self.limits.max_heap_bytes {
2053            return Err(RuntimeError::ResourceLimitExceeded("heap bytes"));
2054        }
2055        Ok(())
2056    }
2057
2058    fn enforce_value_limits(&mut self, value: &Value) -> Result<(), RuntimeError> {
2059        if value.nesting_depth() > MAX_COLLECTION_NESTING {
2060            return Err(RuntimeError::ResourceLimitExceeded("collection nesting"));
2061        }
2062        self.charge_by(value.scan_work().saturating_mul(2))?;
2063        if value.has_string_over(self.limits.max_string_bytes) {
2064            return Err(RuntimeError::ResourceLimitExceeded("string bytes"));
2065        }
2066        let (collections, heap_bytes) = value.resource_usage();
2067        if !matches!(value, Value::Tuple(_)) && (collections > 0 || heap_bytes > 0) {
2068            self.resource_check_dirty = true;
2069        }
2070        if collections > self.limits.max_collection_elements {
2071            return Err(RuntimeError::ResourceLimitExceeded("collection elements"));
2072        }
2073        if heap_bytes > self.limits.max_heap_bytes {
2074            return Err(RuntimeError::ResourceLimitExceeded("heap bytes"));
2075        }
2076        Ok(())
2077    }
2078
2079    fn resource_limit_exceeded(&self, include_history: bool) -> Option<&'static str> {
2080        let shared_states = Arc::ptr_eq(&self.committed.states, &self.working.states);
2081        let shared_function_states = Arc::ptr_eq(
2082            &self.committed.function_states,
2083            &self.working.function_states,
2084        );
2085        let shared_intrinsics = Arc::ptr_eq(&self.committed.intrinsics, &self.working.intrinsics);
2086        let mut collections = 0usize;
2087        let mut heap_bytes = size_of::<CommittedState>()
2088            .saturating_add(size_of::<WorkingState>())
2089            .saturating_add(
2090                self.plan
2091                    .inputs
2092                    .capacity()
2093                    .saturating_mul(size_of::<Value>()),
2094            )
2095            .saturating_add(self.locals.capacity().saturating_mul(size_of::<Value>()))
2096            .saturating_add(
2097                self.committed
2098                    .states
2099                    .capacity()
2100                    .saturating_mul(size_of::<StateValue>()),
2101            )
2102            .saturating_add(
2103                self.working
2104                    .current_series
2105                    .capacity()
2106                    .saturating_mul(size_of::<Option<Value>>()),
2107            )
2108            .saturating_add(
2109                self.committed
2110                    .history
2111                    .values
2112                    .capacity()
2113                    .saturating_mul(size_of::<IndexedHistory>()),
2114            )
2115            .saturating_add(
2116                self.call_site_stack
2117                    .capacity()
2118                    .saturating_mul(size_of::<crate::hir::CallSiteId>()),
2119            );
2120        if !shared_states {
2121            heap_bytes = heap_bytes.saturating_add(
2122                self.working
2123                    .states
2124                    .capacity()
2125                    .saturating_mul(size_of::<StateValue>()),
2126            );
2127        }
2128        heap_bytes = heap_bytes
2129            .saturating_add(size_of::<BTreeMap<crate::hir::CallSiteId, StateValue>>())
2130            .saturating_add(
2131                self.committed
2132                    .function_states
2133                    .len()
2134                    .saturating_mul(size_of::<(crate::hir::CallSiteId, StateValue)>()),
2135            );
2136        if !shared_function_states {
2137            heap_bytes = heap_bytes
2138                .saturating_add(size_of::<BTreeMap<crate::hir::CallSiteId, StateValue>>())
2139                .saturating_add(
2140                    self.working
2141                        .function_states
2142                        .len()
2143                        .saturating_mul(size_of::<(crate::hir::CallSiteId, StateValue)>()),
2144                );
2145        }
2146        heap_bytes = heap_bytes
2147            .saturating_add(size_of::<BTreeMap<crate::hir::CallSiteId, IntrinsicState>>())
2148            .saturating_add(
2149                self.committed
2150                    .intrinsics
2151                    .len()
2152                    .saturating_mul(size_of::<(crate::hir::CallSiteId, IntrinsicState)>()),
2153            );
2154        if !shared_intrinsics {
2155            heap_bytes = heap_bytes
2156                .saturating_add(size_of::<BTreeMap<crate::hir::CallSiteId, IntrinsicState>>())
2157                .saturating_add(
2158                    self.working
2159                        .intrinsics
2160                        .len()
2161                        .saturating_mul(size_of::<(crate::hir::CallSiteId, IntrinsicState)>()),
2162                );
2163        }
2164        let mut inspect_value = |value: &Value| -> Option<&'static str> {
2165            if value.has_string_over(self.limits.max_string_bytes) {
2166                return Some("string bytes");
2167            }
2168            let (value_collections, value_bytes) = value.resource_usage();
2169            collections = collections.saturating_add(value_collections);
2170            heap_bytes = heap_bytes.saturating_add(value_bytes);
2171            None
2172        };
2173
2174        for value in self.plan.inputs.iter().chain(self.locals.iter()) {
2175            if inspect_value(value).is_some() {
2176                return Some("string bytes");
2177            }
2178        }
2179        for value in self.committed.states.iter().filter_map(StateValue::value) {
2180            if inspect_value(value).is_some() {
2181                return Some("string bytes");
2182            }
2183        }
2184        for value in self
2185            .committed
2186            .function_states
2187            .values()
2188            .filter_map(StateValue::value)
2189        {
2190            if inspect_value(value).is_some() {
2191                return Some("string bytes");
2192            }
2193        }
2194        if !shared_states {
2195            for value in self.working.states.iter().filter_map(StateValue::value) {
2196                if inspect_value(value).is_some() {
2197                    return Some("string bytes");
2198                }
2199            }
2200        }
2201        if !shared_function_states {
2202            for value in self
2203                .working
2204                .function_states
2205                .values()
2206                .filter_map(StateValue::value)
2207            {
2208                if inspect_value(value).is_some() {
2209                    return Some("string bytes");
2210                }
2211            }
2212        }
2213        for value in self.working.current_series.iter().flatten() {
2214            if inspect_value(value).is_some() {
2215                return Some("string bytes");
2216            }
2217        }
2218
2219        if include_history {
2220            for usage in self
2221                .committed
2222                .history
2223                .values
2224                .iter()
2225                .map(IndexedHistory::resource_usage)
2226            {
2227                let (value_collections, value_bytes) = usage;
2228                collections = collections.saturating_add(value_collections);
2229                heap_bytes = heap_bytes.saturating_add(value_bytes);
2230            }
2231            heap_bytes = self
2232                .committed
2233                .history
2234                .values
2235                .iter()
2236                .fold(heap_bytes, |total, history| {
2237                    total.saturating_add(history.container_heap_bytes())
2238                });
2239        }
2240
2241        for state in self.committed.intrinsics.values() {
2242            heap_bytes = heap_bytes.saturating_add(state.heap_bytes());
2243        }
2244        if !shared_intrinsics {
2245            for state in self.working.intrinsics.values() {
2246                heap_bytes = heap_bytes.saturating_add(state.heap_bytes());
2247            }
2248        }
2249
2250        if collections > self.limits.max_collection_elements {
2251            Some("collection elements")
2252        } else if heap_bytes > self.limits.max_heap_bytes {
2253            Some("heap bytes")
2254        } else {
2255            None
2256        }
2257    }
2258
2259    fn commit(&mut self, bar_index: usize) -> Result<(), RuntimeError> {
2260        let work = self
2261            .working
2262            .current_series
2263            .iter()
2264            .zip(&self.compiled.program.retained_series)
2265            .filter_map(|(value, retained)| retained.then_some(value.as_ref()).flatten())
2266            .fold(self.working.current_series.len(), |work, value| {
2267                work.saturating_add(value.copy_work())
2268                    .saturating_add(value.scan_work())
2269            });
2270        self.charge_by(work)?;
2271        for (slot, value) in self.working.current_series.iter().enumerate() {
2272            if !self
2273                .compiled
2274                .program
2275                .retained_series
2276                .get(slot)
2277                .copied()
2278                .unwrap_or(false)
2279            {
2280                continue;
2281            }
2282            self.committed
2283                .history
2284                .set(slot, bar_index, value.clone().unwrap_or(Value::Na));
2285        }
2286        self.committed.states = Arc::clone(&self.working.states);
2287        self.committed.index = Some(bar_index);
2288        self.committed.function_states = Arc::clone(&self.working.function_states);
2289        self.committed.intrinsics = Arc::clone(&self.working.intrinsics);
2290        self.working.clear_current_series();
2291        let first_retained = bar_index.saturating_sub(self.max_bars_back);
2292        for values in &mut self.committed.history.values {
2293            values.evict_before(first_retained);
2294        }
2295        Ok(())
2296    }
2297}
2298
2299pub(crate) fn charge_work(used: &mut usize, limit: usize, work: usize) -> bool {
2300    match used.checked_add(work) {
2301        Some(next) if next <= limit => {
2302            *used = next;
2303            true
2304        }
2305        _ => false,
2306    }
2307}