seam

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

commit 628c1342b53897f015b3a4b5bef2e429e8868a2e
parent 29677f1fea13fe474c0e43c960b2d25e533920f1
Author: Demonstrandum <samuel@knutsen.co>
Date:   Mon,  9 Dec 2024 19:03:42 +0000

Added %reverse list macro.

Diffstat:
MREADME.md | 2+-
Mcrates/seam/src/parse/expander.rs | 28++++++++++++++++++++++++++++
Mcrates/seam/src/tests.rs | 32++++++++++++++++++++++----------
3 files changed, 51 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md @@ -92,7 +92,7 @@ seam --sexp <<< '(hello (%define subject world) %subject)' ## Checklist - [ ] User `(%error msg)` macro for aborting compilation. - - [ ] List reverse macro `(%reverse (...))`. + - [x] List reverse macro `(%reverse (...))`. - [x] Sorting macro `(%sort (...))` which sorts alphanumerically on literals. Allow providing a `:key` to sort "by field": e.g. sort by title name `(%sort :key (%lambda ((:title _ &&_)) %title) %posts)` - [ ] Extend the strftime-style `(%date)` to be able to read UNIX numeric timestamps and display relative to timezones. diff --git a/crates/seam/src/parse/expander.rs b/crates/seam/src/parse/expander.rs @@ -1256,6 +1256,33 @@ impl<'a> Expander<'a> { ])) } + fn expand_reverse_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): list, + }?; + let list = args.number.1; + let mut reversed = Vec::with_capacity(list.len()); + + for i in 0..list.len() { + let mut item = list[list.len() - i - 1].clone(); + item.set_leading_whitespace(list[i].leading_whitespace().to_owned()); + reversed.push(item); + } + + let ParseNode::List { site, end_token, leading_whitespace, .. } + = params[0].clone() else { unreachable!() }; + Ok(Box::new([ + ParseNode::List { + nodes: reversed.into_boxed_slice(), + site, + end_token, + leading_whitespace + } + ])) + } + fn expand_sort_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>) -> Result<ParseTree<'a>, ExpansionError<'a>> { let params = self.expand_nodes(params)?; // Eager. @@ -1603,6 +1630,7 @@ impl<'a> Expander<'a> { "glob" => self.expand_glob_macro(node, params), "for" => self.expand_for_macro(node, params), "sort" => self.expand_sort_macro(node, params), + "reverse" => self.expand_reverse_macro(node, params), "date" => self.expand_date_macro(node, params), "join" => self.expand_join_macro(node, params), "concat" => self.expand_concat_macro(node, params), diff --git a/crates/seam/src/tests.rs b/crates/seam/src/tests.rs @@ -11,25 +11,37 @@ fn expand<'a>(source: String) -> Result<String, Box<dyn 'a + std::error::Error>> Ok(ret) } +fn test_output_eq(input: &str, expect: &str) { + let source = String::from(input); + let output = expand(source).unwrap(); + assert_eq!(output, expect); +} + mod tests { use super::*; #[test] fn test_sort_macro_alphanumeric() { - let source = "(%sort (13 d 8 e 14 f 9 10 g 11 12 a b c 0 0.1 1.5 0.2 1 2 -1 -10 -2 -1.4 -0.3) :order ascending)"; - let source = String::from(source); - let output = expand(source).unwrap(); - let expect = "(-10 -2 -1.4 -1 -0.3 0 0.1 0.2 1 1.5 2 8 9 10 11 12 13 14 a b c d e f g)"; - assert_eq!(output, expect); + test_output_eq( + "(%sort (13 d 8 e 14 f 9 10 g 11 12 a b c 0 0.1 1.5 0.2 1 2 -1 -10 -2 -1.4 -0.3) :order ascending)", + "(-10 -2 -1.4 -1 -0.3 0 0.1 0.2 1 1.5 2 8 9 10 11 12 13 14 a b c d e f g)" + ); } #[test] fn test_sort_macro_key() { // sort by the second element in 3-tuples. - let source = "(%sort :key (%lambda ((x y z)) %y) ((x 3 b) (z 1 a) (y 2 c)))"; - let source = String::from(source); - let output = expand(source).unwrap(); - let expect = "((z 1 a) (y 2 c) (x 3 b))"; - assert_eq!(output, expect); + test_output_eq( + "(%sort :key (%lambda ((x y z)) %y) ((x 3 b) (z 1 a) (y 2 c)))", + "((z 1 a) (y 2 c) (x 3 b))" + ); + } + + #[test] + fn test_reverse_macro() { + test_output_eq( + "(%reverse (a 3 2 1 2 s))", + "(s 2 1 2 3 a)" + ); } }