diff --git a/userspace/lib/lysp/examples/macros.lysp b/userspace/lib/lysp/examples/macros.lysp index a917a8f1..698d7acf 100644 --- a/userspace/lib/lysp/examples/macros.lysp +++ b/userspace/lib/lysp/examples/macros.lysp @@ -59,3 +59,10 @@ ) (print "... doesn't fail the execution") + +(defmacro nested-0 (x) `(list 2 ,x)) +(defmacro nested-1 (x) `(list 1 (nested-0 ,x))) +(assert (= '(1 (2 3)) (nested-1 3))) + +;; (explain ...) must work on macros +(assert (explain and)) diff --git a/userspace/lib/lysp/src/prelude.lysp b/userspace/lib/lysp/src/prelude.lysp index abdb3555..e7ce0111 100644 --- a/userspace/lib/lysp/src/prelude.lysp +++ b/userspace/lib/lysp/src/prelude.lysp @@ -31,6 +31,7 @@ ; TODO most of those could be ported to Rust for performance ; TODO tail recursion (defun filter (f xs) + "Returns a new list, formed from elements of xs matching predicate f" (cond ((nil? xs) nil) ((f (car xs)) (cons (car xs) (filter f (cdr xs)))) @@ -38,30 +39,35 @@ ) ) (defun map (f xs) + "Returns a new list, with f applied to elements of xs" (if (cons? xs) (cons (f (car xs)) (map f (cdr xs))) nil ) ) (defun flatmap (f xs) + "Returns a new list, with f applied to list elements of xs, then appended together" (let (ys nil) (while (cons? xs) (setq ys (append ys (f (car xs)))) (setq xs (cdr xs))) ys)) (defun fold (f acc xs) + "Returns the value calculated by sequentially applying f to acc and each element of xs" (while (cons? xs) (setq acc (f acc (car xs))) (setq xs (cdr xs))) acc) (defun reverse (xs) + "Returns the reversed list" (let (ys nil) (while (cons? xs) (setq ys (cons (car xs) ys)) (setq xs (cdr xs))) ys)) (defun unzip (xs) + "Converts a list of lists into a single-level list" (flatmap identity xs)) ; Logic functions @@ -78,6 +84,7 @@ ) )) (defmacro or (&rest forms) + "Sequentially evaluates the forms, stopping and returning a form if it's trueish" ;; (or x y z) -> (cond (x x) (y y) (z z) (&otherwise #t)) (or_ forms)) (defun and_ (forms) @@ -87,6 +94,7 @@ (&otherwise `(if ,(car forms) ,(and_ (cdr forms)))) )) (defmacro and (&rest forms) + "Sequentially evaluates the forms, returning the last form if all the forms are trueish" ;; (and x y z) -> (if x (if y (if z z))) (and_ forms) ) diff --git a/userspace/lib/lysp/src/vm/macros.rs b/userspace/lib/lysp/src/vm/macros.rs index 928adaf8..af9fb4b4 100644 --- a/userspace/lib/lysp/src/vm/macros.rs +++ b/userspace/lib/lysp/src/vm/macros.rs @@ -32,8 +32,6 @@ impl MacroExpand for Value { | Self::Boolean(_) | Self::String(_) | Self::Keyword(_) - // | Self::OpaqueValue(_) - // | Self::BytecodeFunction(_) | Self::Quote(_) | Self::Closure(_) | Self::Function(_) @@ -43,7 +41,6 @@ impl MacroExpand for Value { | Self::HashTable(_) | Self::UnquoteSplice(_) | Self::Unquote(_) => Ok(self.clone()), - // | Self::NativeFunction(_) => Ok(self.clone()), Self::Cons(cons) => { let ConsCell(car, cdr) = cons.as_ref(); let car = car.macro_expand(vm, env, false)?; @@ -65,17 +62,20 @@ impl MacroExpand for Value { return Ok(Self::Cons(cons)); }; - match mac { - Macro::Native(native) => { - let result = native.invoke(vm, env, &args[..]).expect("TODO"); - Ok(result) - } - Macro::Bytecode(bytecode) => { - let closure = ClosureValue { function: bytecode.clone(), upvalues: vec![] }; - let result = vm.evaluate_closure_args(env, closure, &args[..])?; - Ok(result) - } - } + match mac { + Macro::Native(native) => { + let result = native.invoke(vm, env, &args[..]).expect("TODO"); + result.macro_expand(vm, env, tail) + } + Macro::Bytecode(bytecode) => { + let closure = ClosureValue { + function: bytecode.clone(), + upvalues: vec![], + }; + let result = vm.evaluate_closure_args(env, closure, &args[..])?; + result.macro_expand(vm, env, tail) + } + } } Self::Quasi(value) => { let value = expand_quasiquote(value); diff --git a/userspace/lib/lysp/src/vm/prelude/debug.rs b/userspace/lib/lysp/src/vm/prelude/debug.rs index 2d2a2b25..d617b13f 100644 --- a/userspace/lib/lysp/src/vm/prelude/debug.rs +++ b/userspace/lib/lysp/src/vm/prelude/debug.rs @@ -7,7 +7,7 @@ use crate::{ error::MachineError, vm::{ Value, - env::Environment, + env::{Environment, Macro}, value::{Keyword, StringValue, convert::TryFromValue}, }, }; @@ -55,9 +55,35 @@ pub fn load(env: &Rc) { env.defun_native("gensym", "Provides an unique symbol name", |_, env, _| { Ok(env.gensym().into()) }); - env.defun_native( + env.defmacro_native( "explain", "Provides an explanation for a given value", + |_, env, args| { + let [argument] = args else { + return Err(MachineError::InvalidArgumentCount); + }; + if let Value::Identifier(identifier) = argument + && let Some(mac) = env.global_macro(identifier) + { + return match mac { + Macro::Native(mac) => Ok(Value::String( + format!("built-in macro {}: {}", mac.name(), mac.docstring()).into(), + )), + Macro::Bytecode(mac) => Ok(Value::String( + format!("macro {}: {}", mac.name(), mac.docstring()).into(), + )), + }; + } + + Ok(Value::list_or_nil([ + Value::Identifier("explain_".into()), + argument.clone(), + ])) + }, + ); + env.defun_native( + "explain_", + "Provides an explanation for a given value", |_, _, args| { let [argument] = args else { return Err(MachineError::InvalidArgumentCount);