Add prelude and while macro

This commit introduces a new prelude file that defines the `while` macro
and registers it during environment bootstrapping. The `again` macro has
been updated to accept multiple arguments for its recursive call, and
several examples have been adjusted to reflect this change.
Additionally, the `MacroRegistry` in the compiler is now cloneable and
can be moved out of the `MacroExpander`.
This commit is contained in:
Michael Schimmel
2026-02-28 12:41:19 +01:00
parent 83324a1892
commit 7126668934
8 changed files with 56 additions and 26 deletions
+3 -3
View File
@@ -1,10 +1,10 @@
;; Benchmark: 2.5us
;; Benchmark-Repeat: 804
;; Benchmark: 2.5us
;; Benchmark-Repeat: 804
;; Output: 120
(do
(def factorial (fn [n acc]
(if (= n 0)
acc
(again [(- n 1) (* acc n)]))))
(again (- n 1) (* acc n)))))
(factorial 5 1))
+10 -19
View File
@@ -1,21 +1,12 @@
;; Benchmark: 1.7ms
;; Benchmark-Repeat: 3
;; Benchmark: TBD
;; Tests the effect of record inlining and field lookup optimization
;; Output: 10000
(do
(macro while [cond body]
`(do
(def _while_loop (fn []
(if ~cond
(do ~body (_while_loop))
...)))
(_while_loop)))
(def config {:start 0 :limit 10000 :step 2})
(def x (.start config))
(while (< x (.limit config))
(assign x (+ x (.step config))))
x
)
;; Tests the effect of record inlining and field lookup optimization
;; Output: 10000
(do
(def config {:start 0 :limit 10000 :step 2})
(def x (.start config))
(while (< x (.limit config))
(assign x (+ x (.step config))))
x
)
+5
View File
@@ -22,6 +22,7 @@ struct ExpansionState<'a> {
type MacroMap = HashMap<Rc<str>, (Vec<Rc<str>>, Node<UntypedKind>)>;
/// A registry for macro declarations.
#[derive(Clone)]
pub struct MacroRegistry {
scopes: Vec<MacroMap>,
}
@@ -78,6 +79,10 @@ impl<E: MacroEvaluator> MacroExpander<E> {
}
}
pub fn into_registry(self) -> MacroRegistry {
self.registry
}
pub fn expand(&mut self, node: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> {
self.expand_recursive(node)
}
+12 -1
View File
@@ -32,6 +32,7 @@ pub struct Environment {
pub monomorph_cache: Rc<RefCell<MonoCache>>,
pub debug_mode: bool,
pub optimization: bool,
pub macro_registry: Rc<RefCell<MacroRegistry>>,
}
struct EnvFunctionRegistry {
@@ -104,6 +105,7 @@ impl Environment {
monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
debug_mode: false,
optimization: true,
macro_registry: Rc::new(RefCell::new(MacroRegistry::new())),
};
env.register_stdlib();
env
@@ -119,7 +121,15 @@ impl Environment {
global_types: self.global_types.clone(),
global_values: self.global_values.clone(),
};
MacroExpander::new(MacroRegistry::new(), evaluator)
MacroExpander::new(self.macro_registry.borrow().clone(), evaluator)
}
fn eval_prelude(&self, source: &str) {
let mut parser = crate::ast::parser::Parser::new(source).expect("Failed to parse prelude");
let untyped_ast = parser.parse_expression().expect("Failed to parse prelude 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();
}
pub fn register_native(
@@ -174,6 +184,7 @@ 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> {
+15 -2
View File
@@ -153,10 +153,23 @@ impl<'a> Parser<'a> {
}
fn parse_again(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let args = Box::new(self.parse_expression()?);
let mut elements = Vec::new();
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
elements.push(self.parse_expression()?);
}
let args_node = Node {
identity: identity.clone(),
kind: UntypedKind::Tuple { elements },
ty: (),
};
Ok(Node {
identity,
kind: UntypedKind::Again { args },
kind: UntypedKind::Again {
args: Box::new(args_node),
},
ty: (),
})
}
+9
View File
@@ -0,0 +1,9 @@
;; 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))
)))
)
+1 -1
View File
@@ -267,7 +267,7 @@ mod tests {
#[should_panic(expected = "'again' is only allowed in tail position to avoid dead code.")]
fn test_again_non_tail_panic() {
let env = Environment::new();
let source = "(do (def f (fn [x] (do (again [(- x 1)]) x))) (f 5))";
let source = "(do (def f (fn [x] (do (again (- x 1)) x))) (f 5))";
// This will trigger the TCO pass which contains the validation logic
let _ = env.run_script(source);
}
+1
View File
@@ -0,0 +1 @@
(while (< 1 2) (print 1))