Refactor: Introduce system library
- Add `rtl/system.myc` as the system library, containing core utilities like `while` and `cache`. - Remove the duplicate `prelude.myc` from `src/ast/rtl/`. - Modify `Environment::collect_dependencies` to implicitly add `#use system` when necessary. - Update `Environment::with_cwd` to also add the `rtl` subdirectory to search paths. - Add `target/` to `.geminiignore` to exclude build artifacts. - Adjust example `sma.myc` to use the new `cache` macro and reduce sample data size. - Update `rtl/sma.myc` to correctly initialize the `history` series with its `length`. - Add `preload_dependencies` calls to `dump_ast` and `run_script` for proper module loading.
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
*.snap
|
*.snap
|
||||||
*.snap.new
|
*.snap.new
|
||||||
|
target/
|
||||||
|
|||||||
+14
-16
@@ -1,22 +1,20 @@
|
|||||||
#use rtl
|
#use rtl
|
||||||
|
|
||||||
(do
|
(do
|
||||||
(def src (create-random-ohlc 42 20000))
|
(def src (create-random-ohlc 42 200))
|
||||||
|
|
||||||
(macro printx [s] `(fn [x] (print ~s x)))
|
(macro printx [s] `(fn [x] (print ~s x)))
|
||||||
|
|
||||||
; (pipe src print)
|
(pipe src print)
|
||||||
; (pipe [(pipe [(.close src)] (SMA 20))] (printx "sma20="))
|
(pipe [(pipe [(.close src)] (SMA 20))] (printx "sma20="))
|
||||||
; (pipe [(pipe [(.close src)] (SMA 5))] (printx "sma5="))
|
(pipe [(pipe [(.close src)] (SMA 5))] (printx "sma5="))
|
||||||
; (pipe [(pipe [(.close src)] (SMA 100))] (printx "sma100="))
|
(pipe [(pipe [(.close src)] (SMA 100))] (printx "sma100="))
|
||||||
|
|
||||||
(macro cache [lookback type src] `(do (def data (series ~lookback ~type)) (pipe [~src] (fn [s] (do (push data s)))) data))
|
(macro cache [lookback type src] `(do (def data (series ~lookback ~type)) (pipe [~src] (fn [s] (do (push data s)))) data))
|
||||||
|
|
||||||
|
; (def bars (series 20 :float))
|
||||||
|
; (pipe [src] (fn [s] (do (push bars (.close s)))))
|
||||||
; (def bars (series 20 :float))
|
|
||||||
; (pipe [src] (fn [s] (do (push bars (.close s)))))
|
(cache 20 :float src)
|
||||||
|
|
||||||
(cache 20 :float src)
|
)
|
||||||
|
|
||||||
)
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
(def SMA
|
(def SMA
|
||||||
(fn [length]
|
(fn [length]
|
||||||
(do
|
(do
|
||||||
(def history (series :float))
|
(def history (series length :float))
|
||||||
(def sum 0.0)
|
(def sum 0.0)
|
||||||
|
|
||||||
(fn [val]
|
(fn [val]
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
;; Myc System Library
|
||||||
|
;; Standard macros and core utilities.
|
||||||
|
|
||||||
|
(do
|
||||||
|
(macro while [cond body]
|
||||||
|
`((fn [] (if ~cond
|
||||||
|
(do ~body (again))
|
||||||
|
)))
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Creates a stateful cache (Series) from a stateless stream.
|
||||||
|
(macro cache [lookback type src]
|
||||||
|
`(do
|
||||||
|
(def data (series ~lookback ~type))
|
||||||
|
(pipe [~src] (fn [s] (do (push data s))))
|
||||||
|
data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
+19
-13
@@ -177,7 +177,12 @@ impl Environment {
|
|||||||
/// This is a builder method to easily initialize an environment for file-based usage.
|
/// This is a builder method to easily initialize an environment for file-based usage.
|
||||||
pub fn with_cwd(self) -> Self {
|
pub fn with_cwd(self) -> Self {
|
||||||
if let Ok(cwd) = std::env::current_dir() {
|
if let Ok(cwd) = std::env::current_dir() {
|
||||||
self.add_search_path(cwd);
|
self.add_search_path(&cwd);
|
||||||
|
// Also add the rtl subdirectory if it exists
|
||||||
|
let rtl_path = cwd.join("rtl");
|
||||||
|
if rtl_path.exists() {
|
||||||
|
self.add_search_path(rtl_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -213,15 +218,6 @@ impl Environment {
|
|||||||
MacroExpander::new(self.macro_registry.borrow().clone(), evaluator)
|
MacroExpander::new(self.macro_registry.borrow().clone(), evaluator)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_prelude(&self, source: &str) {
|
|
||||||
let mut parser = crate::ast::parser::Parser::new(source);
|
|
||||||
let untyped_ast = parser.parse_expression();
|
|
||||||
let mut expander = self.get_expander();
|
|
||||||
let _ = expander
|
|
||||||
.expand(untyped_ast)
|
|
||||||
.expect("Failed to expand prelude");
|
|
||||||
*self.macro_registry.borrow_mut() = expander.into_registry();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolves a #use module path relative to a base path, then falls back to search paths.
|
/// Resolves a #use module path relative to a base path, then falls back to search paths.
|
||||||
/// Returns a list of all matching .myc files (single file, or all files in a directory).
|
/// Returns a list of all matching .myc files (single file, or all files in a directory).
|
||||||
@@ -274,14 +270,22 @@ impl Environment {
|
|||||||
Err(format!("Could not find module or directory '{}'", module_path))
|
Err(format!("Could not find module or directory '{}'", module_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recursively collects all dependencies starting from a given source string.
|
|
||||||
fn collect_dependencies(
|
fn collect_dependencies(
|
||||||
&self,
|
&self,
|
||||||
source: &str,
|
source: &str,
|
||||||
base_path: &Path,
|
base_path: &Path,
|
||||||
all_files: &mut Vec<(PathBuf, Node<UntypedKind>)>,
|
all_files: &mut Vec<(PathBuf, Node<UntypedKind>)>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let directives = self.extract_use_directives(source);
|
let mut directives = self.extract_use_directives(source);
|
||||||
|
|
||||||
|
// Implicitly add #use system if it's not already there and we can resolve it.
|
||||||
|
// This is only done for the root script (when loaded_modules is empty) to avoid redundancy.
|
||||||
|
if !directives.contains(&"system".to_string())
|
||||||
|
&& self.loaded_modules.borrow().is_empty()
|
||||||
|
&& self.resolve_module_paths("system", base_path).is_ok()
|
||||||
|
{
|
||||||
|
directives.insert(0, "system".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
for module_path in directives {
|
for module_path in directives {
|
||||||
let abs_paths = self.resolve_module_paths(&module_path, base_path)?;
|
let abs_paths = self.resolve_module_paths(&module_path, base_path)?;
|
||||||
@@ -528,10 +532,10 @@ impl Environment {
|
|||||||
|
|
||||||
fn register_stdlib(&self) {
|
fn register_stdlib(&self) {
|
||||||
rtl::register(self);
|
rtl::register(self);
|
||||||
self.eval_prelude(include_str!("rtl/prelude.myc"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dump_ast(&self, source: &str) -> Result<String, String> {
|
pub fn dump_ast(&self, source: &str) -> Result<String, String> {
|
||||||
|
self.preload_dependencies(source, None)?;
|
||||||
let compiled = self.compile(source).into_result()?;
|
let compiled = self.compile(source).into_result()?;
|
||||||
let linked = self.link(compiled);
|
let linked = self.link(compiled);
|
||||||
Ok(Dumper::dump(&linked))
|
Ok(Dumper::dump(&linked))
|
||||||
@@ -717,6 +721,7 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_script(&self, source: &str) -> Result<Value, String> {
|
pub fn run_script(&self, source: &str) -> Result<Value, String> {
|
||||||
|
self.preload_dependencies(source, None)?;
|
||||||
if self.debug_mode {
|
if self.debug_mode {
|
||||||
let (res, logs) = self.run_debug(source)?;
|
let (res, logs) = self.run_debug(source)?;
|
||||||
for line in logs {
|
for line in logs {
|
||||||
@@ -739,6 +744,7 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> {
|
pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> {
|
||||||
|
self.preload_dependencies(source, None)?;
|
||||||
let compiled = self.compile(source).into_result()?;
|
let compiled = self.compile(source).into_result()?;
|
||||||
let linked = self.link(compiled);
|
let linked = self.link(compiled);
|
||||||
let mut vm = VM::new(self.global_values.clone());
|
let mut vm = VM::new(self.global_values.clone());
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
;; Myc Prelude
|
|
||||||
;; This file is evaluated during Environment bootstrapping.
|
|
||||||
;; It contains standard macros and functions.
|
|
||||||
|
|
||||||
(macro while [cond body]
|
|
||||||
`((fn [] (if ~cond
|
|
||||||
(do ~body (again))
|
|
||||||
)))
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Creates a stateful cache (Series) from a stateless stream.
|
|
||||||
;;
|
|
||||||
;; Pipes are highly optimized, stateless stream-routers that do not allocate memory
|
|
||||||
;; for historical data. If multiple indicators need to read historical data from
|
|
||||||
;; a stream, or if you need to access previous items via index (e.g. `(my_data 5)`),
|
|
||||||
;; you must explicitly cache the stream.
|
|
||||||
;;
|
|
||||||
;; Parameters:
|
|
||||||
;; lookback: The maximum number of items to keep in memory (e.g. 100).
|
|
||||||
;; type: The data type or schema of the series (e.g. :float or {:price :float}).
|
|
||||||
;; src: The source stream to observe and cache.
|
|
||||||
;;
|
|
||||||
;; Returns:
|
|
||||||
;; A stateful Series object that automatically updates when the source stream fires.
|
|
||||||
(macro cache [lookback type src]
|
|
||||||
`(do
|
|
||||||
(def data (series ~lookback ~type))
|
|
||||||
(pipe [~src] (fn [s] (do (push data s))))
|
|
||||||
data
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@@ -578,7 +578,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_record_inlining_in_while_loop() {
|
fn test_record_inlining_in_while_loop() {
|
||||||
let env_ast = Environment::new();
|
let env_ast = Environment::new().with_cwd();
|
||||||
// Ensure that constants (like the config record) are inherited into inner lambda scopes (like the body of a while loop).
|
// Ensure that constants (like the config record) are inherited into inner lambda scopes (like the body of a while loop).
|
||||||
let source = r#"
|
let source = r#"
|
||||||
(do
|
(do
|
||||||
@@ -603,7 +603,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// The result of running it should obviously still be correct.
|
// The result of running it should obviously still be correct.
|
||||||
let env_run = Environment::new();
|
let env_run = Environment::new().with_cwd();
|
||||||
let res = env_run.run_script(source).unwrap();
|
let res = env_run.run_script(source).unwrap();
|
||||||
assert_eq!(format!("{}", res), "10");
|
assert_eq!(format!("{}", res), "10");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user