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
+104 -98
View File
@@ -138,37 +138,36 @@ impl VM {
result = self.eval_observed(observer, &closure.exec_node);
self.frames.pop();
}
Value::Object(obj) => {
if let Some(series) = obj.as_series() {
if next_args.len() != 1 {
return Err(format!(
"{} indexer expects exactly 1 argument (the lookback index), got {}",
obj.type_name(),
next_args.len()
));
}
if let Value::Int(idx) = &next_args[0] {
if *idx < 0 {
return Err(format!(
"{} lookback index cannot be negative: {}",
obj.type_name(),
idx
));
}
result = Ok(series.get_item(*idx as usize).unwrap_or(Value::Void));
} else {
return Err(format!(
"{} index must be an integer, got {}",
obj.type_name(),
next_args[0]
));
}
} else {
Value::Series(s) => {
if next_args.len() != 1 {
return Err(format!(
"Tail call target is not callable: {}",
obj.type_name()
"{} indexer expects exactly 1 argument (the lookback index), got {}",
s.series_type_name(),
next_args.len()
));
}
if let Value::Int(idx) = &next_args[0] {
if *idx < 0 {
return Err(format!(
"{} lookback index cannot be negative: {}",
s.series_type_name(),
idx
));
}
result = Ok(s.get_item(*idx as usize).unwrap_or(Value::Void));
} else {
return Err(format!(
"{} index must be an integer, got {}",
s.series_type_name(),
next_args[0]
));
}
}
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,33 +460,33 @@ 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)));
} else {
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>() {
let mapped = build_map_stream(sn.inner.clone(), k);
return Ok(Value::Object(Rc::new(mapped)));
}
return Err(format!(
"Field accessor .{} expects a record, RecordSeries or Stream, got {}",
k.name(),
obj.type_name()
));
}
}
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)));
}
return Err(format!(
"Field accessor .{} expects a record, RecordSeries or Stream, got {}",
k.name(),
obj.type_name()
));
} else {
return Err(format!(
"Field accessor .{} expects a record, RecordSeries or Stream, got {}",
@@ -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,37 +618,36 @@ impl VM {
self.frames.pop();
res
}
Value::Object(obj) => {
if let Some(series) = obj.as_series() {
let arg_len = self.stack.len() - base;
let res = if arg_len != 1 {
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)",
s.series_type_name()
))
} else if let Value::Int(idx) = self.stack[base] {
if idx < 0 {
Err(format!(
"{} indexer expects exactly 1 argument (the lookback index)",
obj.type_name()
"{} lookback index cannot be negative",
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()
))
} else if let Some(val) = series.get_item(idx as usize) {
Ok(val)
} else {
Ok(Value::Void)
}
} else if let Some(val) = s.get_item(idx as usize) {
Ok(val)
} else {
Err(format!(
"{} index must be an integer",
obj.type_name()
))
};
self.stack.truncate(base);
return res;
Ok(Value::Void)
}
} else {
self.stack.truncate(base);
return Err(format!("Object is not callable: {}", obj.type_name()));
}
Err(format!(
"{} index must be an integer",
s.series_type_name()
))
};
self.stack.truncate(base);
return res;
}
Value::Object(obj) => {
self.stack.truncate(base);
return Err(format!("Object is not callable: {}", obj.type_name()));
}
_ => {
self.stack.truncate(base);
@@ -660,6 +665,7 @@ impl VM {
Value::Function(_) => "Function",
Value::Closure(_) => "Closure",
Value::Quote(_) => "Quote",
Value::Series(_) => "Series",
Value::Object(_) => "Object",
Value::Cell(_) => "Cell",
};