Refactor series types and value enum

This commit refactors the way series are represented and handled within
the AST.
Key changes include:

- Introducing `SeriesStorage` and `PushableStorage` traits to provide a
  more
  unified and type-safe interface for series data.
- Renaming `Object` trait and its methods to clarify that it's for RTL
  extensions
  other than series (like Streams).
- Updating the `Value` enum to have a distinct `Series` variant,
  separating it
  from `Object`.
- Adjusting various parts of the `VM` and `register` functions to work
  with the
  new series traits and `Value::Series` variant.

This change aims to improve the type system's clarity and safety when
dealing with
series data, aligning with Rust's best practices for trait design.
This commit is contained in:
2026-03-23 16:05:58 +01:00
parent a5c3f3da04
commit 6042415dfc
4 changed files with 227 additions and 225 deletions
+82 -94
View File
@@ -4,7 +4,7 @@ use std::fmt::{self, Debug};
use std::rc::Rc;
use std::sync::Arc;
use crate::ast::types::{Keyword, Object, PushableSeries, RecordLayout, Series, StaticType, Value};
use crate::ast::types::{Keyword, PushableStorage, RecordLayout, SeriesStorage, StaticType, Value};
// ============================================================================
// 1. Core Storage Engine
@@ -77,6 +77,8 @@ 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>;
/// Returns the `StaticType` corresponding to this scalar type.
fn static_type() -> StaticType;
}
impl ScalarValue for f64 {
@@ -86,6 +88,9 @@ impl ScalarValue for f64 {
fn from_value(v: Value) -> Option<Self> {
v.as_float()
}
fn static_type() -> StaticType {
StaticType::Float
}
}
impl ScalarValue for i64 {
fn to_value(self) -> Value {
@@ -94,6 +99,9 @@ impl ScalarValue for i64 {
fn from_value(v: Value) -> Option<Self> {
v.as_int()
}
fn static_type() -> StaticType {
StaticType::Int
}
}
impl ScalarValue for bool {
fn to_value(self) -> Value {
@@ -102,6 +110,9 @@ impl ScalarValue for bool {
fn from_value(v: Value) -> Option<Self> {
if let Value::Bool(b) = v { Some(b) } else { None }
}
fn static_type() -> StaticType {
StaticType::Bool
}
}
/// A highly optimized, type-safe series for primitive values (Float, Int, Bool).
@@ -131,9 +142,8 @@ impl<T: ScalarValue> Debug for ScalarSeries<T> {
}
}
// Makes the series usable as a dynamic Object for the VM.
impl<T: ScalarValue> Object for ScalarSeries<T> {
fn type_name(&self) -> &'static str {
impl<T: ScalarValue> SeriesStorage for ScalarSeries<T> {
fn series_type_name(&self) -> &'static str {
self.type_name
}
fn as_any(&self) -> &dyn Any {
@@ -142,23 +152,9 @@ impl<T: ScalarValue> Object for ScalarSeries<T> {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
fn inner_static_type(&self) -> StaticType {
T::static_type()
}
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())
}
@@ -168,6 +164,17 @@ impl<T: ScalarValue> Series for ScalarSeries<T> {
fn total_count(&self) -> u64 {
self.data.borrow().total_count()
}
fn as_pushable(&self) -> Option<&dyn PushableStorage> {
Some(self)
}
}
impl<T: ScalarValue> PushableStorage for ScalarSeries<T> {
fn push_value(&self, value: Value) {
if let Some(v) = T::from_value(value) {
self.data.borrow_mut().push(v);
}
}
}
// ============================================================================
@@ -194,8 +201,8 @@ impl Debug for ValueSeries {
}
}
impl Object for ValueSeries {
fn type_name(&self) -> &'static str {
impl SeriesStorage for ValueSeries {
fn series_type_name(&self) -> &'static str {
"ValueSeries"
}
fn as_any(&self) -> &dyn Any {
@@ -204,21 +211,9 @@ impl Object for ValueSeries {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
fn inner_static_type(&self) -> StaticType {
StaticType::Any
}
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()
}
@@ -228,6 +223,15 @@ impl Series for ValueSeries {
fn total_count(&self) -> u64 {
self.data.borrow().total_count()
}
fn as_pushable(&self) -> Option<&dyn PushableStorage> {
Some(self)
}
}
impl PushableStorage for ValueSeries {
fn push_value(&self, value: Value) {
self.data.borrow_mut().push(value);
}
}
// ============================================================================
@@ -235,9 +239,8 @@ impl Series for ValueSeries {
// ============================================================================
/// 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 {}
/// Ensures each field buffer supports indexed access, dynamic dispatch, and value insertion.
pub trait SeriesMember: PushableStorage {}
impl SeriesMember for ScalarSeries<f64> {}
impl SeriesMember for ScalarSeries<i64> {}
@@ -316,13 +319,13 @@ impl Debug for RecordSeries {
f,
"RecordSeries[fields: {}, len: {}]",
self.fields.len(),
Series::len(self)
self.len()
)
}
}
impl Object for RecordSeries {
fn type_name(&self) -> &'static str {
impl SeriesStorage for RecordSeries {
fn series_type_name(&self) -> &'static str {
"RecordSeries"
}
fn as_any(&self) -> &dyn Any {
@@ -331,29 +334,9 @@ impl Object for RecordSeries {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
fn inner_static_type(&self) -> StaticType {
StaticType::Record(self.layout.clone())
}
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)
}
@@ -366,6 +349,23 @@ impl Series for RecordSeries {
.map(|f| f.borrow().total_count())
.unwrap_or(0)
}
fn as_pushable(&self) -> Option<&dyn PushableStorage> {
Some(self)
}
}
impl PushableStorage 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");
}
}
}
// ============================================================================
@@ -390,13 +390,13 @@ impl Debug for SeriesView {
f,
"SeriesView[field: {}, len: {}]",
self.field_name.name(),
Series::len(self)
self.len()
)
}
}
impl Object for SeriesView {
fn type_name(&self) -> &'static str {
impl SeriesStorage for SeriesView {
fn series_type_name(&self) -> &'static str {
"SeriesView"
}
fn as_any(&self) -> &dyn Any {
@@ -405,12 +405,9 @@ impl Object for SeriesView {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
fn inner_static_type(&self) -> StaticType {
self.inner.borrow().inner_static_type()
}
}
impl Series for SeriesView {
fn get_item(&self, index: usize) -> Option<Value> {
self.inner.borrow().get_item(index)
}
@@ -444,8 +441,8 @@ impl<T: ScalarValue> Debug for SharedSeries<T> {
}
}
impl<T: ScalarValue> Object for SharedSeries<T> {
fn type_name(&self) -> &'static str {
impl<T: ScalarValue> SeriesStorage for SharedSeries<T> {
fn series_type_name(&self) -> &'static str {
self.type_name
}
fn as_any(&self) -> &dyn Any {
@@ -454,12 +451,9 @@ impl<T: ScalarValue> Object for SharedSeries<T> {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
fn inner_static_type(&self) -> StaticType {
T::static_type()
}
}
impl<T: ScalarValue> Series for SharedSeries<T> {
fn get_item(&self, index: usize) -> Option<Value> {
self.buffer
.borrow()
@@ -485,8 +479,8 @@ impl Debug for SharedValueSeries {
}
}
impl Object for SharedValueSeries {
fn type_name(&self) -> &'static str {
impl SeriesStorage for SharedValueSeries {
fn series_type_name(&self) -> &'static str {
"SharedValueSeries"
}
fn as_any(&self) -> &dyn Any {
@@ -495,12 +489,9 @@ impl Object for SharedValueSeries {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
fn inner_static_type(&self) -> StaticType {
StaticType::Any
}
}
impl Series for SharedValueSeries {
fn get_item(&self, index: usize) -> Option<Value> {
self.buffer.borrow().get(index).cloned()
}
@@ -524,13 +515,13 @@ impl Debug for SharedRecordSeries {
f,
"SharedRecordSeries[fields: {}, len: {}]",
self.field_buffers.len(),
Series::len(self)
self.len()
)
}
}
impl Object for SharedRecordSeries {
fn type_name(&self) -> &'static str {
impl SeriesStorage for SharedRecordSeries {
fn series_type_name(&self) -> &'static str {
"SharedRecordSeries"
}
fn as_any(&self) -> &dyn Any {
@@ -539,12 +530,9 @@ impl Object for SharedRecordSeries {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
fn inner_static_type(&self) -> StaticType {
StaticType::Record(self.layout.clone())
}
}
impl Series for SharedRecordSeries {
fn get_item(&self, index: usize) -> Option<Value> {
if self.field_buffers.is_empty() {
return None;
+12 -14
View File
@@ -25,10 +25,10 @@ pub fn register(env: &Environment) {
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))),
"float" => Value::Series(Rc::new(ScalarSeries::<f64>::new("FloatSeries", lookback_limit))),
"int" | "datetime" => Value::Series(Rc::new(ScalarSeries::<i64>::new("IntSeries", lookback_limit))),
"bool" => Value::Series(Rc::new(ScalarSeries::<bool>::new("BoolSeries", lookback_limit))),
"text" => Value::Series(Rc::new(ValueSeries::new(lookback_limit))),
_ => panic!("Unknown or unsupported series type keyword: :{}", k.name()),
}
}
@@ -51,7 +51,7 @@ pub fn register(env: &Environment) {
fields.push((*name, ty));
}
let final_layout = RecordLayout::get_or_create(fields);
Value::Object(Rc::new(RecordSeries::new(final_layout, lookback_limit)))
Value::Series(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}})"),
}
@@ -69,14 +69,14 @@ pub fn register(env: &Environment) {
|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;
if let Value::Series(s) = series_val {
match s.as_pushable() {
Some(p) => p.push_value(val.clone()),
None => panic!("push expects a mutable series, got read-only {}", s.series_type_name()),
}
panic!("push expects a pushable series, got: {}", obj.type_name());
return Value::Void;
}
panic!("push expects a series object as the first argument")
panic!("push expects a series as the first argument")
},
);
@@ -89,9 +89,7 @@ pub fn register(env: &Environment) {
})),
Purity::Pure,
|args: &[Value]| {
if let Value::Object(obj) = &args[0]
&& let Some(s) = obj.as_series()
{
if let Value::Series(s) = &args[0] {
return Value::Int(s.len() as i64);
}
Value::Int(0)