Add RecordSeries::get_record and support for (my_ticks 0)

This commit introduces the `get_record` method to `RecordSeries`,
allowing for the dynamic reconstruction of a full record from its
constituent fields at a given index. This enables idiomatic access to
series data using indexing like `(my_ticks 0)`.

The `push-series` function has also been updated to correctly handle the
structure of records when pushing data into a `RecordSeries`.
This commit is contained in:
Michael Schimmel
2026-03-01 18:44:46 +01:00
parent b612df6e10
commit 46b546446f
3 changed files with 58 additions and 17 deletions
+6 -6
View File
@@ -1,15 +1,15 @@
;; Output: 26.7
;; Benchmark: 998ns
;; Benchmark: 998ns
;; Benchmark-Repeat: 2047
(do
(def template {:price 10.0 :volume 100})
(def template {:price 10.0 :volume 100 :msg "hi"})
(def my_ticks (create-series template))
(push-series my_ticks {:price 10.5 :volume 100})
(push-series my_ticks {:price 11.2 :volume 200})
(push-series my_ticks {:price 15.5 :volume 300})
(push-series my_ticks {:price 10.5 :volume 100 :msg "A"})
(push-series my_ticks {:price 11.2 :volume 200 :msg "B"})
(push-series my_ticks {:price 15.5 :volume 300 :msg "C"})
(def prices (.price my_ticks))
(+ (prices 0) (prices 1))
)
)
+34 -11
View File
@@ -251,6 +251,25 @@ impl RecordSeries {
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.
/// This is an O(N) operation where N is the number of fields, meaning it breaks
/// the 0-Copy performance path, but it's essential for idiomatic AST access `(series 0)`.
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_value(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 {
@@ -356,19 +375,23 @@ pub fn register(env: &Environment) {
}
let (series_val, record_val) = (&args[0], &args[1]);
if let Value::Object(obj) = series_val {
if let Some(record_series) = obj.as_any().downcast_ref::<RecordSeries>() {
if let Value::Record(_, values) = record_val {
// Push each value into its corresponding column (SoA push)
for (i, field_val) in values.iter().enumerate() {
if let Some(field_series) = record_series.fields.get(i) {
field_series.borrow_mut().push_value(field_val.clone(), None);
}
let record_series = if let Value::Object(obj) = series_val {
obj.as_any().downcast_ref::<RecordSeries>()
} else {
None
};
if let Some(record_series) = record_series {
if let Value::Record(_, values) = record_val {
// Push each value into its corresponding column (SoA push)
for (i, field_val) in values.iter().enumerate() {
if let Some(field_series) = record_series.fields.get(i) {
field_series.borrow_mut().push_value(field_val.clone(), None);
}
return Value::Void;
} else {
panic!("push-series expects a record as the second argument");
}
return Value::Void;
} else {
panic!("push-series expects a record as the second argument");
}
}
panic!("push-series expects a RecordSeries as the first argument")
+18
View File
@@ -547,6 +547,24 @@ impl VM {
} else {
break Err("Series index must be an integer".to_string());
}
} else if let Some(rs) = obj.as_any().downcast_ref::<crate::ast::rtl::series::RecordSeries>() {
// Support for `(my_ticks 0)` - reconstructs the full Record from SoA!
if arg_vals.len() != 1 {
break Err("RecordSeries indexer expects exactly 1 argument (the lookback index)".to_string());
}
if let Value::Int(idx) = arg_vals[0] {
if idx < 0 {
break Err("RecordSeries lookback index cannot be negative".to_string());
}
if let Some(rec) = rs.get_record(idx as usize) {
break Ok(rec);
} else {
// Out of bounds / not enough data yet
break Ok(Value::Void);
}
} else {
break Err("RecordSeries index must be an integer".to_string());
}
} else {
break Err(format!("Object is not callable: {}", obj.type_name()));
}