Browse Source

Merge pull request #1 from aisamanra/gr/stdlib-revamp

Rework functions and the stdlib
G. D. Ritter 1 year ago
parent
commit
e3701d2db6

+ 9 - 11
README.md

@@ -86,33 +86,31 @@ Matzo offers a handful of other data types in addition to strings. Matzo is dyna
 
 ### Functions
 
-Applying functions is done using an explicit operator: the `.` operator. That is to say, to apply the `to-upper` function to the string `"foo"`, you'd write `to-upper."foo"`.
-
-Functions can only be applied to one value at a time. This means that functions can sometimes take tuples of arguments (e.g. `rep.<3, "na">`) but functions can also be curried. Most stdlib functions take the tuple approach.
+Applying functions is done using parameter lists contained in square brackets. That is to say, to apply the `to-upper` function to the string `"foo"`, you'd write `to-upper["foo"]`.
 
 All functions are defined as anonymous functions and can optionally feature definition-by-cases. A simple function which just returns its argument looks like this:
 
 ```
-id := { x => x };
+id := { [x] => x };
 ```
 
 In order to define a function by cases, you can separate individual cases with `;`.
 
 ```
-if := { <True,  x, _> => x
-      ; <False, _, y> => y
+if := { [True,  x, _] => x
+      ; [False, _, y] => y
       };
 ```
 
-You can also return other functions, which retain access to their environment, which means the above function be expressed isomorphically as:
+Functions can also be manually uncurried:
 
 ```
-if := { True  => { x => { _ => x }}
-      ; False => { _ => { y => y }}
+if := { [True]  => { [x] => { [_] => x }}
+      ; [False] => { [_] => { [y] => y }}
       };
 ```
 
-While these two express the same functionality, the former would be called with `if.<condition, thenCase, elseCase>` while the latter would be called with `if.condition.then-case.else-case`.
+While these two express the same functionality, the former would be called with `if[condition, thenCase, elseCase]` while the latter would be called with `if[condition][then-case][else-case]`.
 
 ### A sample program
 
@@ -123,7 +121,7 @@ consonant ::= p t k w h n;
 vowel ::= a e i o u;
 nucleus := 4: vowel | vowel "'";
 syll := 4: consonant nucleus | nucleus;
