Files
RustAst/src/ast/rtl/series/mod.rs
T
Brummel b436e09840 Add documentation infrastructure for RTL symbols
Introduces `RtlDocEntry` and `RtlRegistration` structs to store and
manage documentation for native functions and constants. The
`Environment` struct is updated to include a registry for these
documentation entries.

The `register_native_fn` and `register_constant` methods now return an
`RtlRegistration` builder, allowing for optional chaining of `.doc()`,
`.description()`, and `.examples()` methods before the registration is
finalized when the builder is dropped.

Macros `rtl_fn!` and `rtl_const!` are introduced to simplify the
registration process with documentation.

The MCP server is extended with `get_rtl_docs` and `get_symbol_doc`
tools to expose this documentation externally.

Specific RTL functions and constants (e.g., `+`, `-`, `*`, `/`, `NaN`,
`true`, `false`, `date`, `now`, math functions, `series`, `push`, `len`,
`create-random-ohlc`, `create-ticker`, `pipe`) have been updated to
include their respective documentation using the new infrastructure.

The `Signature` and `StaticType` types have gained `to_doc_string`
methods for generating human-readable documentation strings.
2026-03-25 15:02:19 +01:00

109 lines
4.9 KiB
Rust

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::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()),
}
}
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::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}})"),
}
},
).doc("Creates a new series (ring buffer) with a defined layout and lookback limit.")
.description("First arg is the lookback limit (int), second is either a type keyword (:float, :int, :bool, :text, :datetime) or a schema record ({:field :type}).")
.examples(&[
"(series 100 :float)",
"(series 200 {:price :float :volume :int})",
]);
// (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::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 series as the first argument")
},
).doc("Pushes a new value into a series. After push, index 0 returns this value.")
.examples(&[
"(push my-ticks {:price 10.5 :volume 100})",
"(push prices 42.0)",
]);
// (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::Series(s) = &args[0] {
return Value::Int(s.len() as i64);
}
Value::Int(0)
},
).doc("Returns the current number of items stored in a series.")
.examples(&["(len my-ticks)"]);
}