Repository: MiSawa/xq (rust)

Issue:
Variables in object binding pattern
jq allows to bind object value by using variable in object keys.
```sh
❯ jq -c '. as {$a:[$b]} | {$a,$b}' <<< '{"a":[1]}'
{"a":[1],"b":1}

❯ xq -c '. as {$a:[$b]} | {$a,$b}' <<< '{"a":[1]}'
Error: compile query

Caused by:
    CompileError(UnknownVariable(Identifier("a")))

❯ xq --version
xq 0.1.9-95558180fabd53143a91d72e226780939461f8c5
```

Pull request:
Fix #50

Patch:
diff --git a/src/compile/compiler.rs b/src/compile/compiler.rs
index 2484ad7..491bd0e 100644
--- a/src/compile/compiler.rs
+++ b/src/compile/compiler.rs
@@ -951,12 +951,16 @@ impl Compiler {
                 BindPattern::Object(obj) => {
                     for entry in obj {
                         match entry {
-                            ObjectBindPatternEntry::KeyValue(_, p) => {
-                                collect_variable_occurrences(p, occurrences);
-                            }
                             ObjectBindPatternEntry::KeyOnly(ident) => {
                                 *occurrences.entry(ident).or_insert(0) += 1;
                             }
+                            ObjectBindPatternEntry::ValueOnly(_, p) => {
+                                collect_variable_occurrences(p, occurrences);
+                            }
+                            ObjectBindPatternEntry::KeyAndValue(key, p) => {
+                                *occurrences.entry(key).or_insert(0) += 1;
+                                collect_variable_occurrences(p, occurrences);
+                            }
                         }
                     }
                 }
@@ -991,16 +995,28 @@ impl Compiler {
                         let mut tmp = next;
                         for (i, entry) in entries.iter().rev().enumerate() {
                             match entry {
-                                ObjectBindPatternEntry::KeyValue(key, value) => {
+                                ObjectBindPatternEntry::KeyOnly(key) => {
+                                    let slot = *compiler.lookup_variable(key)?;
+                                    tmp =
+                                        compiler.emitter.emit_normal_op(ByteCode::Store(slot), tmp);
+                                    tmp = compiler.emitter.emit_normal_op(ByteCode::Index, tmp);
+                                    tmp = compiler.emitter.emit_normal_op(
+                                        ByteCode::Push(Value::string(key.0.clone())),
+                                        tmp,
+                                    );
+                                }
+                                ObjectBindPatternEntry::ValueOnly(key, value) => {
                                     tmp = value.compile(compiler, tmp)?;
                                     tmp = compiler.emitter.emit_normal_op(ByteCode::Index, tmp);
                                     tmp = compiler.compile_query(key, tmp)?;
                                     tmp = compiler.emitter.emit_normal_op(ByteCode::Dup, tmp);
                                 }
-                                ObjectBindPatternEntry::KeyOnly(key) => {
+                                ObjectBindPatternEntry::KeyAndValue(key, value) => {
+                                    tmp = value.compile(compiler, tmp)?;
                                     let slot = *compiler.lookup_variable(key)?;
                                     tmp =
                                         compiler.emitter.emit_normal_op(ByteCode::Store(slot), tmp);
+                                    tmp = compiler.emitter.emit_normal_op(ByteCode::Dup, tmp);
                                     tmp = compiler.emitter.emit_normal_op(ByteCode::Index, tmp);
                                     tmp = compiler.emitter.emit_normal_op(
                                         ByteCode::Push(Value::string(key.0.clone())),
diff --git a/src/lang/ast.rs b/src/lang/ast.rs
index 82b1d2a..8992ccc 100644
--- a/src/lang/ast.rs
+++ b/src/lang/ast.rs
@@ -73,10 +73,12 @@ pub enum StringFragment {
 
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub enum ObjectBindPatternEntry {
-    /// `(<ident> | <variable> | <keyword> | <string> | '(' <query> ')') ':' pattern`
-    KeyValue(Box<Query>, Box<BindPattern>),
     /// `<variable>`
     KeyOnly(Identifier),
+    /// `(<ident> | <keyword> | <string> | '(' <query> ')') ':' pattern`
+    ValueOnly(Box<Query>, Box<BindPattern>),
+    /// `<variable> ':' pattern`
+    KeyAndValue(Identifier, Box<BindPattern>),
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
diff --git a/src/lang/jq.lalrpop b/src/lang/jq.lalrpop
index 6925765..cc88a4e 100644
--- a/src/lang/jq.lalrpop
+++ b/src/lang/jq.lalrpop
@@ -258,19 +258,16 @@ FuncDef: FuncDef = {
 #[inline]
 ObjectBindPatternEntry: ObjectBindPatternEntry = {
     Variable => ObjectBindPatternEntry::KeyOnly(<>),
-    <var: Variable> ":" <value: Boxed<BindPattern>> => ObjectBindPatternEntry::KeyValue(
-        Box::new(Term::Variable(var).into()),
-        value,
-    ),
-    <ident: IdentifierAllowKeyword> ":" <value: Boxed<BindPattern>> => ObjectBindPatternEntry::KeyValue(
+    <Variable> ":" <Boxed<BindPattern>> => ObjectBindPatternEntry::KeyAndValue(<>),
+    <ident: IdentifierAllowKeyword> ":" <value: Boxed<BindPattern>> => ObjectBindPatternEntry::ValueOnly(
         Box::new(Term::String(vec![StringFragment::String(ident.0)]).into()),
         value,
     ),
-    <s: StringMayBeInterpolated> ":" <value: Boxed<BindPattern>> => ObjectBindPatternEntry::KeyValue(
+    <s: StringMayBeInterpolated> ":" <value: Boxed<BindPattern>> => ObjectBindPatternEntry::ValueOnly(
         Box::new(Term::String(s).into()),
         value,
     ),
-    "(" <Boxed<Query>> ")" <Boxed<BindPattern>> => ObjectBindPatternEntry::KeyValue(<>),
+    "(" <Boxed<Query>> ")" ":" <Boxed<BindPattern>> => ObjectBindPatternEntry::ValueOnly(<>),
 }
 #[inline]
 BindPattern: BindPattern = {


Test patch:
diff --git a/tests/hand_written/mod.rs b/tests/hand_written/mod.rs
index fa6c26c..a2d7cfd 100644
--- a/tests/hand_written/mod.rs
+++ b/tests/hand_written/mod.rs
@@ -259,3 +259,29 @@ test!(
     [1,3,4]
     "#
 );
+
+test!(
+    key_value_bind,
+    r#"
+    . as {$a:[$b]} | {$a,$b}
+    "#,
+    r#"
+    {"a":[1]}
+    "#,
+    r#"
+    {"a":[1],"b":1}
+    "#
+);
+
+test!(
+    key_query_bind,
+    r#"
+    . as {$a, (.b):[$b]} | {$a,$b}
+    "#,
+    r#"
+    {"a":[1], "b":"a"}
+    "#,
+    r#"
+    {"a":[1],"b":1}
+    "#
+);