b436e09840
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.
109 lines
4.9 KiB
Rust
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)"]);
|
|
}
|