-puts syll rep.<1..5, syll>;
+puts syll rep[1..5, syll];
 ```
 
 ## Implementation notes

+ 1 - 1
examples/aquan.matzo

@@ -10,4 +10,4 @@ vowel := ("a" | "e" | "i" | "o" | "u") (4: "" | "'");
 syll := 4: cons vowel | vowel;
 
 (* And finally, here's an output statement *)
-puts syll rep.<1..6, syll>;
+puts syll rep[1..6, syll];

+ 1 - 1
examples/auran.matzo

@@ -1,4 +1,4 @@
-word := start (rep.<1..4,syll>) end;
+word := start (rep[1..4, syll]) end;
 syll := vowel cons;
 start := 3: cons | 2: '';
 end := 3: vowel | vowel 's'| vowel 'n';

+ 1 - 1
examples/druidic.matzo

@@ -1,5 +1,5 @@
 (* Druidic *)
-word := init (rep.<1|2,syll>) end;
+word := init (rep[1|2, syll]) end;
 init := 3: vowel | 2: '';
 syll := cons vowel;
 end ::= a e er ir;

+ 1 - 1
examples/giant.matzo

@@ -1,4 +1,4 @@
-word := begin rep.<1..3,syll> end;
+word := begin rep[1..3, syll] end;
 syll := vowel cons;
 end := vowel ('n'| 'ns' | 's' | '');
 begin := 2: cons | beginvowel;

+ 1 - 1
examples/halfling.matzo

@@ -1,4 +1,4 @@
-word := begin rep.<1..4, syll> end;
+word := begin rep[1..4, syll] end;
 syll := cons vowel;
 end := cons vowel final | cons "é" final | cons "a" final | "r" | "n";
 

+ 8 - 11
examples/halfling_ipa.matzo

@@ -1,18 +1,15 @@
 (* these should probably be part of the stdlib eventually *)
-tuple-map := {func => {tup =>
-  tuple-fold.<{xs => {x => concat.<xs, <func.x>>}}, <>, tup>
-}};
-flatten := {t => tuple-fold.<{ x => { xs => x xs } }, "", t>};
+flatten := {[t] => tuple/fold[{ [x, xs] => x xs }, "", t]};
 
-word := {w => flatten.(tuple-map.orthography.w)};
-wd := concat.<begin, middle, end>;
-middle := syll | concat.<syll, syll> | concat.<syll, syll, syll> | concat.<syll, syll, syll, syll>;
+word := {[w] => flatten[tuple/map[orthography, w]]};
+wd := tuple/concat[<begin, middle, end>];
+middle := syll | tuple/concat[<syll, syll>] | tuple/concat[<syll, syll, syll>] | tuple/concat[<syll, syll, syll, syll>];
 syll := <cons, vowel>;
 end := <cons, vowel, final>
      | 2: <cons, ("eː" | "a"), final>
      | <"r"> | <"n"> ;
 
-orthography :=
+orthography := { [x] => case x in
   { "z"  => "s"
   ; "s"  => "ss"
   ; "dz" => "z"
@@ -33,8 +30,8 @@ orthography :=
   ; "ɾ"  => "r"
   ; "tɾ" => "tr"
   ; "ɾt" => "rt"
-  ; x    => x
-  };
+  ; _    => x
+  } };
 
 begin := <vowel> | <initcons, vowel>;
 
@@ -48,4 +45,4 @@ final := "s" | 6: "";
 
 
 fix wd;
-puts (word.wd) " (pronounced /" (flatten.wd) "/)";
+puts word[wd] " (pronounced /" flatten[wd] "/)";

+ 12 - 12
examples/peano.matzo

@@ -1,15 +1,15 @@
-isZero := { Z => True; <S,_> => False };
-add := { Z     => { y => y }
-       ; <S,x> => { y => add.x.<S,y> } };
-incr := add.<S,Z>;
+isZero := { [Z] => True; [<S,_>] => False };
+add := { [Z]     => { [y] => y }
+       ; [<S,x>] => { [y] => add[x][<S,y>] } };
+incr := add[<S,Z>];
 
 two  := <S,<S,Z>>;
 four := <S,<S,<S,<S,Z>>>>;
 
-if := { True  => { x => { _ => x } }
-      ; False => { _ => { y => y } }
+if := { [True]  => { [x] => { [_] => x } }
+      ; [False] => { [_] => { [y] => y } }
       };
-minusOne := { Z => Z; <S,x> => x };
+minusOne := { [Z] => Z; [<S,x>] => x };
 
 puts "S is " S;
 puts "Z is " Z;
@@ -18,16 +18,16 @@ puts "True is " True;
 puts "False is " False;
 
 puts "Is 0 zero?";
-puts isZero.Z;
+puts isZero[Z];
 
 puts "Is 4 zero?";
-puts isZero.four;
+puts isZero[four];
 
 puts "2 + 4 = ";
-puts add.four.two;
+puts add[four][two];
 
 puts "Is zero zero?";
-puts if.(isZero.Z)."yes"."no";
+puts if[isZero[Z]]["yes"]["no"];
 
 puts "Is 0+1 zero?";
-puts if.(isZero.(incr.Z))."yes"."no";
+puts if[isZero[incr[Z]]]["yes"]["no"];

+ 43 - 0
examples/person.matzo

@@ -0,0 +1,43 @@
+gender:= Male | Female | Unspec;
+byGender := { [m,f,n] => { [Male] => m; [Female] => f; [_] => n }};
+
+ending  := byGender["o","a","e"];
+pronoun := byGender["He","She","They"];
+noun    := byGender["man","woman","person"];
+are     := byGender["is","is","are"];
+have    := byGender["has", "has", "have"];
+
+cons  ::= p t c d g r l m n x;
+vowel ::= a e i o u;
+
+name := { [g] => (vowel | "") rep[1..3, cons vowel] cons ending[g] };
+
+hairColor ::= black brown blonde;
+eyeColor  ::= brown green blue;
+
+job := { [g] =>
+    "stonemason"
+  | "baker"
+  | "accountant"
+  | case g in
+      { Male   => "fisherman"
+      ; Female => "fisherwoman"
+      } };
+tool := { ["stonemason"] => "chisel"
+        ; ["baker"]      => "bowl"
+        ; ["accountant"] => "tablet"
+        ; [_]            => "fishing pole"
+        };
+
+adjective ::= happy cheerful focused quiet meek rash;
+
+person :=
+  let fix my-gender := gender in {
+  let fix my-job := job[my-gender] in {
+    "You come across " str/capitalize[name[my-gender]] ", a " noun[my-gender]
+      " from the city of " str/capitalize[name[Female]] ". "
+      pronoun[my-gender] " " are[my-gender] " a hardworking " my-job " with "
+      hairColor " hair and " eyeColor " eyes. "
+      str/capitalize[pronoun[my-gender]] " "
+      have[my-gender] " a " tool[my-job] " and " are[my-gender] " very " adjective "." } };
+puts person;

+ 1 - 1
examples/sylvan.matzo

@@ -1,4 +1,4 @@
-word := syll (rep.<1..4, syll>);
+word := syll rep[1..4, syll];
 
 syll := 2: cons vowel | cons vowel;
 

+ 14 - 9
src/ast.rs

@@ -1,5 +1,5 @@
-use std::fmt;
 pub use crate::lexer::{FileRef, Located, Span};
+use std::fmt;
 
 pub type StrRef = string_interner::DefaultSymbol;
 pub type Name = Located<StrRef>;
@@ -97,7 +97,7 @@ impl ASTArena {
         let end_of_line = end_of_line.unwrap_or_else(|| src.len());
 
         let mut result = format!("{:3} |", line_number);
-        result.push_str(&src[start_of_line .. end_of_line]);
+        result.push_str(&src[start_of_line..end_of_line]);
         result.push_str("\n     ");
         for _ in start_of_line..(span.start as usize) {
             result.push(' ');
@@ -148,12 +148,14 @@ impl ASTArena {
                 writeln!(f, ")")
             }
 
-            Expr::Ap(func, arg) => {
+            Expr::Ap(func, args) => {
                 writeln!(f, "Ap(")?;
                 self.indent(f, depth + 2)?;
                 self.show_expr(&self[*func], f, depth + 2)?;
-                self.indent(f, depth + 2)?;
-                self.show_expr(&self[*arg], f, depth + 2)?;
+                for arg in args {
+                    self.indent(f, depth + 2)?;
+                    self.show_expr(&self[*arg], f, depth + 2)?;
+                }
                 self.indent(f, depth)?;
                 writeln!(f, ")")
             }
@@ -214,7 +216,10 @@ impl ASTArena {
                 writeln!(f, "Fun(")?;
                 for case in cases {
                     self.indent(f, depth + 2)?;
-                    self.show_pat(&case.pat, f)?;
+                    for pat in case.pats.iter() {
+                        self.show_pat(pat, f)?;
+                        writeln!(f, ", ")?;
+                    }
                     writeln!(f, " =>")?;
                     self.indent(f, depth + 4)?;
                     self.show_expr(&self[case.expr], f, depth + 4)?;
@@ -229,7 +234,7 @@ impl ASTArena {
                 self.show_expr(&self[*expr], f, depth)?;
                 for case in cases {
                     self.indent(f, depth + 2)?;
-                    self.show_pat(&case.pat, f)?;
+                    self.show_pat(&case.pats[0], f)?;
                     writeln!(f, " =>")?;
                     self.indent(f, depth + 4)?;
                     self.show_expr(&self[case.expr], f, depth + 4)?;
@@ -315,7 +320,7 @@ pub enum Expr {
     Cat(Vec<ExprRef>),
     Chc(Vec<Choice>),
     Lit(Literal),
-    Ap(ExprRef, ExprRef),
+    Ap(ExprRef, Vec<ExprRef>),
     Tup(Vec<ExprRef>),
     Let(bool, Name, ExprRef, ExprRef),
     Fun(Vec<Case>),
@@ -340,7 +345,7 @@ impl ExprId {
 /// A single case in an anonymous function or `case` statement
 #[derive(Debug, Clone)]
 pub struct Case {
-    pub pat: Pat,
+    pub pats: Vec<Pat>,
     pub expr: ExprRef,
 }
 

+ 246 - 0
src/builtins.rs

@@ -0,0 +1,246 @@
+use crate::ast::*;
+use crate::interp::*;
+
+use anyhow::{bail, Error};
+
+/// The list of builtins provided at startup.
+pub fn builtins() -> Vec<BuiltinFunc> {
+    vec![
+        BuiltinFunc {
+            name: "rep",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [rep, expr] = exprs {
+                        let mut buf = String::new();
+                        let num = state.eval(*rep, env)?.as_num(&state.ast.borrow())?;
+                        for _ in 0..num {
+                            buf.push_str(
+                                &state
+                                    .eval(*expr, env)?
+                                    .as_str(&state.ast.borrow())?
+                                    .to_string(),
+                            );
+                        }
+                        Ok(Value::Lit(Literal::Str(buf)))
+                    } else {
+                        bail!("`rep`: expected two arguments, got {}", exprs.len())
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "str/upper",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [expr] = exprs {
+                        let s = state.eval(*expr, env)?;
+                        Ok(Value::Lit(Literal::Str(
+                            s.as_str(&state.ast.borrow())?.to_uppercase(),
+                        )))
+                    } else {
+                        bail!(
+                            "`str/capitalize`: expected 1 argument1, got {}",
+                            exprs.len()
+                        );
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "str/capitalize",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [expr] = exprs {
+                        let s = state.eval(*expr, env)?;
+                        Ok(Value::Lit(Literal::Str(titlecase::titlecase(
+                            s.as_str(&state.ast.borrow())?,
+                        ))))
+                    } else {
+                        bail!(
+                            "`str/capitalize`: expected 1 argument1, got {}",
+                            exprs.len()
+                        );
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "str/lower",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [expr] = exprs {
+                        let s = state.eval(*expr, env)?;
+                        Ok(Value::Lit(Literal::Str(
+                            s.as_str(&state.ast.borrow())?.to_lowercase(),
+                        )))
+                    } else {
+                        bail!("`str/lower`: expected 1 argument1, got {}", exprs.len());
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "add",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [x, y] = exprs {
+                        let x = state.eval(*x, env)?.as_num(&state.ast.borrow())?;
+                        let y = state.eval(*y, env)?.as_num(&state.ast.borrow())?;
+                        Ok(Value::Lit(Literal::Num(x + y)))
+                    } else {
+                        bail!("`add`: expected 2 arguments, got {}", exprs.len());
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "sub",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [x, y] = exprs {
+                        let x = state.eval(*x, env)?.as_num(&state.ast.borrow())?;
+                        let y = state.eval(*y, env)?.as_num(&state.ast.borrow())?;
+                        Ok(Value::Lit(Literal::Num(x - y)))
+                    } else {
+                        bail!("`sub`: expected 2 arguments, got {}", exprs.len());
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "mul",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [x, y] = exprs {
+                        let x = state.eval(*x, env)?.as_num(&state.ast.borrow())?;
+                        let y = state.eval(*y, env)?.as_num(&state.ast.borrow())?;
+                        Ok(Value::Lit(Literal::Num(x * y)))
+                    } else {
+                        bail!("`mul`: expected 2 arguments, got {}", exprs.len());
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "tuple/len",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [expr] = exprs {
+                        let tup = state.eval(*expr, env)?;
+                        Ok(Value::Lit(Literal::Num(
+                            tup.as_tup(&state.ast.borrow())?.len() as i64,
+                        )))
+                    } else {
+                        bail!("`tuple/len`: expected 1 argument, got {}", exprs.len())
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "tuple/concat",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [expr] = exprs {
+                        let val = state.eval(*expr, env)?;
+                        let tup = val.as_tup(&state.ast.borrow())?;
+                        let mut contents = Vec::new();
+                        for elem in tup {
+                            for th in state.hnf(elem)?.as_tup(&state.ast.borrow())? {
+                                contents.push(th.clone());
+                            }
+                        }
+                        Ok(Value::Tup(contents))
+                    } else {
+                        bail!("tuple/concat: expected 1 argument, got {}", exprs.len());
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "tuple/index",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [tup, idx] = exprs {
+                        let tup = state.eval(*tup, env)?;
+                        let idx = state.eval(*idx, env)?;
+                        state.hnf(
+                            &tup.as_tup(&state.ast.borrow())?
+                                [idx.as_num(&state.ast.borrow())? as usize],
+                        )
+                    } else {
+                        bail!("`tuple/index`: expected 2 arguments, got {}", exprs.len());
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "tuple/replace",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [tup, idx, new] = exprs {
+                        let tup_val = state.eval(*tup, env)?;
+                        let tup = tup_val.as_tup(&state.ast.borrow())?;
+                        let idx = state.eval(*idx, env)?.as_num(&state.ast.borrow())?;
+
+                        let mut modified = Vec::with_capacity(tup.len());
+                        for i in 0..idx {
+                            modified.push(tup[i as usize].clone());
+                        }
+                        modified.push(Thunk::Expr(*new, env.clone()));
+                        for i in (idx + 1)..(tup.len() as i64) {
+                            modified.push(tup[i as usize].clone());
+                        }
+                        Ok(Value::Tup(modified))
+                    } else {
+                        bail!("`tuple/replace`: expected 3 arguments, got {}", exprs.len());
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "tuple/fold",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [func, init, tup] = exprs {
+                        let func = state.eval(*func, env)?;
+                        let tup = state.eval(*tup, env)?;
+
+                        let mut result = Thunk::Expr(*init, env.clone());
+                        for t in tup.as_tup(&state.ast.borrow())? {
+                            result = Thunk::Value(state.eval_closure(
+                                func.as_closure(&state.ast.borrow())?,
+                                vec![result, t.clone()],
+                            )?);
+                        }
+
+                        state.hnf(&result)
+                    } else {
+                        bail!("`tuple/fold`: expected 3 arguments, got {}", exprs.len());
+                    }
+                },
+            ),
+        },
+        BuiltinFunc {
+            name: "tuple/map",
+            callback: Box::new(
+                |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, Error> {
+                    if let [func, tup] = exprs {
+                        let func = state.eval(*func, env)?;
+                        let tup = state.eval(*tup, env)?;
+
+                        let mut new_tup = Vec::new();
+                        let closure = func.as_closure(&state.ast.borrow())?;
+                        for t in tup.as_tup(&state.ast.borrow())? {
+                            new_tup
+                                .push(Thunk::Value(state.eval_closure(closure, vec![t.clone()])?));
+                        }
+
+                        Ok(Value::Tup(new_tup))
+                    } else {
+                        bail!("`tuple/map`: expected 2 arguments, got {}", exprs.len());
+                    }
+                },
+            ),
+        },
+    ]
+}

+ 26 - 5
src/grammar.lalrpop

@@ -14,6 +14,8 @@ extern {
         ")" => Token::RPar,
         "{" => Token::LCurl,
         "}" => Token::RCurl,
+        "[" => Token::LBrac,
+        "]" => Token::RBrac,
         "|" => Token::Pipe,
         ":" => Token::Colon,
         "," => Token::Comma,
@@ -103,7 +105,11 @@ pub Term: ExprId = {
 };
 
 pub Branch: ExprId = {
-    <l:Located<Branch>> "." <r:Located<Subbranch>> => ast.add_expr(Expr::Ap(l, r)),
+//    <l:Located<Branch>> "." <r:Located<Subbranch>> => ast.add_expr(Expr::Ap(l, r)),
+    <l:Located<Branch>> "[" <mut rs:Located<(<Expr> ",")>*> <r:Located<Expr>> "]" => {
+        rs.push(r);
+        ast.add_expr(Expr::Ap(l, rs))
+    },
     <Subbranch> => <>,
 };
 
@@ -125,7 +131,7 @@ pub Leaf: ExprId = {
     },
     "let" <fixed:"fix"?> <name:Name> ":=" <e1:Located<Expr>> "in" "{" <e2:Located<Expr>> "}" =>
         ast.add_expr(Expr::Let(fixed.is_some(), name, e1, e2)),
-    "{" <cs:Cases> "}" =>
+    "{" <cs:FunCases> "}" =>
         ast.add_expr(Expr::Fun(cs)),
     "case" <e:Located<Expr>> "in" "{" <cs:Cases> "}" =>
         ast.add_expr(Expr::Case(e, cs)),
@@ -133,14 +139,29 @@ pub Leaf: ExprId = {
 };
 
 pub Cases: Vec<Case> = {
-    <mut cs:(<Case> ";")*> <c:Case> => {
+  <mut cs:(<Case> ";")*> <c:Case> => {
+    cs.push(c);
+    cs
+  }
+};
+
+pub Case: Case = {
+  <pat:Pat> "=>" <expr:Located<Expr>> =>
+    Case { pats: vec![pat], expr }
+};
+
+pub FunCases: Vec<Case> = {
+    <mut cs:(<FunCase> ";")*> <c:FunCase> => {
         cs.push(c);
         cs
     }
 };
 
-pub Case: Case = {
-    <pat:Pat> "=>" <expr:Located<Expr>> => Case { pat, expr },
+pub FunCase: Case = {
+    "[" <mut pats:(<Pat> ",")*> <pat:Pat> "]" "=>" <expr:Located<Expr>> => {
+        pats.push(pat);
+        Case { pats, expr }
+    }
 };
 
 pub Pat: Pat = {

+ 63 - 147
src/interp.rs

@@ -19,25 +19,25 @@ use std::rc::Rc;
 pub enum Value {
     Lit(Literal),
     Tup(Vec<Thunk>),
-    Builtin(&'static BuiltinFunc),
+    Builtin(BuiltinRef),
     Closure(Closure),
     Nil,
 }
 
+#[derive(Debug, Clone, Copy)]
+pub struct BuiltinRef {
+    idx: usize,
+}
+
 impl Value {
     fn to_string(&self, ast: &ASTArena) -> String {
         self.with_str(ast, |s| s.to_string())
     }
 }
-// impl fmt::Display for Value {
-//     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-//         self.with_str(|s| write!(f, "{}", s))
-//     }
-// }
 
 impl Value {
     /// Convert this value to a Rust integer, failing otherwise
-    fn as_num(&self, ast: &ASTArena) -> Result<i64, Error> {
+    pub fn as_num(&self, ast: &ASTArena) -> Result<i64, Error> {
         match self {
             Value::Lit(Literal::Num(n)) => Ok(*n),
             _ => self.with_str(ast, |s| bail!("Expected number, got {}", s)),
@@ -45,7 +45,7 @@ impl Value {
     }
 
     /// Convert this value to a Rust string, failing otherwise
-    fn as_str(&self, ast: &ASTArena) -> Result<&str, Error> {
+    pub fn as_str(&self, ast: &ASTArena) -> Result<&str, Error> {
         match self {
             Value::Lit(Literal::Str(s)) => Ok(s),
             _ => self.with_str(ast, |s| bail!("Expected string, got {}", s)),
@@ -53,7 +53,7 @@ impl Value {
     }
 
     /// Convert this value to a Rust slice, failing otherwise
-    fn as_tup(&self, ast: &ASTArena) -> Result<&[Thunk], Error> {
+    pub fn as_tup(&self, ast: &ASTArena) -> Result<&[Thunk], Error> {
         match self {
             Value::Tup(vals) => Ok(vals),
             _ => self.with_str(ast, |s| bail!("Expected tuple, got {}", s)),
@@ -61,7 +61,7 @@ impl Value {
     }
 
     /// Convert this value to a closure, failing otherwise
-    fn as_closure(&self, ast: &ASTArena) -> Result<&Closure, Error> {
+    pub fn as_closure(&self, ast: &ASTArena) -> Result<&Closure, Error> {
         match self {
             Value::Closure(closure) => Ok(closure),
             _ => self.with_str(ast, |s| bail!("Expected tuple, got {}", s)),
@@ -73,7 +73,7 @@ impl Value {
     /// not completely forced already: indeed, this can't, since it
     /// doesn't have access to the `State`. Unevaluated fragments of
     /// the value will be printed as `#<unevaluated>`.
