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:
Michael Schimmel
2026-03-08 12:09:32 +01:00
parent 595bcf09e5
commit 4dfdc75545
7 changed files with 56 additions and 63 deletions
+1
View File
@@ -1,2 +1,3 @@
*.snap
*.snap.new
target/
+7 -9
View File
@@ -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
View File
@@ -2,7 +2,7 @@
(def SMA
(fn [length]
(do
(def history (series :float))
(def history (series length :float))
(def sum 0.0)
(fn [val]
+19
View File
@@ -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
View File
@@ -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());
-31
View File
@@ -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
)
)
+2 -2
View File
@@ -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");
}