commit 475e2488777ac9605059b77b0cb7d47001eec6aa
parent ccc75784b65cd8e00bb8934325085553741e02fb
Author: Demonstrandum <samuel@knutsen.co>
Date: Sun, 8 Dec 2024 04:13:07 +0000
Added %get macro.
(%get 0 (a b c)) #=> a
(%get 1/0 (a (b c) d)) #=> b
(%get x (:x c :y d)) #=> c
(%get x/0 (:y e :x (d f))) #=> d
Diffstat:
2 files changed, 66 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
@@ -97,10 +97,10 @@ seam --sexp <<< '(hello (%define subject world) %subject)'
- [ ] The trailing keyword-matching operator. `&&rest` matches excess keyword.
Extracting a value from a map `(:a 1 :b 2 :c 3)` is done with:
`(%match h (:b () &&_) %b)`.
- - [ ] `%get` macro: `(%get b (:a 1 :b 2))` becomes `2`; `(%get 0 (a b c))` becomes `a`.
- - [ ] `(%yaml "...")`, `(%toml "...")` and `(%json "...")` converts
+ - [x] `%get` macro: `(%get b (:a 1 :b 2))` becomes `2`; `(%get 0 (a b c))` becomes `a`.
+ - [x] `(%yaml "...")`, `(%toml "...")` and `(%json "...")` converts
whichever config-lang definition into a seam `%define`-definition.
- - [ ] `(%do ...)` which just expands to the `...`; the identity function.
+ - [x] `(%do ...)` which just expands to the `...`; the identity function.
- [ ] Catch expansion errors: `(%try :catch index-error (%do code-to-try) :error the-error (%do caught-error %the-error))`.
- [x] Implement `(%strip ...)` which evaluates to the `...` without any of the leading whitespace.
- [x] Implement *splat* operation: `(%splat (a b c))` becomes `a b c`.
@@ -131,19 +131,11 @@ seam --sexp <<< '(hello (%define subject world) %subject)'
- [x] `%list` macro which expands from `(%list %a %b %c)` to `( %a %b %c )` but *without* calling `%a` as a macro with `%b` and `%c` as argument.
- [x] `%for`-loop macro, iterating over `%list`s.
- [x] `%glob` which returns a list of files/directories matching a glob.
- - [ ] `%markdown` renders Markdown given to it as `%raw` html-string.
- - [ ] Add keyword macro arguments.
+ - [x] `%markdown` renders Markdown given to it as `%raw` html-string.
+ - [x] Add keyword macro arguments.
- [ ] Caching or checking time-stamps as to not regenerate unmodified source files.
- [ ] HTML object `style="..."` object should handle s-expressions well, (e.g. `(p :style (:color red :border none) Hello World)`)
- [ ] Add more supported formats (`JSON`, `JS`, `TOML`, &c.).
- - [ ] Maybe: a whole JavaScript front-end, e.g.
- ```lisp
- (let x 2)
- (let (y 1) (z 1))
- (const f (=> (a b) (+ a b))
- ((. console log) (== (f y z) x))
- ```
- - [ ] Allow for arbitrary embedding of code, that can be run by
- a LISP interpreter (or any other langauge), for example. (e.g. `(%chez (+ 1 2))` executes
- `(+ 1 2)` with Chez-Scheme LISP, and places the result in the source
- (i.e. `3`).
+ - [ ] Allow for arbitrary embedding of code with their REPLs, that can be run by
+ a LISP interpreter (or any other language), for example. (e.g. `(%chez (+ 1 2))` executes
+ `(+ 1 2)` with Chez-Scheme LISP, and places the result in the source (i.e. `3`).
diff --git a/crates/seam/src/parse/expander.rs b/crates/seam/src/parse/expander.rs
@@ -487,6 +487,11 @@ impl<'a> Expander<'a> {
Ok(expanded)
}
+ fn expand_do_macro(&self, _node: &ParseNode<'a>, params: ParseTree<'a>)
+ -> Result<ParseTree<'a>, ExpansionError<'a>> {
+ self.expand_nodes(params)
+ }
+
fn expand_include_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
-> Result<ParseTree<'a>, ExpansionError<'a>> {
let params: Box<[ParseNode<'a>]> = self.expand_nodes(params)?;
@@ -732,6 +737,57 @@ impl<'a> Expander<'a> {
expand_toml(self, &yaml, &sep, node.site())
}
+ fn expand_get_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
+ -> Result<ParseTree<'a>, ExpansionError<'a>> {
+ let params = self.expand_nodes(params)?; // Eager.
+ let (_parser, args) = arguments! { [¶ms]
+ mandatory(1): literal,
+ mandatory(2): any,
+ optional("separator"): literal,
+ }?;
+ let sep = args.separator.map_or(String::from("/"), |sep| sep.value);
+ let path = args.number.1.value.split(&sep);
+
+ let mut view;
+ let mut node = args.number.2;
+ for item in path {
+ // Verify the current node is a list so it can be indexed.
+ if let Some(list) = node.list() {
+ view = list.to_vec();
+ } else {
+ return Err(ExpansionError(
+ format!("Cannot `%get' on {} node.",node.node_type()),
+ node.owned_site(),
+ ))
+ }
+ let mut got = Err(ExpansionError(
+ format!("Could not find attribute with keyword `:{}`.", item),
+ node.owned_site(),
+ ));
+ if let Ok(index) = item.parse::<usize>() {
+ // Try to find n-th element of `view`.
+ got = view.get(index).map(|node| node.clone()).ok_or(ExpansionError(
+ format!("Index {} is out of bounds in list of length {}.", index, view.len()),
+ node.owned_site(),
+ ));
+ }
+ if got.is_err() {
+ // Try to find :item attribute in list.
+ for node in view.iter() {
+ if let ParseNode::Attribute { keyword, node, .. } = node {
+ if item == keyword {
+ got = Ok(*node.clone());
+ break;
+ }
+ }
+ }
+ }
+ node = got?;
+ }
+
+ Ok(Box::new([node]))
+ }
+
fn expand_date_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
-> Result<ParseTree<'a>, ExpansionError<'a>> {
let params = self.expand_nodes(params)?;
@@ -1242,6 +1298,8 @@ impl<'a> Expander<'a> {
match name {
"define" => self.expand_define_macro(node, params),
"ifdef" => self.expand_ifdef_macro(node, params),
+ "do" => self.expand_do_macro(node, params),
+ "get" => self.expand_get_macro(node, params),
"raw" => self.expand_raw_macro(node, params),
"string" => self.expand_string_macro(node, params),
"include" => self.expand_include_macro(node, params),