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.new
|
||||
target/
|
||||
|
||||
+7
-9
@@ -1,22 +1,20 @@
|
||||
#use rtl
|
||||
|
||||
(do
|
||||
(def src (create-random-ohlc 42 20000))
|
||||
(def src (create-random-ohlc 42 200))
|
||||
|
||||
(macro printx [s] `(fn [x] (print ~s x)))
|
||||
|
||||
; (pipe src print)
|
||||
; (pipe [(pipe [(.close src)] (SMA 20))] (printx "sma20="))
|
||||
; (pipe [(pipe [(.close src)] (SMA 5))] (printx "sma5="))
|
||||
; (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))
|
||||
|
||||
(pipe src print)
|
||||
(pipe [(pipe [(.close src)] (SMA 20))] (printx "sma20="))
|
||||
(pipe [(pipe [(.close src)] (SMA 5))] (printx "sma5="))
|
||||
(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))
|
||||
|
||||
; (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
|
||||
(fn [length]
|
||||
(do
|
||||
(def history (series :float))
|
||||
(def history (series length :float))
|
||||
(def sum 0.0)
|
||||
|
||||
(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.
|
||||
pub fn with_cwd(self) -> Self {
|
||||
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
|
||||
}
|
||||
@@ -213,15 +218,6 @@ impl Environment {
|
||||
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.
|
||||
/// 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))
|
||||
}
|
||||
|
||||
/// Recursively collects all dependencies starting from a given source string.
|
||||
fn collect_dependencies(
|
||||
&self,
|
||||
source: &str,
|
||||
base_path: &Path,
|
||||
all_files: &mut Vec<(PathBuf, Node<UntypedKind>)>,
|
||||
) -> 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 {
|
||||
let abs_paths = self.resolve_module_paths(&module_path, base_path)?;
|
||||
@@ -528,10 +532,10 @@ impl Environment {
|
||||
|
||||
fn register_stdlib(&self) {
|
||||
rtl::register(self);
|
||||
self.eval_prelude(include_str!("rtl/prelude.myc"));
|
||||
}
|
||||
|
||||
pub fn dump_ast(&self, source: &str) -> Result<String, String> {
|
||||
self.preload_dependencies(source, None)?;
|
||||
let compiled = self.compile(source).into_result()?;
|
||||
let linked = self.link(compiled);
|
||||
Ok(Dumper::dump(&linked))
|
||||
@@ -717,6 +721,7 @@ impl Environment {
|
||||
}
|
||||
|
||||
pub fn run_script(&self, source: &str) -> Result<Value, String> {
|
||||
self.preload_dependencies(source, None)?;
|
||||
if self.debug_mode {
|
||||
let (res, logs) = self.run_debug(source)?;
|
||||
for line in logs {
|
||||
@@ -739,6 +744,7 @@ impl Environment {
|
||||
}
|
||||
|
||||
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 linked = self.link(compiled);
|
||||
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]
|
||||
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).
|
||||
let source = r#"
|
||||
(do
|
||||
@@ -603,7 +603,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// 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();
|
||||
assert_eq!(format!("{}", res), "10");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user