jaq-interpret 1.5.0

Interpreter for the jaq language
Documentation
pub mod common;

use common::{fail, give, gives};
use jaq_interpret::Error;
use serde_json::json;

#[test]
fn index_access() {
    give(json!([0, 1, 2]), ".[-4]", json!(null));
    give(json!([0, 1, 2]), ".[-3]", json!(0));
    give(json!([0, 1, 2]), ".[-1]", json!(2));
    give(json!([0, 1, 2]), ".[ 0]", json!(0));
    give(json!([0, 1, 2]), ".[ 2]", json!(2));
    give(json!([0, 1, 2]), ".[ 3]", json!(null));

    give(json!({"a": 1}), ".a", json!(1));
    give(json!({"a": 1}), ".a?", json!(1));
    give(json!({"a": 1}), ".a ?", json!(1));
    give(json!({"a": 1}), r#"."a""#, json!(1));
    give(json!({"a": 1}), r#". "a""#, json!(1));
    give(json!({"a": 1}), r#".["a"]"#, json!(1));
    give(json!({"a": 1}), r#". ["a"]"#, json!(1));
    give(json!({"a_": 1}), ".a_", json!(1));
    give(json!({"_a": 1}), "._a", json!(1));
    give(json!({"_0": 1}), "._0", json!(1));

    give(json!({"a": 1}), r#".[0, "a", 0 == 0]?"#, json!(1));
    give(json!([0, 1, 2]), r#".["a", 0, 0 == 0]?"#, json!(0));
    give(json!([0, 1, 2]), r#".[3]?"#, json!(null));
    gives(json!("asdf"), ".[0]?", []);

    give(json!(1), "[1, 2, 3][.]", json!(2));

    gives(
        json!({"a": 1, "b": 2}),
        r#".["b", "a"]"#,
        [json!(2), json!(1)],
    );
}

#[test]
fn iter_access() {
    gives(json!([0, 1, 2]), ".[]", [json!(0), json!(1), json!(2)]);
    gives(json!({"a": [1, 2]}), ".a[]", [json!(1), json!(2)]);
    gives(json!({"a": [1, 2]}), ".a.[]", [json!(1), json!(2)]);
    gives(json!({"a": 1, "b": 2}), ".[]", [json!(1), json!(2)]);
    // TODO: correct this
    //gives(json!({"b": 2, "a": 1}), ".[]", [json!(2), json!(1)]);
    gives(json!("asdf"), ".[]?", []);
}

#[test]
fn range_access() {
    give(json!("Möwe"), ".[1:-1]", json!("öw"));
    give(json!("नमस्ते"), ".[1:5]", json!("मस्त"));

    give(json!([0, 1, 2]), ".[-4:4]", json!([0, 1, 2]));
    give(json!([0, 1, 2]), ".[0:3]", json!([0, 1, 2]));
    give(json!([0, 1, 2]), ".[1:]", json!([1, 2]));
    give(json!([0, 1, 2]), ".[:-1]", json!([0, 1]));
    give(json!([0, 1, 2]), ".[1:0]", json!([]));
    give(json!([0, 1, 2]), ".[4:5]", json!([]));

    give(json!([0, 1, 2]), ".[0:2,3.14]?", json!([0, 1]));
}

#[test]
fn iter_assign() {
    give(json!([1, 2]), ".[] = .", json!([[1, 2], [1, 2]]));
    give(
        json!({"a": [1,2], "b": 3}),
        ".a[] = .b+.b",
        json!({"a": [6,6], "b": 3}),
    );
}

#[test]
fn index_update() {
    give(json!({"a": 1}), ".b |= .", json!({"a": 1, "b": null}));
    give(json!({"a": 1}), ".b |= 1", json!({"a": 1, "b": 1}));
    give(json!({"a": 1}), ".b |= .+1", json!({"a": 1, "b": 1}));
    give(json!({"a": 1, "b": 2}), ".b |= {}[]", json!({"a": 1}));
    give(json!({"a": 1, "b": 2}), ".a += 1", json!({"a": 2, "b": 2}));

    give(json!([0, 1, 2]), ".[1] |= .+2", json!([0, 3, 2]));
    give(json!([0, 1, 2]), ".[-1,-1] |= {}[]", json!([0]));
    give(json!([0, 1, 2]), ".[ 0, 0] |= {}[]", json!([2]));

    use Error::IndexOutOfBounds as Oob;
    fail(json!([0, 1, 2]), ".[ 3] |=  3", Oob(3));
    fail(json!([0, 1, 2]), ".[-4] |= -1", Oob(-4));

    give(json!({"a": 1}), r#".[0, "a"]? |= .+1"#, json!({"a": 2}));
    give(json!([0, 1, 2]), r#".["a", 0]? |= .+1"#, json!([1, 1, 2]));
    give(json!([0, 1, 2]), r#".[3]? |= .+1"#, json!([0, 1, 2]));
    give(json!("asdf"), ".[0]? |= .+1", json!("asdf"));
}

#[test]
fn iter_update() {
    // precedence tests
    give(json!([]), ".[] |= . or 0", json!([]));
    gives(json!([]), ".[] |= .,.", [json!([]), json!([])]);
    give(json!([]), ".[] |= (.,.)", json!([]));
    give(json!([0]), ".[] |= .+1 | .+[2]", json!([1, 2]));
    // this yields a syntax error in jq, but it is consistent to permit this
    give(json!([[1]]), ".[] |= .[] |= .+1", json!([[2]]));
    // ditto
    give(json!([[1]]), ".[] |= .[] += 1", json!([[2]]));

    give(json!([1]), ".[] |= .+1", json!([2]));
    give(json!([[1]]), ".[][] |= .+1", json!([[2]]));

    give(
        json!({"a": 1, "b": 2}),
        ".[] |= ((if .>1 then . else {}[] end) | .+1)",
        json!({"b": 3}),
    );

    give(json!([[0, 1], "a"]), ".[][]? |= .+1", json!([[1, 2], "a"]));
}

#[test]
fn range_update() {
    give(json!([0, 1, 2]), ".[:2] |= [.[] | .+5]", json!([5, 6, 2]));
    give(json!([0, 1, 2]), ".[-2:-1] |= [5]+.", json!([0, 5, 1, 2]));
    give(
        json!([0, 1, 2]),
        ".[-2:-1,-1] |= [5,6]+.",
        json!([0, 5, 6, 5, 6, 1, 2]),
    );

    give(
        json!([0, 1, 2]),
        ".[:2,3.0]? |= [.[] | .+1]",
        json!([1, 2, 2]),
    );
}

// Test what happens when update filter returns multiple values.
// Watch out: here, jaq diverges frequently from jq;
// jq considers only the first value of the filter regardless of the updated value,
// whereas jaq may consider multiple values depending on the updated value.
// This behaviour is easier to implement and more flexible.
#[test]
fn update_mult() {
    // first the cases where jaq and jq agree
    give(json!({"a": 1}), ".a |= (.,.+1)", json!({"a": 1}));

    // jq returns null here
    gives(json!(1), ". |= {}[]", []);
    // jq returns just 1 here
    gives(json!(1), ". |= (.,.)", [json!(1), json!(1)]);
    // jq returns just [1] here
    give(json!([1]), ".[] |= (., .+1)", json!([1, 2]));
    // jq returns just [1,2] here
    give(json!([1, 3]), ".[] |= (., .+1)", json!([1, 2, 3, 4]));
    // here comes a huge WTF: jq returns [2,4] here
    // this is a known bug: <https://github.com/stedolan/jq/issues/2051>
    give(json!([1, 2, 3, 4, 5]), ".[] |= {}[]", json!([]));
}

#[test]
fn update_complex() {
    // jq returns 1 here, which looks like a bug
    // in general, `a | a |= .`
    // works in jq when `a` is either null, a number, or a boolean --- it
    // does *not* work when `a` is a string, an array, or an object!
    fail(json!(0), "0 |= .+1", Error::PathExp);
}