-    fn with_str<U>(&self, ast: &ASTArena, f: impl FnOnce(&str) -> U) -> U {
+    pub fn with_str<U>(&self, ast: &ASTArena, f: impl FnOnce(&str) -> U) -> U {
         match self {
             Value::Nil => f(""),
             Value::Lit(Literal::Str(s)) => f(s),
@@ -89,13 +89,13 @@ impl Value {
                     match val {
                         Thunk::Value(v) => buf.push_str(&v.to_string(ast)),
                         Thunk::Expr(..) => buf.push_str("#<unevaluated>"),
-                        Thunk::Builtin(func) => buf.push_str(&format!("#<builtin {}>", func.name)),
+                        Thunk::Builtin(func) => buf.push_str(&format!("#<builtin {}>", func.idx)),
                     }
                 }
                 buf.push('>');
                 f(&buf)
             }
-            Value::Builtin(func) => f(&format!("#<builtin {}>", func.name)),
+            Value::Builtin(func) => f(&format!("#<builtin {}>", func.idx)),
             Value::Closure(_) => f("#<lambda ...>"),
         }
     }
@@ -108,10 +108,10 @@ pub struct BuiltinFunc {
     /// The name of the builtin: this is used in error messages, in
     /// printing the value (e.g. in the case of `puts some-builtin`),
     /// and as the Matzo identifier used for this function.
-    name: &'static str,
+    pub name: &'static str,
     /// The callback here is the Rust implementation of the function,
     /// where the provided `ExprRef` is the argument to the function.
-    callback: &'static dyn Fn(&State, ExprRef, &Env) -> Result<Value, Error>,
+    pub callback: Box<dyn Fn(&State, &[ExprRef], &Env) -> Result<Value, Error>>,
 }
 
 impl fmt::Debug for BuiltinFunc {
@@ -120,105 +120,6 @@ impl fmt::Debug for BuiltinFunc {
     }
 }
 
