Add series, SMA, and WMA indicators
Introduce new indicators for Simple Moving Average (SMA) and Weighted Moving Average (WMA), along with their Hull Moving Average (HMA) derivative. Also refactors series implementation to use `RefCell` for interior mutability and adds `SeriesMember` trait for better dynamic series access. Updates `soa_series.myc` example to reflect new series creation and push syntax.
This commit is contained in:
@@ -205,6 +205,132 @@ impl Environment {
|
||||
*self.macro_registry.borrow_mut() = expander.into_registry();
|
||||
}
|
||||
|
||||
/// Loads all .myc files from a directory as a library.
|
||||
/// This uses a two-pass approach:
|
||||
/// 1. Discovery: Identify all top-level globals and macros across all files.
|
||||
/// 2. Compilation: Expand, bind, and type-check each file, then run its initialization.
|
||||
pub fn load_library(&self, path: impl AsRef<std::path::Path>) -> Result<(), String> {
|
||||
let dir = path.as_ref();
|
||||
if !dir.is_dir() {
|
||||
return Err(format!("Path {} is not a directory", dir.display()));
|
||||
}
|
||||
|
||||
let mut files = Vec::new();
|
||||
let mut entries: Vec<_> = std::fs::read_dir(dir)
|
||||
.map_err(|e| e.to_string())?
|
||||
.filter_map(|e| e.ok())
|
||||
.collect();
|
||||
// Sort entries for deterministic loading order
|
||||
entries.sort_by_key(|e| e.path());
|
||||
|
||||
for entry in entries {
|
||||
let path = entry.path();
|
||||
if path.is_file() && path.extension().is_some_and(|ext| ext == "myc") {
|
||||
let source = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
|
||||
let mut parser = Parser::new(&source);
|
||||
let untyped_ast = parser.parse_expression();
|
||||
if parser.diagnostics.has_errors() {
|
||||
return Err(format!(
|
||||
"Parser errors in {}:\n{}",
|
||||
path.display(),
|
||||
parser.diagnostics.items[0].message
|
||||
));
|
||||
}
|
||||
files.push((path, untyped_ast));
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 1: Discovery (Globals and Macros)
|
||||
for (_, untyped_ast) in &files {
|
||||
self.discover_globals(untyped_ast);
|
||||
}
|
||||
|
||||
// Pass 2: Compilation and Initialization
|
||||
for (path, untyped_ast) in files {
|
||||
let typed_ast = self.compile_untyped(untyped_ast).map_err(|e: String| {
|
||||
format!("Compilation error in {}:\n{}", path.display(), e)
|
||||
})?;
|
||||
self.run_script_compiled(typed_ast).map_err(|e: String| {
|
||||
format!("Initialization error in {}:\n{}", path.display(), e)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn discover_globals(&self, node: &Node<UntypedKind>) {
|
||||
match &node.kind {
|
||||
UntypedKind::Def { target, .. } => {
|
||||
if let UntypedKind::Parameter(sym) = &target.kind {
|
||||
let mut names = self.global_names.borrow_mut();
|
||||
if !names.contains_key(sym) {
|
||||
let idx = GlobalIdx(names.len() as u32);
|
||||
names.insert(sym.clone(), (idx, node.identity.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
UntypedKind::MacroDecl { name, params, body } => {
|
||||
let mut registry = self.macro_registry.borrow_mut();
|
||||
|
||||
fn extract_names(node: &Node<UntypedKind>) -> Vec<Rc<str>> {
|
||||
match &node.kind {
|
||||
UntypedKind::Parameter(sym) => vec![sym.name.clone()],
|
||||
UntypedKind::Tuple { elements } => {
|
||||
elements.iter().flat_map(extract_names).collect()
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
let p_names = extract_names(params);
|
||||
registry.define(name.name.clone(), p_names, body.as_ref().clone());
|
||||
}
|
||||
UntypedKind::Block { exprs } => {
|
||||
for expr in exprs {
|
||||
self.discover_globals(expr);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_untyped(&self, untyped_ast: Node<UntypedKind>) -> Result<TypedNode, String> {
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
|
||||
let expanded_ast = self.get_expander().expand(untyped_ast)?;
|
||||
|
||||
let (bound_ast, captures) =
|
||||
Binder::bind_root(self.global_names.clone(), &expanded_ast, &mut diagnostics)?;
|
||||
|
||||
let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
|
||||
|
||||
// Pre-allocate global slots
|
||||
{
|
||||
let mut values = self.global_values.borrow_mut();
|
||||
let names = self.global_names.borrow();
|
||||
let max_idx = names.values().map(|(idx, _)| idx.0).max().unwrap_or(0);
|
||||
if (max_idx as usize) >= values.len() {
|
||||
values.resize((max_idx + 1) as usize, Value::Void);
|
||||
}
|
||||
}
|
||||
|
||||
LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut());
|
||||
|
||||
let checker = TypeChecker::new(self.global_types.clone());
|
||||
let typed_ast = checker.check(&bound_ast, &[], &mut diagnostics);
|
||||
|
||||
if diagnostics.has_errors() {
|
||||
return Err(diagnostics
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|d| d.message)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"));
|
||||
}
|
||||
|
||||
Ok(typed_ast)
|
||||
}
|
||||
|
||||
pub fn register_native(
|
||||
&self,
|
||||
name: &str,
|
||||
|
||||
Reference in New Issue
Block a user