commit 628c1342b53897f015b3a4b5bef2e429e8868a2e
parent 29677f1fea13fe474c0e43c960b2d25e533920f1
Author: Demonstrandum <samuel@knutsen.co>
Date: Mon, 9 Dec 2024 19:03:42 +0000
Added %reverse list macro.
Diffstat:
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! { [¶ms]
+ 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)"
+ );
}
}