seam

Symbolic-Expressions As Markup.
git clone git://git.knutsen.co/seam
Log | Files | Refs | README | LICENSE

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:
MREADME.md | 24++++++++----------------
Mcrates/seam/src/parse/expander.rs | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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! { [&params] + 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),