Refactor series implementation into its own module
This commit reorganizes the series-related code by moving the `RingBuffer`, `ScalarSeries`, `ValueSeries`, `RecordSeries`, `SeriesView`, and `SharedSeries` structs into a new `src/ast/rtl/series/data.rs` file. The `src/ast/rtl/series/mod.rs` file now contains the module declaration, re-exports, and the `register` function for registering series-related native functions with the environment. This improves code organization and maintainability.
This commit is contained in:
@@ -0,0 +1,575 @@
|
||||
use std::any::Any;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ast::types::{Keyword, Object, PushableSeries, RecordLayout, Series, StaticType, Value};
|
||||
|
||||
// ============================================================================
|
||||
// 1. Core Storage Engine
|
||||
// ============================================================================
|
||||
|
||||
/// A generic, efficient ring buffer for time series data.
|
||||
/// (Replaces Delphi's TChunkArray for now with VecDeque for O(1) push/pop)
|
||||
#[derive(Clone)]
|
||||
pub struct RingBuffer<T> {
|
||||
buffer: VecDeque<T>,
|
||||
total_count: u64,
|
||||
lookback_limit: usize,
|
||||
}
|
||||
|
||||
impl<T> RingBuffer<T> {
|
||||
pub fn new(lookback_limit: usize) -> Self {
|
||||
// Pre-allocate up to 500 elements to minimize heap fragmentation
|
||||
let initial_capacity = std::cmp::min(lookback_limit, 500);
|
||||
Self {
|
||||
buffer: VecDeque::with_capacity(initial_capacity),
|
||||
total_count: 0,
|
||||
lookback_limit,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an element. Removes the oldest if the lookback limit is reached.
|
||||
#[inline]
|
||||
pub fn push(&mut self, item: T) {
|
||||
self.buffer.push_back(item);
|
||||
self.total_count += 1;
|
||||
|
||||
while self.buffer.len() > self.lookback_limit {
|
||||
self.buffer.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// Access in "Financial Style": Index 0 is the newest element.
|
||||
#[inline]
|
||||
pub fn get(&self, index: usize) -> Option<&T> {
|
||||
let len = self.buffer.len();
|
||||
if index < len {
|
||||
self.buffer.get(len - 1 - index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn total_count(&self) -> u64 {
|
||||
self.total_count
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.buffer.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buffer.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 2. Trait & Primitives for Fast-Paths
|
||||
// ============================================================================
|
||||
|
||||
/// Marker trait ensuring our fast arrays only store true, flat scalars
|
||||
/// (No pointers/heaps like String or Record).
|
||||
pub trait ScalarValue: Copy + Debug + 'static {
|
||||
fn to_value(self) -> Value;
|
||||
/// Converts a dynamic `Value` back to this scalar type, returning `None` on mismatch.
|
||||
fn from_value(v: Value) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl ScalarValue for f64 {
|
||||
fn to_value(self) -> Value {
|
||||
Value::Float(self)
|
||||
}
|
||||
fn from_value(v: Value) -> Option<Self> {
|
||||
v.as_float()
|
||||
}
|
||||
}
|
||||
impl ScalarValue for i64 {
|
||||
fn to_value(self) -> Value {
|
||||
Value::Int(self)
|
||||
}
|
||||
fn from_value(v: Value) -> Option<Self> {
|
||||
v.as_int()
|
||||
}
|
||||
}
|
||||
impl ScalarValue for bool {
|
||||
fn to_value(self) -> Value {
|
||||
Value::Bool(self)
|
||||
}
|
||||
fn from_value(v: Value) -> Option<Self> {
|
||||
if let Value::Bool(b) = v { Some(b) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
/// A highly optimized, type-safe series for primitive values (Float, Int, Bool).
|
||||
#[derive(Clone)]
|
||||
pub struct ScalarSeries<T: ScalarValue> {
|
||||
pub data: std::cell::RefCell<RingBuffer<T>>,
|
||||
pub type_name: &'static str,
|
||||
}
|
||||
|
||||
impl<T: ScalarValue> ScalarSeries<T> {
|
||||
pub fn new(type_name: &'static str, lookback_limit: usize) -> Self {
|
||||
Self {
|
||||
data: std::cell::RefCell::new(RingBuffer::new(lookback_limit)),
|
||||
type_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ScalarValue> Debug for ScalarSeries<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"ScalarSeries<{}>[len: {}]",
|
||||
self.type_name,
|
||||
self.data.borrow().len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Makes the series usable as a dynamic Object for the VM.
|
||||
impl<T: ScalarValue> Object for ScalarSeries<T> {
|
||||
fn type_name(&self) -> &'static str {
|
||||
self.type_name
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
fn as_series(&self) -> Option<&dyn Series> {
|
||||
Some(self)
|
||||
}
|
||||
fn as_pushable_series(&self) -> Option<&dyn PushableSeries> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ScalarValue> PushableSeries for ScalarSeries<T> {
|
||||
fn push_value(&self, value: Value) {
|
||||
if let Some(v) = T::from_value(value) {
|
||||
self.data.borrow_mut().push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ScalarValue> Series for ScalarSeries<T> {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.data.borrow().get(index).map(|&v| v.to_value())
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.data.borrow().len()
|
||||
}
|
||||
fn total_count(&self) -> u64 {
|
||||
self.data.borrow().total_count()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 2b. Fallback Series for non-scalar types
|
||||
// ============================================================================
|
||||
|
||||
/// A generic series for all non-scalar types (Strings, Tuples, etc.) or mixed data.
|
||||
#[derive(Clone)]
|
||||
pub struct ValueSeries {
|
||||
pub data: std::cell::RefCell<RingBuffer<Value>>,
|
||||
}
|
||||
|
||||
impl ValueSeries {
|
||||
pub fn new(lookback_limit: usize) -> Self {
|
||||
Self {
|
||||
data: std::cell::RefCell::new(RingBuffer::new(lookback_limit)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ValueSeries {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "ValueSeries[len: {}]", self.data.borrow().len())
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for ValueSeries {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"ValueSeries"
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
fn as_series(&self) -> Option<&dyn Series> {
|
||||
Some(self)
|
||||
}
|
||||
fn as_pushable_series(&self) -> Option<&dyn PushableSeries> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PushableSeries for ValueSeries {
|
||||
fn push_value(&self, value: Value) {
|
||||
self.data.borrow_mut().push(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl Series for ValueSeries {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.data.borrow().get(index).cloned()
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.data.borrow().len()
|
||||
}
|
||||
fn total_count(&self) -> u64 {
|
||||
self.data.borrow().total_count()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 3. RecordSeries (SoA - Struct of Arrays)
|
||||
// ============================================================================
|
||||
|
||||
/// Marker supertrait for RecordSeries field buffers.
|
||||
/// Ensures each field buffer supports indexed access (Series), dynamic dispatch (Object),
|
||||
/// and value insertion (PushableSeries).
|
||||
pub trait SeriesMember: PushableSeries {}
|
||||
|
||||
impl SeriesMember for ScalarSeries<f64> {}
|
||||
impl SeriesMember for ScalarSeries<i64> {}
|
||||
impl SeriesMember for ScalarSeries<bool> {}
|
||||
impl SeriesMember for ValueSeries {}
|
||||
|
||||
/// The "Struct of Arrays" implementation for Records (e.g., Ticks).
|
||||
/// Instead of holding a large array of Records (AoS), this series
|
||||
/// splits the record and stores each field in a separate (parallel) series.
|
||||
#[derive(Clone)]
|
||||
pub struct RecordSeries {
|
||||
layout: Arc<RecordLayout>,
|
||||
/// Each field of the record gets its own series (SoA).
|
||||
/// We use Rc<RefCell> so we can iterate fields individually,
|
||||
/// but also pass them as 0-Copy references to indicators!
|
||||
fields: Vec<Rc<std::cell::RefCell<dyn SeriesMember>>>,
|
||||
}
|
||||
|
||||
impl RecordSeries {
|
||||
/// Creates a new RecordSeries matching the given layout.
|
||||
pub fn new(layout: Arc<RecordLayout>, lookback_limit: usize) -> Self {
|
||||
let mut fields: Vec<Rc<std::cell::RefCell<dyn SeriesMember>>> =
|
||||
Vec::with_capacity(layout.fields.len());
|
||||
|
||||
for (_, static_type) in &layout.fields {
|
||||
// Automatically select the optimal storage representation based on the field's static type.
|
||||
let member: Rc<std::cell::RefCell<dyn SeriesMember>> = match static_type {
|
||||
StaticType::Float => Rc::new(std::cell::RefCell::new(ScalarSeries::<f64>::new(
|
||||
"FloatSeries",
|
||||
lookback_limit,
|
||||
))),
|
||||
StaticType::Int | StaticType::DateTime => Rc::new(std::cell::RefCell::new(
|
||||
ScalarSeries::<i64>::new("IntSeries", lookback_limit),
|
||||
)),
|
||||
StaticType::Bool => Rc::new(std::cell::RefCell::new(ScalarSeries::<bool>::new(
|
||||
"BoolSeries",
|
||||
lookback_limit,
|
||||
))),
|
||||
// Fallback for everything else (Text, Lists, nested Records without SoA, etc.)
|
||||
_ => Rc::new(std::cell::RefCell::new(ValueSeries::new(lookback_limit))),
|
||||
};
|
||||
fields.push(member);
|
||||
}
|
||||
|
||||
Self { layout, fields }
|
||||
}
|
||||
|
||||
/// The magical 0-Copy Field Mapper!
|
||||
pub fn field(&self, key: Keyword) -> Option<Rc<std::cell::RefCell<dyn SeriesMember>>> {
|
||||
self.layout
|
||||
.index_of(key)
|
||||
.map(|idx| self.fields[idx].clone())
|
||||
}
|
||||
|
||||
/// Dynamically reconstructs the full Record at the given lookback index.
|
||||
pub fn get_record(&self, index: usize) -> Option<Value> {
|
||||
if self.fields.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut vals = Vec::with_capacity(self.fields.len());
|
||||
for field in &self.fields {
|
||||
match field.borrow().get_item(index) {
|
||||
Some(v) => vals.push(v),
|
||||
None => return None, // Out of bounds or incomplete
|
||||
}
|
||||
}
|
||||
|
||||
Some(Value::Record(self.layout.clone(), Rc::new(vals)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for RecordSeries {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"RecordSeries[fields: {}, len: {}]",
|
||||
self.fields.len(),
|
||||
Series::len(self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for RecordSeries {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"RecordSeries"
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
fn as_series(&self) -> Option<&dyn Series> {
|
||||
Some(self)
|
||||
}
|
||||
fn as_pushable_series(&self) -> Option<&dyn PushableSeries> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PushableSeries for RecordSeries {
|
||||
fn push_value(&self, value: Value) {
|
||||
if let Value::Record(_, values) = value {
|
||||
for (i, v) in values.iter().enumerate() {
|
||||
if let Some(f) = self.fields.get(i) {
|
||||
f.borrow().push_value(v.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("push to RecordSeries expects a record");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Series for RecordSeries {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.get_record(index)
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.fields.first().map(|f| f.borrow().len()).unwrap_or(0)
|
||||
}
|
||||
fn total_count(&self) -> u64 {
|
||||
self.fields
|
||||
.first()
|
||||
.map(|f| f.borrow().total_count())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 4. Series View (Safe, 0-Copy access bridge for the VM)
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SeriesView {
|
||||
pub inner: Rc<std::cell::RefCell<dyn SeriesMember>>,
|
||||
pub field_name: Keyword,
|
||||
}
|
||||
|
||||
impl SeriesView {
|
||||
pub fn new(inner: Rc<std::cell::RefCell<dyn SeriesMember>>, field_name: Keyword) -> Self {
|
||||
Self { inner, field_name }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SeriesView {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"SeriesView[field: {}, len: {}]",
|
||||
self.field_name.name(),
|
||||
Series::len(self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for SeriesView {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"SeriesView"
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
fn as_series(&self) -> Option<&dyn Series> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Series for SeriesView {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.inner.borrow().get_item(index)
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.inner.borrow().len()
|
||||
}
|
||||
fn total_count(&self) -> u64 {
|
||||
self.inner.borrow().total_count()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 4b. Shared Series (Read-Only Reactive Storage)
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SharedSeries<T: ScalarValue> {
|
||||
pub buffer: Rc<std::cell::RefCell<RingBuffer<T>>>,
|
||||
pub type_name: &'static str,
|
||||
pub converter: fn(T) -> Value,
|
||||
}
|
||||
|
||||
impl<T: ScalarValue> Debug for SharedSeries<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"SharedSeries<{}>[len: {}]",
|
||||
self.type_name,
|
||||
self.buffer.borrow().len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ScalarValue> Object for SharedSeries<T> {
|
||||
fn type_name(&self) -> &'static str {
|
||||
self.type_name
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
fn as_series(&self) -> Option<&dyn Series> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ScalarValue> Series for SharedSeries<T> {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.buffer
|
||||
.borrow()
|
||||
.get(index)
|
||||
.map(|&v| (self.converter)(v))
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.buffer.borrow().len()
|
||||
}
|
||||
fn total_count(&self) -> u64 {
|
||||
self.buffer.borrow().total_count()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SharedValueSeries {
|
||||
pub buffer: Rc<std::cell::RefCell<RingBuffer<Value>>>,
|
||||
}
|
||||
|
||||
impl Debug for SharedValueSeries {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "SharedValueSeries[len: {}]", self.buffer.borrow().len())
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for SharedValueSeries {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"SharedValueSeries"
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
fn as_series(&self) -> Option<&dyn Series> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Series for SharedValueSeries {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.buffer.borrow().get(index).cloned()
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.buffer.borrow().len()
|
||||
}
|
||||
fn total_count(&self) -> u64 {
|
||||
self.buffer.borrow().total_count()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SharedRecordSeries {
|
||||
pub layout: Arc<RecordLayout>,
|
||||
pub field_buffers: Vec<Rc<std::cell::RefCell<dyn SeriesMember>>>,
|
||||
}
|
||||
|
||||
impl Debug for SharedRecordSeries {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"SharedRecordSeries[fields: {}, len: {}]",
|
||||
self.field_buffers.len(),
|
||||
Series::len(self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for SharedRecordSeries {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"SharedRecordSeries"
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
fn as_series(&self) -> Option<&dyn Series> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Series for SharedRecordSeries {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
if self.field_buffers.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut vals = Vec::with_capacity(self.field_buffers.len());
|
||||
for buffer in &self.field_buffers {
|
||||
match buffer.borrow().get_item(index) {
|
||||
Some(v) => vals.push(v),
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
|
||||
Some(Value::Record(self.layout.clone(), Rc::new(vals)))
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.field_buffers
|
||||
.first()
|
||||
.map(|f| f.borrow().len())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
fn total_count(&self) -> u64 {
|
||||
self.field_buffers
|
||||
.first()
|
||||
.map(|f| f.borrow().total_count())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
pub mod data;
|
||||
pub use data::*;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::ast::environment::Environment;
|
||||
use crate::ast::types::{Purity, RecordLayout, Signature, StaticType, Value};
|
||||
|
||||
// ============================================================================
|
||||
// 5. Script Integration (RTL Registration)
|
||||
// ============================================================================
|
||||
|
||||
pub fn register(env: &Environment) {
|
||||
// (series lookback_limit template_or_type_keyword) -> Series
|
||||
env.register_native_fn(
|
||||
"series",
|
||||
StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Int, StaticType::Any]),
|
||||
ret: StaticType::Any,
|
||||
})),
|
||||
Purity::Impure,
|
||||
|args: &[Value]| {
|
||||
let lookback_limit = args[0].as_int().unwrap_or(0) as usize;
|
||||
let arg = &args[1];
|
||||
match arg {
|
||||
Value::Keyword(k) => {
|
||||
match k.name().to_lowercase().as_str() {
|
||||
"float" => Value::Object(Rc::new(ScalarSeries::<f64>::new("FloatSeries", lookback_limit))),
|
||||
"int" | "datetime" => Value::Object(Rc::new(ScalarSeries::<i64>::new("IntSeries", lookback_limit))),
|
||||
"bool" => Value::Object(Rc::new(ScalarSeries::<bool>::new("BoolSeries", lookback_limit))),
|
||||
"text" => Value::Object(Rc::new(ValueSeries::new(lookback_limit))),
|
||||
_ => panic!("Unknown or unsupported series type keyword: :{}", k.name()),
|
||||
}
|
||||
}
|
||||
Value::Record(schema_layout, schema_values) => {
|
||||
// Interpret the record as a schema: { :field :type_keyword }
|
||||
let mut fields = Vec::with_capacity(schema_layout.fields.len());
|
||||
for (i, (name, _)) in schema_layout.fields.iter().enumerate() {
|
||||
let type_keyword = match &schema_values[i] {
|
||||
Value::Keyword(tk) => tk.name().to_lowercase(),
|
||||
_ => panic!("Field :{} in series schema must be a type keyword (e.g., :float, :int)", name.name()),
|
||||
};
|
||||
let ty = match type_keyword.as_str() {
|
||||
"float" => StaticType::Float,
|
||||
"int" => StaticType::Int,
|
||||
"bool" => StaticType::Bool,
|
||||
"datetime" => StaticType::DateTime,
|
||||
"text" => StaticType::Text,
|
||||
_ => panic!("Unsupported type keyword :{} for series field :{}", type_keyword, name.name()),
|
||||
};
|
||||
fields.push((*name, ty));
|
||||
}
|
||||
let final_layout = RecordLayout::get_or_create(fields);
|
||||
Value::Object(Rc::new(RecordSeries::new(final_layout, lookback_limit)))
|
||||
}
|
||||
_ => panic!("series expects a lookback_limit and a type keyword (:float, :int...) or a schema record (e.g., {{:price :float}})"),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// (push series value) -> Void
|
||||
env.register_native_fn(
|
||||
"push",
|
||||
StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]),
|
||||
ret: StaticType::Void,
|
||||
})),
|
||||
Purity::Impure,
|
||||
|args: &[Value]| {
|
||||
let (series_val, val) = (&args[0], &args[1]);
|
||||
|
||||
if let Value::Object(obj) = series_val {
|
||||
if let Some(p) = obj.as_pushable_series() {
|
||||
p.push_value(val.clone());
|
||||
return Value::Void;
|
||||
}
|
||||
panic!("push expects a pushable series, got: {}", obj.type_name());
|
||||
}
|
||||
panic!("push expects a series object as the first argument")
|
||||
},
|
||||
);
|
||||
|
||||
// (len series) -> Int
|
||||
env.register_native_fn(
|
||||
"len",
|
||||
StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Any]),
|
||||
ret: StaticType::Int,
|
||||
})),
|
||||
Purity::Pure,
|
||||
|args: &[Value]| {
|
||||
if let Value::Object(obj) = &args[0]
|
||||
&& let Some(s) = obj.as_series()
|
||||
{
|
||||
return Value::Int(s.len() as i64);
|
||||
}
|
||||
Value::Int(0)
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user