-/// The list of builtins provided at startup.
-///
-/// TODO: move this to a separate file and clean it up
-const BUILTINS: &[BuiltinFunc] = &[
-    BuiltinFunc {
-        name: "rep",
-        callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
-            let (rep, expr) = {
-                let ast = state.ast.borrow();
-                let args = match &ast[expr] {
-                    Expr::Tup(tup) => tup,
-                    _ => {
-                        let span = state.ast.borrow().get_line(expr.file, expr.span);
-                        bail!("`rep`: expected tuple\n{}", span)
-                    }
-                };
-                if args.len() != 2 {
-                    let span = state.ast.borrow().get_line(expr.file, expr.span);
-                    bail!("`rep`: expected two arguments, got {}\n{}", args.len(), span)
-                }
-                (args[0], args[1])
-            };
-            let mut buf = String::new();
-            let num = state.eval(rep, env)?.as_num(&state.ast.borrow())?;
-            for _ in 0..num {
-                buf.push_str(&state.eval(expr, env)?.as_str(&state.ast.borrow())?.to_string());
-            }
-            Ok(Value::Lit(Literal::Str(buf)))
-        },
-    },
-    BuiltinFunc {
-        name: "length",
-        callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
-            let args = match state.eval(expr, env)? {
-                Value::Tup(tup) => tup,
-                _ => bail!("`length`: expected tuple"),
-            };
-            Ok(Value::Lit(Literal::Num(args.len() as i64)))
-        },
-    },
-    BuiltinFunc {
-        name: "to-upper",
-        callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
-            let s = state.eval(expr, env)?;
-            Ok(Value::Lit(Literal::Str(s.as_str(&state.ast.borrow())?.to_uppercase())))
-        },
-    },
-    BuiltinFunc {
-        name: "capitalize",
-        callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
-            let s = state.eval(expr, env)?;
-            Ok(Value::Lit(Literal::Str(titlecase::titlecase(s.as_str(&state.ast.borrow())?))))
-
-        },
-    },
-    BuiltinFunc {
-        name: "to-lower",
-        callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
-            let s = state.eval(expr, env)?;
-            Ok(Value::Lit(Literal::Str(s.as_str(&state.ast.borrow())?.to_lowercase())))
-        },
-    },
-    BuiltinFunc {
-        name: "concat",
-        callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
-            let val = state.eval(expr, env)?;
-            let tup = val.as_tup(&state.ast.borrow())?;
-            let mut contents = Vec::new();
-            for elem in tup {
-                for th in state.hnf(elem)?.as_tup(&state.ast.borrow())? {
-                    contents.push(th.clone());
-                }
-            }
-            Ok(Value::Tup(contents))
-        },
-    },
-    BuiltinFunc {
-        name: "tuple-fold",
-        callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
-            let val = state.eval(expr, env)?;
-            let args = val.as_tup(&state.ast.borrow())?;
-            if let [func, init, tup] = args {
-                let func = state.hnf(func)?;
-                let tup = state.hnf(tup)?;
-
-                let mut result = init.clone();
-                for t in tup.as_tup(&state.ast.borrow())? {
-                    let partial = state.eval_closure(func.as_closure(&state.ast.borrow())?, result)?;
-                    result = Thunk::Value(state.eval_closure(partial.as_closure(&state.ast.borrow())?, t.clone())?);
-                }
-
-                state.hnf(&result)
-            } else {
-                bail!("`tuple-fold`: expected 3 arguments, got {}", args.len());
-            }
-        },
-    },
-];
-
 /// The name `Thunk` is a bit of a misnomer here: this is
 /// _potentially_ a `Thunk`, but represents anything that can be
 /// stored in a variable: it might be an unevaluated expression (along
