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:
+82
-94
@@ -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
@@ -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());
|
||||
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()),
|
||||
}
|
||||
return Value::Void;
|
||||
}
|
||||
panic!("push expects a pushable series, got: {}", obj.type_name());
|
||||
}
|
||||
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)
|
||||
|
||||
+28
-18
@@ -177,7 +177,8 @@ impl RecordLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface for custom objects (Series, Streams, SyntaxNodes, and custom extensions).
|
||||
/// Interface for custom RTL objects (Streams and custom extensions).
|
||||
/// For series data, use `Value::Series` with the `SeriesStorage` trait instead.
|
||||
pub trait Object: fmt::Debug {
|
||||
fn type_name(&self) -> &'static str;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
@@ -185,26 +186,20 @@ pub trait Object: fmt::Debug {
|
||||
/// Converts `Rc<Self>` into `Rc<dyn Any>`, enabling zero-copy downcasting to a concrete
|
||||
/// `Rc<T>` via `Rc::downcast::<T>()`. Requires `arbitrary_self_types` (stable since 1.81).
|
||||
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any>;
|
||||
|
||||
/// If this object supports indexed lookback access, returns a `Series` reference.
|
||||
fn as_series(&self) -> Option<&dyn Series> {
|
||||
None
|
||||
}
|
||||
|
||||
/// If this object supports pushing new values, returns a `PushableSeries` reference.
|
||||
fn as_pushable_series(&self) -> Option<&dyn PushableSeries> {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// Read interface for all series types: indexed lookback access and type metadata.
|
||||
pub trait SeriesStorage: fmt::Debug {
|
||||
fn series_type_name(&self) -> &'static str;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Extension of `Object + Series` for types that also accept new values via `push_value`.
|
||||
/// Only series types that are mutable (ScalarSeries, ValueSeries, RecordSeries) implement this.
|
||||
pub trait PushableSeries: Object + Series {
|
||||
fn push_value(&self, val: Value);
|
||||
}
|
||||
/// Converts `Rc<Self>` into `Rc<dyn Any>` for zero-copy concrete downcast.
|
||||
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any>;
|
||||
|
||||
/// Returns the inner `StaticType` of this series' elements
|
||||
/// (e.g. `StaticType::Float` for a float series, `StaticType::Record(layout)` for a record series).
|
||||
fn inner_static_type(&self) -> StaticType;
|
||||
|
||||
/// Unified interface for all series types (Local RecordSeries, SeriesViews, and future SharedSeries).
|
||||
pub trait Series {
|
||||
/// Gets the value at the specified lookback index (0 = newest).
|
||||
fn get_item(&self, index: usize) -> Option<Value>;
|
||||
|
||||
@@ -218,6 +213,17 @@ pub trait Series {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns a write interface if this series supports push. Read-only series return `None`.
|
||||
fn as_pushable(&self) -> Option<&dyn PushableStorage> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Write interface for mutable series (ScalarSeries, ValueSeries, RecordSeries).
|
||||
/// Obtained via `SeriesStorage::as_pushable()`.
|
||||
pub trait PushableStorage: SeriesStorage {
|
||||
fn push_value(&self, val: Value);
|
||||
}
|
||||
|
||||
/// A shared sequence of values, used by both Tuples and Records.
|
||||
@@ -265,7 +271,8 @@ pub enum Value {
|
||||
Function(Rc<NativeFunction>),
|
||||
Closure(Rc<Closure>), // Compiled function with captured upvalues
|
||||
Quote(Rc<SyntaxNode>), // Quoted AST node (from 'expr or macro expansion)
|
||||
Object(Rc<dyn Object>), // RTL extensions: Series, Streams, and custom types
|
||||
Series(Rc<dyn SeriesStorage>), // Time series: ScalarSeries, RecordSeries, SeriesView, etc.
|
||||
Object(Rc<dyn Object>), // RTL extensions: Streams and custom types
|
||||
Cell(Rc<RefCell<Value>>), // Boxed value for captures
|
||||
}
|
||||
|
||||
@@ -287,6 +294,7 @@ impl PartialEq for Value {
|
||||
(Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b),
|
||||
(Value::Closure(a), Value::Closure(b)) => Rc::ptr_eq(a, b),
|
||||
(Value::Quote(a), Value::Quote(b)) => Rc::ptr_eq(a, b),
|
||||
(Value::Series(a), Value::Series(b)) => Rc::ptr_eq(a, b),
|
||||
(Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
|
||||
(Value::Cell(a), Value::Cell(b)) => Rc::ptr_eq(a, b),
|
||||
_ => false,
|
||||
@@ -645,6 +653,7 @@ impl Value {
|
||||
Value::Function(_) => StaticType::Any, // Dynamic function
|
||||
Value::Closure(_) => StaticType::Object("closure"),
|
||||
Value::Quote(_) => StaticType::Object("ast-node"),
|
||||
Value::Series(s) => StaticType::Series(Box::new(s.inner_static_type())),
|
||||
Value::Object(o) => StaticType::Object(o.type_name()),
|
||||
Value::Cell(c) => c.borrow().static_type(),
|
||||
}
|
||||
@@ -690,6 +699,7 @@ impl fmt::Display for Value {
|
||||
Value::Function(f_meta) => write!(f, "<native fn, {:?}>", f_meta.purity),
|
||||
Value::Closure(_) => write!(f, "<closure>"),
|
||||
Value::Quote(_) => write!(f, "<ast-node>"),
|
||||
Value::Series(s) => write!(f, "<series:{}>", s.series_type_name()),
|
||||
Value::Object(o) => write!(f, "<{:?}>", o),
|
||||
Value::Cell(c) => write!(f, "{}", c.borrow()),
|
||||
}
|
||||
|
||||
+55
-49
@@ -138,12 +138,11 @@ impl VM {
|
||||
result = self.eval_observed(observer, &closure.exec_node);
|
||||
self.frames.pop();
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if let Some(series) = obj.as_series() {
|
||||
Value::Series(s) => {
|
||||
if next_args.len() != 1 {
|
||||
return Err(format!(
|
||||
"{} indexer expects exactly 1 argument (the lookback index), got {}",
|
||||
obj.type_name(),
|
||||
s.series_type_name(),
|
||||
next_args.len()
|
||||
));
|
||||
}
|
||||
@@ -151,25 +150,25 @@ impl VM {
|
||||
if *idx < 0 {
|
||||
return Err(format!(
|
||||
"{} lookback index cannot be negative: {}",
|
||||
obj.type_name(),
|
||||
s.series_type_name(),
|
||||
idx
|
||||
));
|
||||
}
|
||||
result = Ok(series.get_item(*idx as usize).unwrap_or(Value::Void));
|
||||
result = Ok(s.get_item(*idx as usize).unwrap_or(Value::Void));
|
||||
} else {
|
||||
return Err(format!(
|
||||
"{} index must be an integer, got {}",
|
||||
obj.type_name(),
|
||||
s.series_type_name(),
|
||||
next_args[0]
|
||||
));
|
||||
}
|
||||
} else {
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
return Err(format!(
|
||||
"Tail call target is not callable: {}",
|
||||
obj.type_name()
|
||||
));
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(format!("Tail call target is not callable: {}", other));
|
||||
}
|
||||
@@ -348,21 +347,26 @@ impl VM {
|
||||
Err(format!("Record does not have field :{}", field.name()))
|
||||
}
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
let any_ptr = obj.as_any();
|
||||
if let Some(record_series) =
|
||||
any_ptr.downcast_ref::<RecordSeries>()
|
||||
{
|
||||
Value::Series(s) => {
|
||||
if let Some(record_series) = s.as_any().downcast_ref::<RecordSeries>() {
|
||||
if let Some(field_series) = record_series.field(*field) {
|
||||
let view =
|
||||
SeriesView::new(field_series, *field);
|
||||
return Ok(Value::Object(std::rc::Rc::new(view))); } else {
|
||||
let view = SeriesView::new(field_series, *field);
|
||||
return Ok(Value::Series(std::rc::Rc::new(view)));
|
||||
} else {
|
||||
return Err(format!(
|
||||
"RecordSeries does not have field :{}",
|
||||
field.name()
|
||||
));
|
||||
}
|
||||
} else if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
|
||||
}
|
||||
Err(format!(
|
||||
"Field accessor .{} expects a record or RecordSeries, got series:{}",
|
||||
field.name(),
|
||||
s.series_type_name()
|
||||
))
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
|
||||
let mapped = build_map_stream(sn.inner.clone(), *field);
|
||||
return Ok(Value::Object(Rc::new(mapped)));
|
||||
}
|
||||
@@ -433,7 +437,7 @@ impl VM {
|
||||
self.stack.truncate(base);
|
||||
|
||||
match func_val {
|
||||
Value::Closure(_) | Value::Object(_) => {
|
||||
Value::Closure(_) | Value::Object(_) | Value::Series(_) => {
|
||||
self.tail_call = Some((func_val, arg_vals));
|
||||
return Ok(Value::Void);
|
||||
}
|
||||
@@ -456,25 +460,25 @@ impl VM {
|
||||
k.name()
|
||||
));
|
||||
}
|
||||
} else if let Value::Object(obj) = rec {
|
||||
// Polymorphic Field Access: Allow `.field` on a RecordSeries
|
||||
if let Some(rs) = obj
|
||||
.as_any()
|
||||
.downcast_ref::<RecordSeries>()
|
||||
{
|
||||
} else if let Value::Series(s) = rec {
|
||||
if let Some(rs) = s.as_any().downcast_ref::<RecordSeries>() {
|
||||
if let Some(field_series) = rs.field(k) {
|
||||
let view = SeriesView::new(
|
||||
field_series,
|
||||
k,
|
||||
);
|
||||
return Ok(Value::Object(std::rc::Rc::new(view)));
|
||||
let view = SeriesView::new(field_series, k);
|
||||
return Ok(Value::Series(std::rc::Rc::new(view)));
|
||||
} else {
|
||||
return Err(format!(
|
||||
"RecordSeries does not have field :{}",
|
||||
k.name()
|
||||
));
|
||||
}
|
||||
} else if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
|
||||
}
|
||||
return Err(format!(
|
||||
"Field accessor .{} expects a record or RecordSeries, got series:{}",
|
||||
k.name(),
|
||||
s.series_type_name()
|
||||
));
|
||||
} else if let Value::Object(obj) = rec {
|
||||
if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
|
||||
let mapped = build_map_stream(sn.inner.clone(), k);
|
||||
return Ok(Value::Object(Rc::new(mapped)));
|
||||
}
|
||||
@@ -526,24 +530,26 @@ impl VM {
|
||||
} else {
|
||||
Err(format!("Record does not have field :{}", k.name()))
|
||||
}
|
||||
} else if let Value::Object(obj) = rec {
|
||||
if let Some(rs) = obj
|
||||
.as_any()
|
||||
.downcast_ref::<RecordSeries>()
|
||||
{
|
||||
} else if let Value::Series(s) = rec {
|
||||
if let Some(rs) = s.as_any().downcast_ref::<RecordSeries>() {
|
||||
if let Some(field_series) = rs.field(*k) {
|
||||
let view = SeriesView::new(
|
||||
field_series,
|
||||
*k,
|
||||
);
|
||||
Ok(Value::Object(std::rc::Rc::new(view)))
|
||||
let view = SeriesView::new(field_series, *k);
|
||||
Ok(Value::Series(std::rc::Rc::new(view)))
|
||||
} else {
|
||||
Err(format!(
|
||||
"RecordSeries does not have field :{}",
|
||||
k.name()
|
||||
))
|
||||
}
|
||||
} else if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
|
||||
} else {
|
||||
Err(format!(
|
||||
"Field accessor .{} expects a record or RecordSeries, got series:{}",
|
||||
k.name(),
|
||||
s.series_type_name()
|
||||
))
|
||||
}
|
||||
} else if let Value::Object(obj) = rec {
|
||||
if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
|
||||
let mapped = build_map_stream(sn.inner.clone(), *k);
|
||||
Ok(Value::Object(Rc::new(mapped)))
|
||||
} else {
|
||||
@@ -612,21 +618,20 @@ impl VM {
|
||||
self.frames.pop();
|
||||
res
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if let Some(series) = obj.as_series() {
|
||||
Value::Series(s) => {
|
||||
let arg_len = self.stack.len() - base;
|
||||
let res = if arg_len != 1 {
|
||||
Err(format!(
|
||||
"{} indexer expects exactly 1 argument (the lookback index)",
|
||||
obj.type_name()
|
||||
s.series_type_name()
|
||||
))
|
||||
} else if let Value::Int(idx) = self.stack[base] {
|
||||
if idx < 0 {
|
||||
Err(format!(
|
||||
"{} lookback index cannot be negative",
|
||||
obj.type_name()
|
||||
s.series_type_name()
|
||||
))
|
||||
} else if let Some(val) = series.get_item(idx as usize) {
|
||||
} else if let Some(val) = s.get_item(idx as usize) {
|
||||
Ok(val)
|
||||
} else {
|
||||
Ok(Value::Void)
|
||||
@@ -634,16 +639,16 @@ impl VM {
|
||||
} else {
|
||||
Err(format!(
|
||||
"{} index must be an integer",
|
||||
obj.type_name()
|
||||
s.series_type_name()
|
||||
))
|
||||
};
|
||||
self.stack.truncate(base);
|
||||
return res;
|
||||
} else {
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
self.stack.truncate(base);
|
||||
return Err(format!("Object is not callable: {}", obj.type_name()));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.stack.truncate(base);
|
||||
let variant_name = match current_func {
|
||||
@@ -660,6 +665,7 @@ impl VM {
|
||||
Value::Function(_) => "Function",
|
||||
Value::Closure(_) => "Closure",
|
||||
Value::Quote(_) => "Quote",
|
||||
Value::Series(_) => "Series",
|
||||
Value::Object(_) => "Object",
|
||||
Value::Cell(_) => "Cell",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user