Repository: MiSawa/xq
Pull request #100 (2022-03-05 12:01:31)

Implement format/1 function
This PR implements format/1 function.
```sh
❯ jq 'format("html","uri","csv","tsv","sh","base64")' <<< '[1,2,3]'
"[1,2,3]"
"%5B1%2C2%2C3%5D"
"1,2,3"
"1\t2\t3"
"1 2 3"
"WzEsMiwzXQ=="
```


Patch:
diff --git a/src/compile/compiler.rs b/src/compile/compiler.rs
index 4fc0556..a045926 100644
--- a/src/compile/compiler.rs
+++ b/src/compile/compiler.rs
@@ -1195,7 +1195,7 @@ impl Compiler {
                 self.lookup_and_compile_func_call(name.clone(), args, next)?
             }
             Term::Format(format, str) => {
-                let stringifier = intrinsic::stringifier(format)
+                let stringifier = intrinsic::stringifier(&format.0)
                     .ok_or_else(|| CompileError::UnknownStringFormatter(format.clone()))?;
                 match str {
                     Some(s) => self.compile_string(
diff --git a/src/intrinsic/mod.rs b/src/intrinsic/mod.rs
index 7bacf6b..ca2d702 100644
--- a/src/intrinsic/mod.rs
+++ b/src/intrinsic/mod.rs
@@ -83,6 +83,7 @@ static INTRINSICS1: phf::Map<&'static str, NamedFn1> = phf_map! {
     "split" => NamedFn1 { name: "split", func: split1 },
     "delpaths" => NamedFn1 { name: "delpaths", func: path::del_paths },
     "bsearch" => NamedFn1 { name: "bsearch", func: binary_search },
+    "format" => NamedFn1 { name: "format", func: format },
 
     "strftime" => NamedFn1 { name: "strftime", func: time::format_time },
     "strptime" => NamedFn1 { name: "strptime", func: time::parse_time },
@@ -327,3 +328,12 @@ fn binary_search(context: Value, x: Value) -> Result<Value> {
         _ => Err(QueryExecutionError::InvalidArgType("bsearch", context)),
     }
 }
+
+fn format(context: Value, s: Value) -> Result<Value> {
+    match (context, s) {
+        (lhs, Value::String(fmt)) => (stringifier(&fmt)
+            .ok_or_else(|| QueryExecutionError::UnknownStringFormatter(fmt))?
+            .func)(lhs),
+        (_, s) => Err(QueryExecutionError::InvalidArgType("format", s)),
+    }
+}
diff --git a/src/intrinsic/string.rs b/src/intrinsic/string.rs
index 0c5dcb2..949482d 100644
--- a/src/intrinsic/string.rs
+++ b/src/intrinsic/string.rs
@@ -1,5 +1,4 @@
 use crate::{
-    lang::ast::Identifier,
     util::make_owned,
     vm::{bytecode::NamedFn0, QueryExecutionError, Result},
     Array, Number, Value,
@@ -20,8 +19,8 @@ pub(crate) fn to_number(value: Value) -> Result<Value> {
     }
 }
 
-pub(crate) fn stringifier(id: &Identifier) -> Option<NamedFn0> {
-    match id.0.as_str() {
+pub(crate) fn stringifier(fmt: &String) -> Option<NamedFn0> {
+    match fmt.as_str() {
         "text" => NamedFn0 {
             name: "text",
             func: text,
diff --git a/src/vm/error.rs b/src/vm/error.rs
index 2e5302c..95080b3 100644
--- a/src/vm/error.rs
+++ b/src/vm/error.rs
@@ -59,6 +59,8 @@ pub enum QueryExecutionError {
     InvalidStringToNumber(RcString),
     #[error("Given string was invalid as a json `{0:}`")]
     InvalidJson(RcString),
+    #[error("Unknown string formatter `{0:?}`")]
+    UnknownStringFormatter(RcString),
     #[error("Unable to parse date time")]
     InvalidDateTimeString(#[from] chrono::format::ParseError),
     #[error("{0:?}")]


Test patch:
diff --git a/tests/hand_written/mod.rs b/tests/hand_written/mod.rs
index 4bc1ebf..df7dbbb 100644
--- a/tests/hand_written/mod.rs
+++ b/tests/hand_written/mod.rs
@@ -617,3 +617,21 @@ test!(
     "0 null true 'a b c'"
     "#
 );
+
+test!(
+    format,
+    r#"
+    format("html", "uri", "csv", "tsv", "sh", "base64")
+    "#,
+    r#"
+    [1,2,3]
+    "#,
+    r#"
+    "[1,2,3]"
+    "%5B1%2C2%2C3%5D"
+    "1,2,3"
+    "1\t2\t3"
+    "1 2 3"
+    "WzEsMiwzXQ=="
+    "#
+);