@@ -229,13 +130,13 @@ const BUILTINS: &[BuiltinFunc] = &[
 pub enum Thunk {
     Expr(ExprRef, Env),
     Value(Value),
-    Builtin(&'static BuiltinFunc),
+    Builtin(BuiltinRef),
 }
 
 /// An environment is either `None` (i.e. in the root scope) or `Some`
 /// of some reference-counted scope (since those scopes might be
 /// shared in several places, e.g. as pointers in thunks or closures).
-type Env = Option<Rc<Scope>>;
+pub type Env = Option<Rc<Scope>>;
 
 /// A `Scope` represents a _non-root_ scope (since the root scope is
 /// treated in a special way) and contains a map from variables to
@@ -265,10 +166,12 @@ pub struct Closure {
 pub struct State {
     /// An `ASTArena` that contains all the packed information that
     /// results from parsing a program.
-    ast: RefCell<ASTArena>,
+    pub ast: RefCell<ASTArena>,
     /// The root scope of the program, which contains all the
     /// top-level definitions and builtins.
     root_scope: RefCell<HashMap<StrRef, Thunk>>,
+    /// The set of builtin (i.e. implemented-in-Rust) functions
+    builtins: Vec<BuiltinFunc>,
     /// The thread-local RNG.
     rand: RefCell<Box<dyn MatzoRand>>,
     /// The instantiated parser used to parse Matzo programs
@@ -287,18 +190,21 @@ impl State {
     /// This initializes a new `State` and adds all the builtin
     /// functions to the root scope
     pub fn new() -> State {
-        let s = State {
+        let mut s = State {
             root_scope: RefCell::new(HashMap::new()),
             rand: RefCell::new(Box::new(DefaultRNG::new())),
             parser: crate::grammar::StmtsParser::new(),
             expr_parser: crate::grammar::ExprRefParser::new(),
             ast: RefCell::new(ASTArena::new()),
+            builtins: Vec::new(),
         };
-        for builtin in BUILTINS {
-            let sym = s.ast.borrow_mut().add_string(builtin.name);
+        for builtin in crate::builtins::builtins() {
+            let idx = s.builtins.len();
+            let sym = s.ast.borrow_mut().add_string(&builtin.name);
             s.root_scope
                 .borrow_mut()
-                .insert(sym, Thunk::Builtin(builtin));
+                .insert(sym, Thunk::Builtin(BuiltinRef { idx }));
+            s.builtins.push(builtin);
         }
         s
     }
@@ -306,18 +212,21 @@ impl State {
     /// This initializes a new `State` and adds all the builtin
     /// functions to the root scope
     pub fn new_from_seed(seed: u64) -> State {
-        let s = State {
+        let mut s = State {
             root_scope: RefCell::new(HashMap::new()),
             rand: RefCell::new(Box::new(SeededRNG::from_seed(seed))),
             parser: crate::grammar::StmtsParser::new(),
             expr_parser: crate::grammar::ExprRefParser::new(),
             ast: RefCell::new(ASTArena::new()),
+            builtins: Vec::new(),
         };
-        for builtin in BUILTINS {
-            let sym = s.ast.borrow_mut().add_string(builtin.name);
+        for builtin in crate::builtins::builtins() {
+            let idx = s.builtins.len();
+            let sym = s.ast.borrow_mut().add_string(&builtin.name);
             s.root_scope
                 .borrow_mut()
-                .insert(sym, Thunk::Builtin(builtin));
+                .insert(sym, Thunk::Builtin(BuiltinRef { idx }));
+            s.builtins.push(builtin);
         }
         s
     }
@@ -400,7 +309,7 @@ impl State {
                 for stmt in stmts {
                     self.execute(&stmt, io::stdout())?;
                 }
-            },
+            }
             Err(err) => {
                 let lexed = crate::lexer::tokens(src);
                 let expr = {
@@ -489,10 +398,9 @@ impl State {
                 if *fixed {
                     let choice = &strs[self.rand.borrow_mut().gen_range_usize(0, strs.len())];
                     let str = self.ast.borrow()[choice.item].to_string();
-                    self.root_scope.borrow_mut().insert(
-                        name.item,
-                        Thunk::Value(Value::Lit(Literal::Str(str))),
-                    );
+                    self.root_scope
+                        .borrow_mut()
+                        .insert(name.item, Thunk::Value(Value::Lit(Literal::Str(str))));
                     return Ok(());
                 }
 
@@ -544,17 +452,17 @@ impl State {
     }
 
     /// Given a thunk, force it to WHNF.
-    fn hnf(&self, thunk: &Thunk) -> Result<Value, Error> {
+    pub fn hnf(&self, thunk: &Thunk) -> Result<Value, Error> {
         match thunk {
             Thunk::Expr(expr, env) => self.eval(*expr, env),
             Thunk::Value(val) => Ok(val.clone()),
-            Thunk::Builtin(b) => Ok(Value::Builtin(b)),
+            Thunk::Builtin(b) => Ok(Value::Builtin(*b)),
         }
     }
 
     /// Given an `ExprRef` and an environment, fetch that expression
     /// and then evalute it in that environment
-    fn eval(&self, expr_ref: ExprRef, env: &Env) -> Result<Value, Error> {
+    pub fn eval(&self, expr_ref: ExprRef, env: &Env) -> Result<Value, Error> {
         let expr = &self.ast.borrow()[expr_ref.item];
         match expr {
             // literals should be mostly cheap-ish to copy, so a
@@ -615,7 +523,7 @@ impl State {
                 let from = self.eval(*from, env)?.as_num(&self.ast.borrow())?;
                 let to = self.eval(*to, env)?.as_num(&self.ast.borrow())?;
                 Ok(Value::Lit(Literal::Num(
-                    self.rand.borrow_mut().gen_range_i64(from, to+1),
+                    self.rand.borrow_mut().gen_range_i64(from, to + 1),
                 )))
             }
 
@@ -630,12 +538,15 @@ impl State {
             // either a closure (i.e. the result of evaluating a
             // function) or a builtin, and then handle it
             // appropriately
-            Expr::Ap(func, val) => match self.eval(*func, env)? {
+            Expr::Ap(func, vals) => match self.eval(*func, env)? {
                 Value::Closure(c) => {
-                    let scrut = Thunk::Expr(*val, env.clone());
-                    self.eval_closure(&c, scrut)
+                    let scruts = vals.iter().map(|v| Thunk::Expr(*v, env.clone())).collect();
+                    self.eval_closure(&c, scruts)
+                }
+                Value::Builtin(b) => {
+                    let builtin = &self.builtins[b.idx];
+                    (builtin.callback)(self, vals, env)
                 }
-                Value::Builtin(builtin) => (builtin.callback)(self, *val, env),
                 _ => bail!("Bad function: {:?}", func),
             },
 
@@ -663,7 +574,7 @@ impl State {
                     func: expr_ref,
                     scope: env.clone(),
                 };
-                self.eval_closure(&closure, Thunk::Expr(*scrut, env.clone()))
+                self.eval_closure(&closure, vec![Thunk::Expr(*scrut, env.clone())])
             }
         }
     }
@@ -685,7 +596,7 @@ impl State {
     /// careful is here:
     ///
     /// ```ignore
-    /// {Foo => "1"; Foo => "2"; _ => "..."}.(Foo | Bar)
+    /// {[Foo] => "1"; [Foo] => "2"; _ => "..."}[Foo | Bar]
     /// ```
     ///
     /// It should be impossible to get `"2"` in this case. That means
@@ -694,7 +605,7 @@ impl State {
     /// contain non-determinism:
     ///
     /// ```ignore
-    /// {<Foo, x> => x x "!"; <Bar, x> => x x "?"}.<Foo | Bar, "a" | "b">
+    /// {[<Foo, x>] => x x "!"; [<Bar, x>] => x x "?"}[<Foo | Bar, "a" | "b">]
     /// ```
     ///
     /// The above program should print one of "aa!", "bb!", "aa?", or
@@ -711,7 +622,7 @@ impl State {
     /// function to mutably replace it with progressively more
     /// evaluated versions of the same expression, and then that's the
     /// thing we put into scope in the body of the function.
-    fn eval_closure(&self, closure: &Closure, mut scrut: Thunk) -> Result<Value, Error> {
+    pub fn eval_closure(&self, closure: &Closure, mut scruts: Vec<Thunk>) -> Result<Value, Error> {
         let ast = self.ast.borrow();
         let cases = match &ast[closure.func] {
             Expr::Fun(cases) => cases,
@@ -721,15 +632,20 @@ impl State {
         };
 
         // for each case
-        for c in cases {
+        'cases: for c in cases {
             // build a set of potential bindings, which `match_pat`
             // will update if it finds matching variables
             let mut bindings = Vec::new();
-            if !self.match_pat(&c.pat, &mut scrut, &mut bindings)? {
-                // if we didn't match, we don't care about any
-                // bindings we've found: simply skip it
+            if scruts.len() != c.pats.len() {
                 continue;
             }
+            for (scrut, pat) in scruts.iter_mut().zip(c.pats.iter()) {
+                if !self.match_pat(&pat, scrut, &mut bindings)? {
+                    // if we didn't match, we don't care about any
+                    // bindings we've found: simply skip it
+                    continue 'cases;
+                }
+            }
 
             // build a new scope from the bindings discovered
             let mut new_scope = HashMap::new();
@@ -747,7 +663,7 @@ impl State {
         }
 
         // we couldn't find a matching pattern, so throw an error
-        bail!("No pattern in {:?} matched {:?}", cases, scrut);
+        bail!("No pattern in {:?} matched {:?}", cases, scruts);
     }
 
     /// attempt to match the thunk `scrut` against the pattern

+ 8 - 3
src/lexer.rs

@@ -38,7 +38,7 @@ impl<T> Located<T> {
     }
 }
 
-impl <T: Clone> Located<T> {
+impl<T: Clone> Located<T> {
     pub fn map<R>(&self, func: impl FnOnce(T) -> R) -> Located<R> {
         Located {
             item: func(self.item.clone()),
@@ -90,6 +90,11 @@ pub enum Token<'a> {
     #[token("}")]
     RCurl,
 
+    #[token("[")]
+    LBrac,
+    #[token("]")]
+    RBrac,
+
     #[token("|")]
     Pipe,
 
@@ -135,10 +140,10 @@ pub enum Token<'a> {
     #[token("fix")]
     Fix,
 
-    #[regex(r"\p{Ll}(\pL|[0-9_-])*")]
+    #[regex(r"\p{Ll}(\pL|[0-9_/-])*")]
     Var(&'a str),
 
-    #[regex(r"\p{Lu}(\pL|[0-9_-])*")]
+    #[regex(r"\p{Lu}(\pL|[0-9_/-])*")]
     Atom(&'a str),
 
     #[regex(r"[0-9]+", parse_num)]

+ 1 - 0
src/lib.rs

@@ -2,6 +2,7 @@
 extern crate lalrpop_util;
 
 pub mod ast;
+pub mod builtins;
 pub mod interp;
 pub mod lexer;
 pub mod rand;

+ 16 - 12
src/main.rs

@@ -1,6 +1,6 @@
 use matzo::interp::State;
 
-use clap::{Arg, App};
+use clap::{App, Arg};
 
 #[derive(Debug)]
 struct Opts {
@@ -15,17 +15,21 @@ impl Opts {
             .version(format!("git {}", env!("VERGEN_GIT_SHA")).as_ref())
             .author("Getty Ritter <matzo@infinitenegativeutility.com>")
             .about("A language for random text")
-            .arg(Arg::with_name("seed")
-                 .short("s")
-                 .long("seed")
-                 .value_name("NUMBER")
-                 .help("Sets a custom RNG seed")
-                 .takes_value(true))
-            .arg(Arg::with_name("input")
-                 .value_name("FILES")
-                 .help("Files to evaluate")
-                 .multiple(true)
-                 .takes_value(true))
+            .arg(
+                Arg::with_name("seed")
+                    .short("s")
+                    .long("seed")
+                    .value_name("NUMBER")
+                    .help("Sets a custom RNG seed")
+                    .takes_value(true),
+            )
+            .arg(
+                Arg::with_name("input")
+                    .value_name("FILES")
+                    .help("Files to evaluate")
+                    .multiple(true)
+                    .takes_value(true),
+            )
             .get_matches();
 
         let seed = matches.value_of("seed").map(|s| s.parse().unwrap());

+ 3 - 3
src/rand.rs

@@ -1,4 +1,4 @@
-use rand::{SeedableRng, Rng};
+use rand::{Rng, SeedableRng};
 
 pub trait MatzoRand {
     fn gen_range_i64(&mut self, min: i64, max: i64) -> i64;
@@ -12,7 +12,7 @@ pub struct DefaultRNG {
 impl DefaultRNG {
     pub fn new() -> DefaultRNG {
         DefaultRNG {
-            rand: rand::thread_rng()
+            rand: rand::thread_rng(),
         }
     }
 }
@@ -40,7 +40,7 @@ pub struct SeededRNG {
 impl SeededRNG {
     pub fn from_seed(seed: u64) -> SeededRNG {
         SeededRNG {
-            rand: rand::rngs::StdRng::seed_from_u64(seed)
+            rand: rand::rngs::StdRng::seed_from_u64(seed),
         }
     }
 }

+ 1 - 1
tests/aquan.matzo

@@ -10,4 +10,4 @@ vowel := ("a" | "e" | "i" | "o" | "u") (4: "" | "'");
 syll := 4: cons vowel | vowel;
 
 (* And finally, here's an output statement *)
-puts syll rep.<1..6, syll>;
+puts syll rep[1..6, syll];

+ 4 - 6
tests/aquan.parsed

@@ -28,13 +28,11 @@ Puts Cat(
   Var(syll)
   Ap(
     Var(rep)
-    Tup(
-      Range(
-        Num(1)
-        Num(6)
-      )
-      Var(syll)
+    Range(
+      Num(1)
+      Num(6)
     )
+    Var(syll)
   )
 )
 

+ 4 - 4
tests/exprs.matzo

@@ -6,7 +6,7 @@ puts This | That | The-Other;
 puts 5: This | That;
 
 (* application *)
-puts foo.bar.baz;
+puts foo[bar][baz];
 
 
 (* tuples *)
@@ -20,8 +20,8 @@ puts 0..20;
 puts x..y;
 
 (* application *)
-puts f.x;
+puts f[x];
 
 (* lambdas *)
-puts { x => x };
-puts { True => "yes" ; False => "no" };
+puts { [x] => x };
+puts { [True] => "yes" ; [False] => "no" };

+ 6 - 3
tests/exprs.parsed

@@ -58,14 +58,17 @@ Puts Ap(
 )
 
 Puts Fun(
-  x =>
+  x, 
+ =>
     Var(x)
 )
 
 Puts Fun(
-  True =>
+  True, 
+ =>
     Str("yes")
-  False =>
+  False, 
+ =>
     Str("no")
 )
 

+ 9 - 9
tests/patterns.matzo

@@ -1,14 +1,14 @@
 (* simple variables *)
-f := {x => x};
-f := {_ => x};
+f := {[x] => x};
+f := {[_] => x};
 
 (* literals *)
-f := {"foo" => "!"};
-f := {Bar => "?"};
-f := {55 => "~"};
+f := {["foo"] => "!"};
+f := {[Bar] => "?"};
+f := {[55] => "~"};
 
 (* tuples *)
-f := {<> => x};
-f := {<x> => x};
-f := {<x,y> => x};
-f := {<x,y,z> => x};
+f := {[<>] => x};
+f := {[<x>] => x};
+f := {[<x,y>] => x};
+f := {[<x,y,z>] => x};

+ 18 - 9
tests/patterns.parsed

@@ -1,45 +1,54 @@
 Assn  f Fun(
-  x =>
+  x, 
+ =>
     Var(x)
 )
 
 Assn  f Fun(
-  _ =>
+  _, 
+ =>
     Var(x)
 )
 
 Assn  f Fun(
-  Str("foo") =>
+  Str("foo"), 
+ =>
     Str("!")
 )
 
 Assn  f Fun(
-  Bar =>
+  Bar, 
+ =>
     Str("?")
 )
 
 Assn  f Fun(
-  Num(55) =>
+  Num(55), 
+ =>
     Str("~")
 )
 
 Assn  f Fun(
-  Tup( ) =>
+  Tup( ), 
+ =>
     Var(x)
 )
 
 Assn  f Fun(
-  Tup( x ) =>
+  Tup( x ), 
+ =>
     Var(x)
 )
 
 Assn  f Fun(
-  Tup( x y ) =>
+  Tup( x y ), 
+ =>
     Var(x)
 )
 
 Assn  f Fun(
-  Tup( x y z ) =>
+  Tup( x y z ), 
+ =>
     Var(x)
 )
 

+ 5 - 2
tools/regenerate.rs

@@ -1,6 +1,6 @@
 use matzo::grammar;
-use matzo::lexer;
 use matzo::interp;
+use matzo::lexer;
 
 use std::collections::BTreeMap;
 use std::io::Write;
@@ -18,7 +18,10 @@ fn generate_runs(source: &str) -> Result<BTreeMap<String, String>, Box<dyn std::
             let _ = found_results.insert(out, seed);
         }
     }
-    let output = found_results.into_iter().map(|(k, v)| (format!("{}", v), k)).collect();
+    let output = found_results
+        .into_iter()
+        .map(|(k, v)| (format!("{}", v), k))
+        .collect();
     Ok(output)
 }