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 pub max_evaluated_nodes: usize,
133 pub max_call_depth: usize,
134 pub max_heap_bytes: usize,
135 pub max_collection_elements: usize,
136 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) => (®ion.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) => (®ion.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 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 pub fn evaluate_closed(&mut self, frame: IndicatorFrame) -> Result<FrameOutput, RuntimeError> {
738 self.evaluate_frame(frame, false)
739 }
740
741 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 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}