commit f2996ce6e7cd5c22ef8a71ec700cac73ce6f3b98 Author: upstream Date: Fri Sep 4 08:20:57 2026 +0200 Upstream itchyny/gojq state of the touched files before 3194dce466 diff --git a/cli/test.yaml b/cli/test.yaml new file mode 100644 index 0000000..4818048 --- /dev/null +++ b/cli/test.yaml @@ -0,0 +1,9029 @@ +- name: empty input + args: + - '.' + input: '' + expected: '' + +- name: empty args + input: 'null' + expected: | + null + +- name: missing query + args: + - '' + error: | + compile error: missing query (try ".") + exit_code: 3 + +- name: query with comment + args: + - '. # comment' + input: 'null' + expected: | + null + +- name: missing query with comment + args: + - '# comment' + error: | + compile error: missing query (try ".") + exit_code: 3 + +- name: query with comment with newline + args: + - | + # comment + 1, + # comment \ + comment \ 0 \ + comment + 2, + # comment \ + comment \\ + 3, # \ + comment \\\ + comment \\ + 4 # + input: 'null' + expected: | + 1 + 2 + 3 + 4 + +- name: query with comment with newline in dos format + args: + - "1, # \r\n2, # \\\r\ncomment\\\\\r\n3" + input: 'null' + expected: | + 1 + 2 + 3 + +- name: query with comment with newline in mac format + args: + - "1, # \r2, # \\\rcomment\\\\\r3" + input: 'null' + expected: | + 1 + 2 + 3 + +- name: number input + args: + - '.' + input: '128 3.14 10.0 6.02e23 5e1000' + expected: | + 128 + 3.14 + 10.0 + 6.02e23 + 5e1000 + +- name: number query + args: + - '0, 128, 01000, 3.14, 1.2e3, 1E+3, 1E-9' + input: '0' + expected: | + 0 + 128 + 1000 + 3.14 + 1200 + 1000 + 1e-9 + +- name: number literal error + args: + - '0..[empty]' + input: '0' + error: | + invalid query: 0..[empty] + 0..[empty] + ^ invalid token "0.." + exit_code: 3 + +- name: string input + args: + - '.' + input: '"Hello, world! \r\n\t\f\b\"\\\/\uff06"' + expected: | + "Hello, world! \r\n\t\f\b\"\\/&" + +- name: string query + args: + - '" Hello, world! \r\n\t\f\b\"\\\/\uff10 "' + input: 'null' + expected: | + " Hello, world! \r\n\t\f\b\"\\/0 " + +- name: string query including newlines + args: + - "\"a\n\\(\"\nb\nc\n\")\nd\n\"" + input: 'null' + expected: | + "a\n\nb\nc\n\nd\n" + +- name: string query including control characters + args: + - "\"\x01\x07\x0f\\n\", \"\x7f\\n\"" + input: 'null' + expected: | + "\u0001\u0007\u000f\n" + "\u007f\n" + +- name: string query including surrogate pairs + args: + - '"\ud83d\ude04" | length, utf8bytelength, explode[]' + input: 'null' + expected: | + 1 + 4 + 128516 + +- name: string query error + args: + - '. + "foo' + input: 'null' + error: | + invalid query: . + "foo + . + "foo + ^ unterminated string literal + exit_code: 3 + +- name: string query error + args: + - '"foo\,bar"' + input: 'null' + error: | + invalid query: "foo\,bar" + "foo\,bar" + ^ invalid escape sequence "\," in string literal + exit_code: 3 + +- name: string query error + args: + - '"\u"' + input: 'null' + error: | + invalid query: "\u" + "\u" + ^ invalid escape sequence "\u" in string literal + exit_code: 3 + +- name: string query error + args: + - "\"\n\\u\n\"" + input: 'null' + error: | + invalid query: :2 + 2 | \u + ^ invalid escape sequence "\u" in string literal + exit_code: 3 + +- name: string query error + args: + - '"\u' + input: 'null' + error: | + invalid query: "\u + "\u + ^ invalid escape sequence "\u" in string literal + exit_code: 3 + +- name: string query error + args: + - '"\' + input: 'null' + error: | + invalid query: "\ + "\ + ^ unterminated string literal + exit_code: 3 + +- name: string query error + args: + - '"\"' + input: 'null' + error: | + invalid query: "\" + "\" + ^ unterminated string literal + exit_code: 3 + +- name: string query error + args: + - '"\u````"' + input: 'null' + error: | + invalid query: "\u````" + "\u````" + ^ invalid escape sequence "\u" in string literal + exit_code: 3 + +- name: string query error + args: + - '"\uAA\uAA"' + input: 'null' + error: | + invalid query: "\uAA\uAA" + "\uAA\uAA" + ^ invalid escape sequence "\uAA" in string literal + exit_code: 3 + +- name: string query error + args: + - '"\uAA@AA"' + input: 'null' + error: | + invalid query: "\uAA@AA" + "\uAA@AA" + ^ invalid escape sequence "\uAA" in string literal + exit_code: 3 + +- name: string query error + args: + - '"\uAAGA"' + input: 'null' + error: | + invalid query: "\uAAGA" + "\uAAGA" + ^ invalid escape sequence "\uAA" in string literal + exit_code: 3 + +- name: string query error + args: + - '"\ufffg"' + input: 'null' + error: | + invalid query: "\ufffg" + "\ufffg" + ^ invalid escape sequence "\ufff" in string literal + exit_code: 3 + +- name: string query error + args: + - '"0\u00:00"' + input: 'null' + error: | + invalid query: "0\u00:00" + "0\u00:00" + ^ invalid escape sequence "\u00" in string literal + exit_code: 3 + +- name: string query error + args: + - '"0\u0000\u000/0"' + input: 'null' + error: | + invalid query: "0\u0000\u000/0" + "0\u0000\u000/0" + ^ invalid escape sequence "\u000" in string literal + exit_code: 3 + +- name: string query error + args: + - '"\(1) \x "' + input: 'null' + error: | + invalid query: "\(1) \x " + "\(1) \x " + ^ invalid escape sequence "\x" in string literal + exit_code: 3 + +- name: string query error + args: + - '"\a"' + input: 'null' + error: | + invalid query: "\a" + "\a" + ^ invalid escape sequence "\a" in string literal + exit_code: 3 + +- name: string query error + args: + - '"\u\(' + input: 'null' + error: | + invalid query: "\u\( + "\u\( + ^ invalid escape sequence "\u" in string literal + exit_code: 3 + +- name: object input + input: '{"foo": 128}' + expected: | + { + "foo": 128 + } + +- name: object input and string escape + input: '{"foo\\u0000\\t\\n\\r\\u001f !\"#$/09:;<>?@AZ[\\\\]^_`az{|}~\\u007f": "<\\t\\n\\r>"}' + expected: | + { + "foo\\u0000\\t\\n\\r\\u001f !\"#$/09:;<>?@AZ[\\\\]^_`az{|}~\\u007f": "<\\t\\n\\r>" + } + +- name: object indexing + args: + - '.foo' + input: '{"foo": 128}' + expected: | + 128 + +- name: object indexing with large number value + args: + - '.foo' + input: '{"foo": 4722366482869645213696}' + expected: | + 4722366482869645213696 + +- name: object indexing with floating-point number with trailing zeros + args: + - '.foo' + input: '{"foo": 100.000}' + expected: | + 100.000 + +- name: object indexing by keywords + args: + - '.and,.or,.try' + input: '{"and":1,"or":2,"try":3}' + expected: | + 1 + 2 + 3 + +- name: object indexing by brackets + args: + - '.["foo"]' + input: '{"foo": 128}' + expected: | + 128 + +- name: object indexing by string + args: + - '."foo", ."2\(.foo[])3"' + input: '{"foo":[1,2],"213":3,"223":6}' + expected: | + [ + 1, + 2 + ] + 3 + 6 + +- name: object indexing by query + args: + - '.c[.a + .b]' + input: '{ "a": "b", "b": "c", "c": {"bc": 128} }' + expected: | + 128 + +- name: object nested indexing by query + args: + - '.d[.a][.b][.c]' + input: '{ "a": "b", "b": "c", "c": "d", "d": { "b": { "c": { "d": 128 } } } }' + expected: | + 128 + +- name: object indexing by iterator after iterator + args: + - '.c[][.a[] + .b[]]' + input: '{ "a": ["a", "b"], "b": ["c", "d"], "c": [{"ac": 1, "bc": 2, "ad": 3, "bd": 4}, {"ac": 5, "bc": 6, "ad": 7, "bd": 8}] }' + expected: | + 1 + 5 + 2 + 6 + 3 + 7 + 4 + 8 + +- name: object indexing by string iterator after iterator + args: + - '.c[]."\(.a[])"' + input: '{ "a": [0,1], "b": [0,2], "c": [{"0":1,"1":2},{"0":3,"1":4}] }' + expected: | + 1 + 3 + 2 + 4 + +- name: object indexing in object value + args: + - '{ message: .[].message }' + input: '[ {"message": "Hello, world" } ]' + expected: | + { + "message": "Hello, world" + } + +- name: object indexing error + args: + - '.[.a + .b]' + input: '{"a": 10, "b": 20}' + error: | + expected an array but got: object ({"a":10,"b":20}) + +- name: object indexing value error + args: + - '.["foo"]' + input: '[]' + error: | + expected an object but got: array ([]) + +- name: object indexing key error + args: + - '.[null]' + input: '{}' + error: | + expected a string for object key but got: null + +- name: object indexing syntax error + args: + - '. a' + input: '{}' + error: | + invalid query: . a + . a + ^ unexpected token "a" + exit_code: 3 + +- name: expected object but got number error + args: + - '.foo.bar' + input: '{"foo": 128}' + error: | + expected an object but got: number (128) + +- name: expected object but got string error + args: + - '"a"+"☆"*100 | .foo' + input: '0' + error: | + expected an object but got: string ("a☆☆☆☆☆☆☆ ...") + +- name: expected object error with pipe operator + args: + - '.foo|.bar' + input: '{"foo": 128}' + error: | + expected an object but got: number (128) + +- name: expected object error after iterator + args: + - '.[]|.foo|.bar' + input: '[128]' + error: | + expected an object but got: number (128) + +- name: object indexing against null + args: + - '.foo.bar.baz' + input: 'null' + expected: | + null + +- name: object optional indexing + args: + - '.foo?.bar?.baz?' + input: '{"foo": 128}' + +- name: object optional indexing error + args: + - '.foo.bar.baz?' + input: '{"foo": 128}' + error: | + expected an object but got: number (128) + +- name: object optional indexing after iterator error + args: + - '.[].foo?' + input: '128' + error: | + cannot iterate over: number (128) + +- name: object optional indexing with optional operator + args: + - '.foo??, .' + input: '0' + expected: | + 0 + +- name: error after optional operator + args: + - '1? | .foo' + input: 'null' + error: | + expected an object but got: number (1) + +- name: error after optional operator with fork + args: + - '(1,2)? | .foo' + input: 'null' + error: | + expected an object but got: number (1) + +- name: array indexing + args: + - '.[-4,-3,-2,-1,0,1,2,3,nan]' + input: '[0, 1, 2]' + expected: | + null + 0 + 1 + 2 + 0 + 1 + 2 + null + null + +- name: array slicing + args: + - -c + - '[.[-4,-3,-2,-1,0,1,2,3:]], [.[:-4,-3,-2,-1,0,1,2,3]], [.[-1,0,1,2,3:-1,0,1,2,3]]' + input: '[0, 1, 2]' + expected: | + [[0,1,2],[0,1,2],[1,2],[2],[0,1,2],[1,2],[2],[]] + [[],[],[0],[0,1],[],[0],[0,1],[0,1,2]] + [[],[],[],[],[2],[0,1],[],[0],[0,1],[0,1,2],[1],[],[],[1],[1,2],[],[],[],[],[2],[],[],[],[],[]] + +- name: array indexing and slicing by floating-point number + args: + - -c + - '[.[-2.8,-1.2,0.3,1.7,2.2]], [.[0.3,1.7:]], [.[:0.3,1.7]], [.[0.3,1.7,2.2:0.3,1.7,2.2]]' + input: '[0, 1, 2]' + expected: | + [1,2,0,1,2] + [[0,1,2],[1,2]] + [[0],[0,1]] + [[0],[0,1],[0,1,2],[],[1],[1,2],[],[],[2]] + +- name: array indexing and slicing by large number + args: + - -c + - '.[1e300,-1e300], .[-1e300,1e300:], .[:-1e300,1e300], .[-1e300:1e300], .[1e300:-1e300]' + input: '[0, 1, 2]' + expected: | + null + null + [0,1,2] + [] + [] + [0,1,2] + [0,1,2] + [] + +- name: array indexing and slicing by large integer + args: + - -c + - '. as $x | 4722366482869645213696 as $y | $x[$y,-$y], $x[-$y,$y:], $x[:-$y,$y], $x[-$y:$y], $x[$y-$y], $x[$y/$y]' + input: '[0, 1, 2]' + expected: | + null + null + [0,1,2] + [] + [] + [0,1,2] + [0,1,2] + 0 + 1 + +- name: array indexing and slicing by infinite + args: + - -c + - '.[infinite,-infinite], .[-infinite,infinite:], .[:-infinite,infinite], .[-infinite:infinite], .[infinite:-infinite]' + input: '[0, 1, 2]' + expected: | + null + null + [0,1,2] + [] + [] + [0,1,2] + [0,1,2] + [] + +- name: array and string slicing by object + args: + - -c + - '[.[{"start": (null,range(-3;4)), "end": (null,range(-3;4))}]]' + input: '[0, 1, 2] "abcde"' + expected: | + [[0,1,2],[],[0],[0,1],[],[0],[0,1],[0,1,2],[0,1,2],[],[0],[0,1],[],[0],[0,1],[0,1,2],[1,2],[],[],[1],[],[],[1],[1,2],[2],[],[],[],[],[],[],[2],[0,1,2],[],[0],[0,1],[],[0],[0,1],[0,1,2],[1,2],[],[],[1],[],[],[1],[1,2],[2],[],[],[],[],[],[],[2],[],[],[],[],[],[],[],[]] + ["abcde","ab","abc","abcd","","a","ab","abc","cde","","c","cd","","","","c","de","","","d","","","","","e","","","","","","","","abcde","ab","abc","abcd","","a","ab","abc","bcde","b","bc","bcd","","","b","bc","cde","","c","cd","","","","c","de","","","d","","","",""] + +- name: array slicing by object against null + args: + - '.[{"start": 1, "end": 2}]' + input: 'null' + expected: | + null + +- name: array slicing by object error + args: + - '.[{"start": 1}]' + input: '[]' + error: | + expected "start" and "end" for slicing but got: object ({"start":1}) + +- name: array nested indexing by iterator + args: + - -c + - '[.[.[]],.[.[]:],.[:.[]],.[.[]:.[]],.[-.[]],.[--.[]]]' + input: '[0, 1, 2]' + expected: | + [0,1,2,[0,1,2],[1,2],[2],[],[0],[0,1],[],[0],[0,1],[],[],[1],[],[],[],0,2,1,0,1,2] + +- name: array indexing by iterator + args: + - '.c[][.a[] + .b[]]' + input: '{ "a": [0,1], "b": [0,2], "c": [[1,2,3,4],[5,6,7,8]] }' + expected: | + 1 + 5 + 2 + 6 + 3 + 7 + 4 + 8 + +- name: array slicing by iterator + args: + - -c + - '.c[][.a[] : .b[]]' + input: '{ "a": [0,1], "b": [0,2], "c": [[1,2,3,4],[5,6,7,8]] }' + expected: | + [] + [] + [1,2] + [5,6] + [] + [] + [2] + [6] + +- name: expected array but got object error + args: + - '.[0:1]' + input: '{"foo": 128}' + error: | + expected an array but got: object ({"foo":128}) + +- name: array indexing against null + args: + - '.[0]' + input: 'null' + expected: | + null + +- name: array slicing against null + args: + - '.[0:1]' + input: 'null' + expected: | + null + +- name: array indexing by array + args: + - -c + - '.[[1],[5,7,9],[],5,[3]]' + input: '[1,3,5,7,9,8,6,5,4,3,2,1]' + expected: | + [0,11] + [2] + [] + 8 + [1,9] + +- name: array indexing not number error + args: + - '.[true]' + input: '[]' + error: | + expected a number for indexing an array but got: boolean (true) + +- name: array slicing start error + args: + - '.[[]:0]' + input: '[]' + error: | + expected a number for indexing an array but got: array ([]) + +- name: array slicing end error + args: + - '.[0:{}]' + input: '[]' + error: | + expected a number for indexing an array but got: object ({}) + +- name: string indexing + args: + - '.[-5,-1,0,3,4,6]' + input: '"12345"' + expected: | + "1" + "5" + "1" + "4" + "5" + null + +- name: string slicing + args: + - '.[3:], .[:3], .[2:4], .[-4:-2], .[1.5:3.5]' + input: '"12345"' + expected: | + "45" + "123" + "34" + "23" + "234" + +- name: string indexing not number error + args: + - '.[null]' + input: '""' + error: | + expected a number for indexing a string but got: null + +- name: string slicing start error + args: + - '.[[]:]' + input: '""' + error: | + expected a number for indexing a string but got: array ([]) + +- name: string slicing end error + args: + - '.[:{}]' + input: '""' + error: | + expected a number for indexing a string but got: object ({}) + +- name: array construction + args: + - -c + - '.,[3,[[2]]],[1],[[.],3]|[.,.]' + input: 'null' + expected: | + [null,null] + [[3,[[2]]],[3,[[2]]]] + [[1],[1]] + [[[null],3],[[null],3]] + +- name: array iterator + args: + - '.foo | .[] | .' + input: '{"foo": [1,2,{"bar":[]},[3,4,5]]}' + expected: | + 1 + 2 + { + "bar": [] + } + [ + 3, + 4, + 5 + ] + +- name: iterator with optional operator + args: + - '.[]?' + input: '0' + +- name: iterator with optional operator after indexing error + args: + - '.x[]?' + input: '0' + error: | + expected an object but got: number (0) + +- name: nested iterator + args: + - '.[][]' + input: '[[10]]' + expected: | + 10 + +- name: nested iterator error + args: + - '.[][]' + input: '[10]' + error: | + cannot iterate over: number (10) + +- name: nested iterator with optional operator + args: + - '.[][]?' + input: '[10]' + +- name: object iterator + args: + - '.[]' + input: '{"foo": 128}' + expected: | + 128 + +- name: object optional indexing after iterator + args: + - '[.[].name?]' + input: '[ 1, { "name": 2 }, 3, { "name": 4 } ]' + expected: | + [ + 2, + 4 + ] + +- name: pipe + args: + - '.foo | .bar' + input: '{"foo": {"bar": {"baz": 128}}}' + expected: | + { + "baz": 128 + } + +- name: null value + args: + - '.[] | null' + input: '["a", 1, [], {}]' + expected: | + null + null + null + null + +- name: boolean values + args: + - '.[] | true, false' + input: '["a", 1]' + expected: | + true + false + true + false + +- name: empty function + args: + - '.[] | empty, empty, . + [empty,.][], [empty,.][] / 1, empty < ., . and empty, empty or .' + input: '[0, 1]' + expected: | + 0 + 0 + 2 + 1 + +- name: empty function in array construction + args: + - '[empty], [0, empty, 1]' + input: 'null' + expected: | + [] + [ + 0, + 1 + ] + +- name: empty function in function declaration + args: + - 'def f: 1,empty,empty,2; f,f' + input: '[0, 1]' + expected: | + 1 + 2 + 1 + 2 + +- name: unary operators + args: + - -c + - 'map(-.[]), map(+.[])' + input: '[[1,2,3,4722366482869645213696]]' + expected: | + [-1,-2,-3,-4722366482869645213696] + [1,2,3,4722366482869645213696] + +- name: unary operator against string + args: + - '(-.)' + input: '"abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde"' + error: | + cannot negate: string ("abcdeabcdeabcdeabcdeabcd ...") + +- name: unary operator with variable binding + args: + - '[-1 as $x | 1, $x]' + input: 'null' + expected: | + [ + 1, + -1 + ] + +- name: unary operator with type function + args: + - '., -. | type' + input: '-100' + expected: | + "number" + "number" + +- name: arithmetic operator with variable binding + args: + - '1 + 2 as $x | -$x' + input: 'null' + expected: | + -3 + +- name: alternative operator with variable binding + args: + - 'true // 1 as $x | [$x]' + input: 'null' + expected: | + [ + true + ] + +- name: object construction + args: + - -c + - '{ foo: .foo.bar, "bar": .foo, "": false }' + input: '{"foo":{"bar":["a","b"]}}' + expected: | + {"":false,"bar":{"bar":["a","b"]},"foo":["a","b"]} + +- name: object construction shortcuts + args: + - '{foo,"bar",qux,quxx:.foo}' + input: '{"foo": 1, "bar": 2, "baz": 3}' + expected: | + { + "bar": 2, + "foo": 1, + "qux": null, + "quxx": 1 + } + +- name: object construction with trailing comma + args: + - '{foo,bar,}' + input: '{"foo": 1, "bar": 2, "baz": 3}' + expected: | + { + "bar": 2, + "foo": 1 + } + +- name: object construction by keywords + args: + - '{as, and, null, module}' + input: '{"and": 1, "as": 2, "module": 3, "null": 4}' + expected: | + { + "and": 1, + "as": 2, + "module": 3, + "null": 4 + } + +- name: object construction with duplicate keys + args: + - -c + - '{foo: 1, foo: 2, bar: 3}, {foo, bar, foo: length}' + input: '{"foo": 1, "bar": 2, "baz": 3}' + expected: | + {"bar":3,"foo":2} + {"bar":2,"foo":3} + +- name: object construction with pipe in value + args: + - '{"bar": {"baz":.} | select(.baz)}' + input: '{"foo":100}' + expected: | + { + "bar": { + "baz": { + "foo": 100 + } + } + } + +- name: object construction with unary operator + args: + - '{x:-1}, {x:-.}, {x:-.|abs}' + input: '1' + expected: | + { + "x": -1 + } + { + "x": -1 + } + { + "x": 1 + } + +- name: object construction with binary operators + args: + - '{x: 1 + 2, y: false or true, z: null // 3}' + input: 'null' + expected: | + { + "x": 3, + "y": true, + "z": 3 + } + +- name: object construction with condition + args: + - '{x: if true then -. end}' + input: '1' + expected: | + { + "x": -1 + } + +- name: empty object + args: + - '{}' + input: '{"foo":{"bar":["a","b"]}}' + expected: | + {} + +- name: unary operators in object values + args: + - '{ x: -128, y: +128 }' + input: '0' + expected: | + { + "x": -128, + "y": 128 + } + +- name: iterator in object values + args: + - -c + - '{ foo: .foo[], bar: .bar[] }' + input: '{"foo":[1,2],"bar":["a","b"]}' + expected: | + {"bar":"a","foo":1} + {"bar":"b","foo":1} + {"bar":"a","foo":2} + {"bar":"b","foo":2} + +- name: iterator in object key + args: + - -c + - '{ (.[]): .. }' + input: '["a", "b"]' + expected: | + {"a":["a","b"]} + {"a":"a"} + {"a":"b"} + {"b":["a","b"]} + {"b":"a"} + {"b":"b"} + +- name: binary operators in object key and value + args: + - -c + - '{ (.[] + .[]): (.[] + .[]) }' + input: '["foo", "bar"]' + expected: | + {"foofoo":"foofoo"} + {"foofoo":"barfoo"} + {"foofoo":"foobar"} + {"foofoo":"barbar"} + {"barfoo":"foofoo"} + {"barfoo":"barfoo"} + {"barfoo":"foobar"} + {"barfoo":"barbar"} + {"foobar":"foofoo"} + {"foobar":"barfoo"} + {"foobar":"foobar"} + {"foobar":"barbar"} + {"barbar":"foofoo"} + {"barbar":"barfoo"} + {"barbar":"foobar"} + {"barbar":"barbar"} + +- name: error in object key + args: + - -c + - '{ (.[]): .. }' + input: '["a", 1]' + expected: | + {"a":["a",1]} + {"a":"a"} + {"a":1} + error: | + expected a string for object key but got: number (1) + +- name: error in object value + args: + - -c + - '.[] | { (.k): .a.b }' + input: '[{"a":{"b":10},"k":"a"}, {"a":10,"k":"b"}]' + expected: | + {"a":10} + error: | + expected an object but got: number (10) + +- name: variable in object keys + args: + - -c + - '. as [$x, $y, $z] | {$x: 1, $y: 2, $z: 3}' + input: | + ["a","b","c"] + expected: | + {"a":1,"b":2,"c":3} + +- name: pipe in object key + args: + - '{ (.foo|.bar): .foo.bar }' + input: '{"foo":{"bar":"baz"}}' + expected: | + { + "baz": "baz" + } + +- name: number in object key + args: + - '{ (.foo): .foo }' + input: '{"foo":10}' + error: | + expected a string for object key but got: number (10) + +- name: null in object key + args: + - '{ (.foo): .foo }' + input: '{}' + error: | + expected a string for object key but got: null + +- name: keyword in object keys + args: + - -c + - '{if:0,and:1,or:2,then:3,else:4,elif:5,end:6,as:7,def:8,reduce:9,foreach:10,try:11,catch:12,label:13,import:14,include:15,module:16}' + input: 'null' + expected: | + {"and":1,"as":7,"catch":12,"def":8,"elif":5,"else":4,"end":6,"foreach":10,"if":0,"import":14,"include":15,"label":13,"module":16,"or":2,"reduce":9,"then":3,"try":11} + +- name: array + args: + - '[.foo, ., false]' + input: '{"foo": {"bar": 128}}' + expected: | + [ + { + "bar": 128 + }, + { + "foo": { + "bar": 128 + } + }, + false + ] + +- name: empty array + args: + - '[]' + input: '{"foo": {"bar": 128}}' + expected: | + [] + +- name: pipe in array + args: + - '[.foo|.bar]' + input: '{"foo": {"bar": 128}}' + expected: | + [ + 128 + ] + +- name: iterator in array + args: + - '[.foo|.bar[][]]' + input: '{"foo": {"bar": [[1],[2],[3]]}}' + expected: | + [ + 1, + 2, + 3 + ] + +- name: error after iterator + args: + - '[ .[] | .foo ]' + input: '[ [1, 2, 3], 4, 5]' + error: | + expected an object but got: array ([1,2,3]) + +- name: function after iterator error + args: + - '.[] | .foo | null' + input: '[1, 2, 3]' + error: | + expected an object but got: number (1) + +- name: multiple iterators in array + args: + - -c + - '[.[],..,.[]]' + input: '[1, 2, 3]' + expected: | + [1,2,3,[1,2,3],1,2,3,1,2,3] + +- name: recurse + args: + - -c + - '..' + input: '{"foo":{"bar":["a","b"]}}' + expected: | + {"foo":{"bar":["a","b"]}} + {"bar":["a","b"]} + ["a","b"] + "a" + "b" + +- name: recurse after iterator + args: + - '.[] | ..' + input: '{"foo":[{"bar":128}]}' + expected: | + [ + { + "bar": 128 + } + ] + { + "bar": 128 + } + 128 + +- name: recurse and pipe + args: + - '.. | .foo?' + input: '{"foo":{"bar":["a","b"]}}' + expected: | + { + "bar": [ + "a", + "b" + ] + } + null + +- name: recurse in object value + args: + - -c + - '{ foo: .. }' + input: '{"foo":[1,2,3]}' + expected: | + {"foo":{"foo":[1,2,3]}} + {"foo":[1,2,3]} + {"foo":1} + {"foo":2} + {"foo":3} + +- name: abs function + args: + - 'abs' + input: | + 0 + 42 + -42 + 0.0 + 3.14 + -3.14 + 4722366482869645213696 + -4722366482869645213696 + expected: | + 0 + 42 + 42 + 0.0 + 3.14 + 3.14 + 4722366482869645213696 + 4722366482869645213696 + +- name: abs function with minimum integer + args: + - '(-9223372036854775807 - 1) | abs' + input: 'null' + expected: | + 9223372036854775808 + +- name: length function + args: + - 'length' + input: | + 42 + -42 + {"a":1,"b":2} + [3,4,5] + "Hello, world" + "12345" + null + -4722366482869645213696 + expected: | + 42 + 42 + 2 + 3 + 12 + 5 + 0 + 4722366482869645213696 + +- name: length function with minimum integer + args: + - '(-9223372036854775807 - 1) | length' + input: 'null' + expected: | + 9223372036854775808 + +- name: length function error + args: + - 'length' + input: 'false' + error: | + length cannot be applied to: boolean (false) + +- name: keys function + args: + - 'keys' + input: | + {"a":1,"b":2} + [3,4,5] + expected: | + [ + "a", + "b" + ] + [ + 0, + 1, + 2 + ] + +- name: utf8bytelength function + args: + - 'utf8bytelength' + input: '"" "☆12345☆"' + expected: | + 0 + 21 + +- name: has function on objects + args: + - 'has(.c[])' + input: | + {"a":{"b":1},"c":["a","b"]} + {"c":["b","c"]} + expected: | + true + false + false + true + +- name: has function on arrays + args: + - 'has(2, nan, infinite, -infinite)' + input: | + [1, 2, 3] + [1, 3] + expected: | + true + false + false + false + false + false + false + false + +- name: has function against null + args: + - 'has(2, nan, infinite, -infinite)' + input: 'null' + expected: | + false + false + false + false + +- name: has/0 is not defined + args: + - 'has' + input: '[0]' + error: | + function not defined: has/0 + exit_code: 3 + +- name: has/2 is not defined + args: + - 'has(1; 2)' + input: '[0]' + error: | + function not defined: has/2 + exit_code: 3 + +- name: has function type error + args: + - 'has(0)' + input: '{}' + error: | + has(0) cannot be applied to: object ({}) + +- name: in function for object + args: + - 'in({"foo": 42})' + input: '"foo" "bar"' + expected: | + true + false + +- name: in function for array + args: + - 'in([3, 4])' + input: '-1 0 1 2' + expected: | + false + true + true + false + +- name: in function for null + args: + - 'in(null)' + input: '0' + expected: | + false + +- name: in function type error + args: + - 'in([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])' + input: '"x"' + error: | + has("x") cannot be applied to: array ([0,0,0,0,0,0,0,0,0,0,0,0, ...]) + +- name: map function + args: + - -c + - 'map(..)' + input: '[{"a":{"b":1}}, [2,3,4]]' + expected: | + [{"a":{"b":1}},{"b":1},1,[2,3,4],2,3,4] + +- name: to_entries function + args: + - -c + - 'to_entries' + input: | + [10, 20] + {"a": 10, "b": []} + expected: | + [{"key":0,"value":10},{"key":1,"value":20}] + [{"key":"a","value":10},{"key":"b","value":[]}] + +- name: from_entries function + args: + - -c + - 'from_entries' + input: | + [] + [ + {"key":"a","Key":"b","value":1}, + {"Key":"b","Value":2}, + {"key":null,"name":"c","Name":"d","value":3}, + {"name":false,"Name":"d","value":null,"Value":4}, + {"key":"e"} + ] + expected: | + {} + {"a":1,"b":2,"c":3,"d":null,"e":null} + +- name: from_entries function missing key + args: + - 'from_entries' + input: '[{"value":1}]' + error: | + expected a string for object key but got: null + +- name: from_entries function key type error + args: + - 'from_entries' + input: '[{"key":{},"value":1}]' + error: | + expected a string for object key but got: object ({}) + +- name: with_entries function + args: + - -c + - 'with_entries(.key |= "x" + . | .value += 1)' + input: '{"a": 1, "b": 2}' + expected: | + {"xa":2,"xb":3} + +- name: add/0 function + args: + - -c + - 'add' + input: | + [] + [1,2,3] + ["a","b","c"] + [[3],[4,5],[6]] + [{"a":1}, {"b":2}, {"a":3}] + {} + {"a":1,"b":9} + {"a":"a","b":"b","c":"c"} + {"a":[1],"b":[2],"c":[3],"d":[4]} + expected: | + null + 6 + "abc" + [3,4,5,6] + {"a":3,"b":2} + null + 10 + "abc" + [1,2,3,4] + +- name: add/0 function with a variable + args: + - '. as $x | [$x, {y:1}] | add, $x' + input: '{"x":0}' + expected: | + { + "x": 0, + "y": 1 + } + { + "x": 0 + } + +- name: add/0 function with slicing + args: + - -c + - '[.[:2], [0], .[1:]] | add' + input: '[1,2,3]' + expected: | + [1,2,0,2,3] + +- name: add/0 function error + args: + - 'add' + input: '[{}, [], {}]' + error: | + cannot add: object ({}) and array ([]) + +- name: add/0 function error + args: + - 'add' + input: '["x", null, "y", {}, "z"]' + error: | + cannot add: string ("xy") and object ({}) + +- name: add/1 function + args: + - 'add(.), add(empty), add(1,2,3), add(range(10))' + input: 'null' + expected: | + null + null + 6 + 45 + +- name: add/1 function with iterator + args: + - 'add(.[])' + input: | + [1,2,3] + ["a","b","c"] + expected: | + 6 + "abc" + +- name: add/1 function with update operator + args: + - -c + - '.y = add(.x[])' + input: | + {"x":[1,2,3]} + {"x":["a","b","c"]} + expected: | + {"x":[1,2,3],"y":6} + {"x":["a","b","c"],"y":"abc"} + +- name: flatten/0 function + args: + - -c + - 'flatten' + input: | + [] + [0, [[[[[1]]]]]] + [0, [1, [2]], [1, [[3], 2]]] + {"x": [1], "y": [[2]], "z": [[[3]]]} + expected: | + [] + [0,1] + [0,1,2,1,3,2] + [1,2,3] + +- name: flatten/0 function error + args: + - 'flatten, flatten("")' + input: '0 [[]]' + expected: | + [] + error: | + flatten cannot be applied to: number (0) + flatten cannot be applied to: string ("") + +- name: flatten/1 function + args: + - -c + - 'flatten(0, 1.5, 2)' + input: | + [] + [0, [[[[[1]]]]]] + [0, [1, [2]], [1, [[3], 2]]] + {"x": [1], "y": [[2]], "z": [[[3]]]} + expected: | + [] + [] + [] + [0,[[[[[1]]]]]] + [0,1] + [0,[[[1]]]] + [0,[1,[2]],[1,[[3],2]]] + [0,1,2,1,3,2] + [0,1,2,1,[3],2] + [[1],[[2]],[[[3]]]] + [1,2,3] + [1,2,[3]] + +- name: flatten/1 function depth error + args: + - 'flatten(-1)' + input: '[]' + error: | + flatten depth should not be negative: -1 + +- name: flatten/1 function depth error with nan + args: + - 'flatten(nan)' + input: '[]' + error: | + flatten depth should not be negative: null + +- name: min, min_by, max, max_by functions + args: + - -c + - '[min, min_by(.[1]), min_by(.[2]), max, max_by(.[1]), max_by(.[2])]' + input: | + [] + [[4,2,"a"],[3,1,"a"],[2,4,"a"],[1,3,"a"]] + expected: | + [null,null,null,null,null,null] + [[1,3,"a"],[3,1,"a"],[4,2,"a"],[4,2,"a"],[2,4,"a"],[1,3,"a"]] + +- name: sort, sort_by, group_by functions + args: + - -c + - 'sort, sort_by(.a), sort_by(.b), sort_by(.b, .c), group_by(.b), group_by(.c, .a), group_by(.a + .b - .c == 2)' + input: '[{"a": 1, "b": 4, "c": 5}, {"a": 4, "b": 1, "c": 2}, {"a": 1, "b": 4, "c": 3}, {"a": 3, "b": 2, "c": 1}]' + expected: | + [{"a":1,"b":4,"c":3},{"a":1,"b":4,"c":5},{"a":3,"b":2,"c":1},{"a":4,"b":1,"c":2}] + [{"a":1,"b":4,"c":5},{"a":1,"b":4,"c":3},{"a":3,"b":2,"c":1},{"a":4,"b":1,"c":2}] + [{"a":4,"b":1,"c":2},{"a":3,"b":2,"c":1},{"a":1,"b":4,"c":5},{"a":1,"b":4,"c":3}] + [{"a":4,"b":1,"c":2},{"a":3,"b":2,"c":1},{"a":1,"b":4,"c":3},{"a":1,"b":4,"c":5}] + [[{"a":4,"b":1,"c":2}],[{"a":3,"b":2,"c":1}],[{"a":1,"b":4,"c":5},{"a":1,"b":4,"c":3}]] + [[{"a":3,"b":2,"c":1}],[{"a":4,"b":1,"c":2}],[{"a":1,"b":4,"c":3}],[{"a":1,"b":4,"c":5}]] + [[{"a":1,"b":4,"c":5},{"a":4,"b":1,"c":2},{"a":3,"b":2,"c":1}],[{"a":1,"b":4,"c":3}]] + +- name: unique, unique_by functions + args: + - -c + - 'unique, unique_by(length)' + input: | + [7,3,5,1,4,2,5,3,6,1,7,4,3] + ["cat", "dog", "hello", "world", "banana", "apple"] + expected: | + [1,2,3,4,5,6,7] + [1,2,3,4,5,6,7] + ["apple","banana","cat","dog","hello","world"] + ["cat","hello","banana"] + +- name: min, max, sort, unique functions error + args: + - 'try min catch ., try max catch ., try sort catch ., try unique catch .' + input: '0' + expected: | + "min cannot be applied to: number (0)" + "max cannot be applied to: number (0)" + "sort cannot be applied to: number (0)" + "unique cannot be applied to: number (0)" + +- name: arrays, objects, iterables, booleans, numbers, strings, nulls, values, scalars functions + args: + - -c + - 'map(arrays), map(objects), map(iterables), map(booleans), map(numbers), map(strings), map(nulls), map(values), map(scalars)' + input: '[null, true, false, 0, 1, "", "foo", [], [2, []], {}, { "foo": [] }, 4722366482869645213696]' + expected: | + [[],[2,[]]] + [{},{"foo":[]}] + [[],[2,[]],{},{"foo":[]}] + [true,false] + [0,1,4722366482869645213696] + ["","foo"] + [null] + [true,false,0,1,"","foo",[],[2,[]],{},{"foo":[]},4722366482869645213696] + [null,true,false,0,1,"","foo",4722366482869645213696] + +- name: recurse/0 function + args: + - -c + - 'recurse' + input: '{"a":{"b":[1]}}' + expected: | + {"a":{"b":[1]}} + {"b":[1]} + [1] + 1 + +- name: recurse/1 function + args: + - -c + - 'recurse(.a[])' + input: '{"a":[{"a": []}, {"a":[{"a":[]}]}]}' + expected: | + {"a":[{"a":[]},{"a":[{"a":[]}]}]} + {"a":[]} + {"a":[{"a":[]}]} + {"a":[]} + +- name: recurse/2 function + args: + - -c + - '[recurse(. + .; . < 1000)]' + input: '1' + expected: | + [1,2,4,8,16,32,64,128,256,512] + +- name: while function + args: + - -c + - '[while(.[0]<100; [.[1], .[0]+.[1]]) | .[0]]' + input: '[0, 1]' + expected: | + [0,1,1,2,3,5,8,13,21,34,55,89] + +- name: until function + args: + - '[.,1] | until(.[0]==0; [.[0]-1, .[0]*.[1]]) | .[1]' + input: '20' + expected: | + 2432902008176640000 + +- name: repeat function + args: + - -c + - '[limit(.; repeat(1,2,3))]' + input: '10' + expected: | + [1,2,3,1,2,3,1,2,3,1] + +- name: range function + args: + - -c + - '[range(.)], [range(range(.))], [range(3.5)], [range(3; 5)], [range(1.5; 4)], + [range(-5; -1.5)], [range(3.5; 5.5)], [range(-5.5; -3.5)], [range(0; 10; 3)], + [range(0; 10; -1)], [range(5; -5; -3)], [range(0.25; 9.5; 2.25)], [range(-5; -9.5; -1)]' + input: '5' + expected: | + [0,1,2,3,4] + [0,0,1,0,1,2,0,1,2,3] + [0,1,2,3] + [3,4] + [1.5,2.5,3.5] + [-5,-4,-3,-2] + [3.5,4.5] + [-5.5,-4.5] + [0,3,6,9] + [] + [5,2,-1,-4] + [0.25,2.5,4.75,7,9.25] + [-5,-6,-7,-8,-9] + +- name: range function overflow + args: + - 'range(9223372036854775700; 9223372036854775805; 10)' + input: 'null' + expected: | + 9223372036854775700 + 9223372036854775710 + 9223372036854775720 + 9223372036854775730 + 9223372036854775740 + 9223372036854775750 + 9223372036854775760 + 9223372036854775770 + 9223372036854775780 + 9223372036854775790 + 9223372036854775800 + +- name: range function with large numbers + args: + - 'range(0; 20000000000000000000; 2000000000000000000)' + input: 'null' + expected: | + 0 + 2000000000000000000 + 4000000000000000000 + 6000000000000000000 + 8000000000000000000 + 10000000000000000000 + 12000000000000000000 + 14000000000000000000 + 16000000000000000000 + 18000000000000000000 + +- name: range function with iterators in arguments + args: + - -c + - '[range(-.; .+1)] | [range(.[]; .[]; .[])] | recurse(.[26:];.[0])[:26] | map(.+3)' + input: '3' + expected: | + [0,0,0,0,1,0,0,0,1,2,0,2,0,0,1,2,3,0,2,0,3,0,1,2,3,4] + [0,2,4,0,3,0,1,2,3,4,5,0,2,4,0,3,1,1,1,1,1,1,1,2,1,1] + [1,2,3,1,3,1,1,2,3,4,1,3,1,4,1,2,3,4,5,1,3,5,1,4,2,2] + [2,1,2,2,2,2,2,2,2,3,2,2,2,3,4,2,4,2,2,3,4,5,2,4,2,5] + [3,3,1,3,2,1,3,3,3,2,3,3,3,3,3,3,3,4,3,3,3,4,5,3,5,3] + [4,1,4,2,4,3,2,1,4,4,2,4,3,2,4,4,4,3,4,4,4,4,4,4,4,5] + [4,4,5,2,5,3,1,5,4,3,2,1,5,2,5,3,5,4,3,2,5,5,3,5,4,3] + [5,5,5,4,5,5,5,5,5,5,6,3,6,4,2,6,5,4,3,2,1,6,3,6,4,2] + [6,5,4,3,2,6,3,6,4,6,5,4,3,6,6,4,6,5,4,6,6,6,5,6,6,6] + +- name: range function error + args: + - '(null, "", [], {} | try range(.) catch .), try range(""; 0) catch ., try range(0; 1; []) catch .' + input: 'null' + expected: | + "range cannot be applied to: null" + "range cannot be applied to: string (\"\")" + "range cannot be applied to: array ([])" + "range cannot be applied to: object ({})" + "range cannot be applied to: string (\"\")" + "range cannot be applied to: array ([])" + +- name: toboolean function + args: + - 'toboolean' + input: | + "true" + "false" + true + false + expected: | + true + false + true + false + +- name: toboolean function error + args: + - 'try toboolean catch .' + input: | + null + "" + " true " + "FALSE" + [] + {} + expected: | + "toboolean cannot be applied to: null" + "toboolean cannot be applied to \"\": invalid boolean" + "toboolean cannot be applied to \" true \": invalid boolean" + "toboolean cannot be applied to \"FALSE\": invalid boolean" + "toboolean cannot be applied to: array ([])" + "toboolean cannot be applied to: object ({})" + +- name: tonumber function + args: + - 'tonumber' + input: | + "0" + "12345" + "-43210" + "10000000000000000000" + "-10000000000000000000" + "+4.2" + "05" + "9." + "+.123" + "-.1" + "1e-2" + "2.e-2" + "1e300" + "-1e300" + "1e1000" + "-1e1000" + 3.14 + expected: | + 0 + 12345 + -43210 + 10000000000000000000 + -10000000000000000000 + 4.2 + 5 + 9 + 0.123 + -0.1 + 0.01 + 0.02 + 1e+300 + -1e+300 + 1.7976931348623157e+308 + -1.7976931348623157e+308 + 3.14 + +- name: tonumber function error + args: + - 'try tonumber catch .' + input: | + "" + "." + " " + "1a" + ".e1" + "-.e" + expected: | + "tonumber cannot be applied to \"\": invalid number" + "tonumber cannot be applied to \".\": invalid number" + "tonumber cannot be applied to \" \": invalid number" + "tonumber cannot be applied to \"1a\": invalid number" + "tonumber cannot be applied to \".e1\": invalid number" + "tonumber cannot be applied to \"-.e\": invalid number" + +- name: tostring function + args: + - 'tostring' + input: | + null + false + true + 3.14 + "foo bar" + {"foo": "-43210"} + [1,2,3] + ["<&><>"] + expected: | + "null" + "false" + "true" + "3.14" + "foo bar" + "{\"foo\":\"-43210\"}" + "[1,2,3]" + "[\"<&><>\"]" + +- name: type function + args: + - 'type' + input: | + null + false + true + 0 + 3.14 + 4722366482869645213696 + "foo" + [] + {} + expected: | + "null" + "boolean" + "boolean" + "number" + "number" + "number" + "string" + "array" + "object" + +- name: reverse function + args: + - 'reverse' + input: '[0, 1, 2, 3, 4]' + expected: | + [ + 4, + 3, + 2, + 1, + 0 + ] + +- name: contains function + args: + - -c + - '.[] as $x | (null,false,true,[],[3,4],[3,1],"foo","ll",{},{"a":1},{"a":""},{"b":[5]},{"a":3,"b":[5]}) as $y | $x | [$x,$y,contains($y)]?' + input: '[null,false,true,[],[1,2,3],"","hello",{"a":3,"b":[4,5,6]}]' + expected: | + [null,null,true] + [false,false,true] + [true,true,true] + [[],[],true] + [[],[3,4],false] + [[],[3,1],false] + [[1,2,3],[],true] + [[1,2,3],[3,4],false] + [[1,2,3],[3,1],true] + ["","foo",false] + ["","ll",false] + ["hello","foo",false] + ["hello","ll",true] + [{"a":3,"b":[4,5,6]},{},true] + [{"a":3,"b":[4,5,6]},{"a":1},false] + [{"a":3,"b":[4,5,6]},{"a":""},false] + [{"a":3,"b":[4,5,6]},{"b":[5]},true] + [{"a":3,"b":[4,5,6]},{"a":3,"b":[5]},true] + +- name: contains function against unicode strings + args: + - -c + - '. as $x | ("@", "\u0000@", "\u0000dc", "b\u0000c", "b\u0000cd", "b\u0000cd") as $y | $x | [$x,$y,contains($y)]' + input: '"ab\u0000cd"' + expected: | + ["ab\u0000cd","@",false] + ["ab\u0000cd","\u0000@",false] + ["ab\u0000cd","\u0000dc",false] + ["ab\u0000cd","b\u0000c",true] + ["ab\u0000cd","b\u0000cd",true] + ["ab\u0000cd","b\u0000cd",true] + +- name: contains function error + args: + - 'nan | contains([nan])' + input: '0' + error: | + contains([null]) cannot be applied to: number (null) + +- name: indices, index, rindex functions against null + args: + - 'indices(0), index(0), rindex(0)' + input: 'null' + expected: | + null + null + null + +- name: indices, index, rindex functions against array + args: + - -c + - '[indices(0, 1, [1, 2, 1])], [index(0, 1, [1, 2])], [rindex(0, 1, [1, 2])]' + input: | + [] + [0] + [0, 1, 2, 1, 2, 1, 0, 1, 0] + expected: | + [[],[],[]] + [null,null,null] + [null,null,null] + [[0],[],[]] + [0,null,null] + [0,null,null] + [[0,6,8],[1,3,5,7],[1,3]] + [0,1,1] + [8,7,3] + +- name: indices, index, rindex functions against string + args: + - -c + - '[indices("", ",", "x")], [index("", ",", "x")], [rindex("", ",", "x")]' + input: '"a,bc,def,ghij,klmno"' + expected: | + [[],[1,4,8,13],[]] + [null,1,null] + [null,13,null] + +- name: indices, index, rindex functions against multibyte string + args: + - -c + - '[indices("0", "1", "34")], [index("0", "1", "34")], [rindex("0", "1", "34")]' + input: '"012340"' + expected: | + [[0,5],[1],[3]] + [0,1,3] + [5,1,3] + +- name: indices, index, rindex functions type error + args: + - '(try indices([]) catch .), (try index([]) catch .), (try rindex([]) catch .)' + input: '{}' + expected: | + "indices([]) cannot be applied to: object ({})" + "index([]) cannot be applied to: object ({})" + "rindex([]) cannot be applied to: object ({})" + +- name: inside function + args: + - -c + - '.[] as $x | (false,"abc",[],["foobaz","foobar"],{},{"a":10,"b":20,"c":30}) as $y | $x | [$x,$y,inside($y)]?' + input: '["a",["bar","baz"],{"a":10,"b":20}]' + expected: | + ["a","abc",true] + [["bar","baz"],[],false] + [["bar","baz"],["foobaz","foobar"],true] + [{"a":10,"b":20},{},false] + [{"a":10,"b":20},{"a":10,"b":20,"c":30},true] + +- name: startswith function + args: + - 'startswith("foo")' + input: | + "" + "fo" + "foo" + "fooo" + "barfoo" + expected: | + false + false + true + true + false + +- name: startswith function error + args: + - '("x" | try startswith({}) catch .), (10 | try startswith("x") catch .)' + input: '0' + expected: | + "startswith({}) cannot be applied to: string (\"x\")" + "startswith(\"x\") cannot be applied to: number (10)" + +- name: endswith function + args: + - 'endswith("foo")' + input: | + "" + "fo" + "foo" + "fooo" + "barfoo" + expected: | + false + false + true + false + true + +- name: endswith function error + args: + - '("x" | try endswith({}) catch .), (10 | try endswith("x") catch .)' + input: '0' + expected: | + "endswith({}) cannot be applied to: string (\"x\")" + "endswith(\"x\") cannot be applied to: number (10)" + +- name: combinations/0 function + args: + - -c + - '[combinations]' + input: '[] [[0]] [[1, 2, 3], [4, 5, 6]]' + expected: | + [[]] + [[0]] + [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] + +- name: combinations/1 function + args: + - -c + - '[combinations(3)]' + input: '[] [0] [1, 2]' + expected: | + [] + [[0,0,0]] + [[1,1,1],[1,1,2],[1,2,1],[1,2,2],[2,1,1],[2,1,2],[2,2,1],[2,2,2]] + +- name: ltrimstr function + args: + - 'ltrimstr("foo")' + input: | + "" + "fo" + "foo" + "fooo" + "barfoo" + expected: | + "" + "fo" + "" + "o" + "barfoo" + +- name: ltrimstr function error + args: + - '("x" | try ltrimstr({}) catch .), (10 | try ltrimstr("x") catch .)' + input: '0' + expected: | + "ltrimstr({}) cannot be applied to: string (\"x\")" + "ltrimstr(\"x\") cannot be applied to: number (10)" + +- name: rtrimstr function + args: + - 'rtrimstr("foo")' + input: | + "" + "fo" + "foo" + "fooo" + "barfoo" + expected: | + "" + "fo" + "" + "fooo" + "bar" + +- name: rtrimstr function error + args: + - '("x" | try rtrimstr({}) catch .), (10 | try rtrimstr("x") catch .)' + input: '0' + expected: | + "rtrimstr({}) cannot be applied to: string (\"x\")" + "rtrimstr(\"x\") cannot be applied to: number (10)" + +- name: trimstr function + args: + - 'trimstr("foo")' + input: | + "" + "fo" + "foo" + "fofoo" + "foobarfoo" + "foofoofoo" + expected: | + "" + "fo" + "" + "fo" + "bar" + "foo" + +- name: trimstr function error + args: + - '("x" | try trimstr({}) catch .), (10 | try trimstr("x") catch .)' + input: '0' + expected: | + "trimstr({}) cannot be applied to: string (\"x\")" + "trimstr(\"x\") cannot be applied to: number (10)" + +- name: ltrim function + args: + - 'ltrim' + input: | + "" + " \tfoo\n " + "\t\u000b\u000c\u0085\u00a0f\ro\no\u2000\u2001\u200a\u3000" + expected: "\"\"\n\"foo\\n \"\n\"f\\ro\\no\u2000\u2001\u200a\u3000\"\n" + +- name: ltrim function error + args: + - 'try ltrim catch .' + input: '0 [] {}' + expected: | + "ltrim cannot be applied to: number (0)" + "ltrim cannot be applied to: array ([])" + "ltrim cannot be applied to: object ({})" + +- name: rtrim function + args: + - 'rtrim' + input: | + "" + " \tfoo\n " + "\t\u000b\u000c\u0085\u00a0f\ro\no\u2000\u2001\u200a\u3000" + expected: "\"\"\n\" \\tfoo\"\n\"\\t\\u000b\\f\u0085\u00a0f\\ro\\no\"\n" + +- name: rtrim function error + args: + - 'try rtrim catch .' + input: '0 [] {}' + expected: | + "rtrim cannot be applied to: number (0)" + "rtrim cannot be applied to: array ([])" + "rtrim cannot be applied to: object ({})" + +- name: trim function + args: + - 'trim' + input: | + "" + " \tfoo\n " + "\t\u000b\u000c\u0085\u00a0f\ro\no\u2000\u2001\u200a\u3000" + expected: | + "" + "foo" + "f\ro\no" + +- name: trim function error + args: + - 'try trim catch .' + input: '0 [] {}' + expected: | + "trim cannot be applied to: number (0)" + "trim cannot be applied to: array ([])" + "trim cannot be applied to: object ({})" + +- name: explode function + args: + - -c + - 'explode' + input: | + "" + "foo bar" + "12345" + "\n\t" + expected: | + [] + [102,111,111,32,98,97,114] + [65297,65298,65299,65300,65301] + [10,9] + +- name: implode function + args: + - 'implode' + input: | + [] + [102,111,111,32,98,97,114] + [65297,65298,65299,65300,65301] + [10,9] + expected: | + "" + "foo bar" + "12345" + "\n\t" + +- name: implode function replacement character + args: + - 'implode' + input: '[-1,0,1114111,1114112]' + expected: "\"\ufffd\\u0000\U0010ffff\ufffd\"\n" + +- name: implode function error + args: + - 'try implode catch .' + input: '[[]] [{}]' + expected: | + "implode cannot be applied to: array ([[]])" + "implode cannot be applied to: array ([{}])" + +- name: split/1 function + args: + - 'split(", ","") | join("/")' + input: | + "a,b, c, d, e,f" + ", a,b, c, d, e,f, " + expected: | + "a,b/c/d/e,f" + "a/,/b/,/ /c/,/ /d/,/ /e/,/f" + "/a,b/c/d/e,f/" + ",/ /a/,/b/,/ /c/,/ /d/,/ /e/,/f/,/ " + +- name: split/1 function against empty string + args: + - -c + - '.[] as $t | .[] as $s | $s | split($t)' + input: '["abc", "a", "b", ""]' + expected: | + ["",""] + ["a"] + ["b"] + [] + ["","bc"] + ["",""] + ["b"] + [] + ["a","c"] + ["a"] + ["",""] + [] + ["a","b","c"] + ["a"] + ["b"] + [] + +- name: join function + args: + - 'join(",")' + input: '[null, false, true, 0, 1, "", "a", "b", "abc"]' + expected: | + ",false,true,0,1,,a,b,abc" + +- name: join function with iterator argument + args: + - '. / "" | join(.[])' + input: '"abcde"' + expected: | + "aabacadae" + "abbbcbdbe" + "acbcccdce" + "adbdcddde" + "aebecedee" + +- name: join object + args: + - 'join(",")' + input: '{"a": null, "b": false, "c": true, "d": 0, "e": 1, "f": "", "g": "a", "h": "b", "i": "abc"}' + expected: | + ",false,true,0,1,,a,b,abc" + +- name: join function error item + args: + - 'try join(",") catch .' + input: '["1","2",[3,4,5]] [[]] [{}]' + expected: | + "cannot add: string (\"1,2,\") and array ([3,4,5])" + "cannot add: string (\"\") and array ([])" + "cannot add: string (\"\") and object ({})" + +- name: join function error input + args: + - '.[] | try join(",") catch .' + input: '[null, false, true, 1, "a"]' + expected: | + "join(\",\") cannot be applied to: null" + "join(\",\") cannot be applied to: boolean (false)" + "join(\",\") cannot be applied to: boolean (true)" + "join(\",\") cannot be applied to: number (1)" + "join(\",\") cannot be applied to: string (\"a\")" + +- name: join function null separator + args: + - 'join(null)' + input: '[] ["a"] ["a","b","c"] ["a",null,"b"] [null,null,null] [null]' + expected: | + "" + "a" + "abc" + "ab" + "" + "" + +- name: join function null elements + args: + - 'join(",")' + input: '[null] [null,null,null] ["a",null,"b"]' + expected: | + "" + ",," + "a,,b" + +- name: join function non-string separator + args: + - '.[] | try join(1) catch .' + input: '[[],[1],[1,2]]' + expected: | + "" + "1" + "cannot add: string (\"1\") and number (1)" + +- name: ascii_downcase function + args: + - 'ascii_downcase' + input: '"@ABC XYZ[] `abc xyz{} Αα"' + expected: | + "@abc xyz[] `abc xyz{} Αα" + +- name: ascii_upcase function + args: + - 'ascii_upcase' + input: '"@ABC XYZ[] `abc xyz{} Αα"' + expected: | + "@ABC XYZ[] `ABC XYZ{} Αα" + +- name: walk function + args: + - -c + - 'walk(if type == "boolean" then not else . + . end) | .[]' + input: '[1, ["a", {"b": []}], {"c": [null, false, true]}]' + expected: | + 2 + ["aa",{"b":[]},"aa",{"b":[]}] + {"c":[null,true,false,null,true,false]} + 2 + ["aa",{"b":[]},"aa",{"b":[]}] + {"c":[null,true,false,null,true,false]} + +- name: walk function with multiple values + args: + - -c + - 'walk(.,0)' + input: '[1,2,3] {"x":1,"y":2,"z":3}' + expected: | + [1,0,2,0,3,0] + 0 + {"x":1,"y":2,"z":3} + 0 + +- name: transpose function + args: + - -c + - 'transpose' + input: | + [] + [[1,2,3,[]],[4,"foo",{}],[5,6]] + expected: | + [] + [[1,4,5],[2,"foo",6],[3,{},null],[[],null,null]] + +- name: function not defined + args: + - 'abc' + input: '{}' + error: | + function not defined: abc/0 + exit_code: 3 + +- name: variable not defined + args: + - '$abc' + input: '{}' + error: | + variable not defined: $abc + exit_code: 3 + +- name: argument count error + args: + - 'map(.;.)' + input: '{}' + error: | + function not defined: map/2 + exit_code: 3 + +- name: function declaration with an argument + args: + - 'def f(g): g | g; f(..)' + input: '[0, 1]' + expected: | + [ + 0, + 1 + ] + 0 + 1 + 0 + 1 + +- name: function nested declaration + args: + - -c + - 'def f: def g: .; g; f' + input: '[0, 1]' + expected: | + [0,1] + +- name: function declaration with call of another declared function + args: + - -c + - 'def f: .,.; def g: [f,f]; f,g' + input: '[0, 1]' + expected: | + [0,1] + [0,1] + [[0,1],[0,1],[0,1],[0,1]] + +- name: function declaration with update operator + args: + - -c + - 'def f: def f($x): .; f(.x) | .x; f += f' + input: '{"x": 1}' + expected: | + {"x":2} + +- name: function declaration and recursive call + args: + - 'def f(g): if type == "array" then map(f(g * 2)) else g end; f(.)' + input: '[[[1,2]]]' + expected: | + [ + [ + [ + 8, + 16 + ] + ] + ] + +- name: function declaration and recursive call + args: + - 'def f(g): if . < 1000 then .,(g|f(g|g)) else g end; f(.*2)' + input: '1' + expected: | + 1 + 2 + 8 + 128 + 2147483648 + +- name: function declaration and recursive call + args: + - 'def f: . as $x | $x, ($x + 1 | f); last(limit(100000; f))' + input: '0' + expected: | + 99999 + +- name: function declaration and recursive call + args: + - 'def f: . as $x | $x, (if $x < 3 then $x + 1 | f else empty end), $x; f' + input: '0' + expected: | + 0 + 1 + 2 + 3 + 3 + 2 + 1 + 0 + +- name: function declaration and recursive call + args: + - 'def f: ., if . < 2 then . + (1,2) | f else empty end; f' + input: '0' + expected: | + 0 + 1 + 2 + 3 + 2 + +- name: function declaration and recursive call + args: + - '100000 as $x | def f: if . == $x then . else . + 1 | f end; f' + input: '0' + expected: | + 100000 + +- name: function declaration and recursive call + args: + - 'def f: if .a then .a | f end; {}, {a:{}} | f | .,0' + input: '0' + expected: | + {} + 0 + {} + 0 + +- name: function declaration with duplicate function names + args: + - 'def f(g): def f: def g: 3; g * 2; g * f; def f: . * 5; f(f)' + input: '1' + expected: | + 30 + +- name: function declaration with duplicate function names + args: + - '(def f(g): def g: 1; g; f(2)), (def g: 3; def f(g): g; f(4))' + input: '0' + expected: | + 1 + 4 + +- name: function declaration with duplicate argument names + args: + - 'def f(g;g): g; f(1;2)' + input: 'null' + expected: | + 2 + +- name: function declaration with duplicate variable names + args: + - 'def f($g;$g): $g; f(1;2,3)' + input: 'null' + expected: | + 2 + 3 + +- name: function declaration with builtin function name conflict + args: + - 'def empty: . % 2; range(5) | select(empty > 0)' + input: 'null' + expected: | + 1 + 3 + +- name: function declaration with builtin function name conflict + args: + - 'def select(f): f; null, 0 | numbers, select(.)' + input: 'null' + expected: | + null + 0 + 0 + +- name: function declaration with argument name conflict + args: + - 'def update: .[1:]; [range(3)] | while(length>0; update)' + input: 'null' + expected: | + [ + 0, + 1, + 2 + ] + [ + 1, + 2 + ] + [ + 2 + ] + +- name: function declaration with argument name conflict + args: + - 'def f($keys): with_entries(.key *= $keys); {x: 1} | f(3)' + input: 'null' + expected: | + { + "xxx": 1 + } + +- name: function declaration with mixed argument types + args: + - -c + - 'def f0( a; b; c): a*100 + b*10 + c; + def f1( a;$b; c): a*100 + $b*10 + c; + def f2($a; b;$c): $a*100 + b*10 + $c; + def f3($a;$b;$c): $a*100 + $b*10 + $c; + [f0(.[];.[];.[])], [f1(.[];.[];.[])], [f2(.[];.[];.[])], [f3(.[];.[];.[])]' + input: '[1,2]' + expected: | + [111,211,121,221,112,212,122,222] + [111,211,112,212,121,221,122,222] + [111,121,112,122,211,221,212,222] + [111,112,121,122,211,212,221,222] + +- name: function declaration with function reference to value argument + args: + - -c + - 'def f($x; $y): [$x, x, $y, y]; f(1,2; 3,4)' + input: 'null' + expected: | + [1,1,2,3,3,4] + [1,1,2,4,3,4] + [2,1,2,3,3,4] + [2,1,2,4,3,4] + +- name: function declaration inside query + args: + - -c + - '[0, def f: 1, def g: 2; g, 3 as $x | def h: $x + 1; h * g; f, f + 1, 5]' + input: 'null' + expected: | + [0,1,2,8,2,3,9,5] + +- name: argument count error for custom function + args: + - 'def f(g): g | g; f' + input: '{}' + error: | + function not defined: f/0 + exit_code: 3 + +- name: function declaration name error + args: + - 'def $f: .; $f' + input: 'null' + error: | + invalid query: def $f: .; $f + def $f: .; $f + ^ unexpected token "$f" + exit_code: 3 + +- name: function declaration without query + args: + - 'def f: 0;' + error: | + compile error: missing query (try ".") + exit_code: 3 + +- name: add, subtract, multiply, divide, modulo numbers + args: + - -c + - '[.[] + .[]], [.[] - .[]], [.[] * .[]], [.[] / .[]], [.[] % .[]]' + input: '[-1, 5, 16]' + expected: | + [-2,4,15,4,10,21,15,21,32] + [0,6,17,-6,0,11,-17,-11,0] + [1,-5,-16,-5,25,80,-16,80,256] + [1,-5,-16,-0.2,1,3.2,-0.0625,0.3125,1] + [0,0,0,-1,0,1,-1,5,0] + +- name: add, subtract, multiply, divide, modulo large numbers + args: + - -c + - '[.[] + .[]], [.[] - .[]], [.[] * .[]], [.[] / .[]], [.[] % .[]]' + input: '[-1, 47, 9223372036854775807, 557702036893939360447070208]' + expected: | + [-2,46,9223372036854775806,557702036893939360447070207,46,94,9223372036854775854,557702036893939360447070255,9223372036854775806,9223372036854775854,18446744073709551614,557702046117311397301846015,557702036893939360447070207,557702036893939360447070255,557702046117311397301846015,1115404073787878720894140416] + [0,48,9223372036854775808,557702036893939360447070209,-48,0,9223372036854775760,557702036893939360447070161,-9223372036854775808,-9223372036854775760,0,557702027670567323592294401,-557702036893939360447070209,-557702036893939360447070161,-557702027670567323592294401,0] + [1,-47,-9223372036854775807,-557702036893939360447070208,-47,2209,433498485732174462929,26211995734015149941012299776,-9223372036854775807,433498485732174462929,85070591730234615847396907784232501249,5143893371984510803678792604831111805828857856,-557702036893939360447070208,26211995734015149941012299776,5143893371984510803678792604831111805828857856,311031561955648899562865430629603887429045530881163264] + [1,-47,-9223372036854775807,-557702036893939360447070208,-0.02127659574468085,1,196241958230952670,1.1866000784977433e+25,-1.0842021724855044e-19,5.095750210681871e-18,1,60466176,-1.7930721672981344e-27,8.427439186301232e-26,1.65381716879202e-8,1] + [0,0,0,0,-1,0,35,37,-1,47,0,60466176,-1,47,9223372036854775807,0] + +- name: add, subtract, multiply, divide, modulo operators precedence + args: + - -c + - '[.[] + .[] % .[] * .[] - .[] / .[] % .[] + .[]] | while(length>0; .[32:])[:32]' + input: '[3, 7]' + expected: | + [5,9,8,12,14,18,5,9,5,9,12,16,26,30,5,9,4,8,7,11,13,17,4,8,4,8,11,15,25,29,4,8] + [6,10,9,13,15,19,6,10,6,10,13,17,27,31,6,10,5,9,8,12,14,18,5,9,5,9,12,16,26,30,5,9] + [5,9,8,12,14,18,5,9,5,9,12,16,26,30,5,9,4,8,7,11,13,17,4,8,4,8,11,15,25,29,4,8] + [6,10,9,13,15,19,6,10,6,10,13,17,27,31,6,10,5,9,8,12,14,18,5,9,5,9,12,16,26,30,5,9] + [9,13,12,16,18,22,9,13,9,13,16,20,30,34,9,13,8,12,11,15,17,21,8,12,8,12,15,19,29,33,8,12] + [10,14,13,17,19,23,10,14,10,14,17,21,31,35,10,14,9,13,12,16,18,22,9,13,9,13,16,20,30,34,9,13] + [9,13,12,16,18,22,9,13,9,13,16,20,30,34,9,13,8,12,11,15,17,21,8,12,8,12,15,19,29,33,8,12] + [10,14,13,17,19,23,10,14,10,14,17,21,31,35,10,14,9,13,12,16,18,22,9,13,9,13,16,20,30,34,9,13] + +- name: negate, multiply, divide, modulo minimum integer + args: + - -c + - '(-9223372036854775807-1) | -., .*-1, -1*., ./-1, .%-1' + input: 'null' + expected: | + 9223372036854775808 + 9223372036854775808 + 9223372036854775808 + 9223372036854775808 + 0 + +- name: integer division precision + args: + - '72 / (1, -2, 3, -4, 6, -8) * 100000000000000000000' + input: '0' + expected: | + 7200000000000000000000 + -3600000000000000000000 + 2400000000000000000000 + -1800000000000000000000 + 1200000000000000000000 + -900000000000000000000 + +- name: optional and division operator + args: + - '1?/1' + input: '0' + expected: | + 1 + +- name: optional and division operator parse error + args: + - '.?/' + input: '0' + error: | + invalid query: .?/ + .?/ + ^ unexpected EOF + exit_code: 3 + +- name: factorial calculation + args: + - -n + - -r + - 'def fact($n): if $n < 1 then 1 else $n * fact($n - 1) end; (range(30;51) | "\(.)! = \(fact(.))"), fact(50) / fact(30), fact(50) % (fact(30) + 1), fact(100), fact(1000)*1.0, 1/fact(1000), -1/fact(1000), 1.0/fact(1000)' + expected: | + 30! = 265252859812191058636308480000000 + 31! = 8222838654177922817725562880000000 + 32! = 263130836933693530167218012160000000 + 33! = 8683317618811886495518194401280000000 + 34! = 295232799039604140847618609643520000000 + 35! = 10333147966386144929666651337523200000000 + 36! = 371993326789901217467999448150835200000000 + 37! = 13763753091226345046315979581580902400000000 + 38! = 523022617466601111760007224100074291200000000 + 39! = 20397882081197443358640281739902897356800000000 + 40! = 815915283247897734345611269596115894272000000000 + 41! = 33452526613163807108170062053440751665152000000000 + 42! = 1405006117752879898543142606244511569936384000000000 + 43! = 60415263063373835637355132068513997507264512000000000 + 44! = 2658271574788448768043625811014615890319638528000000000 + 45! = 119622220865480194561963161495657715064383733760000000000 + 46! = 5502622159812088949850305428800254892961651752960000000000 + 47! = 258623241511168180642964355153611979969197632389120000000000 + 48! = 12413915592536072670862289047373375038521486354677760000000000 + 49! = 608281864034267560872252163321295376887552831379210240000000000 + 50! = 30414093201713378043612608166064768844377641568960512000000000000 + 114660755112113373922453094400000 + 150592104700077684713855385600001 + 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 + 1.7976931348623157e+308 + 0 + -0 + 0 + +- name: negative large numbers + args: + - -n + - 'def f($n): if $n < 1 then 1 else (-$n) * (-f($n - 1)) end; -f(range(30;40))' + expected: | + -265252859812191058636308480000000 + -8222838654177922817725562880000000 + -263130836933693530167218012160000000 + -8683317618811886495518194401280000000 + -295232799039604140847618609643520000000 + -10333147966386144929666651337523200000000 + -371993326789901217467999448150835200000000 + -13763753091226345046315979581580902400000000 + -523022617466601111760007224100074291200000000 + -20397882081197443358640281739902897356800000000 + +- name: large number literal + args: + - -n + - | + 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, + -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0, + -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0, + 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, + -10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0, + -10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0 + expected: | + 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e+308 + -1e+308 + 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + -10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1.7976931348623157e+308 + -1.7976931348623157e+308 + +- name: zero division + args: + - -c + - '.[] as $x | .[] as $y | [$x, $y, try ($x / $y) catch .]' + input: '[1, 0]' + expected: | + [1,1,1] + [1,0,"cannot divide number (1) by: number (0)"] + [0,1,0] + [0,0,"cannot divide number (0) by: number (0)"] + +- name: zero modulo + args: + - -c + - '.[] as $x | .[] as $y | [$x, $y, try ($x % $y) catch .]' + input: '[1, 0]' + expected: | + [1,1,0] + [1,0,"cannot modulo number (1) by: number (0)"] + [0,1,0] + [0,0,"cannot modulo number (0) by: number (0)"] + +- name: modulo with near 0.0 + args: + - '1 % 0.9' + input: 'null' + error: | + cannot modulo number (1) by: number (0.9) + +- name: modulo with nan + args: + - '(1, nan) % (1, nan) | isnan' + input: 'null' + expected: | + false + true + true + true + +- name: zero division and modulo for large number + args: + - -c + - '[try (.[] / .[]) catch .], [try (.[] % .[]) catch .]' + input: '[1267650600228229401496703205376, 0]' + expected: | + [1,0,"cannot divide number (12676506002282294014967032 ...) by: number (0)"] + [0,0,"cannot modulo number (12676506002282294014967032 ...) by: number (0)"] + +- name: add strings + args: + - '.[] + .[] + "c"' + input: '["a", "b"]' + expected: | + "aac" + "bac" + "abc" + "bbc" + +- name: multiply constant + args: + - '3 * .[], 2.5 * .[], .054e2 * .[]' + input: '[1, 2.5, "abc"]' + expected: | + 3 + 7.5 + "abcabcabc" + 2.5 + 6.25 + "abcabc" + 5.4 + 13.5 + "abcabcabcabcabc" + +- name: multiply strings + args: + - '(-1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 3.7, 10, nan) * "abc"' + input: 'null' + expected: | + null + null + "" + "" + "abc" + "abc" + "abcabcabc" + "abcabcabcabcabcabcabcabcabcabc" + null + +- name: multiply strings error + args: + - -r + - '(infinite, 1e300, 720000000) as $n | try ($n * "abc") catch .' + input: 'null' + expected: | + repeat string result too large: "abc" * 1.7976931348623157e+308 + repeat string result too large: "abc" * 1e+300 + repeat string result too large: "abc" * 720000000 + +- name: multiply empty string + args: + - '(-9223372036854775808, -1, 0, 0.001, 0.999, 1, 7, 9223372036854775807, 1e300) * ""' + input: 'null' + expected: | + null + null + "" + "" + "" + "" + "" + "" + "" + +- name: add objects + args: + - -c + - '.[0] + .[]' + input: '[{"a": 1}, {"b": 2}, {"a": 3}]' + expected: | + {"a":1} + {"a":1,"b":2} + {"a":3} + +- name: multiply objects + args: + - -c + - '.[] * .[]' + input: '[{"foo": 10, "bar": {"qux": 20}}, {"foo": 20, "bar": {"baz": "quux"}}]' + expected: | + {"bar":{"qux":20},"foo":10} + {"bar":{"baz":"quux","qux":20},"foo":10} + {"bar":{"baz":"quux","qux":20},"foo":20} + {"bar":{"baz":"quux"},"foo":20} + +- name: divide strings + args: + - -c + - '.[] / .[]' + input: '["abc", "a", "b", ""]' + expected: | + ["",""] + ["a"] + ["b"] + [] + ["","bc"] + ["",""] + ["b"] + [] + ["a","c"] + ["a"] + ["",""] + [] + ["a","b","c"] + ["a"] + ["b"] + [] + +- name: add and subtract arrays + args: + - -c + - '.[] + .[] - .[]' + input: '[[1, 2], [3, 4]]' + expected: | + [] + [3,4] + [3,4] + [3,4,3,4] + [1,2,1,2] + [1,2] + [1,2] + [] + +- name: arrays are immutable on addition + args: + - -c + - '. as $v | .[:0] + [6, 7, 8] | ., $v' + input: | + [1,2,3,4,5] + expected: | + [6,7,8] + [1,2,3,4,5] + +- name: arrays are immutable on subtraction + args: + - -c + - '. as $v | . - [2, 3] | ., $v' + input: | + [1,2,3,4,5] + expected: | + [1,4,5] + [1,2,3,4,5] + +- name: subtract arrays + args: + - -c + - '[1,2,3,4,1,4722366482869645213696] - [.,3,4722366482869645213696]' + input: '1' + expected: | + [2,4] + +- name: subtract arrays with large numbers + args: + - -c + - '. as $x | [$x-$x] - [0]' + input: '10000000000000' + expected: | + [] + +- name: subtract arrays with nan + args: + - -c + - '[nan,{x:nan}] - [nan,{x:nan}]' + input: '1' + expected: | + [null,{"x":null}] + +- name: add object + args: + - -c + - '.[] + .[]' + input: '[{"a":1},{"b":2}]' + expected: | + {"a":1} + {"a":1,"b":2} + {"a":1,"b":2} + {"b":2} + +- name: add null + args: + - '.[] + .[]' + input: '[1, 2, null]' + expected: | + 2 + 3 + 1 + 3 + 4 + 2 + 1 + 2 + null + +- name: subtract null + args: + - '.[] - .[]' + input: '[1, 2, null]' + expected: | + 0 + 1 + error: | + cannot subtract: null and number (1) + +- name: subtract object + args: + - '.[] - .[]' + input: '[{"a":1},{"b":2}]' + error: | + cannot subtract: object ({"a":1}) and object ({"a":1}) + +- name: comparison operators + args: + - '[.[] > .[], .[] < .[], .[] >= .[], .[] <= .[], .[] == .[], .[] != .[]] | while(length>0; .[120:]) | .[:120] | map(if . then 1 else 0 end) | join("")' + input: '[null, false, true, 0, 1, "", "a", "b", "abc", [], [1], [2], [3, 4], ["a", "b"], [[]], [[], []], [{}], {}, {"a": ""}, {"a":[1]}, {"b":[1]}, {"b": []}, {"b": [[]]}, {"a": 0, "b": 1}]' + expected: | + "011111111111111111111111001111111111111111111111000111111111111111111111000011111111111111111111000001111111111111111111" + "000000111111111111111111000000011111111111111111000000000111111111111111000000010111111111111111000000000011111111111111" + "000000000001111111111111000000000000111111111111000000000000011111111111000000000000001111111111000000000000000111111111" + "000000000000000011111111000000000000000001111111000000000000000000111111000000000000000000011111000000000000000000001111" + "000000000000000000000010000000000000000000001010000000000000000000000000000000000000000000001110000000000000000000000000" + "100000000000000000000000110000000000000000000000111000000000000000000000111100000000000000000000111110000000000000000000" + "111111000000000000000000111111101000000000000000111111100000000000000000111111111000000000000000111111111100000000000000" + "111111111110000000000000111111111111000000000000111111111111100000000000111111111111110000000000111111111111111000000000" + "111111111111111100000000111111111111111110000000111111111111111111000000111111111111111111100000111111111111111111110101" + "111111111111111111110001111111111111111111111101111111111111111111110000111111111111111111111111011111111111111111111111" + "001111111111111111111111000111111111111111111111000011111111111111111111000001111111111111111111000000111111111111111111" + "000000010111111111111111000000011111111111111111000000000111111111111111000000000011111111111111000000000001111111111111" + "000000000000111111111111000000000000011111111111000000000000001111111111000000000000000111111111000000000000000011111111" + "000000000000000001111111000000000000000000111111000000000000000000011111000000000000000000001010000000000000000000001110" + "000000000000000000000010000000000000000000001111100000000000000000000000110000000000000000000000111000000000000000000000" + "111100000000000000000000111110000000000000000000111111000000000000000000111111100000000000000000111111111000000000000000" + "111111101000000000000000111111111100000000000000111111111110000000000000111111111111000000000000111111111111100000000000" + "111111111111110000000000111111111111111000000000111111111111111100000000111111111111111110000000111111111111111111000000" + "111111111111111111100000111111111111111111110000111111111111111111111101111111111111111111110101111111111111111111111111" + "111111111111111111110001100000000000000000000000010000000000000000000000001000000000000000000000000100000000000000000000" + "000010000000000000000000000001000000000000000000000000100000000000000000000000010000000000000000000000001000000000000000" + "000000000100000000000000000000000010000000000000000000000001000000000000000000000000100000000000000000000000010000000000" + "000000000000001000000000000000000000000100000000000000000000000010000000000000000000000001000000000000000000000000100000" + "000000000000000000010000000000000000000000001000000000000000000000000100000000000000000000000010000000000000000000000001" + "011111111111111111111111101111111111111111111111110111111111111111111111111011111111111111111111111101111111111111111111" + "111110111111111111111111111111011111111111111111111111101111111111111111111111110111111111111111111111111011111111111111" + "111111111101111111111111111111111110111111111111111111111111011111111111111111111111101111111111111111111111110111111111" + "111111111111111011111111111111111111111101111111111111111111111110111111111111111111111111011111111111111111111111101111" + "111111111111111111110111111111111111111111111011111111111111111111111101111111111111111111111110" + +- name: comparison and arithmetic operators + args: + - '[.[] + .[] > .[] - .[], .[] * .[] < .[] / .[] + .[] / .[], .[] == .[] * .[]] | while(length>0; .[120:]) | .[:120] | map(if . then 1 else 0 end) | join("")' + input: '[1, 2, 3]' + expected: | + "111111111111111111011111111111111111111111111111111111111111111111111111111111111100000000110100000111100100100000000100" + "000000110100000100000000100000000100000000110100000111100100111110100110100000110100000111100100110100000110100000110100" + "000111100100111110100111110100111100100111100100111110100111100100111100100111100100100000000110100000111100100000000000" + "100000000100000000000000000100000000100000000100000000110100000111100100100000000100000000110100000100000000100000000100" + "000000110100000111100100111110100100000000110100000110100000100000000110100000110100000100000000110100000111100100000000" + "000100000000100000000000000000000000000100000000100000000110100000111100100100000000100000000110100000000000000100000000" + "100000000100000000110100000111100100100000000100000000110100000100000000100000000100000000100010001010000000001000000" + +- name: comparison operators with large numbers + args: + - '[.[] > .[], .[] < .[], .[] >= .[], .[] <= .[], .[] == .[], .[] != .[]] | while(length>0; .[120:]) | .[:120] | map(if . then 1 else 0 end) | join("")' + input: '[-9999999999999999999999999999999999, -4722366482869645213697, -2, 0, 47, 4722366482869645213696, 4722366482869645213697, 99999999999999999999999999999999999]' + expected: | + "011111110011111100011111000011110000011100000011000000010000000000000000100000001100000011100000111100001111100011111100" + "111111101111111101111111001111110001111100001111000001110000001100000001100000001100000011100000111100001111100011111100" + "111111101111111110000000010000000010000000010000000010000000010000000010000000010111111110111111110111111110111111110111" + "111110111111110111111110" + +- name: comparison operators associativity error + args: + - '. == . == .' + input: '1' + error: | + invalid query: . == . == . + . == . == . + ^ unexpected token "==" + exit_code: 3 + +- name: logical operators + args: + - '[.[] > .[] and .[] or .[] < .[]] | while(length>0; .[120:]) | .[:120] | map(if . then 1 else 0 end) | join("")' + input: '[null, false, true, 0, "", [], {}]' + expected: | + "000000010000001100000111000011110001111100111111000000001000000110000011100001111000111110011111100000000100000011000001" + "110000111100011111001111110111110000000100000011000001110000111100011111001111110000000010000001100000111000011110001111" + "100111111011111000000010000001100000111000011110001111100111111000000001000000110000011100001111000111110011111101111100" + "000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001111110111110000000100000011000" + "001110000111100011111001111110000000010000001100000111000011110001111100111111011111000000010000001100000111000011110001" + "111100111111000000001000000110000011100001111000111110011111101111100000001000000110000011100001111000111110011111100000" + "000100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111000000001000000110000011100" + "001111000111110011111101111100000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001" + "111110111110000000100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111011111000000" + "010000001100000111000011110001111100111111000000001000000110000011100001111000111110011111101111100000001000000110000011" + "100001111000111110011111100000000100000011000001110000111100011111001111110111110000000100000011000001110000111100011111" + "001111110000000010000001100000111000011110001111100111111000000001000000110000011100001111000111110011111100000000100000" + "011000001110000111100011111001111110000000010000001100000111000011110001111100111111011111000000010000001100000111000011" + "110001111100111111000000001000000110000011100001111000111110011111101111100000001000000110000011100001111000111110011111" + "100000000100000011000001110000111100011111001111110111110000000100000011000001110000111100011111001111110000000010000001" + "100000111000011110001111100111111011111000000010000001100000111000011110001111100111111000000001000000110000011100001111" + "000111110011111100000000100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111000000" + "001000000110000011100001111000111110011111100000000100000011000001110000111100011111001111110111110000000100000011000001" + "110000111100011111001111110000000010000001100000111000011110001111100111111011111000000010000001100000111000011110001111" + "100111111000000001000000110000011100001111000111110011111101111100000001000000110000011100001111000111110011111100000000" + "100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111000000001000000110000011100001" + "111000111110011111100000000100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111000" + "000001000000110000011100001111000111110011111101111100000001000000110000011100001111000111110011111100000000100000011000" + "001110000111100011111001111110111110000000100000011000001110000111100011111001111110000000010000001100000111000011110001" + "111100111111000000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001111110000000010" + "000001100000111000011110001111100111111000000001000000110000011100001111000111110011111100000000100000011000001110000111" + "100011111001111110000000010000001100000111000011110001111100111111011111000000010000001100000111000011110001111100111111" + "000000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001111110000000010000001100000" + "111000011110001111100111111000000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001" + "1111100000000100000011000001110000111100011111001111110" + +- name: alternative operator + args: + - -c + - '[.a // .b, .b // null // .a, .c[] // .d[], .d[] // .c, .c // .a, .e.a // .c[]]' + input: '{ "a": 5, "b": false, "c": [null,false], "d": [false,3], "e": null }' + expected: | + [5,5,false,3,3,[null,false],null,false] + +- name: condition + args: + - 'if . then . else [.] end' + input: 'null false true 0' + expected: | + [ + null + ] + [ + false + ] + true + 0 + +- name: condition with elif clause + args: + - 'if . == null then . elif not then [.] elif . == true then [[.]] else [[[.]]] end' + input: 'null false true 0' + expected: | + null + [ + false + ] + [ + [ + true + ] + ] + [ + [ + [ + 0 + ] + ] + ] + +- name: condition without else clause + args: + - 'map(if . > 2 then . / 2 end)' + input: '[1, 2, 3, 4, 5]' + expected: | + [ + 1, + 2, + 1.5, + 2, + 2.5 + ] + +- name: condition precedence + args: + - '. + if 1 then 2 else 3 end + 4' + input: '1' + expected: | + 7 + +- name: condition with unary operator + args: + - '[-if true then -. end]' + input: '1' + expected: | + [ + 1 + ] + +- name: condition with optional operator + args: + - 'if . then tonumber end ?' + input: '1' + expected: | + 1 + +- name: condition with identity in then clause + args: + - 'if 0 then . else 0|0 end' + input: 'null' + expected: | + null + +- name: condition with identity in else clause + args: + - 'if 0 then 0|0 else . end' + input: 'null' + expected: | + 0 + +- name: condition with suffix + args: + - 'if true then [.] end []' + input: '1' + expected: | + 1 + +- name: not function + args: + - 'not' + input: | + true + false + expected: | + false + true + +- name: iterator in condition + args: + - 'if .. then [] else {} end' + input: '{ "foo": false }' + expected: | + [] + {} + +- name: try + args: + - 'map(try utf8bytelength)' + input: '[[], {}, [1,2], "hello", 55, true, false]' + expected: | + [ + 5 + ] + +- name: try catch + args: + - 'map(try utf8bytelength catch .)' + input: '[[], {}, [1,2], "hello", 55, true, false]' + expected: | + [ + "utf8bytelength cannot be applied to: array ([])", + "utf8bytelength cannot be applied to: object ({})", + "utf8bytelength cannot be applied to: array ([1,2])", + 5, + "utf8bytelength cannot be applied to: number (55)", + "utf8bytelength cannot be applied to: boolean (true)", + "utf8bytelength cannot be applied to: boolean (false)" + ] + +- name: try catch with function declaration + args: + - 'def f: fromjson; try ({} | f) catch ., 1' + input: 'null' + expected: | + "fromjson cannot be applied to: object ({})" + 1 + +- name: try and comma operator precedence + args: + - '1, try error(2), 3' + input: 'null' + expected: | + 1 + 3 + +- name: try and alternative operator precedence + args: + - 'try error(0) // 1' + input: 'null' + expected: | + 1 + +- name: try and update operator precedence + args: + - 'try error(0) = 1' + input: 'null' + expected: | + null + +- name: try and arithmetic operator precedence + args: + - 'try 0 * error(0)' + input: 'null' + error: | + error: 0 + exit_code: 5 + +- name: try and indexing precedence + args: + - 'try 0 .[error(0)]' + input: 'null' + +- name: try catch and arithmetic operator precedence + args: + - '1 + try 2 catch 3 + 4' + input: 'null' + expected: | + 7 + +- name: try and unary operator + args: + - '[-try .]' + input: '1' + expected: | + [ + -1 + ] + +- name: reduce iterator + args: + - '[range(.)] | reduce .[] as $i (0; . + $i)' + input: '10' + expected: | + 45 + +- name: reduce arrays + args: + - 'reduce .[] as [$i,$j] (0; . + $i * $j)' + input: '[[1,2,10], [3,4,10]]' + expected: | + 14 + +- name: reduce objects + args: + - 'reduce .[] as {$i, j:$j} (0; . + $i - $j)' + input: '[{"i":2, "j":1}, {"i":5, "j":3}, {"i":6, "j":4}]' + expected: | + 5 + +- name: reduce null + args: + - 'reduce . as $n (.; .)' + input: 'null' + expected: | + null + +- name: reduce precedence + args: + - '. + reduce . as $n (. + [2]; [.]) + .' + input: '[1]' + expected: | + [ + 1, + [ + 1, + 2 + ], + 1 + ] + +- name: reduce with unary operator + args: + - '[-reduce -.[] as $i (0; . + $i)]' + input: '[1,2,3]' + expected: | + [ + 6 + ] + +- name: reduce with binary operator + args: + - 'reduce .[] / .[] as $i (0; . + $i)' + input: '[1,2]' + expected: | + 4.5 + +- name: reduce with select in update + args: + - 'reduce range(5) as $x (0; . + $x | select($x != 2))' + input: 'null' + expected: | + 8 + +- name: reduce with assignment of multiple paths in update + args: + - -c + - 'reduce (1,2) as $i (.; (.[0], .[.[0]]) = $i), + reduce (1,2) as $i (.; (.[0], .[.[0]]) |= $i)' + input: '[0,0,0]' + expected: | + [2,2,0] + [2,2,0] + +- name: reduce with query in start + args: + - 'reduce range(5) as $x (range(3); . + $x)' + input: 'null' + expected: | + 10 + 11 + 12 + +- name: reduce with variable binding + args: + - 'reduce .[] as $x (0; . + $x) as $x | $x' + input: '[1,2,3]' + expected: | + 6 + +- name: reduce variable scope + args: + - -c + - '. as $x | reduce .[] as $x (.; . + .) | [., $x]' + input: '[1]' + expected: | + [[1,1],[1]] + +- name: foreach iterator + args: + - 'foreach range(5) as $item (0; $item)' + input: 'null' + expected: | + 0 + 1 + 2 + 3 + 4 + +- name: foreach arrays + args: + - 'foreach .[] as [$i, $j] (0; . + $i - $j)' + input: '[[2,1], [5,3], [6,4]]' + expected: | + 1 + 3 + 5 + +- name: foreach objects + args: + - 'foreach .[] as {a:$a} (0; . + $a; -.)' + input: '[{"a":1}, {"b":2}, {"a":3, "b":4}]' + expected: | + -1 + -1 + -4 + +- name: foreach with iterator in update + args: + - 'foreach .[] as $i (1; ., . + $i, range(.))' + input: '[1, 2, 3]' + expected: | + 1 + 2 + 0 + 0 + 2 + 2 + 5 + 0 + 1 + +- name: foreach with select in update + args: + - -c + - 'foreach range(5) as $i (0; . + $i | select($i != 2); [$i, .])' + input: 'null' + expected: | + [0,0] + [1,1] + [3,4] + [4,8] + +- name: foreach with query in start + args: + - -c + - 'foreach range(3) as $i (range(3); . + $i; [$i, .])' + input: 'null' + expected: | + [0,0] + [1,1] + [2,3] + [0,1] + [1,2] + [2,4] + [0,2] + [1,3] + [2,5] + +- name: foreach with unary operator + args: + - '[-foreach -.[] as $i (0; . + $i)]' + input: '[1,2,3]' + expected: | + [ + 1, + 3, + 6 + ] + +- name: foreach with binary operator + args: + - '[foreach .[] / .[] as $i (0; . + $i)]' + input: '[1,2]' + expected: | + [ + 1, + 3, + 3.5, + 4.5 + ] + +- name: foreach with variable binding + args: + - '[foreach .[] as $x (0; . + $x) as $x | $x]' + input: '[1,2,3]' + expected: | + [ + 1, + 3, + 6 + ] + +- name: foreach variable scope + args: + - -c + - '. as $x | foreach .[] as $x (.; . + .) | [., $x]' + input: '[1]' + expected: | + [[1,1],[1]] + +- name: label and break syntax + args: + - '[(label $x | .[] | if . > 2 then break $x else . end), 0]' + input: '[1,2,3,2,1]' + expected: | + [ + 1, + 2, + 0 + ] + +- name: label and break in foreach syntax + args: + - '[range(.)] | label $x | foreach .[] as $i (0; if . > 5 then break $x else . + $i end)' + input: '10' + expected: | + 0 + 1 + 3 + 6 + +- name: label and break in try catch syntax + args: + - 'label $x | (label $y | try break $y catch .), .' + input: '10' + expected: | + 10 + +- name: label and break in function argument + args: + - 'range(.) | label $out | limit(3; 1, if . > 1 then break $out end, 2)' + input: '3' + expected: | + 1 + 0 + 2 + 1 + 1 + 2 + 1 + +- name: label and break in function argument with foreach + args: + - 'label $out | last(foreach range(0; 10) as $i ({x: 1}; if .x > 10 then break $out else .x += 1 end))' + input: '0' + expected: | + { + "x": 11 + } + +- name: label in recursive call + args: + - 'def f: label $x | if . < 5 then ., (. + 1 | f, break $x, .) else . end; f' + input: '0' + expected: | + 0 + 1 + 2 + 3 + 4 + 5 + +- name: duplicate label names + args: + - 'label $x | (label $x | break $x), .' + input: '1' + expected: | + 1 + +- name: invalid label name + args: + - 'label foo | .' + input: '1' + error: | + invalid query: label foo | . + label foo | . + ^ unexpected token "foo" + exit_code: 3 + +- name: label not defined error for break without label + args: + - 'break $test' + input: '1' + error: | + label not defined: $test + exit_code: 3 + +- name: label not defined error for break with label in parenthesis + args: + - '(label $x | 1) | break $x' + input: '1' + error: | + label not defined: $x + exit_code: 3 + +- name: invalid break name + args: + - 'label $x | break x' + input: '1' + error: | + invalid query: label $x | break x + label $x | break x + ^ unexpected token "x" + exit_code: 3 + +- name: invalid break syntax + args: + - 'label $x | break' + input: '0' + error: | + invalid query: label $x | break + label $x | break + ^ unexpected EOF + exit_code: 3 + +- name: variable and label name conflict + args: + - '. as $x | (label $x | break $x), $x' + input: '0' + expected: | + 0 + +- name: string interpolation + args: + - -c + - '["\(.foo[])_\(.bar[])_\(.baz[])"]' + input: '{ "foo": [0, 1], "bar": ["x", "y"], "baz": [{"a": 1}, [1], null] }' + expected: | + ["0_x_{\"a\":1}","1_x_{\"a\":1}","0_y_{\"a\":1}","1_y_{\"a\":1}","0_x_[1]","1_x_[1]","0_y_[1]","1_y_[1]","0_x_null","1_x_null","0_y_null","1_y_null"] + +- name: string interpolation in object + args: + - -c + - '[{a:"\(.foo[])"}, {"\(.foo[])": 1}, {"\(.bar[])"}]' + input: '{ "foo": [0, 1], "bar": ["x", "y"], "x": 10, "y": 20 }' + expected: | + [{"a":"0"},{"a":"1"},{"0":1},{"1":1},{"x":10},{"y":20}] + +- name: string interpolation nested + args: + - -c + - '" \("(") \("x" + "\(1 + 2)") \("\\") \("\("\\")") \(")") "' + input: '0' + expected: | + " ( x3 \\ \\ ) " + +- name: string interpolation nested deeply + args: + - -c + - '"\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\(1+2)")")")")")")")")")")")")")")")")")")")")")")")"' + input: '0' + expected: | + "3" + +- name: string interpolation multiple lines + args: + - -c + - | + "\( # " + "\( + "\(2 * (4 # " + +# + 6))" + )" # " + # " + + (def f: "\("\("(" + "\\\((1 + 2) * 3)\"" + ")")")"; f) + # " + # " + # " + )" + input: '0' + expected: | + "20(\\9\")" + +- name: string interpolation parse error + args: + - '"\(1 +)"' + input: '0' + error: | + invalid query: "\(1 +)" + "\(1 +)" + ^ unexpected token ")" + exit_code: 3 + +- name: tojson function + args: + - 'tojson' + input: | + null + false + true + 4722366482869645213696 + "foo" + ["foo"] + {"a": [1,2,3]} + expected: | + "null" + "false" + "true" + "4722366482869645213696" + "\"foo\"" + "[\"foo\"]" + "{\"a\":[1,2,3]}" + +- name: fromjson function + args: + - -c + - 'fromjson' + input: | + "null" + "false" + "true" + "4722366482869645213696" + "\"foo\"" + "[\"foo\"]" + "{\"a\":[1,2,3]}" + expected: | + null + false + true + 4722366482869645213696 + "foo" + ["foo"] + {"a":[1,2,3]} + +- name: fromjson function error + args: + - 'fromjson' + input: '"[0"' + error: | + fromjson cannot be applied to "[0": unexpected EOF + +- name: variable definition + args: + - -c + - '.foo.bar as $foo | .foo.bar as $bar | {$foo} + {bar: ($bar / 2)} * { baz: 20 }' + input: | + {"foo":{"bar":10}} + expected: | + {"bar":5,"baz":20,"foo":10} + +- name: variable definition + args: + - -c + - '.ys as $ys | .xs[] | {a, b: $ys[.b]}' + input: | + {"xs":[{"a":"a0","b":"b0"},{"a":"a1","b":"b1"}],"ys":{"b0":"bb0","b1":"bb1"}} + expected: | + {"a":"a0","b":"bb0"} + {"a":"a1","b":"bb1"} + +- name: variable name error + args: + - '.foo as foo | {$bar}' + input: 'null' + error: | + invalid query: .foo as foo | {$bar} + .foo as foo | {$bar} + ^ unexpected token "foo" + exit_code: 3 + +- name: variable name not found error + args: + - '.foo as $foo | {$bar}' + input: | + {"foo":10} + error: | + variable not defined: $bar + exit_code: 3 + +- name: binding an array + args: + - -c + - '. as [$i, $j] | [$j, $i]' + input: | + [] + [10] + [20,30] + expected: | + [null,null] + [null,10] + [30,20] + +- name: binding an array deeply + args: + - -c + - '. as [$i, [$j], [[$k]]] | [$i, $j, $k]' + input: | + [] + [10] + [20,[30],[[40]]] + expected: | + [null,null,null] + [10,null,null] + [20,30,40] + +- name: binding an array error + args: + - '. as [$i, $j] | [$j, $i]' + input: | + {"foo":10} + error: | + expected an array but got: object ({"foo":10}) + +- name: binding an object + args: + - -c + - '. as {$foo, bar: $bar} | {$bar, $foo}' + input: | + {} + {"foo":10} + {"foo":20,"bar":30} + expected: | + {"bar":null,"foo":null} + {"bar":null,"foo":10} + {"bar":30,"foo":20} + +- name: binding an object deeply + args: + - -c + - '. as {$foo, bar: {"baz": {$qux}, "": $quy}} | {$foo, $qux, $quy}' + input: | + {} + {"foo":10} + {"foo":20,"bar":{"baz":{"qux":30},"":-1}} + expected: | + {"foo":null,"qux":null,"quy":null} + {"foo":10,"qux":null,"quy":null} + {"foo":20,"qux":30,"quy":-1} + +- name: binding an object with expression + args: + - -c + - '. as {as: $foo, ("e"+"x"+"p"): $bar, (.foo): $baz, "\(.qux)": $qux} | [$foo, $bar, $baz, $qux]' + input: | + {"as":1,"exp":2,"foo":"bar","bar":3,"qux":"quux","quux":4} + expected: | + [1,2,3,4] + +- name: binding an object error + args: + - '. as {$i} | [$i]' + input: | + [10] + error: | + expected an object but got: array ([10]) + +- name: binding object and array deeply + args: + - -c + - '. as {$a, $b: [$c, $d]} | [$a, $b, $c, $d]' + input: | + {"a":1,"b":[2,{"d":3}]} + expected: | + [1,[2,{"d":3}],2,{"d":3}] + +- name: binding variable scope in parenthesis + args: + - -c + - '. as $x | (.foo as $x | [$x]) | .+[$x]' + input: | + {"foo": 42} + expected: | + [42,{"foo":42}] + +- name: binding variable scope in array + args: + - -c + - '. as $x | [.foo as $x | $x] | $x' + input: | + {"foo":42} + expected: | + {"foo":42} + +- name: binding variable scope in object + args: + - -c + - '. as $x | {bar: (.foo as $x | $x)} | $x' + input: | + {"foo":"bar"} + expected: | + {"foo":"bar"} + +- name: binding variable scope in object key + args: + - -c + - '. as $x | {(.foo as $x | $x): $x}' + input: | + {"foo":"bar"} + expected: | + {"bar":{"foo":"bar"}} + +- name: binding variable scope in condition + args: + - -c + - '. as $x | if (.foo,.bar) as $x | $x then 1 as $x | $x else 2 as $x | $x end | [., $x]' + input: | + {"foo":"bar"} + expected: | + [1,{"foo":"bar"}] + [2,{"foo":"bar"}] + +- name: binding variable scope in reduce + args: + - '[range(.)] | 3 as $x | reduce .[] as $i (.[5] as $x | $x; . + $x + $i)' + input: '10' + expected: | + 80 + +- name: binding variable scope in foreach + args: + - -c + - '[2 as $x | foreach range(.) as $item (. as $x | $x; . + $item * $x; . + $x)]' + input: '5' + expected: | + [7,9,13,19,27] + +- name: binding variable scope error in parenthesis + args: + - '(. as $i | $i) | $i' + input: '0' + error: | + variable not defined: $i + exit_code: 3 + +- name: binding variable scope error in array + args: + - '[. as $i | $i] | $i' + input: '0' + error: | + variable not defined: $i + exit_code: 3 + +- name: binding variable scope error in object + args: + - '{x: (. as $i | $i)} | $i' + input: '0' + error: | + variable not defined: $i + exit_code: 3 + +- name: binding variable scope error in object key + args: + - '{(. as $i | $i): $i}' + input: '"x"' + error: | + variable not defined: $i + exit_code: 3 + +- name: binding variable scope with function declaration + args: + - -n + - -c + - '"x" as $v | def f: $v; "y" as $v | [., f, $v]' + input: 'null' + expected: | + [null,"x","y"] + +- name: function scope in parenthesis + args: + - 'def f: 1; . | (def f: 2; f) | f, .' + input: '0' + expected: | + 1 + 2 + +- name: function scope in array + args: + - -c + - 'def f: 1; . | [def f: 2; f] | f, .' + input: '0' + expected: | + 1 + [2] + +- name: function scope in object + args: + - -c + - 'def f: 1; . | {bar: (def f: .foo; f)} | f, .' + input: | + {"foo":"bar"} + expected: | + 1 + {"bar":"bar"} + +- name: function scope in object key + args: + - -c + - 'def f: 1; . | {(def f: "foo"; f): f}' + input: '0' + expected: | + {"foo":1} + +- name: function scope error in parenthesis + args: + - '(def f: 1; f) | f' + input: '0' + error: | + function not defined: f/0 + exit_code: 3 + +- name: function scope error in array + args: + - '[def f: 1; f] | f' + input: '0' + error: | + function not defined: f/0 + exit_code: 3 + +- name: function scope error in object + args: + - '{x: (def f: 1; f)} | f' + input: '0' + error: | + function not defined: f/0 + exit_code: 3 + +- name: function scope error in object key + args: + - '{(def f: 1; f): f}' + input: '"x"' + error: | + function not defined: f/0 + exit_code: 3 + +- name: first/0, last/0, nth/1 functions + args: + - -c + - '[first, last, nth(-6,-5,-1,0,2,5)]' + input: '[1,2,3,4,5]' + expected: | + [1,5,null,1,5,1,3,null] + +- name: first/1, last/1, nth/2 functions + args: + - -c + - '[first(.[]), last(.[]), nth(0,3,5;.[]), nth(9;range(infinite))]' + input: '[1,2,3,4,5]' + expected: | + [1,5,1,4,9] + +- name: first/1, last/1, nth/2 functions on empty + args: + - -c + - '[first(.[]), last(.[]), nth(0;.[])]' + input: '[]' + expected: | + [] + +- name: first/1 function with optional operator + args: + - 'first(.), first(.?), first(.,.), first(.?,.), first(.,.?), first(.?,.?)' + input: 'null' + expected: | + null + null + null + null + null + null + +- name: first/1 function nested + args: + - 'first((null, null) | first(null))' + input: 'null' + expected: | + null + +- name: nth/2 function with negative index + args: + - 'nth(-1;.[])' + input: '[]' + error: | + error: nth doesn't support negative index + +- name: nth/2 function with infinite range + args: + - 'nth(range(3); range(infinite))' + input: 'null' + expected: | + 0 + 1 + 2 + +- name: nth/2 function with error + args: + - 'nth(1; 0,1,error)' + input: 'null' + expected: | + 1 + +- name: all/2 function in first/1 argument + args: + - 'first(range(1; 200) | . as $x | select(all(range(1; 7); $x % . == 0)))' + input: 'null' + expected: | + 60 + +- name: limit/2 function + args: + - -c + - '[limit(0,3,13; range(infinite))]' + input: 'null' + expected: | + [0,1,2,0,1,2,3,4,5,6,7,8,9,10,11,12] + +- name: limit/2 function with negative count + args: + - 'limit(-1; range(3))' + input: 'null' + error: | + limit doesn't support negative count + +- name: skip/2 function + args: + - -c + - '[skip(0,3,5,7; range(5))]' + input: 'null' + expected: | + [0,1,2,3,4,3,4] + +- name: skip/2 function with negative count + args: + - 'skip(-1; range(3))' + input: 'null' + error: | + skip doesn't support negative count + +- name: all/0, any/0 functions + args: + - -c + - '[all, any]' + input: | + [] + [false, false] + [false, true] + [true, true] + expected: | + [true,false] + [false,false] + [false,true] + [true,true] + +- name: all/1, any/1 functions + args: + - -c + - '[all(not), any(not)]' + input: | + [] + [false, false] + [false, true] + [true, true] + expected: | + [true,false] + [true,true] + [false,true] + [false,false] + +- name: all/2, any/2 functions + args: + - -c + - '[all(range(.); not), any(range(.); not)]' + input: '0 1' + expected: | + [true,false] + [false,false] + +- name: isempty function + args: + - -c + - '[isempty(empty), isempty(range(infinite)), isempty(1,error("foo"))]' + input: 'null' + expected: | + [true,false,false] + +- name: isempty function nested + args: + - 'isempty(isempty(empty))' + input: 'null' + expected: | + false + +- name: first/1 in isempty function + args: + - 'isempty(first(null))' + input: 'null' + expected: | + false + +- name: sin, cos, tan functions + args: + - -c + - 'map(sin), map(cos), map(tan) | map(. * 1000000000 | floor / 1000000000)' + input: '[0, 1, 2]' + expected: | + [0,0.841470984,0.909297426] + [1,0.540302305,-0.416146837] + [0,1.557407724,-2.185039864] + +- name: asin, acos, atan functions + args: + - -c + - 'map(asin), map(acos), map(atan) | map(. * 1000000000 | floor / 1000000000)' + input: '[0, 1, 2]' + expected: | + [0,1.570796326,null] + [1.570796326,0,null] + [0,0.785398163,1.107148717] + +- name: sinh, cosh, tanh functions + args: + - -c + - 'map(sinh), map(cosh), map(tanh) | map(. * 1000000000 | floor / 1000000000)' + input: '[0, 1, 2]' + expected: | + [0,1.175201193,3.626860407] + [1,1.543080634,3.762195691] + [0,0.761594155,0.96402758] + +- name: asinh, acosh, atanh functions + args: + - -c + - 'map(asinh), map(acosh), map(atanh) | map(. * 1000000000 | floor / 1000000000)' + input: '[0, 1, 2]' + expected: | + [0,0.881373587,1.443635475] + [null,0,1.316957896] + [0,1.7976931348623157e+308,null] + +- name: floor, ceil, trunc, fabs functions + args: + - -c + - 'map(floor), map(ceil), map(trunc), map(fabs)' + input: '[-3.7, -3.3, -2.5, -1.7, -1.0, -0.7, -0.3, 0.0, 0.3, 0.5, 0.7, 1.0, 1.7, 2.5, 3.3, 3.7]' + expected: | + [-4,-4,-3,-2,-1,-1,-1,0,0,0,0,1,1,2,3,3] + [-3,-3,-2,-1,-1,-0,-0,0,1,1,1,1,2,3,4,4] + [-3,-3,-2,-1,-1,-0,-0,0,0,0,0,1,1,2,3,3] + [3.7,3.3,2.5,1.7,1,0.7,0.3,0,0.3,0.5,0.7,1,1.7,2.5,3.3,3.7] + +- name: round, nearbyint, rint functions + args: + - -c + - 'map(round), map(nearbyint), map(rint)' + input: '[-2.5, -1.7, -1.5, -1.2, -0.7, -0.5, -0.3, 0.0, 0.3, 0.5, 0.7, 1.2, 1.5, 1.7, 2.5]' + expected: | + [-3,-2,-2,-1,-1,-1,-0,0,0,1,1,1,2,2,3] + [-2,-2,-2,-1,-1,-0,-0,0,0,0,1,1,2,2,2] + [-2,-2,-2,-1,-1,-0,-0,0,0,0,1,1,2,2,2] + +- name: sqrt and cbrt functions + args: + - -c + - 'map(sqrt), map(cbrt) | map(. * 1000000000 | floor / 1000000000)' + input: '[-8, -3.375, 0, 0.125, 1, 4, 8, 9, 16.777216]' + expected: | + [null,null,0,0.35355339,1,2,2.828427124,3,4.096] + [-2,-1.5,0,0.5,1,1.587401051,2,2.080083823,2.56] + +- name: significand function + args: + - -c + - 'map(significand)' + input: '[0, -0.5, 1, -1.5, 2, 10, 64, 100, 5e-324, -5e-324]' + expected: | + [0,-1,1,-1.5,1,1.25,1,1.5625,1,-1] + +- name: exp, exp10, exp2, expm1 functions + args: + - -c + - 'map(exp), map(exp10), map(exp2), map(expm1) | map(. * 1000000000 | floor / 1000000000)' + input: '[0, -0.5, 1, -1.5, 2, 10, 20]' + expected: | + [1,0.606530659,2.718281828,0.22313016,7.389056098,22026.465794806,485165195.4097903] + [1,0.316227766,10,0.031622776,100,10000000000,99999999999999980000] + [1,0.707106781,2,0.35355339,4,1024,1048576] + [0,-0.393469341,1.718281828,-0.77686984,6.389056098,22025.465794806,485165194.4097903] + +- name: frexp, modf function + args: + - -c + - 'map(frexp), map(modf)' + input: '[0, 0.25, -0.5, 1, -1.5, 2, 3.125]' + expected: | + [[0,0],[0.5,-1],[-0.5,0],[0.5,1],[-0.75,1],[0.5,2],[0.78125,2]] + [[0,0],[0.25,0],[-0.5,-0],[0,1],[-0.5,-1],[0,2],[0.125,3]] + +- name: modf against infinite + args: + - -c + - 'infinite, -infinite | modf' + input: 'null' + expected: | + [0,1.7976931348623157e+308] + [-0,-1.7976931348623157e+308] + +- name: log, log10, log1p, log2, logb functions + args: + - -c + - 'map(log), map(log10), map(log1p), map(log2), map(logb) | map(. * 1000000000 | floor / 1000000000)' + input: '[0, -0.5, 1, 1.5, 2, 10, 64, 100]' + expected: | + [-1.7976931348623157e+308,null,0,0.405465108,0.69314718,2.302585092,4.158883083,4.605170185] + [-1.7976931348623157e+308,null,0,0.176091259,0.301029995,1,1.806179973,2] + [0,-0.693147181,0.69314718,0.916290731,1.098612288,2.397895272,4.174387269,4.615120516] + [-1.7976931348623157e+308,null,0,0.5849625,1,3.321928094,6,6.643856189] + [-1.7976931348623157e+308,-1,0,0,1,3,6,6] + +- name: gamma, tgamma, lgamma functions + args: + - -c + - 'map(gamma), map(tgamma), map(lgamma) | map(. * 1000000000 | floor / 1000000000)' + input: '[0, -0.5, 1, -1.5, 2, 10]' + expected: | + [1.7976931348623157e+308,-3.544907702,1,2.363271801,1,362880] + [1.7976931348623157e+308,-3.544907702,1,2.363271801,1,362880] + [1.7976931348623157e+308,1.265512123,0,0.860047015,0,12.80182748] + +- name: erf, erfc, j0, j1, y0, y1 functions + args: + - -c + - 'map(erf), map(erfc), map(j0), map(j1), map(y0), map(y1) | map(. * 1000000000 | floor / 1000000000)' + input: '[0, -0.5, 1, -1.5, 2, 10]' + expected: | + [0,-0.520499878,0.842700792,-0.966105147,0.995322265,1] + [1,1.520499877,0.157299207,1.966105146,0.004677734,0] + [1,0.938469807,0.765197686,0.511827671,0.223890779,-0.245935765] + [0,-0.242268458,0.440050585,-0.557936508,0.576724807,0.043472746] + [-1.7976931348623157e+308,null,0.088256964,null,0.510375672,0.055671167] + [-1.7976931348623157e+308,null,-0.781212822,null,-0.107032432,0.249015424] + +- name: fdim/2, fmax/2, fmin/2 functions + args: + - -c + - '[fdim(0.25,0.625; 0.125,0.375)], [fmax(0.1,0.3; 0.2,0.4)], [fmin(0.1,0.3; 0.2,0.4)]' + input: 'null' + expected: | + [0.125,0.5,0,0.25] + [0.2,0.3,0.4,0.4] + [0.1,0.2,0.1,0.3] + +- name: fdim/2, fmax/2, fmin/2 functions with nan + args: + - -c + - '[fdim(1,nan; 1,nan)], [fmax(1,nan; 1,nan)], [fmin(1,nan; 1,nan)]' + input: 'null' + expected: | + [0,null,null,null] + [1,1,1,null] + [1,1,1,null] + +- name: copysign/2, drem/2, fmod/2, remainder/2 functions + args: + - -c + - '[copysign(0,0.5,-1.5; 0,1,-3.5)], [drem(0.5,-0.75,1.125; 0.5,-0.25)], + [fmod(0.5,-0.75,1.125; 0.5,-0.25)], [remainder(0.5,-0.75,1.125; 0.5,-0.25)]' + input: 'null' + expected: | + [0,0.5,1.5,0,0.5,1.5,-0,-0.5,-1.5] + [0,0.25,0.125,0,-0,0.125] + [0,-0.25,0.125,0,-0,0.125] + [0,0.25,0.125,0,-0,0.125] + +- name: nextafter/2, nexttoward/2 functions + args: + - -c + - '[nextafter(0,0.5,-0.75; 0.5,-0.25)], [nexttoward(0,0.5,-0.75; 0.5,-0.25)]' + input: 'null' + expected: | + [5e-324,0.5,-0.7499999999999999,-5e-324,0.49999999999999994,-0.7499999999999999] + [5e-324,0.5,-0.7499999999999999,-5e-324,0.49999999999999994,-0.7499999999999999] + +- name: atan2/2, hypot/2 functions + args: + - -c + - '[atan2(0,1; 0,1,-3)], [hypot(0,-3.75,12; 0,5,-16)] | map(. * 1000000000 | floor / 1000000000)' + input: 'null' + expected: | + [0,1.570796326,0,0.785398163,3.141592653,2.819842099] + [0,3.75,12,5,6.25,13,16,16.433578429,20] + +- name: ldexp/2, scalb/2, scalbln/2 functions + args: + - -c + - '[ldexp(0,0.5,2; 0,0.5,-2,3,10)], [scalb(0,0.5,2; 0,0.5,-2,3,10)], [scalbln(0,0.5,2; 0,0.5,-2,3,10)]' + input: 'null' + expected: | + [0,0.5,2,0,0.5,2,0,0.125,0.5,0,4,16,0,512,2048] + [0,0.5,2,0,0.5,2,0,0.125,0.5,0,4,16,0,512,2048] + [0,0.5,2,0,0.5,2,0,0.125,0.5,0,4,16,0,512,2048] + +- name: jn/2, pow/2, yn/2 functions + args: + - -c + - '[jn(0,0.5,2; 0,0.5,-2,3,10)], [pow(0,0.5,2; 0,0.5,-2,3,10)], [yn(0,0.5,2; 0.5,-2,3,10)] | map(. * 1000000000 | floor / 1000000000)' + input: 'null' + expected: | + [1,1,0,0.938469807,0.938469807,0.030604023,0.223890779,0.223890779,0.352834028,-0.260051955,-0.260051955,0.48609126,-0.245935765,-0.245935765,0.254630313] + [1,1,1,0,0.707106781,1.414213562,1.7976931348623157e+308,4,0.25,0,0.125,8,0,0.000976562,1024] + [-0.444518734,-0.444518734,-5.441370838,null,null,null,0.37685001,0.37685001,-0.160400394,0.055671167,0.055671167,-0.005868083] + +- name: fma/3 function + args: + - -c + - '[fma(1.5,2.5; 3.5,4.5; 5.25,6.75)]' + input: 'null' + expected: | + [10.5,14,12,16.5,12,15.5,13.5,18] + +- name: infinite function + args: + - -c + - '[infinite, -infinite] | (.[], .[]+.[], .[]-.[], .[]*.[], .[]/.[], .[]%.[]%256, 1/.[], -1/.[], .[]-1e300, .[]+1e300) | [., . == infinite, . == -infinite, contains(infinite)]' + input: 'null' + expected: | + [1.7976931348623157e+308,true,false,true] + [-1.7976931348623157e+308,false,true,false] + [1.7976931348623157e+308,true,false,true] + [null,false,false,false] + [null,false,false,false] + [-1.7976931348623157e+308,false,true,false] + [null,false,false,false] + [-1.7976931348623157e+308,false,true,false] + [1.7976931348623157e+308,true,false,true] + [null,false,false,false] + [1.7976931348623157e+308,true,false,true] + [-1.7976931348623157e+308,false,true,false] + [-1.7976931348623157e+308,false,true,false] + [1.7976931348623157e+308,true,false,true] + [null,false,false,false] + [null,false,false,false] + [null,false,false,false] + [null,false,false,false] + [0,false,false,false] + [-1,false,false,false] + [255,false,false,false] + [0,false,false,false] + [0,false,false,false] + [-0,false,false,false] + [-0,false,false,false] + [0,false,false,false] + [1.7976931348623157e+308,true,false,true] + [-1.7976931348623157e+308,false,true,false] + [1.7976931348623157e+308,true,false,true] + [-1.7976931348623157e+308,false,true,false] + +- name: isfinite and isinfinite functions + args: + - -c + - 'infinite | ., -., 1/., 1/(-.), nan, null, "", {}, [] | [., isfinite, isinfinite]' + input: 'null' + expected: | + [1.7976931348623157e+308,false,true] + [-1.7976931348623157e+308,false,true] + [0,true,false] + [-0,true,false] + [null,true,false] + [null,false,false] + ["",false,false] + [{},false,false] + [[],false,false] + +- name: finites function + args: + - 'nan, infinite, -infinite | ., -., 1/. | finites' + input: 'null' + expected: | + null + null + null + 0 + -0 + +- name: nan and isnan functions + args: + - -c + - 'nan, infinite-infinite, infinite/infinite, 0, null | [., .+1, isnan]' + input: 'null' + expected: | + [null,null,true] + [null,null,true] + [null,null,true] + [0,1,false] + [null,1,false] + +- name: nan and comparison + args: + - -c + - 'nan | [.<., .>., .<0, .>0, .==., .!=.], ([., 1, ., 2, .] | [min, max], sort, group_by(.))' + input: 'null' + expected: | + [true,false,true,false,false,true] + [null,2] + [null,null,null,1,2] + [[null],[null],[null],[1],[2]] + +- name: isnormal function + args: + - '0, 2.2250738585072011e-308, 2.2250738585072014e-308, 1, 1.7976931348623157e+308, nan, infinite, "", [], {} | isnormal' + input: 'null' + expected: | + false + false + true + true + true + false + false + false + false + false + +- name: normals function + args: + - '0, 2.2250738585072011e-308, 2.2250738585072014e-308, 1, 1.7976931348623157e+308, nan, infinite | ., -. | normals' + input: 'null' + expected: | + 2.2250738585072014e-308 + -2.2250738585072014e-308 + 1 + -1 + 1.7976931348623157e+308 + -1.7976931348623157e+308 + +- name: normals with nextafter function + args: + - '0, 2.2250738585072014e-308, 1.7976931348623157e+308, nan, infinite | nextafter(.; 0, infinite) | normals' + input: 'null' + expected: | + 2.225073858507202e-308 + 1.7976931348623155e+308 + 1.7976931348623157e+308 + +- name: stringify nan and infinite + args: + - -c + - 'nan, infinite, -infinite | [., tostring]' + input: 'null' + expected: | + [null,"null"] + [1.7976931348623157e+308,"1.7976931348623157e+308"] + [-1.7976931348623157e+308,"-1.7976931348623157e+308"] + +- name: truncate_stream function + args: + - -c + - '[truncate_stream([[0],1],[[1,0],2],[[1,0]],[[1]])]' + input: '0 1 2' + expected: | + [[[0],1],[[1,0],2],[[1,0]],[[1]]] + [[[0],2],[[0]]] + [] + +- name: fromstream function + args: + - -c + - 'fromstream(range(5) | truncate_stream({x:[1,{y:[2]},3],z:[4]} | tostream))' + input: 'null' + expected: | + {"x":[1,{"y":[2]},3],"z":[4]} + [1,{"y":[2]},3] + [4] + {"y":[2]} + [2] + +- name: tostream function + args: + - -c + - 'tostream' + input: '[0,[1,{"a":1},{"b":2}]]' + expected: | + [[0],0] + [[1,0],1] + [[1,1,"a"],1] + [[1,1,"a"]] + [[1,2,"b"],2] + [[1,2,"b"]] + [[1,2]] + [[1]] + +- name: setpath function + args: + - -c + - 'setpath([["a","b",0,1,2],[1,2,"a","b"]][]; 1,2)' + input: 'null' + expected: | + {"a":{"b":[[null,[null,null,1]]]}} + [null,[null,null,{"a":{"b":1}}]] + {"a":{"b":[[null,[null,null,2]]]}} + [null,[null,null,{"a":{"b":2}}]] + +- name: setpath function deeply + args: + - -c + - 'setpath(["a","b",0,1]; 1,2)' + input: '{"a":{"b":[]}}' + expected: | + {"a":{"b":[[null,1]]}} + {"a":{"b":[[null,2]]}} + +- name: setpath function negative index + args: + - -c + - 'setpath(-2,0,2 | [.]; 4,5)' + input: '[1,2,3]' + expected: | + [1,4,3] + [4,2,3] + [1,2,4] + [1,5,3] + [5,2,3] + [1,2,5] + +- name: setpath function empty path + args: + - -c + - 'setpath([]; 10)' + input: 'null' + expected: | + 10 + +- name: setpath function error + args: + - 'setpath([-1]; 10)' + input: '[]' + error: | + setpath([-1]; 10) cannot be applied to []: array index should not be negative: -1 + +- name: setpath function error + args: + - 'setpath(["a","b","c"]; 10)' + input: '{"a":{"b":[]}}' + error: | + setpath(["a","b","c"]; 10) cannot be applied to {"a":{"b":[]}}: expected an object but got: array ([]) + +- name: setpath function error + args: + - 'setpath(["a",1]; 10)' + input: '{"a":{"b":[]}}' + error: | + setpath(["a",1]; 10) cannot be applied to {"a":{"b":[]}}: expected an array but got: object ({"b":[]}) + +- name: setpath function error + args: + - 'setpath([{}]; 10)' + input: 'null' + error: | + setpath([{}]; 10) cannot be applied to null: expected "start" and "end" for slicing but got: object ({}) + +- name: setpath function error + args: + - 'setpath([{start:0}]; 10)' + input: 'null' + error: | + setpath([{"start":0}]; 10) cannot be applied to null: expected "start" and "end" for slicing but got: object ({"start":0}) + +- name: setpath function error + args: + - 'setpath([[]]; 10)' + input: '[]' + error: | + setpath([[]]; 10) cannot be applied to []: expected a number for indexing an array but got: array ([]) + +- name: setpath function error + args: + - 'setpath([{start:0,end:1}]; 10)' + input: '[]' + error: | + setpath([{"end":1,"start":0}]; 10) cannot be applied to []: expected an array but got: number (10) + +- name: setpath function error + args: + - 'setpath([{start:"x",end:2}]; [9])' + input: '[1,2,3]' + error: | + setpath([{"end":2,"start":"x"}]; [9]) cannot be applied to [1,2,3]: expected a number for indexing an array but got: string ("x") + +- name: delpaths function + args: + - -c + - 'delpaths([["a",1,"b",1]])' + input: '{"a":[{},{"b":[1,2,3]}]}' + expected: | + {"a":[{},{"b":[1,3]}]} + +- name: delpaths function against array + args: + - -c + - 'delpaths([[2],[6],[4]])' + input: '[1,2,3,4,5]' + expected: | + [1,2,4] + +- name: delpaths function not found key + args: + - -c + - 'delpaths([["a","b","c"]])' + input: '{}' + expected: | + {} + +- name: delpaths function out of index + args: + - -c + - 'delpaths([[2],[-10],[10],[-3]])' + input: '[1,2,3,4,5]' + expected: | + [1,2,4,5] + +- name: delpaths function with overlapping paths + args: + - -c + - 'delpaths([["x"],["x",0,"y"]], [["x",0],["x",0,"y"]], [["x"],["x",{"start":0}]])' + input: '{"x":[{"y":0}]}' + expected: | + {} + {"x":[]} + {} + +- name: delpaths function against null + args: + - 'delpaths([["a"]]), delpaths([["a","b"]]), delpaths([[0]]), delpaths([[-1]]), delpaths([[{start:1,end:2}]])' + input: 'null' + expected: | + null + null + null + null + null + +- name: delpaths function error + args: + - 'delpaths([{}])' + input: '[]' + error: | + delpaths([{}]) cannot be applied to []: expected an array but got: object ({}) + +- name: delpaths function error + args: + - 'delpaths([["x"]])' + input: '[]' + error: | + delpaths([["x"]]) cannot be applied to []: expected an object but got: array ([]) + +- name: delpaths function error + args: + - 'delpaths([[{"start":1,"end":{}}]])' + input: '[1,2,3]' + error: | + delpaths([[{"end":{},"start":1}]]) cannot be applied to [1,2,3]: expected a number for indexing an array but got: object ({}) + +- name: getpath function + args: + - -c + - 'getpath(["a",1,"b",2])' + input: '{"a":[{},{"b":[1,2,3]}]}' + expected: | + 3 + +- name: getpath function empty path + args: + - -c + - 'getpath([])' + input: '{"a":[{},{"b":[1,2,3]}]}' + expected: | + {"a":[{},{"b":[1,2,3]}]} + +- name: getpath function against string + args: + - -c + - '[getpath([{"start":1,"end":3}]), getpath([{"start":-2,"end":null}]), getpath([1]), getpath([])]' + input: '"abcde"' + expected: | + ["bc","de","b","abcde"] + +- name: getpath function against string in a container + args: + - -c + - 'getpath(["a",{"start":0,"end":2}]), getpath(["a",1])' + input: '{"a":"abc"}' + expected: | + "ab" + "b" + +- name: getpath function against string round trip with path function + args: + - -c + - '[getpath(path(.[1:3])), getpath(path(.[1]))]' + input: '"abcde"' + expected: | + ["bc","b"] + +- name: getpath function against string round trip in a container + args: + - -c + - '. as $d | [path(.a[0:2]), path(.b[1])] | map(. as $p | $d | getpath($p))' + input: '{"a":"abc","b":"xyz"}' + expected: | + ["ab","y"] + +- name: getpath function against string with object key + args: + - 'getpath(["k"])' + input: '"abc"' + error: | + getpath(["k"]) cannot be applied to "abc": expected an object but got: string ("abc") + +- name: getpath function error + args: + - 'getpath(["a",1,"b","c"])' + input: '{"a":[{},{"b":[1,2,3,4,5,6,7]}]}' + error: | + getpath(["a",1,"b","c"]) cannot be applied to {"a":[{},{"b":[1,2,3,4,5, ...}: expected an object but got: array ([1,2,3,4,5,6,7]) + +- name: setpath, delpaths, getpath functions + args: + - -c + - '["foo",1] as $p | getpath($p), setpath($p; 2), delpaths([$p]), .' + input: | + {"foo":[0,1]} + {"bar":false} + expected: | + 1 + {"foo":[0,2]} + {"foo":[0]} + {"foo":[0,1]} + null + {"bar":false,"foo":[null,2]} + {"bar":false} + {"bar":false} + +- name: setpath to getpath + args: + - -c + - '.[] | try (getpath(["a",0,"b"]) |= 5) catch .' + input: '[null,{"b":0},{"a":0},{"a":null},{"a":[0,1]},{"a":{"b":1}},{"a":[{}]},{"a":[{"c":3}]}]' + expected: | + {"a":[{"b":5}]} + {"a":[{"b":5}],"b":0} + "getpath([\"a\",0,\"b\"]) cannot be applied to {\"a\":0}: expected an array but got: number (0)" + {"a":[{"b":5}]} + "getpath([\"a\",0,\"b\"]) cannot be applied to {\"a\":[0,1]}: expected an object but got: number (0)" + "getpath([\"a\",0,\"b\"]) cannot be applied to {\"a\":{\"b\":1}}: expected an array but got: object ({\"b\":1})" + {"a":[{"b":5}]} + {"a":[{"b":5,"c":3}]} + +- name: pick function + args: + - -c + - 'pick(.x, .y[2], .y[0], .z)' + input: '{"x":1, "y": [1,2,3]}' + expected: | + {"x":1,"y":[1,null,3],"z":null} + +- name: map_values function + args: + - -c + - 'map_values(.*2)' + input: '[1,2,3]' + expected: | + [2,4,6] + +- name: map_values function with optional operator + args: + - -c + - 'map_values(tonumber? // .)' + input: '{"x": "1", "y": "1.5", "z": "z"}' + expected: | + {"x":1,"y":1.5,"z":"z"} + +- name: del function + args: + - -c + - 'del(.), del(empty), del((.foo,.bar,.baz) | .[2,3,0]), del(.foo[0], .bar[0], .foo, .baz.bar[0].x), del(.foo[1:][:3][1:], .bar[:2][1:])' + input: '{"foo": [0,1,2,3,4], "bar": [0,1]}' + expected: | + null + {"bar":[0,1],"foo":[0,1,2,3,4]} + {"bar":[1],"foo":[1,4]} + {"bar":[1]} + {"bar":[0],"foo":[0,1,4]} + +- name: del function with array slicing + args: + - -c + - 'del(.[1], .[-6], .[2], .[-3:9])' + input: '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' + expected: | + [0,3,5,6,9] + +- name: del function against large array with select + args: + - '[range(.)] | (5,7) as $x | del(.[] | select(. % $x == 0)) | add' + input: '50000' + expected: | + 1000000000 + 1071421429 + +- name: del function against large array with slicing + args: + - '[range(.)] | del(.[range(length) | {start:.,end:(.+1)}])' + input: '50000' + expected: | + [] + +- name: del function against null + args: + - 'del(.a), del(.a.b), del(.[0]), del(.[-1]), del(.[1:2])' + input: 'null' + expected: | + null + null + null + null + null + +- name: assignment operator against object + args: + - -c + - '.foo.bar = 1' + input: 'null' + expected: | + {"foo":{"bar":1}} + +- name: assignment operator against array + args: + - -c + - '.[-1,0] = 3' + input: '[0,1,2]' + expected: | + [3,1,3] + +- name: assignment operator against number + args: + - '. = 1' + input: '0' + expected: | + 1 + +- name: assignment operator against number error + args: + - '.foo = 1' + input: '0' + error: | + expected an object but got: number (0) + +- name: assignment operator against object with multiple paths + args: + - -c + - '(.foo,.bar,.baz) = 1' + input: 'null' + expected: | + {"bar":1,"baz":1,"foo":1} + +- name: assignment operator against array with slicing + args: + - -c + - '.[2:4] = ([], [0], [4,3,2]), .[1:-1] = [3,2], .[2:0] = [.[]], .[10:] = [1], + .[-20:-10] = [1], .[0:5] = [9], .[1:][1:] = [9], .[1:][:3] = [6,7,8], + .[:4][1:][:3] = [9], .[1:][1:-2] = [9], .[:4][-3:][:5] = [9], (null | .[:1][2:] = [9])' + input: '[1,2,3,4,5]' + expected: | + [1,2,5] + [1,2,0,5] + [1,2,4,3,2,5] + [1,3,2,5] + [1,2,1,2,3,4,5,3,4,5] + [1,2,3,4,5,1] + [1,1,2,3,4,5] + [9] + [1,2,9] + [1,6,7,8,5] + [1,9,5] + [1,2,9,4,5] + [1,9,5] + [9] + +- name: assignment operator with object and array indexing + args: + - -c + - '.foo.[0].bar.[1] = 1' + input: 'null' + expected: | + {"foo":[{"bar":[null,1]}]} + +- name: assignment operator against array with suffix array indexing + args: + - -c + - '.[2][2] = 1' + input: '[1]' + expected: | + [1,null,[null,null,1]] + +- name: assignment operator against array with suffix object indexing + args: + - -c + - '.[2].foo = 1' + input: '[1]' + expected: | + [1,null,{"foo":1}] + +- name: assignment operator against object with multiple suffix indexing + args: + - -c + - '.foo[2].bar = 2' + input: '{"foo":[1], "bar":3}' + expected: | + {"bar":3,"foo":[1,null,{"bar":2}]} + +- name: assignment operator with query and suffix object indexing + args: + - -c + - '(.foo).bar = 1' + input: 'null' + expected: | + {"foo":{"bar":1}} + +- name: assignment operator with object indexing and iterator suffix + args: + - -c + - '.foo[].bar = 1' + input: '{"foo":[null,null]}' + expected: | + {"foo":[{"bar":1},{"bar":1}]} + +- name: assignment operator with overlapping paths + args: + - -c + - '[(.x,.x.y,.x.y.z) = {}, (.x.y.z,.x.y,.x) = {}]' + input: 'null' + expected: | + [{"x":{"y":{"z":{}}}},{"x":{}}] + +- name: assignment operator with query value + args: + - -c + - '.foo = .bar' + input: '{"bar":42}' + expected: | + {"bar":42,"foo":42} + +- name: assignment operator with multiple paths and query values + args: + - -c + - '(.foo,.bar,.baz) = .bar[]' + input: '{"bar":[0,1,2]}' + expected: | + {"bar":0,"baz":0,"foo":0} + {"bar":1,"baz":1,"foo":1} + {"bar":2,"baz":2,"foo":2} + +- name: assignment operator with overlapping paths and multiple values + args: + - -c + - '.. = ..' + input: '{"x":{"y":{}}} [[[]]]' + expected: | + {"x":{"x":{"y":{}},"y":{"x":{"y":{}}}}} + {"x":{"y":{"y":{}}},"y":{}} + {"x":{"y":{}}} + [[[[[]]]]] + [[[[]]]] + [[[]]] + +- name: assignment operator with overlapping and slicing paths + args: + - -c + - '..[:0] = ..' + input: '[[[]]]' + expected: | + [[[[[]],[]],[]],[[]]] + [[[[]]],[[]]] + [[[]]] + +- name: assignment operator array index negative error + args: + - '.[-1,0] = 1' + input: '[]' + error: | + setpath([-1]; 1) cannot be applied to []: array index should not be negative: -1 + +- name: assignment operator array index limit error + args: + - '.[999999999] = 1' + input: '[]' + error: | + array index too large: 999999999 + +- name: assignment operator with select + args: + - -c + - '(.[] | select(.k == "b") | .v) = 0' + input: '[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "c", "v": 3}]' + expected: | + [{"k":"a","v":1},{"k":"b","v":0},{"k":"c","v":3}] + +- name: assignment operator with fork + args: + - -c + - '[range(3)] | .[] = 0 | [.[] = 1, .[] = 2]' + input: 'null' + expected: | + [[1,1,1],[2,2,2]] + +- name: assignment operator against large array + args: + - '[range(.)] | .[] = 1 | add' + input: '50000' + expected: | + 50000 + +- name: assignment operator against large array with select + args: + - '[range(.)] | (.[] | select(. % 2 > 0)) = 1 | add' + input: '50000' + expected: | + 625000000 + +- name: assignment operator against large array with range + args: + - '[range(.)] | .[range(length)] = 1 | add' + input: '50000' + expected: | + 50000 + +- name: assignment operator against large array with slicing + args: + - '[range(.)] | .[range(length) | {start:.,end:(.+1)}] = [1] | add' + input: '50000' + expected: | + 50000 + +- name: assignment operator against large object + args: + - '[{key:range(.)|tostring}] | from_entries | .[] = 1 | add' + input: '50000' + expected: | + 50000 + +- name: assignment operator constructing large array + args: + - '.[range(50000)] = 1 | add' + input: 'null' + expected: | + 50000 + +- name: update-assignment operator + args: + - -c + - '.foo |= .+1' + input: '{"foo": 1}' + expected: | + {"foo":2} + +- name: update-assignment operator deeply + args: + - -c + - '.[0].a |= {"old":., "new":(.+1)}' + input: '[{"a":1,"b":2}]' + expected: | + [{"a":{"new":2,"old":1},"b":2}] + +- name: update-assignment operator in function + args: + - -c + - 'def inc(x): x |= .+1; inc(.[].a)' + input: '[{"a":1,"b":2},{"a":2,"b":4},{"a":7,"b":8}]' + expected: | + [{"a":2,"b":2},{"a":3,"b":4},{"a":8,"b":8}] + +- name: update-assignment operator with empty + args: + - -c + - '.foo |= empty' + input: '{"foo": 42, "bar": 48}' + expected: | + {"bar":48} + +- name: update-assignment operator with repeat + args: + - -c + - '.foo |= repeat(.)' + input: '{"foo": 42, "bar": 48}' + expected: | + {"bar":48,"foo":42} + +- name: update-assignment operator with same paths + args: + - -c + - '(.foo,.foo,.foo) |= .+1' + input: '{"foo": 0}' + expected: | + {"foo":3} + +- name: update-assignment operator against array with empty function + args: + - -c + - '(.[] | select(. % 2 > 0)) |= empty' + input: '[1,2,3,4,5]' + expected: | + [2,4] + +- name: update-assignment operator against array with conditional empty function + args: + - -c + - '[range(.)] | .[] |= if . % 2 == 0 then empty else [., -.] end' + input: '10' + expected: | + [[1,-1],[3,-3],[5,-5],[7,-7],[9,-9]] + +- name: update-assignment operator with fork + args: + - -c + - '[range(3)] | .[] |= 0 | [.[] |= 1, .[] |= 2]' + input: 'null' + expected: | + [[1,1,1],[2,2,2]] + +- name: update-assignment operator against large array + args: + - '[range(.)] | .[] |= . * 2 | add' + input: '50000' + expected: | + 2499950000 + +- name: update-assignment operator against large array with select + args: + - '[range(.)] | (.[] | select(. % 2 > 0)) |= (. * 2) | add' + input: '50000' + expected: | + 1874975000 + +- name: update-assignment operator against large object + args: + - '[range(.) | {key:tostring,value:.}] | from_entries | .[] |= . * 2 | add' + input: '50000' + expected: | + 2499950000 + +- name: update-assignment operator against array with overlapping paths + args: + - -c + - '(.[1:3][],.[2:4][],.[2]) |= .+1' + input: '[1,2,3,4,5]' + expected: | + [1,3,6,5,5] + +- name: update-assignment operator with optional operator + args: + - -c + - '.foo |= .?' + input: '{"foo": 42}' + expected: | + {"foo":42} + +- name: update-assignment operator with infinite range + args: + - -c + - '[range(.)] | .[] |= range(infinite)' + input: '5' + expected: | + [0,0,0,0,0] + +- name: update-assignment operator with try catch + args: + - -c + - '. |= try . catch .' + input: '1' + expected: | + 1 + +- name: update-assignment operator with alternative operator + args: + - -c + - '.foo |= (fromjson? // "x")' + input: | + {"foo": 42} + {"foo": "42"} + {"foo": "a"} + {"foo": "0 a"} + {"foo": "true"} + {"foo": "[{}]"} + {"foo": "[][]"} + expected: | + {"foo":"x"} + {"foo":42} + {"foo":"x"} + {"foo":"x"} + {"foo":true} + {"foo":[{}]} + {"foo":"x"} + +- name: update operators + args: + - -c + - '.[] += 2, .[] -= 2, .[] *= 2, .[] /= 2, .[] %= 2' + input: '[1,2,3]' + expected: | + [3,4,5] + [-1,0,1] + [2,4,6] + [0.5,1,1.5] + [1,0,1] + +- name: update operator with query in right hand side + args: + - -c + - '.foo += .foo, .bar //= .foo, .[] /= .foo, .[] = .foo' + input: '{"foo":2}' + expected: | + {"foo":4} + {"bar":2,"foo":2} + {"foo":1} + {"foo":2} + +- name: update operators associativity error + args: + - '. += 1 -= 2' + input: '1' + error: | + invalid query: . += 1 -= 2 + . += 1 -= 2 + ^ unexpected token "-=" + exit_code: 3 + +- name: error/0 function + args: + - '.foo[] | error' + input: '{ "foo": ["error message", "bar"] }' + error: | + error: error message + exit_code: 5 + +- name: error/0 function with null + args: + - 'error' + input: 'null' + error: | + error: null + +- name: error/0 function on string and try catch + args: + - 'try error catch .' + input: '"foo"' + expected: | + "foo" + +- name: error/0 function on values and try catch + args: + - 'null,1,[],{x:42} | try error catch .' + input: '0' + expected: | + null + 1 + [] + { + "x": 42 + } + +- name: error/1 function + args: + - '.foo | error("error message")' + input: '{ "foo": 1 }' + error: | + error: error message + exit_code: 5 + +- name: error/1 function and try catch + args: + - 'try error({ x: 42 }) catch .' + input: '0' + expected: | + { + "x": 42 + } + +- name: error/1 function with null + args: + - 'error(null)' + input: '1' + error: | + error: null + +- name: path function identity + args: + - 'path(.)' + input: '{}' + expected: | + [] + +- name: path function deep + args: + - -c + - 'path(.a|.[1,2:3,4].b[5,6])' + input: '{}' + expected: | + ["a",{"end":3,"start":1},"b",5] + ["a",{"end":4,"start":1},"b",5] + ["a",{"end":3,"start":2},"b",5] + ["a",{"end":4,"start":2},"b",5] + ["a",{"end":3,"start":1},"b",6] + ["a",{"end":4,"start":1},"b",6] + ["a",{"end":3,"start":2},"b",6] + ["a",{"end":4,"start":2},"b",6] + +- name: path function select + args: + - -c + - '[path(.[] | select(. > 3))]' + input: '[1,3,5,7,3,8,9,1]' + expected: | + [[2],[3],[5],[6]] + +- name: path function recurse + args: + - -c + - '[path(..)]' + input: '{"a":{"b":[0,1,2]},"c":[3,4]}' + expected: | + [[],["a"],["a","b"],["a","b",0],["a","b",1],["a","b",2],["c"],["c",0],["c",1]] + +- name: path function and pattern + args: + - -c + - '(.a as $x | (.b,.c)) = "b"' + input: '{"a":null,"b":null}' + expected: | + {"a":null,"b":"b","c":"b"} + +- name: path function result + args: + - -c + - '.a[path(.b)[0]]' + input: '{"a":{"b":0}}' + expected: | + 0 + +- name: path function with select + args: + - -c + - 'path(.[] | select(.id | startswith("x")) | .id)' + input: '[{"id": "xy"}, {"id": "yx"}]' + expected: | + [0,"id"] + +- name: path function with getpath + args: + - -c + - '[path(getpath([[0]] | .[0],.[]))]' + input: 'null' + expected: | + [[0],[0]] + +- name: path function against nan + args: + - -c + - '[nan,{x:nan}] | path(.[1].x)' + input: 'null' + expected: | + [1,"x"] + +- name: path function nested + args: + - -c + - 'path(.a[path(.b[.a:.b])[0]])' + input: '{"a":{"b":0}}' + expected: | + ["a","b"] + +- name: path function error with arithmetic operator + args: + - 'path(. + 1)' + input: 'null' + error: | + invalid path against: number (1) + +- name: path function error with object indexing + args: + - 'path({}.x)' + input: '{}' + error: | + invalid path against: object ({}) + +- name: path function error with array indexing + args: + - 'path([0][0 + 0])' + input: '[0]' + error: | + invalid path against: array ([0]) + +- name: path function error with array slicing + args: + - 'path([0][0:1])' + input: '[0]' + error: | + invalid path against: array ([0]) + +- name: path function error with getpath function + args: + - 'path({} | getpath([]))' + input: '{}' + error: | + invalid path against: object ({}) + +- name: path function with binding array slicing + args: + - '.[:1] as $x | path($x.[0])' + input: '[0]' + expected: | + [ + 0 + ] + +- name: path function error with binding array slicing + args: + - '.[:0] as $x | path($x.[0])' + input: '[0]' + error: | + invalid path against: array ([]) + +- name: path function with function declaration + args: + - 'def f($x): .y; path(f(.x))' + input: 'null' + expected: | + [ + "y" + ] + +- name: path function with assignment operator + args: + - 'path(. = .x)' + input: 'null' + expected: | + [] + +- name: path function iterate error of array + args: + - '[.[]][].a = 1' + input: '[{"a":0}]' + error: | + invalid path on iterating against: array ([{"a":0}]) + +- name: path function iterate error of object + args: + - '{}[] = 10' + input: '{}' + error: | + invalid path on iterating against: object ({}) + +- name: paths/0 function + args: + - -c + - 'paths' + input: '[1,[[1,{"a":2},3],{"a":[2]}]]' + expected: | + [0] + [1] + [1,0] + [1,0,0] + [1,0,1] + [1,0,1,"a"] + [1,0,2] + [1,1] + [1,1,"a"] + [1,1,"a",0] + +- name: paths/1 function + args: + - -c + - 'paths(. > 2)' + input: '[1,[[1,{"a":2},3],{"a":[2]}]]' + expected: | + [1] + [1,0] + [1,0,1] + [1,0,2] + [1,1] + [1,1,"a"] + +- name: bsearch function + args: + - 'bsearch(0,1,2,3,4,5,6)' + input: '[1,3,5]' + expected: | + -1 + 0 + -2 + 1 + -3 + 2 + -4 + +- name: bsearch function with objects + args: + - '[{x:range(.),y:range(.)}] | bsearch({x:1,y:1})' + input: '3' + expected: | + 4 + +- name: gmtime, localtime functions + args: + - -c + - 'gmtime, localtime | map(. * 1000000 | floor / 1000000)' + input: '1500000000 1567890123.456 1600000000.111' + expected: | # UTC-7 (TZ=Etc/GMT+7) + [2017,6,14,2,40,0,5,194] + [2017,6,13,19,40,0,4,193] + [2019,8,7,21,2,3.456,6,249] + [2019,8,7,14,2,3.456,6,249] + [2020,8,13,12,26,40.111,0,256] + [2020,8,13,5,26,40.111,0,256] + +- name: mktime, strftime, strflocaltime, todate functions + args: + - -cr + - '(.,mktime | .,strftime("%Y-%m-%dT%H:%M:%SZ"),strflocaltime("%Y-%m-%dT%H:%M:%S%z")), todate' + input: | + [2017,6,14,2,40,0,5,194] + [2019,8,7,21,2,3.456,6,249] + [2020,8,13,12,26,40.111,0,256] + [2024,7,20] + [2025] + expected: | + [2017,6,14,2,40,0,5,194] + 2017-07-14T02:40:00Z + 2017-07-14T02:40:00-0700 + 1500000000 + 2017-07-14T02:40:00Z + 2017-07-13T19:40:00-0700 + 2017-07-14T02:40:00Z + [2019,8,7,21,2,3.456,6,249] + 2019-09-07T21:02:03Z + 2019-09-07T21:02:03-0700 + 1567890123.456 + 2019-09-07T21:02:03Z + 2019-09-07T14:02:03-0700 + 2019-09-07T21:02:03Z + [2020,8,13,12,26,40.111,0,256] + 2020-09-13T12:26:40Z + 2020-09-13T12:26:40-0700 + 1600000000.111 + 2020-09-13T12:26:40Z + 2020-09-13T05:26:40-0700 + 2020-09-13T12:26:40Z + [2024,7,20] + 2024-08-20T00:00:00Z + 2024-08-20T00:00:00-0700 + 1724112000 + 2024-08-20T00:00:00Z + 2024-08-19T17:00:00-0700 + 2024-08-20T00:00:00Z + [2025] + 2024-12-31T00:00:00Z + 2024-12-31T00:00:00-0700 + 1735603200 + 2024-12-31T00:00:00Z + 2024-12-30T17:00:00-0700 + 2024-12-31T00:00:00Z + +- name: strptime, fromdate functions + args: + - -c + - 'strptime("%Y-%m-%dT%H:%M:%S%z"), fromdate' + input: | + "2017-07-14T02:40:00Z" + "2019-09-08T06:02:03+0900" + "2020-09-13T07:26:40-05:00" + expected: | + [2017,6,14,2,40,0,5,194] + 1500000000 + [2019,8,7,21,2,3,6,249] + 1567890123 + [2020,8,13,12,26,40,0,256] + 1600000000 + +- name: strftime, strptime functions + args: + - 'strptime("%c") | strftime("%c")' + input: | + "Thu Jul 23 20:41:06 2020" + expected: | + "Thu Jul 23 20:41:06 2020" + +- name: now function + args: + - -c + - 'now | type' + input: 'null' + expected: | + "number" + +- name: debug/0 function + args: + - -c + - 'recurse(debug|.[]?)' + input: | + [{}] + [1,{"a":[2,"b"]}] + expected: | + [{}] + {} + [1,{"a":[2,"b"]}] + 1 + {"a":[2,"b"]} + [2,"b"] + 2 + "b" + error: | + ["DEBUG:",[{}]] + ["DEBUG:",{}] + ["DEBUG:",[1,{"a":[2,"b"]}]] + ["DEBUG:",1] + ["DEBUG:",{"a":[2,"b"]}] + ["DEBUG:",[2,"b"]] + ["DEBUG:",2] + ["DEBUG:","b"] + +- name: debug/1 function + args: + - 'debug("value: \(.)", [.,.])' + input: 'null false "test" [] {}' + expected: | + null + false + "test" + [] + {} + error: | + ["DEBUG:","value: null"] + ["DEBUG:",[null,null]] + ["DEBUG:","value: false"] + ["DEBUG:",[false,false]] + ["DEBUG:","value: test"] + ["DEBUG:",["test","test"]] + ["DEBUG:","value: []"] + ["DEBUG:",[[],[]]] + ["DEBUG:","value: {}"] + ["DEBUG:",[{},{}]] + +- name: stderr function + args: + - -c + - 'recurse(stderr|.[]?)' + input: | + [{}] + [1,{"a":[2,"b"]}] + "DEBUG:" + expected: | + [{}] + {} + [1,{"a":[2,"b"]}] + 1 + {"a":[2,"b"]} + [2,"b"] + 2 + "b" + "DEBUG:" + error: '[{}]{}[1,{"a":[2,"b"]}]1{"a":[2,"b"]}[2,"b"]2bDEBUG:' + +- name: debug and stderr in builtins + args: + - 'builtins | sort[] | select(test("debug|stderr"))' + input: 'null' + expected: | + "debug/0" + "debug/1" + "stderr/0" + +- name: input_filename function with stdin input + args: + - 'input_filename' + input: '0 1 2' + expected: | + "" + "" + "" + +- name: input_filename function with null input option + args: + - -n + - 'input_filename' + input: '0' + expected: | + null + +- name: input_filename function with raw string input option + args: + - --raw-input + - 'input_filename' + input: | + 1 + 2 + 3 + expected: | + "" + "" + "" + +- name: input_filename function with slurp option + args: + - --slurp + - 'input_filename' + input: | + 1 + 2 + 3 + expected: | + "" + +- name: input_filename function with input and slurp option + args: + - --raw-input + - --slurp + - 'input_filename' + input: | + 1 + 2 + 3 + expected: | + "" + +- name: input_filename function with json file arguments + args: + - 'input_filename' + - 'testdata/1.json' + - 'testdata/2.json' + input: '0' + expected: | + "testdata/1.json" + "testdata/2.json" + +- name: input_filename function with null input option and json file arguments + args: + - -n + - '., inputs, (try input catch .) | input_filename' + - 'testdata/1.json' + - 'testdata/2.json' + input: '0' + expected: | + null + "testdata/1.json" + "testdata/2.json" + null + +- name: input_filename function with hyphen for stdin + args: + - 'input_filename' + - '-' + - 'testdata/1.json' + - '-' + - 'testdata/2.json' + - '-' + input: '0' + expected: | + "" + "testdata/1.json" + "testdata/2.json" + +- name: input_filename function with yaml input option + args: + - --yaml-input + - 'input_filename' + input: '0' + expected: | + "" + +- name: input_filename function with yaml file arguments + args: + - --yaml-input + - 'input_filename' + - 'testdata/1.yaml' + input: '0' + expected: | + "testdata/1.yaml" + "testdata/1.yaml" + "testdata/1.yaml" + +- name: input_filename in builtins + args: + - 'builtins[] | select(test("input_filename"))' + input: 'null' + expected: | + "input_filename/0" + +- name: halt function + args: + - 'def f($n): if $n > 0 then $n, f($n - 1) else halt end; f(.), .' + input: '3' + expected: | + 3 + 2 + 1 + +- name: halt function and raw string input option + args: + - -R + - '., halt, .' + input: | + foo + bar + baz + expected: | + "foo" + +- name: halt function and json file arguments + args: + - 'halt' + - 'testdata/1.json' + - 'testdata/2.json' + +- name: halt_error/0 function + args: + - 'halt_error' + input: '1' + error: | + 1 + exit_code: 5 + +- name: halt_error/0 function on nan + args: + - 'nan | halt_error' + input: 'null' + error: | + null + exit_code: 5 + +- name: halt_error/0 function on string + args: + - 'halt_error' + input: '"foo\nbar\nbaz\n"' + error: | + foo + bar + baz + exit_code: 5 + +- name: halt_error/0 function and try catch + args: + - 'range(5) | try halt_error catch .' + input: 'null' + error: | + 0 + exit_code: 5 + +- name: halt_error/0 function with null input and try catch + args: + - 'try halt_error catch .' + input: 'null' + exit_code: 5 + +- name: halt_error/0 function with multiple input values + args: + - 'halt_error' + input: | + 1 + 2 + 3 + error: | + 1 + exit_code: 5 + +- name: halt_error/0 function and raw string input option + args: + - -R + - 'halt_error' + input: | + foo + bar + baz + error: foo + exit_code: 5 + +- name: halt_error/1 function + args: + - 'halt_error(42)' + input: 'null' + exit_code: 42 + +- name: halt_error/1 function on string + args: + - 'halt_error(42)' + input: '"foo\nbar\nbaz\n"' + error: | + foo + bar + baz + exit_code: 42 + +- name: halt_error/1 function with multiple input values + args: + - 'halt_error(.)' + input: | + 1 + 2 + 3 + error: | + 1 + exit_code: 1 + +- name: builtins function + args: + - 'builtins | length > 100' + input: 'null' + expected: | + true + +- name: env function + args: + - 'env|length > 0' + input: 'null' + expected: | + true + +- name: env in function + args: + - 'def f: env; f|length > 0' + input: 'null' + expected: | + true + +- name: env in builtins + args: + - 'builtins[] | select(test("env"))' + input: 'null' + expected: | + "env/0" + +- name: $ENV variable + args: + - '[$ENV|length > 0, 1 as $ENV | $ENV]' + input: 'null' + expected: | + [ + true, + 1 + ] + +- name: env arguments count error + args: + - 'env(0)' + input: 'null' + error: | + function not defined: env/1 + exit_code: 3 + +- name: input function + args: + - -n + - 'input, input' + input: '1' + expected: | + 1 + error: | + break + +- name: input function with binomial operator + args: + - -n + - 'input + input[0]' + input: | + [1] + 2 + expected: | + 3 + +- name: input function in function + args: + - -n + - 'def f: [def f: input; range(3) | f]; f' + input: '1 2 3' + expected: | + [ + 1, + 2, + 3 + ] + +- name: input function with files + args: + - -n + - -c + - 'range(5) | input' + - 'testdata/1.json' + - 'testdata/7.json' + - 'testdata/1.json' + expected: | + {"foo":10} + 1 + 2 + {"foo":42} + {"foo":10} + +- name: input function without null input option + args: + - '. + input' + input: '1 2' + expected: | + 3 + +- name: input function with stream option + args: + - -n + - -c + - --stream + - 'range(5) | input' + input: '{"a":[1,2,3]}' + expected: | + [["a",0],1] + [["a",1],2] + [["a",2],3] + [["a",2]] + [["a"]] + +- name: inputs function + args: + - -n + - 'reduce inputs as $x (0; . + $x)' + input: '1 2 3 4 5' + expected: | + 15 + +- name: inputs function with stream option + args: + - -n + - -c + - --stream + - 'range(5) | input' + input: '{"a":[1,2,3]}' + expected: | + [["a",0],1] + [["a",1],2] + [["a",2],3] + [["a",2]] + [["a"]] + +- name: inputs function with stream option and fromstream function + args: + - -n + - -c + - --stream + - 'fromstream(inputs)' + input: 'null [false] {"x":[null]}' + expected: | + null + [false] + {"x":[null]} + +- name: inputs function with stream option and truncate_stream function + args: + - -n + - -c + - --stream + - 'fromstream(1 | truncate_stream(inputs))' + input: '{"x":[1,{"y":[2]},3],"z":[4]}' + expected: | + [1,{"y":[2]},3] + [4] + +- name: inputs function with stream option + args: + - -n + - -c + - --stream + - '[inputs][]' + input: '{"a":1} {"b":2} {"c":3}' + expected: | + [["a"],1] + [["a"]] + [["b"],2] + [["b"]] + [["c"],3] + [["c"]] + +- name: inputs function with stream and slurp option + args: + - -n + - -c + - --stream + - --slurp + - 'inputs[]' + input: '{"a":1} {"b":2} {"c":3}' + expected: | + [["a"],1] + [["a"]] + [["b"],2] + [["b"]] + [["c"],3] + [["c"]] + +- name: inputs function with raw input option + args: + - -n + - -R + - 'inputs' + input: | + a b + c + d + 1 + expected: | + "a b" + "c" + "d" + "1" + +- name: inputs function with raw input and slurp option + args: + - -n + - -R + - -s + - 'inputs' + input: | + a b + c + expected: | + "a b\nc\n" + +- name: inputs function with yaml input option + args: + - -n + - --yaml-input + - 'inputs' + input: | + foo: 1 + --- + bar: 2 + --- + baz: 3 + expected: | + { + "foo": 1 + } + { + "bar": 2 + } + { + "baz": 3 + } + +- name: inputs function with yaml input, slurp option + args: + - -n + - --slurp + - --yaml-input + - 'inputs' + input: | + foo: 1 + --- + bar: 2 + --- + baz: 3 + expected: | + [ + { + "foo": 1 + }, + { + "bar": 2 + }, + { + "baz": 3 + } + ] + +- name: match function + args: + - -c + - '[match("bcd")], [match("☆★"; "g")], (("","g","i","ig") as $flag | [match("bcd"; $flag)]), [match("ABC.?D.?E"; "mg")], [match("A(?B(C)(.?D))(?.?E)")], [match("b(?c)(d)"; "igm")], [match("bc(c(.*))?d")]' + input: '"abcde☆★abcde☆★ABCDE☆ABC\nD\nE"' + expected: | + [{"captures":[],"length":3,"offset":1,"string":"bcd"}] + [{"captures":[],"length":2,"offset":5,"string":"☆★"},{"captures":[],"length":2,"offset":12,"string":"☆★"}] + [{"captures":[],"length":3,"offset":1,"string":"bcd"}] + [{"captures":[],"length":3,"offset":1,"string":"bcd"},{"captures":[],"length":3,"offset":8,"string":"bcd"}] + [{"captures":[],"length":3,"offset":1,"string":"bcd"}] + [{"captures":[],"length":3,"offset":1,"string":"bcd"},{"captures":[],"length":3,"offset":8,"string":"bcd"},{"captures":[],"length":3,"offset":15,"string":"BCD"}] + [{"captures":[],"length":5,"offset":14,"string":"ABCDE"},{"captures":[],"length":7,"offset":20,"string":"ABC\nD\nE"}] + [{"captures":[{"length":3,"name":"bar","offset":15,"string":"BCD"},{"length":1,"name":null,"offset":16,"string":"C"},{"length":1,"name":null,"offset":17,"string":"D"},{"length":1,"name":"foo","offset":18,"string":"E"}],"length":5,"offset":14,"string":"ABCDE"}] + [{"captures":[{"length":1,"name":"foo","offset":2,"string":"c"},{"length":1,"name":null,"offset":3,"string":"d"}],"length":3,"offset":1,"string":"bcd"},{"captures":[{"length":1,"name":"foo","offset":9,"string":"c"},{"length":1,"name":null,"offset":10,"string":"d"}],"length":3,"offset":8,"string":"bcd"},{"captures":[{"length":1,"name":"foo","offset":16,"string":"C"},{"length":1,"name":null,"offset":17,"string":"D"}],"length":3,"offset":15,"string":"BCD"}] + [{"captures":[{"length":0,"name":null,"offset":-1,"string":null},{"length":0,"name":null,"offset":-1,"string":null}],"length":3,"offset":1,"string":"bcd"}] + +- name: match function type error + args: + - 'match("x"; "g")' + input: '{}' + error: | + match("x"; "g") cannot be applied to: object ({}) + +- name: match function flag error + args: + - 'match("x"; "a")' + input: '""' + error: | + unsupported regular expression flag: "a" + +- name: match function regular expression error + args: + - 'match("["; "g")' + input: '""' + error: | + invalid regular expression "[": error parsing regexp: missing closing ]: `[` + +- name: test function + args: + - -c + - '[test("xyz"), test("bcd"), test("efg"), test("efg"; "i"), test("☆★")]' + input: '"abcde☆★abcde☆★ABCDEFG"' + expected: | + [false,true,false,true,true] + +- name: capture function + args: + - -c + - 'capture("bcd"), capture("b(?c.?(?d..))"), [capture("(?b(?c).?(?d..))"; "gmi")]' + input: '"abcdeABCDEabcde"' + expected: | + {} + {"bar":"deA","foo":"cdeA"} + [{"a":"bcdeA","bar":"deA","foo":"c"},{"a":"BCDEa","bar":"DEa","foo":"C"}] + +- name: scan function + args: + - -c + - 'scan("abc"), scan("abc"; "i"), scan("(b(c*)(d*))"), scan("(b(c*)(d*))"; "i")' + input: '"abcdeABCCDDEabcce"' + expected: | + "abc" + "abc" + "abc" + "ABC" + "abc" + ["bcd","c","d"] + ["bcc","cc",""] + ["bcd","c","d"] + ["BCCDD","CC","DD"] + ["bcc","cc",""] + +- name: split/2 function + args: + - -c + - 'split("a"), split("a+"), split("a+"; ""), split("a+"; "i"), split(""; "")' + input: '"abaacaAad"' + expected: | + ["","b","","c","A","d"] + ["abaacaAad"] + ["","b","c","A","d"] + ["","b","c","d"] + ["","a","b","a","a","c","a","A","a","d",""] + +- name: splits function + args: + - -c + - '[splits("a")], [splits("a+")], [splits("a+"; "")], [splits("a+"; "i")], [splits(""; "")]' + input: '"abaacaAad"' + expected: | + ["","b","","c","A","d"] + ["","b","c","A","d"] + ["","b","c","A","d"] + ["","b","c","d"] + ["","a","b","a","a","c","a","A","a","d",""] + +- name: sub function + args: + - 'sub("a"; "b"), sub("aaa"; "b"), sub("a"; "b","c"), sub("a(?.)"; "\(.a)b\(.a)"; "ig"), sub("(?★)"; "\(.foo)☆\(.foo)"), sub("^"; "b"), sub("a"; empty)' + input: '"abc☆★☆ABC"' + expected: | + "bbc☆★☆ABC" + "abc☆★☆ABC" + "bbc☆★☆ABC" + "cbc☆★☆ABC" + "bbbc☆★☆BbBC" + "abc☆★☆★☆ABC" + "babc☆★☆ABC" + "abc☆★☆ABC" + +- name: gsub function + args: + - 'gsub("a"; "b"), gsub("a.c"; "b"), gsub("a"; "b","c"), gsub("a(?..)"; "\(.a+.a)"; "i"), gsub("(?★)"; "\(.foo)☆\(.foo)"), gsub("^"; "a"), gsub("a"; empty)' + input: '"abcABC☆★☆ABCabc"' + expected: | + "bbcABC☆★☆ABCbbc" + "bABC☆★☆ABCb" + "bbcABC☆★☆ABCbbc" + "cbcABC☆★☆ABCcbc" + "bcbcBCBC☆★☆BCBCbcbc" + "abcABC☆★☆★☆ABCabc" + "aabcABC☆★☆ABCabc" + "abcABC☆★☆ABCabc" + +- name: INDEX function + args: + - -c + - 'INDEX(range(5); .*2)' + input: 'null' + expected: | + {"0":0,"2":1,"4":2,"6":3,"8":4} + +- name: JOIN function + args: + - -c + - '[range(5)] | JOIN(map(.*3); .*2)' + input: 'null' + expected: | + [[0,0],[1,6],[2,12],[3,null],[4,null]] + +- name: IN function + args: + - -c + - '[range(5; 13)|IN(range(0; 10; 3))]' + input: 'null' + expected: | + [false,true,false,false,true,false,false,false] + +- name: format function + args: + - 'format("html","uri","csv","tsv","sh","base64")' + input: '[1,2,3]' + expected: | + "[1,2,3]" + "%5B1%2C2%2C3%5D" + "1,2,3" + "1\t2\t3" + "1 2 3" + "WzEsMiwzXQ==" + +- name: format function error + args: + - -n + - 'format("t")' + error: | + format not defined: @t + +- name: format strings @text + args: + - -n + - '@text' + expected: | + "null" + +- name: format strings @json + args: + - '@json' + input: '{"a":42}' + expected: | + "{\"a\":42}" + +- name: format strings @json with string interpolation + args: + - '@json "0\(.)1\(. + .)2\([2])3"' + input: '[1]' + expected: | + "0[1]1[1,1]2[2]3" + +- name: format strings @html + args: + - '@html' + input: | + {"foo": "
&'\"
"} + expected: | + "{"foo":"<div>&'\\"</div>"}" + +- name: format strings @html with string interpolation + args: + - '@html "x\(.)x\(.+.)x\("<>")x"' + input: | + ["<>"] + expected: | + "x["<>"]x["<>","<>"]x<>x" + +- name: format strings @uri + args: + - '@uri' + input: | + [1, {"foo": "
&'\"()+
"}] + expected: | + "%5B1%2C%7B%22foo%22%3A%22%3Cdiv%3E%26%27%5C%22%28%29%2B%3C%2Fdiv%3E%22%7D%5D" + +- name: format strings @uri with string interpolation + args: + - '@uri "q=\(.)&q=\(. + .)"' + input: | + ["<>", "{&}"] + expected: | + "q=%5B%22%3C%3E%22%2C%22%7B%26%7D%22%5D&q=%5B%22%3C%3E%22%2C%22%7B%26%7D%22%2C%22%3C%3E%22%2C%22%7B%26%7D%22%5D" + +- name: format strings @urid + args: + - '@urid' + input: | + "%5B1%2C%7B%22foo%22%3A%22%3Cdiv%3E%26%27%5C%22%28%29%2B%3C%2Fdiv%3E%22%7D%5D" + expected: | + "[1,{\"foo\":\"
&'\\\"()+
\"}]" + +- name: format strings @urid error + args: + - '@urid' + input: | + "%5B1%2C%" + error: | + @urid cannot be applied to "%5B1%2C%": invalid URL escape "%" + +- name: format strings @csv + args: + - '@csv' + input: | + [1, "foo", null, "foo,\n\"bar\"\tbaz\u0000\\0"] + expected: | + "1,\"foo\",,\"foo,\n\"\"bar\"\"\tbaz\\0\\0\"" + +- name: format strings @csv with string interpolation + args: + - '@csv "\(.),\(. + .),\([nan])"' + input: | + [1, "foo", null, 2] + expected: | + "1,\"foo\",,2,1,\"foo\",,2,1,\"foo\",,2," + +- name: format strings @csv error + args: + - '@csv' + input: 'null' + error: | + @csv cannot be applied to: null + +- name: format strings @csv row error + args: + - '@csv' + input: | + [1, "foo", null, {"foo":1}] + error: | + @csv cannot format an array including: object ({"foo":1}) + +- name: format strings @tsv + args: + - '@tsv' + input: | + [1, "foo", null, "foo,\n\"bar\"\tbaz\u0000\\0"] + expected: | + "1\tfoo\t\tfoo,\\n\"bar\"\\tbaz\\0\\\\0" + +- name: format strings @tsv with string interpolation + args: + - '@tsv "\(.),\(. + .),\([nan])"' + input: | + [1, "foo", null, 2] + expected: | + "1\tfoo\t\t2,1\tfoo\t\t2\t1\tfoo\t\t2," + +- name: format strings @tsv error + args: + - '@tsv' + input: '0' + error: | + @tsv cannot be applied to: number (0) + +- name: format strings @tsv row error + args: + - '@tsv' + input: | + [1, "foo", null, ["foo"]] + error: | + @tsv cannot format an array including: array (["foo"]) + +- name: format strings @sh + args: + - '@sh' + input: | + null + true + [1, "f'o'o\u0000\\0", null, false] + expected: | + "null" + "true" + "1 'f'\\''o'\\''o\\0\\0' null false" + +- name: format strings @sh error + args: + - '@sh' + input: | + [{"foo": "<>"}] + error: | + @sh cannot format an array including: object ({"foo":"<>"}) + +- name: format strings @base64 + args: + - '@base64' + input: '{"foo":"bar"}' + expected: | + "eyJmb28iOiJiYXIifQ==" + +- name: format strings @base64 with string interpolation + args: + - '@base64 "\(.),\(. + .),\(. + . + .)"' + input: '[1,2]' + expected: | + "WzEsMl0=,WzEsMiwxLDJd,WzEsMiwxLDIsMSwyXQ==" + +- name: format strings @base64d + args: + - -R + - '@base64d' + input: 'eyJmb28iOiJiYXIifQ==' + expected: | + "{\"foo\":\"bar\"}" + +- name: format strings @base64d without padding + args: + - -R + - '@base64d' + input: 'eyJmb28iOiJiYXIifQ' + expected: | + "{\"foo\":\"bar\"}" + +- name: format strings @base64d with trailing characters after padding + args: + - -R + - '@base64d' + input: 'eyJmb28iOiJiYXIifQ=x=y' + expected: | + "{\"foo\":\"bar\"}" + +- name: format strings @base64d error + args: + - '@base64d' + input: | + ":" + error: | + @base64d cannot be applied to ":": illegal base64 data at input byte 0 + +- name: format strings not defined error + args: + - -n + - '@foo' + error: | + format not defined: @foo + exit_code: 5 + +- name: format strings not defined error with numeric characters + args: + - -n + - '@0' + error: | + format not defined: @0 + exit_code: 5 + +- name: format strings not defined error with optional operator + args: + - -n + - '@foo?, @bar"\(0|debug)"?, 1' + expected: | + 1 + error: | + ["DEBUG:",0] + +- name: destructuring alternative operator + args: + - '. as {$a} ?// [$a] ?// $a | $a' + input: '[1] [2] {"a":3} 4' + expected: | + 1 + 2 + 3 + 4 + +- name: destructuring alternative operator with error backtrack + args: + - -c + - '. as {$a} ?// [$a] ?// $a | if 1 < $a and $a < 4 then $a|error else $a end' + input: '[1] [2] {"a":3} 4' + expected: | + 1 + [2] + {"a":3} + 4 + +- name: destructuring alternative operator with break + args: + - -c + - 'label $out | . as {$a} ?// [$a] ?// $a | {$a}, break $out' + input: '[1] [2] {"a":3} 4' + expected: | + {"a":1} + {"a":2} + {"a":3} + {"a":4} + +- name: destructuring alternative operator with halt + args: + - -c + - 'label $out | . as {$a} ?// [$a] ?// $a | {$a}, halt' + input: '[1] [2] {"a":3} 4' + expected: | + {"a":1} + +- name: destructuring alternative operator with halt_error + args: + - -c + - '. as {$a} ?// [$a] ?// $a | {$a}, halt_error' + input: '[1]' + expected: | + {"a":1} + error: | + [1] + exit_code: 5 + +- name: destructuring alternative operator with multiple variables + args: + - -c + - '. as {$a, b: [$c, {$d}]} ?// [$a, {$b}, $e] ?// $f | [$a, $b, $c, $d, $e, $f]' + input: '{"a":1,"b":[2,{"d":3}]} [4,{"b":5,"c":6},7,8,9] "foo"' + expected: | + [1,null,2,3,null,null] + [4,5,null,null,7,null] + [null,null,null,null,null,"foo"] + +- name: destructuring alternative operator with array pattern against string + args: + - '. as [$a] ?// $a | $a' + input: 'null "" "abc" [] ["abc"]' + expected: | + null + "" + "abc" + null + "abc" + +- name: compact output option + args: + - -c + - '.' + input: '{ "foo": ["hello", "world"] }' + expected: | + {"foo":["hello","world"]} + +- name: compact output long option + args: + - --compact-output + - '.' + input: '{ "foo": ["hello", "world"] }' + expected: | + {"foo":["hello","world"]} + +- name: compact output long option with value + args: + - --compact-output=1 + - '.' + input: '0' + error: | + boolean flag `--compact-output' cannot have an argument + exit_code: 2 + +- name: raw string output short option + args: + - -r + - '.foo[]' + input: '{ "foo": ["hello", "world", "multiple\nline"] }' + expected: | + hello + world + multiple + line + +- name: raw string output long option + args: + - --raw-output + - '.foo[]' + input: '{ "foo": ["hello", "world", "multiple\nline"] }' + expected: | + hello + world + multiple + line + +- name: raw string output option against non-string + args: + - -r + - '.' + input: '{ "foo": "multiple\nline" }' + expected: | + { + "foo": "multiple\nline" + } + +- name: raw string output with nul delimiter option + args: + - --raw-output0 + - '.[]' + input: '["foo",[{},{}],1,[2,3]]' + expected: "foo\x00[\n {},\n {}\n]\x001\x00[\n 2,\n 3\n]\x00" + +- name: raw string output with nul delimiter option error with string containing nul character + args: + - --raw-output0 + - '"foo","bar","b\u0000a\u0000z","qux"' + input: 'null' + expected: "foo\x00bar\x00" + error: | + cannot output a string containing NUL character: "b\x00a\x00z" + +- name: raw string output without and with nul delimiter options + args: + - --raw-output + - --raw-output0 + - '.[]' + input: '["foo",1,2,3]' + expected: "foo\x001\x002\x003\x00" + +- name: raw string output with nul delimiter option with compact output option + args: + - --raw-output0 + - --compact-output + - '.[]' + input: '["foo",[{},{}],1,[2,3]]' + expected: "foo\x00[{},{}]\x001\x00[2,3]\x00" + +- name: join output option + args: + - -j + - '.[]' + input: '["foo",1,2,3]' + expected: "foo123" + +- name: join output long option + args: + - --join-output + - '.[]' + input: '["foo",[{},{}],1,[2,3]]' + expected: "foo[\n {},\n {}\n]1[\n 2,\n 3\n]" + +- name: join output option with raw string output with nul delimiter option + args: + - --raw-output0 + - --join-output + - '.[]' + input: '["foo",1,2,3]' + expected: "foo\x001\x002\x003\x00" + +- name: color output option + args: + - -C + - '., null, nan, infinite' + input: '{"x":"foo","y":128,"z":[null,false,true,3.14,"foo",{}]}' + expected: + "{\n\ + \ \e[34;1m\"x\"\e[0m: \e[32m\"foo\"\e[0m,\n\ + \ \e[34;1m\"y\"\e[0m: \e[36m128\e[0m,\n\ + \ \e[34;1m\"z\"\e[0m: [\n\ + \ \e[90mnull\e[0m,\n\ + \ \e[33mfalse\e[0m,\n\ + \ \e[33mtrue\e[0m,\n\ + \ \e[36m3.14\e[0m,\n\ + \ \e[32m\"foo\"\e[0m,\n\ + \ {}\n\ + \ ]\n\ + }\n\ + \e[90mnull\e[0m\n\ + \e[90mnull\e[0m\n\ + \e[36m1.7976931348623157e+308\e[0m\n" + +- name: color output option configured by environment variable + args: + - -C + - '., null, nan, infinite' + input: | + { "x": "foo", "y": 128, "z": [null, false, true, 3.14, "foo", {}] } + env: + - GOJQ_COLORS=31:32:33:34:35:36:41:42 + expected: + "\e[42m{\e[0m\n\ + \ \e[36m\"x\"\e[0m\e[42m:\e[0m \e[35m\"foo\"\e[0m\e[42m,\e[0m\n\ + \ \e[36m\"y\"\e[0m\e[42m:\e[0m \e[34m128\e[0m\e[42m,\e[0m\n\ + \ \e[36m\"z\"\e[0m\e[42m:\e[0m \e[41m[\e[0m\n\ + \ \e[31mnull\e[0m\e[41m,\e[0m\n\ + \ \e[32mfalse\e[0m\e[41m,\e[0m\n\ + \ \e[33mtrue\e[0m\e[41m,\e[0m\n\ + \ \e[34m3.14\e[0m\e[41m,\e[0m\n\ + \ \e[35m\"foo\"\e[0m\e[41m,\e[0m\n\ + \ \e[42m{\e[0m\e[42m}\e[0m\n\ + \ \e[41m]\e[0m\n\ + \e[42m}\e[0m\n\ + \e[31mnull\e[0m\n\ + \e[31mnull\e[0m\n\ + \e[34m1.7976931348623157e+308\e[0m\n" + +- name: color output option configured by environment variable with fewer colors + args: + - -C + - '.' + input: | + { "foo": [null, false, true, 3.14, "bar"] } + env: + - GOJQ_COLORS=31;1:::32;2 + expected: + "{\n\ + \ \"foo\": [\n\ + \ \e[31;1mnull\e[0m,\n\ + \ false,\n\ + \ true,\n\ + \ \e[32;2m3.14\e[0m,\n\ + \ \"bar\"\n\ + \ ]\n\ + }\n" + +- name: invalid colors environment variable + args: + - -C + - '.' + input: | + 0 + env: + - GOJQ_COLORS=30:31;;:32 + error: | + invalid color: "31;;" + +- name: color output long option + args: + - --color-output + - '.' + input: '{"foo":42}' + expected: + "{\n\ + \ \e[34;1m\"foo\"\e[0m: \e[36m42\e[0m\n\ + }\n" + +- name: color output with compact output option + args: + - -C + - -c + - '.' + input: '{"x":42,"y":3.14,"z":[10000000000000000000]}' + expected: + "{\ + \e[34;1m\"x\"\e[0m:\e[36m42\e[0m,\ + \e[34;1m\"y\"\e[0m:\e[36m3.14\e[0m,\ + \e[34;1m\"z\"\e[0m:[\e[36m10000000000000000000\e[0m]\ + }\n" + +- name: monochrome output option + args: + - -C + - -M + - '.' + input: '{"foo":42}' + expected: | + { + "foo": 42 + } + +- name: monochrome output long option + args: + - -C + - --monochrome-output + - '.' + input: '{"foo":42}' + expected: | + { + "foo": 42 + } + +- name: NO_COLOR environment variable + args: + - '.' + input: '{"foo":42}' + env: + - NO_COLOR=1 + expected: | + { + "foo": 42 + } + +- name: NO_COLOR environment variable with color output option + args: + - --color-output + - '.' + input: '{"foo":42}' + env: + - NO_COLOR=1 + expected: + "{\n\ + \ \e[34;1m\"foo\"\e[0m: \e[36m42\e[0m\n\ + }\n" + +- name: indent option + args: + - --indent + - '1' + - '.' + input: '{ "foo": ["hello", "world"] }' + expected: | + { + "foo": [ + "hello", + "world" + ] + } + +- name: indent option with value 0 + args: + - --indent + - '0' + - '.' + input: '{ "foo": ["hello", "world"] }' + expected: | + { + "foo": [ + "hello", + "world" + ] + } + +- name: indent option with deeply nested array + args: + - --indent + - '7' + - '.' + input: '{ "foo": ["hello", ["world", [[[[[[[[[]]]]]]]]]]] }' + expected: | + { + "foo": [ + "hello", + [ + "world", + [ + [ + [ + [ + [ + [ + [ + [ + [] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + } + +- name: indent option too many indentation count error + args: + - --indent=10 + - '.' + input: '{ "foo": { "baz": ["hello", "world"] } }' + error: | + too many indentation count: 10 + +- name: indent option negative indentation count error + args: + - --indent + - -1 + - '.' + input: '{ "foo": { "baz": ["hello", "world"] } }' + error: | + negative indentation count: -1 + +- name: indent option without argument + args: + - --indent + input: '0' + error: | + expected argument for flag `--indent' + exit_code: 2 + +- name: indent option parse error + args: + - --indent=foo + input: '0' + error: | + invalid argument for flag `--indent': strconv.Atoi: parsing "foo": invalid syntax + exit_code: 2 + +- name: tab option + args: + - --tab + - '.' + input: '{ "foo": { "baz": ["hello", ["world", [[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]] } }' + expected: | + { + "foo": { + "baz": [ + "hello", + [ + "world", + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + } + } + +- name: raw string input option + args: + - -R + - '.' + input: "null\nfalse\r\nfoo\nbar" + expected: | + "null" + "false\r" + "foo" + "bar" + +- name: raw string input long option + args: + - --raw-input + - '.' + input: "null\nfalse\r\nfoo\nbar\n" + expected: | + "null" + "false\r" + "foo" + "bar" + +- name: raw string input option error + args: + - -R + - '. / 2' + input: | + { + "foo": "bar" + } + error: | + cannot divide: string ("{") and number (2) + cannot divide: string (" \"foo\": \"bar\"") and number (2) + cannot divide: string ("}") and number (2) + +- name: null input value option + args: + - -n + - '[.]' + expected: | + [ + null + ] + +- name: null input value long option + args: + - --null-input + - '[.]' + expected: | + [ + null + ] + +- name: null input value option with compact output option + args: + - -n + - -c + - '[.,.]' + expected: | + [null,null] + +- name: null input value option with raw string input option + args: + - -n + - -R + - '.' + input: 'foo' + expected: | + null + +- name: slurp option + args: + - -s + - '.' + input: | + null + false + [] + { "foo": "bar" } + expected: | + [ + null, + false, + [], + { + "foo": "bar" + } + ] + +- name: slurp long option + args: + - --slurp + - --compact-output + - '.' + input: | + null + false + expected: | + [null,false] + +- name: slurp option with raw string input option + args: + - -s + - -R + - '.' + input: "null\nfalse\r\nfoo\nbar" + expected: | + "null\nfalse\r\nfoo\nbar" + +- name: slurp option with null input option + args: + - -s + - -n + - '.' + expected: | + null + +- name: slurp option with multiple json files + args: + - -s + - '.' + - 'testdata/1.json' + - 'testdata/2.json' + expected: | + [ + { + "foo": 10 + }, + [ + { + "bar": [] + } + ] + ] + +- name: slurp and raw input option with multiple json files + args: + - -R + - -s + - '.' + - 'testdata/1.json' + - 'testdata/2.json' + expected: | + "{\"foo\":10}\n[{\n\"bar\"\n:\n[]\n}]\n" + +- name: stream option + args: + - -c + - --stream + - '.' + input: | + 1 + 2 + [] + {} + [3] + {"x":1,"y":2} + [1,2,{"x":{"y":[3,[{}],{"z":{}}]}}] + {"a":"b","c":[1,[2,{}]],"d":null,"e":1.234,"x":{"y":{"z":[1,2],"q":2},"w":3},"z":[{}]} + expected: | + [[],1] + [[],2] + [[],[]] + [[],{}] + [[0],3] + [[0]] + [["x"],1] + [["y"],2] + [["y"]] + [[0],1] + [[1],2] + [[2,"x","y",0],3] + [[2,"x","y",1,0],{}] + [[2,"x","y",1,0]] + [[2,"x","y",2,"z"],{}] + [[2,"x","y",2,"z"]] + [[2,"x","y",2]] + [[2,"x","y"]] + [[2,"x"]] + [[2]] + [["a"],"b"] + [["c",0],1] + [["c",1,0],2] + [["c",1,1],{}] + [["c",1,1]] + [["c",1]] + [["d"],null] + [["e"],1.234] + [["x","y","z",0],1] + [["x","y","z",1],2] + [["x","y","z",1]] + [["x","y","q"],2] + [["x","y","q"]] + [["x","w"],3] + [["x","w"]] + [["z",0],{}] + [["z",0]] + [["z"]] + +- name: stream option with empty input + args: + - --stream + - '.' + input: '' + expected: '' + +- name: stream option with null input option + args: + - -n + - --stream + - '.' + input: '1' + expected: | + null + +- name: stream option with raw input option + args: + - -R + - --stream + - '.' + input: | + 1 + 2 + 3 + expected: | + "1" + "2" + "3" + +- name: stream option with slurp option + args: + - -c + - --stream + - --slurp + - '.[]' + input: '{"a":1} {"b":2} {"c":3}' + expected: | + [["a"],1] + [["a"]] + [["b"],2] + [["b"]] + [["c"],3] + [["c"]] + +- name: stream option with unterminated input + args: + - -c + - --stream + - '.' + input: '{"a":1,' + expected: | + [["a"],1] + error: | + invalid json: + {"a":1, + ^ unexpected EOF + +- name: yaml input option + args: + - --yaml-input + - '.' + input: | + foo: + bar: 42 + baz: | + a + b + qux: 100 + --- + foo: 1 + --- + bar + expected: | + { + "foo": { + "bar": 42, + "baz": "a\nb\n" + }, + "qux": 100 + } + { + "foo": 1 + } + "bar" + +- name: yaml file input + args: + - --yaml-input + - '.' + - 'testdata/1.yaml' + expected: | + { + "foo": { + "bar": 42, + "baz": "a\nb\n" + }, + "qux": 100 + } + { + "foo": 1 + } + "bar" + +- name: yaml input option with non-string keys + args: + - --yaml-input + - '.' + input: | + foo: + - 42.10: bar + expected: | + { + "foo": [ + { + "42.10": "bar" + } + ] + } + +- name: yaml input option with timestamps + args: + - --yaml-input + - '.' + input: | + foo: 2021-01-02 + bar: 2021-01-02t10:11:12Z + baz: "2021-01-02" + qux: !!timestamp 2021-01-02 + expected: | + { + "bar": "2021-01-02t10:11:12Z", + "baz": "2021-01-02", + "foo": "2021-01-02", + "qux": "2021-01-02" + } + +- name: yaml input option with large numbers + args: + - --yaml-input + - '.[] + 1' + input: | + - 9223372036854775807 + - 18446744073709551615 + - 18446744073709551616 + - 99999999999999999999999 + - 0b111111111111111111111111111111111111111111111111111111111111111 + - 0o777777777777777777777 + - 0x7FFFFFFFFFFFFFFF + expected: | + 9223372036854775808 + 18446744073709551616 + 18446744073709551617 + 100000000000000000000000 + 9223372036854775808 + 9223372036854775808 + 9223372036854775808 + +- name: yaml input option with slurp option + args: + - --yaml-input + - --slurp + - '.' + input: | + foo: + bar: 42 + baz: | + a + b + qux: 100 + --- + foo: 1 + --- + bar + expected: | + [ + { + "foo": { + "bar": 42, + "baz": "a\nb\n" + }, + "qux": 100 + }, + { + "foo": 1 + }, + "bar" + ] + +- name: yaml input option with hyphen for stdin + args: + - --yaml-input + - '.' + - '-' + input: | + foo: 1 + expected: | + { + "foo": 1 + } + +- name: yaml input option error + args: + - --yaml-input + - '.' + input: | + foo: 10 + 10 + bar: + error: | + invalid yaml: :2 + 2 | 10 + ^ could not find expected ':' + +- name: yaml input option error + args: + - --yaml-input + - '.' + input: | + foo: 10 + bar: ] + baz: + error: | + invalid yaml: :2 + 2 | bar: ] + ^ did not find expected node content + +- name: yaml input option multiple errors + args: + - --yaml-input + - '.' + input: | + foo: 1 + bar: 2 + foo: 3 + bar: 4 + error: | + invalid yaml: :3 + 3 | foo: 3 + ^ mapping key "foo" already defined at line 1 + +- name: yaml file input error + args: + - --yaml-input + - '.' + - 'testdata/2.yaml' + error: | + invalid yaml: testdata/2.yaml:5 + 5 | a: b: c + ^ mapping values are not allowed in this context + +- name: yaml file input error in dos format + args: + - --yaml-input + - '.' + - 'testdata/3.yaml' + error: | + invalid yaml: testdata/3.yaml:5 + 5 | a: b: c + ^ mapping values are not allowed in this context + +- name: yaml file input error in mac format + args: + - --yaml-input + - '.' + - 'testdata/4.yaml' + error: | + invalid yaml: testdata/4.yaml:5 + 5 | a: b: c + ^ mapping values are not allowed in this context + +- name: yaml output option + args: + - --yaml-output + - '.' + input: '{"foo": 128, "bar": {"baz": [1,"qux", []]}}' + expected: | + bar: + baz: + - 1 + - qux + - [] + foo: 128 + +- name: yaml output option with multiple values + args: + - --yaml-output + - '..' + input: '{"foo": 128, "bar": [1,2,"foo"]}' + expected: | + bar: + - 1 + - 2 + - foo + foo: 128 + --- + - 1 + - 2 + - foo + --- + 1 + --- + 2 + --- + foo + --- + 128 + +- name: yaml input and output option + args: + - --yaml-input + - --yaml-output + - '.' + input: | + foo: 42 + --- + foo: + bar: 42 + baz: | + a + b + qux: 100 + --- + foo: 1 + --- + bar + expected: | + foo: 42 + --- + foo: + bar: 42 + baz: | + a + b + qux: 100 + --- + foo: 1 + --- + bar + +- name: yaml output with indent option + args: + - --yaml-output + - --indent + - '8' + - '.' + input: '{ "foo": { "baz": ["hello", "world"] } }' + expected: | + foo: + baz: + - hello + - world + +- name: yaml output with indent option error + args: + - --yaml-output + - --indent + - '10' + - '.' + input: '{ "foo": { "baz": ["hello", "world"] } }' + error: | + too many indentation count: 10 + +- name: yaml output with tab option error + args: + - --yaml-output + - --tab + - '.' + input: '{"foo": 128, "bar": {"baz": [1,"qux", []]}}' + error: | + cannot use tabs for YAML output + +- name: source query from file + args: + - -f + - testdata/1.jq + input: '{"foo":{"bar":42}}' + expected: | + 42 + +- name: source query from file short option in clumped options + args: + - -cfr + - testdata/1.jq + input: '{"foo":{"bar":42}}' + expected: | + 42 + +- name: source query from file long option + args: + - --from-file + - testdata/1.jq + input: '{"foo":{"bar":42}}' + expected: | + 42 + +- name: source query from file with --args + args: + - --args + - --compact-output + - --null-input + - --from-file + - testdata/14.jq + - 1 + - 2 + - 3 + expected: | + {"named":{},"positional":["1","2","3"]} + +- name: invalid query + args: + - '>abc' + input: '{}' + error: | + invalid query: >abc + >abc + ^ unexpected token ">" + exit_code: 3 + +- name: invalid query + args: + - '.abc[' + input: '{}' + error: | + invalid query: .abc[ + .abc[ + ^ unexpected EOF + exit_code: 3 + +- name: invalid query + args: + - '[ .[] | { .id } ]' + input: '{}' + error: | + invalid query: [ .[] | { .id } ] + [ .[] | { .id } ] + ^ unexpected token ".id" + exit_code: 3 + +- name: invalid query + args: + - 'if 0then. else. end' + input: '{}' + error: | + invalid query: if 0then. else. end + if 0then. else. end + ^ invalid token "0t" + exit_code: 3 + +- name: invalid query + args: + - '.foo & .bar' + input: '{}' + error: | + invalid query: .foo & .bar + .foo & .bar + ^ unexpected token "&" + exit_code: 3 + +- name: invalid query + args: + - 'foo☆' + input: '{}' + error: | + invalid query: foo☆ + foo☆ + ^ unexpected token "☆" + exit_code: 3 + +- name: invalid query + args: + - "\\/" + input: '{}' + error: | + invalid query: \/ + \/ + ^ unexpected token "\\" + exit_code: 3 + +- name: invalid query + args: + - "{\n[]}" + input: '{}' + error: | + invalid query: :2 + 2 | []} + ^ unexpected token "[" + exit_code: 3 + +- name: invalid multiple line query + args: + - | + [ + .[] + | { id } } + ] + input: '{}' + error: | + invalid query: :3 + 3 | | { id } } + ^ unexpected token "}" + exit_code: 3 + +- name: invalid multiple line query in file + args: + - -f + - testdata/10.jq + error: | + invalid query: testdata/10.jq:5 + 5 | bar ] + ^ unexpected token "]" + exit_code: 3 + +- name: invalid multiple line query in dos format + args: + - -f + - testdata/11.jq + error: | + invalid query: testdata/11.jq:5 + 5 | bar ] + ^ unexpected token "]" + exit_code: 3 + +- name: invalid multiple line query in mac format + args: + - -f + - testdata/12.jq + error: | + invalid query: testdata/12.jq:5 + 5 | bar ] + ^ unexpected token "]" + exit_code: 3 + +- name: invalid query in file + args: + - -f + - testdata/2.jq + error: | + invalid query: testdata/2.jq:3 + 3 | bar, [] + ^ unexpected token "[" + exit_code: 3 + +- name: query file not found + args: + - -f + - testdata/3.jq + error: 'open testdata/3.jq:' + +- name: query file not specified + args: + - -f + error: | + expected a query file for flag `-f' + +- name: invalid json unexpected eof error + input: '{' + error: | + invalid json: + { + ^ unexpected EOF + +- name: invalid json unexpected eof error with multibyte characters + input: '{ "12345":' + error: | + invalid json: + { "12345": + ^ unexpected EOF + +- name: invalid json unexpected eof error with multiple lines + input: | + [0 + ,[ + error: | + invalid json: :2 + 2 | ,[ + ^ unexpected EOF + +- name: invalid json invalid character with multibyte characters + input: | + { + "12345" 100 + } + error: | + invalid json: :2 + 2 | "12345" 100 + ^ invalid character '1' after object key + +- name: invalid json string literal + input: | + { + "123": n + } + error: | + invalid json: :2 + 2 | "123": n + ^ invalid character '\n' in literal null (expecting 'u') + +- name: multiple json in input + input: '{}[]{"foo":10}{"bar":[]}' + expected: | + {} + [] + { + "foo": 10 + } + { + "bar": [] + } + +- name: hyphen for stdin + args: + - '.' + - '-' + input: '{}' + expected: | + {} + +- name: json file arguments + args: + - '.' + - 'testdata/1.json' + - 'testdata/2.json' + expected: | + { + "foo": 10 + } + [ + { + "bar": [] + } + ] + +- name: json file error json + args: + - '.' + - 'testdata/3.json' + error: | + invalid json: testdata/3.json + { + ^ unexpected EOF + +- name: json file error json + args: + - '.' + - 'testdata/4.json' + error: | + invalid json: testdata/4.json:3 + 3 | bar + ^ invalid character 'b' looking for beginning of value + +- name: json file error json + args: + - '.' + - 'testdata/8.json' + error: | + invalid json: testdata/8.json + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, , 1, 1, 1, 1, 1, 1, 1 + ^ invalid character ',' looking for beginning of value + +- name: json file error json + args: + - '.' + - 'testdata/9.json' + error: | + invalid json: testdata/9.json:3 + 3 | 0", "0", "0", "0", "0", "0", "0", , "0", "0"] + ^ invalid character ',' looking for beginning of value + +- name: json file error json with multibyte character in the error line + args: + - '.' + - 'testdata/5.json' + error: | # file contents is read by inputReader so the entire error line is shown unlike the following input json test + invalid json: testdata/5.json:3 + 3 | "abcde": :★★★★★★★★★★★★★★★ + ^ invalid character ':' looking for beginning of value + +- name: json input error json with multibyte character in the error line + args: + - '.' + input: | + { + "a": "This is a test for multibyte character in the error line of JSON. Since the first buffer size of JSON decoder is 512 (see encoding/json/stream.go) and the decoder stops reading bytes on encountering an error, the error line can be an invalid UTF-8 string even if the complete input is a valid UTF-8 string. Note that the error happens before 512 bytes and the corresponding line continues beyond 512 bytes with a multibyte character at the boundary.", + "abcde": :★★★★★★★★★★★★★★★ + error: | + invalid json: :3 + 3 | "abcde": :★★★★★★★★★★★★ + ^ invalid character ':' looking for beginning of value + +- name: json file not found error + args: + - '.' + - 'testdata/1.json' + - 'testdata/6.json' + - 'testdata/2.json' + expected: | + { + "foo": 10 + } + [ + { + "bar": [] + } + ] + error: 'open testdata/6.json:' + +- name: module directory option + args: + - -c + - -L + - 'testdata' + - 'include "4"; 1 | f | g' + input: '0' + expected: | + {"foo":[1,2,3,4,[1,2,{"foo":42}]]} + +- name: module directory option + args: + - -c + - -L + - 'testdata' + - 'import "4" as m1; 1 | m1::f | m1::g' + input: '0' + expected: | + {"foo":[1,2,3,4,[1,2,{"foo":42}]]} + +- name: module directory option + args: + - -c + - -L + - 'testdata' + - 'include "m1"; include "m3"; [f, g]' + input: '0' + expected: | + [42,43,44,45] + +- name: module directory option + args: + - -c + - --library-path + - 'testdata' + - 'import "7" as $foo; $foo, $foo::foo' + input: '0' + expected: | + [1,2,{"foo":42}] + [1,2,{"foo":42}] + +- name: module directory option + args: + - -c + - --library-path + - 'testdata' + - 'import "m1" as $x; $x, $x::x, $x[1].m1*100000000000000000000' + input: '0' + expected: | + [42,{"m1":42}] + [42,{"m1":42}] + 4200000000000000000000 + +- name: module directory option with argjson + args: + - -c + - -L + - 'testdata' + - --argjson + - 'x' + - '{}' + - 'import "m1" as $x; $x' + input: '0' + expected: | + [42,{"m1":42}] + +- name: module directory option parse error + args: + - 'include "m"; f' + input: '0' + error: 'compile error: module not found: "m"' + exit_code: 3 + +- name: module directory option function error + args: + - -L + - 'testdata' + - 'include "5"; i' + input: '0' + error: | + function not defined: i/0 + exit_code: 3 + +- name: module directory option variable error + args: + - -L + - 'testdata' + - 'include "4"; $x' + input: '0' + error: 'compile error: variable not defined: $x' + exit_code: 3 + +- name: module directory option variable name conflict + args: + - -c + - -L + - 'testdata' + - 'include "6"; include "13"; . as $x | $x, g, h' + input: '1' + expected: | + 1 + [1,2,{"foo":42}] + [[{"foo":10}],[1,2,{"foo":42}]] + +- name: module directory option json parse error + args: + - -Ltestdata + - 'import "4" as $x; $x' + input: '0' + error: | + compile error: invalid json: testdata/4.json:3 + 3 | bar + ^ invalid character 'b' looking for beginning of value + exit_code: 3 + +- name: module directory option with same alias name + args: + - -c + - -L=testdata + - 'include "4"; import "6" as bar; bar::g' + input: '1' + expected: | + [1,2,{"foo":42}] + +- name: module directory option without argument + args: + - -c + - -L + input: '0' + error: | + expected argument for flag `-L' + exit_code: 2 + +- name: search directory in query + args: + - 'include "m2" { search: "testdata/m2" }; g' + input: '0' + expected: | + 0 + 43 + 44 + +- name: empty module + args: + - -L + - 'testdata' + - 'include "8"; .' + input: '0' + expected: | + 0 + +- name: invalid query in a module + args: + - -L + - 'testdata' + - 'include "7"; .' + input: '0' + error: | + invalid query: testdata/7.jq:1 + 1 | def f: 1 + + ^ unexpected EOF + exit_code: 3 + +- name: module directive + args: + - -c + - -L + - 'testdata' + - 'include "9"; f(2)' + input: '3' + expected: | + 6 + +- name: module in query + args: + - -n + - 'module {}; .' + expected: | + null + +- name: modulemeta function + args: + - -c + - -L + - 'testdata' + - 'tostring | modulemeta | .defs |= sort' + input: '4 5 6 8 9' + expected: | + {"defs":["f/0","g/0"],"deps":[{"is_data":false,"relpath":"5"},{"as":"bar","is_data":false,"relpath":"6"}]} + {"defs":["h/0"],"deps":[{"as":"foo","is_data":false,"relpath":"6","test":"foo"}]} + {"defs":["g/0","i/0"],"deps":[{"as":"x","is_data":true,"relpath":"7"}]} + {"defs":[],"deps":[]} + {"defs":["f/1"],"deps":[],"description":"This is a description","name":"sample-module","x":[[],{},{"y":10,"z":20}],"y":{"z":[128,"",false,true,null,[[]]]}} + +- name: modulemeta function error + args: + - -c + - -L + - 'testdata' + - '"3" | modulemeta' + input: '0' + error: | + module not found: "3" + +- name: arg option + args: + - --arg + - 'foo' + - 'value 1' + - --arg + - 'bar' + - '42' + - '{ $foo, $bar }' + input: '123' + expected: | + { + "bar": "42", + "foo": "value 1" + } + +- name: arg option with function declaration + args: + - -n + - --arg + - 'foo' + - 'bar' + - 'def f: $foo; "baz" as $foo | ., f, $foo' + expected: | + null + "bar" + "baz" + +- name: arg option arguments count error + args: + - --arg + error: | + expected 2 arguments for flag `--arg' + exit_code: 2 + +- name: arg option arguments count error + args: + - --arg + - 'foo' + error: | + expected 2 arguments for flag `--arg' + exit_code: 2 + +- name: arg option variable name error + args: + - --arg + - 'f.oo' + - '10' + - '.' + error: | + invalid variable name: $f.oo + exit_code: 3 + +- name: argjson option + args: + - --argjson + - 'foo' + - '{ "foo": 42 }' + - --argjson + - 'bar' + - '10' + - '{ $foo, $bar }' + input: '123' + expected: | + { + "bar": 10, + "foo": { + "foo": 42 + } + } + +- name: argjson option with big integer + args: + - --argjson + - 'foo' + - '10000000000000000000000000000000' + - '$foo * 2' + input: '0' + expected: | + 20000000000000000000000000000000 + +- name: arg and argjson options + args: + - --arg + - 'x' + - '1' + - --argjson + - 'x' + - '1' + - --argjson + - 'y' + - '2' + - --arg + - 'y' + - '2' + - '{ $x, $y }' + input: '0' + expected: | + { + "x": "1", + "y": 2 + } + +- name: argjson option error + args: + - --argjson + - '0x' + - '0' + - '.' + error: | + invalid variable name: $0x + exit_code: 3 + +- name: argjson option parse error + args: + - --argjson + - 'foo' + - '{ foo }' + - '.' + error: | + invalid json: $foo + { foo } + ^ invalid character 'f' looking for beginning of object key string + +- name: slurpfile option + args: + - --slurpfile + - 'foo' + - 'testdata/7.json' + - '{ $foo }' + input: 'null' + expected: | + { + "foo": [ + 1, + 2, + { + "foo": 42 + } + ] + } + +- name: slurpfile option invalid json error + args: + - --slurpfile + - 'foo' + - 'testdata/1.jq' + - '{ $foo }' + input: 'null' + error: | + invalid json: testdata/1.jq + .foo.bar + ^ invalid character '.' looking for beginning of value + +- name: slurpfile option invalid file error + args: + - --slurpfile + - 'foo' + - '-' + - '{ $foo }' + input: 'null' + error: 'open -:' + +- name: rawfile option + args: + - --rawfile + - 'foo' + - 'testdata/7.json' + - '{ $foo }' + input: 'null' + expected: | + { + "foo": "1\n2\n{ \"foo\": 42 }\n" + } + +- name: rawfile option error + args: + - --rawfile + - 'foo' + - 'testdata/6.json' + - '{ $foo }' + input: 'null' + error: 'open testdata/6.json:' + +- name: $ARGS variable + args: + - --arg + - 'a' + - '1' + - --argjson + - 'b' + - '1' + - --slurpfile + - 'c' + - 'testdata/1.json' + - --rawfile + - 'd' + - 'testdata/1.json' + - '$ARGS | ., .named.a' + input: 'null' + expected: | + { + "named": { + "a": "1", + "b": 1, + "c": [ + { + "foo": 10 + } + ], + "d": "{\"foo\":10}\n" + }, + "positional": [] + } + "1" + +- name: $ARGS variable with args option + args: + - --arg + - 'foo' + - 'value' + - '$ARGS | ., .positional[]' + - --args + - '1' + - '"bar"' + - '{"x":1}' + input: '0' + expected: | + { + "named": { + "foo": "value" + }, + "positional": [ + "1", + "\"bar\"", + "{\"x\":1}" + ] + } + "1" + "\"bar\"" + "{\"x\":1}" + +- name: args option without argument + args: + - '$ARGS' + - --args + input: '0' + expected: | + { + "named": {}, + "positional": [] + } + +- name: query and options after args option + args: + - --args + - '$ARGS' + - 1 + - -n + - 2 + - -c + - -- + - 3 + - -- + - -c + input: '0' + expected: | + {"named":{},"positional":["1","2","3","--","-c"]} + +- name: double dash with args option + args: + - -c + - --args + - -- + - $ARGS + - -- + - 0 + input: '0' + expected: | + {"named":{},"positional":["--","0"]} + +- name: $ARGS variable with jsonargs option + args: + - '$ARGS | ., .positional[]' + - --arg + - 'foo' + - 'value' + - --jsonargs + - '1' + - '"bar"' + - '{"x":1}' + input: '0' + expected: | + { + "named": { + "foo": "value" + }, + "positional": [ + 1, + "bar", + { + "x": 1 + } + ] + } + 1 + "bar" + { + "x": 1 + } + +- name: jsonargs option without argument + args: + - '$ARGS' + - --jsonargs + input: '0' + expected: | + { + "named": {}, + "positional": [] + } + +- name: jsonargs option parse error + args: + - '$ARGS' + - --jsonargs + - '1' + - '"bar"' + - '{"x":}' + input: '0' + error: | + invalid json: --jsonargs + {"x":} + ^ invalid character '}' looking for beginning of value + +- name: args with jsonargs option + args: + - -c + - '$ARGS' + - --args + - 1 + - 2 + - --jsonargs + - 1 + - 2 + - --args + - 1 + - 2 + input: '0' + expected: | + {"named":{},"positional":["1","2",1,2,"1","2"]} + +- name: args with jsonargs option + args: + - -c + - '$ARGS' + - --args + - 1 + - 2 + - --jsonargs + - 1 + - 2 + - --args + - 1 + - 2 + - --jsonargs + - 1 + - 2 + input: '0' + expected: | + {"named":{},"positional":["1","2",1,2,"1","2",1,2]} + +- name: exit status option with null result + args: + - -e + - '.' + input: 'null' + expected: | + null + exit_code: 1 + +- name: exit status option with empty function + args: + - -e + - 'empty' + input: 'null' + exit_code: 4 + +- name: exit status long option with false result + args: + - --exit-status + - '.' + input: 'false' + expected: | + false + exit_code: 1 + +- name: exit status long option with true result + args: + - --exit-status + - 'not' + input: 'false' + expected: | + true + +- name: exit status long option with multiple results + args: + - --exit-status + - '1,not,2' + input: 'false' + expected: | + 1 + true + 2 + +- name: exit status long option with error function + args: + - --exit-status + - 'error' + input: 'false' + error: | + error: false + exit_code: 5 + +- name: exit status long option with error function + args: + - --exit-status + - 'error' + input: 'null' + error: | + error: null + +- name: exit status long option with halt function + args: + - --exit-status + - 'halt' + input: 'false' + exit_code: 0 + +- name: exit status long option with halt_error function + args: + - --exit-status + - 'halt_error(0)' + input: 'null' + exit_code: 0 + +- name: exit status long option with halt_error function + args: + - --exit-status + - 'halt_error(42)' + input: 'null' + exit_code: 42 + +- name: query with leading hyphen + args: + - '-.' + input: '1' + expected: | + -1 + +- name: query with leading hyphen + args: + - '-j0' + input: '0 1 2' + expected: | + -1 + -0.7651976865579666 + -0.22389077914123567 + +- name: query with leading hyphen after a flag + args: + - -r + - '-.' + input: '1' + expected: | + -1 + +- name: double dash + args: + - '--' + - '.' + input: '1' + expected: | + 1 + +- name: double dash with query with leading hyphen + args: + - '--' + - '-j' + input: 'null' + error: | + compile error: function not defined: j/0 + exit_code: 3 + +- name: double double dash + args: + - '--' + - '--' + - '.' + input: '1' + error: | + invalid query: -- + -- + ^ unexpected EOF + exit_code: 3 + +- name: short clumped options + args: + - -cRr + - '.' + input: 'foo' + expected: | + foo + +- name: invalid short option error + args: + - -q + - '.' + error: | + unknown flag `q' + exit_code: 2 + +- name: invalid short option error in clumped options + args: + - -nqe + - '.' + error: | + unknown flag `q' + exit_code: 2 + +- name: invalid long option error + args: + - --qqq + - '.' + error: | + unknown flag `--qqq' + exit_code: 2 diff --git a/compiler.go b/compiler.go new file mode 100644 index 0000000..6b51e0e --- /dev/null +++ b/compiler.go @@ -0,0 +1,1706 @@ +package gojq + +import ( + "cmp" + "context" + "errors" + "fmt" + "slices" + "strconv" + "strings" + "sync" +) + +type compiler struct { + moduleLoader ModuleLoader + environLoader func() []string + variables []string + customFuncs map[string]function + inputIter Iter + codes []*code + codeinfos []codeinfo + builtinScope *scopeinfo + scopes []*scopeinfo + scopecnt int + regexpCache sync.Map +} + +// Code is a compiled jq query. +type Code struct { + variables []string + codes []*code + codeinfos []codeinfo +} + +// Run runs the code with the variable values (which should be in the +// same order as the given variables using [WithVariables]) and returns +// a result iterator. +// +// It is safe to call this method in goroutines, to reuse a compiled [*Code]. +func (c *Code) Run(v any, values ...any) Iter { + return c.RunWithContext(context.Background(), v, values...) +} + +// RunWithContext runs the code with context. +func (c *Code) RunWithContext(ctx context.Context, v any, values ...any) Iter { + if len(values) > len(c.variables) { + return NewIter(&tooManyVariableValuesError{}) + } else if len(values) < len(c.variables) { + return NewIter(&expectedVariableError{c.variables[len(values)]}) + } + return newEnv(ctx).execute(c, v, values...) +} + +type scopeinfo struct { + variables []*varinfo + funcs []*funcinfo + id int + depth int + variablecnt int +} + +type varinfo struct { + name string + index [2]int + depth int +} + +type funcinfo struct { + name string + pc int + argcnt int +} + +// Compile compiles a query. +func Compile(q *Query, options ...CompilerOption) (*Code, error) { + c := &compiler{} + for _, opt := range options { + opt(c) + } + c.builtinScope = c.newScope() + scope := c.newScope() + c.scopes = []*scopeinfo{scope} + setscope := c.lazy(func() *code { + return &code{op: opscope, v: [3]int{scope.id, scope.variablecnt, 0}} + }) + for _, name := range c.variables { + if !newLexer(name).validVarName() { + return nil, &variableNameError{name} + } + c.appendCodeInfo(name) + c.append(&code{op: opstore, v: c.pushVariable(name)}) + } + if c.moduleLoader != nil { + if moduleLoader, ok := c.moduleLoader.(interface { + LoadInitModules() ([]*Query, error) + }); ok { + qs, err := moduleLoader.LoadInitModules() + if err != nil { + return nil, err + } + for _, q := range qs { + if err := c.compileModule(q, ""); err != nil { + return nil, err + } + } + } + } + if err := c.compile(q); err != nil { + return nil, err + } + setscope() + c.optimizeTailRec() + c.optimizeCodeOps() + return &Code{ + variables: c.variables, + codes: c.codes, + codeinfos: c.codeinfos, + }, nil +} + +func (c *compiler) compile(q *Query) error { + for _, i := range q.Imports { + if err := c.compileImport(i); err != nil { + return err + } + } + if err := c.compileQuery(q); err != nil { + return err + } + c.append(&code{op: opret}) + return nil +} + +func (c *compiler) compileImport(i *Import) error { + path, alias := cmp.Or(i.ImportPath, i.IncludePath), i.ImportAlias + if c.moduleLoader == nil { + return fmt.Errorf("cannot load module: %q", path) + } + if strings.HasPrefix(alias, "$") { + var vals any + var err error + if moduleLoader, ok := c.moduleLoader.(interface { + LoadJSONWithMeta(string, map[string]any) (any, error) + }); ok { + if vals, err = moduleLoader.LoadJSONWithMeta(path, i.Meta.ToValue()); err != nil { + return err + } + } else if moduleLoader, ok := c.moduleLoader.(interface { + LoadJSON(string) (any, error) + }); ok { + if vals, err = moduleLoader.LoadJSON(path); err != nil { + return err + } + } else { + return fmt.Errorf("module not found: %q", path) + } + c.append(&code{op: oppush, v: vals}) + c.append(&code{op: opstore, v: c.pushVariable(alias)}) + c.append(&code{op: oppush, v: vals}) + c.append(&code{op: opstore, v: c.pushVariable(alias + "::" + alias[1:])}) + return nil + } + var q *Query + var err error + if moduleLoader, ok := c.moduleLoader.(interface { + LoadModuleWithMeta(string, map[string]any) (*Query, error) + }); ok { + if q, err = moduleLoader.LoadModuleWithMeta(path, i.Meta.ToValue()); err != nil { + return err + } + } else if moduleLoader, ok := c.moduleLoader.(interface { + LoadModule(string) (*Query, error) + }); ok { + if q, err = moduleLoader.LoadModule(path); err != nil { + return err + } + } + c.appendCodeInfo("module " + path) + if err = c.compileModule(q, alias); err != nil { + return err + } + c.appendCodeInfo("end of module " + path) + return nil +} + +func (c *compiler) compileModule(q *Query, alias string) error { + scope := c.scopes[len(c.scopes)-1] + scope.depth++ + defer func(l int) { + scope.depth-- + scope.variables = scope.variables[:l] + }(len(scope.variables)) + if alias != "" { + defer func(l int) { + for _, f := range scope.funcs[l:] { + f.name = alias + "::" + f.name + } + }(len(scope.funcs)) + } + for _, i := range q.Imports { + if err := c.compileImport(i); err != nil { + return err + } + } + for _, fd := range q.FuncDefs { + if err := c.compileFuncDef(fd, false); err != nil { + return err + } + } + return nil +} + +func (c *compiler) newVariable() [2]int { + return c.createVariable("") +} + +func (c *compiler) pushVariable(name string) [2]int { + s := c.scopes[len(c.scopes)-1] + for _, v := range s.variables { + if v.name == name && v.depth == s.depth { + return v.index + } + } + return c.createVariable(name) +} + +func (c *compiler) createVariable(name string) [2]int { + s := c.scopes[len(c.scopes)-1] + v := [2]int{s.id, s.variablecnt} + s.variablecnt++ + s.variables = append(s.variables, &varinfo{name, v, s.depth}) + return v +} + +func (c *compiler) lookupVariable(name string) ([2]int, error) { + for i := len(c.scopes) - 1; i >= 0; i-- { + s := c.scopes[i] + for j := len(s.variables) - 1; j >= 0; j-- { + if w := s.variables[j]; w.name == name { + return w.index, nil + } + } + } + return [2]int{}, &variableNotFoundError{name} +} + +func (c *compiler) lookupFuncOrVariable(name string) (*funcinfo, *varinfo) { + for i, isFunc := len(c.scopes)-1, name[0] != '$'; i >= 0; i-- { + s := c.scopes[i] + if isFunc { + for j := len(s.funcs) - 1; j >= 0; j-- { + if f := s.funcs[j]; f.name == name && f.argcnt == 0 { + return f, nil + } + } + } + for j := len(s.variables) - 1; j >= 0; j-- { + if v := s.variables[j]; v.name == name { + return nil, v + } + } + } + return nil, nil +} + +func (c *compiler) lookupBuiltin(name string, argcnt int) *funcinfo { + s := c.builtinScope + for i := len(s.funcs) - 1; i >= 0; i-- { + if f := s.funcs[i]; f.name == name && f.argcnt == argcnt { + return f + } + } + return nil +} + +func (c *compiler) appendBuiltin(name string, argcnt int) func() { + setjump := c.lazy(func() *code { + return &code{op: opjump, v: len(c.codes)} + }) + c.appendCodeInfo(name) + c.builtinScope.funcs = append( + c.builtinScope.funcs, + &funcinfo{name, len(c.codes), argcnt}, + ) + return func() { + setjump() + c.appendCodeInfo("end of " + name) + } +} + +func (c *compiler) newScope() *scopeinfo { + i := c.scopecnt // do not use len(c.scopes) because it pops + c.scopecnt++ + return &scopeinfo{id: i} +} + +func (c *compiler) newScopeDepth() func() { + scope := c.scopes[len(c.scopes)-1] + l, m := len(scope.variables), len(scope.funcs) + scope.depth++ + return func() { + scope.depth-- + scope.variables = scope.variables[:l] + scope.funcs = scope.funcs[:m] + } +} + +func (c *compiler) compileFuncDef(e *FuncDef, builtin bool) error { + var scope *scopeinfo + if builtin { + scope = c.builtinScope + } else { + scope = c.scopes[len(c.scopes)-1] + } + defer c.lazy(func() *code { + return &code{op: opjump, v: len(c.codes)} + })() + c.appendCodeInfo(e.Name) + scope.funcs = append(scope.funcs, &funcinfo{e.Name, len(c.codes), len(e.Args)}) + defer func(scopes []*scopeinfo, variables []string) { + c.scopes, c.variables = scopes, variables + }(c.scopes, c.variables) + c.variables = c.variables[len(c.variables):] + scope = c.newScope() + if builtin { + c.scopes = []*scopeinfo{c.builtinScope, scope} + } else { + c.scopes = append(c.scopes, scope) + } + defer c.lazy(func() *code { + return &code{op: opscope, v: [3]int{scope.id, scope.variablecnt, len(e.Args)}} + })() + if len(e.Args) > 0 { + type varIndex struct { + name string + index [2]int + } + vis := make([]varIndex, 0, len(e.Args)) + v := c.newVariable() + c.append(&code{op: opstore, v: v}) + for _, arg := range e.Args { + if arg[0] == '$' { + c.appendCodeInfo(arg[1:]) + w := c.createVariable(arg[1:]) + c.append(&code{op: opstore, v: w}) + vis = append(vis, varIndex{arg, w}) + } else { + c.appendCodeInfo(arg) + c.append(&code{op: opstore, v: c.createVariable(arg)}) + } + } + for _, w := range vis { + c.append(&code{op: opload, v: v}) + c.append(&code{op: opexpbegin}) + c.append(&code{op: opload, v: w.index}) + c.append(&code{op: opcallpc}) + c.appendCodeInfo(w.name) + c.append(&code{op: opstore, v: c.pushVariable(w.name)}) + c.append(&code{op: opexpend}) + } + c.append(&code{op: opload, v: v}) + } + if err := c.compile(e.Body); err != nil { + return err + } + c.appendCodeInfo("end of " + e.Name) + return nil +} + +func (c *compiler) compileQuery(e *Query) error { + for _, fd := range e.FuncDefs { + if err := c.compileFuncDef(fd, false); err != nil { + return err + } + } + if e.Term != nil { + return c.compileTerm(e.Term) + } + switch e.Op { + case Operator(0): + return errors.New(`missing query (try ".")`) + case OpPipe: + if len(e.Patterns) > 0 { + return c.compileBind(e.Left, e.Right, e.Patterns) + } + if err := c.compileQuery(e.Left); err != nil { + return err + } + return c.compileQuery(e.Right) + case OpComma: + return c.compileComma(e.Left, e.Right) + case OpAlt: + return c.compileAlt(e.Left, e.Right) + case OpAssign, OpModify, OpUpdateAdd, OpUpdateSub, + OpUpdateMul, OpUpdateDiv, OpUpdateMod, OpUpdateAlt: + return c.compileQueryUpdate(e.Left, e.Right, e.Op) + case OpOr: + return c.compileIf( + &If{ + Cond: e.Left, + Then: &Query{Term: &Term{Type: TermTypeTrue}}, + Else: &Query{Term: &Term{Type: TermTypeIf, If: &If{ + Cond: e.Right, + Then: &Query{Term: &Term{Type: TermTypeTrue}}, + Else: &Query{Term: &Term{Type: TermTypeFalse}}, + }}}, + }, + ) + case OpAnd: + return c.compileIf( + &If{ + Cond: e.Left, + Then: &Query{Term: &Term{Type: TermTypeIf, If: &If{ + Cond: e.Right, + Then: &Query{Term: &Term{Type: TermTypeTrue}}, + Else: &Query{Term: &Term{Type: TermTypeFalse}}, + }}}, + Else: &Query{Term: &Term{Type: TermTypeFalse}}, + }, + ) + default: + return c.compileCall( + e.Op.getFunc(), + []*Query{e.Left, e.Right}, + ) + } +} + +func (c *compiler) compileComma(l, r *Query) error { + setfork := c.lazy(func() *code { + return &code{op: opfork, v: len(c.codes)} + }) + if err := c.compileQuery(l); err != nil { + return err + } + defer c.lazy(func() *code { + return &code{op: opjump, v: len(c.codes)} + })() + setfork() + return c.compileQuery(r) +} + +func (c *compiler) compileAlt(l, r *Query) error { + c.append(&code{op: oppush, v: false}) + found := c.newVariable() + c.append(&code{op: opstore, v: found}) + setfork := c.lazy(func() *code { + return &code{op: opfork, v: len(c.codes)} // opload found + }) + if err := c.compileQuery(l); err != nil { + return err + } + c.append(&code{op: opdup}) + c.append(&code{op: opjumpifnot, v: len(c.codes) + 4}) // oppop + c.append(&code{op: oppush, v: true}) // found some value + c.append(&code{op: opstore, v: found}) + defer c.lazy(func() *code { + return &code{op: opjump, v: len(c.codes)} + })() + c.append(&code{op: oppop}) + c.append(&code{op: opbacktrack}) + setfork() + c.append(&code{op: opload, v: found}) + c.append(&code{op: opjumpifnot, v: len(c.codes) + 3}) + c.append(&code{op: opbacktrack}) // if found, backtrack + c.append(&code{op: oppop}) + return c.compileQuery(r) +} + +func (c *compiler) compileQueryUpdate(l, r *Query, op Operator) error { + switch op { + case OpAssign: + // optimize assignment operator with constant indexing and slicing + // .foo.[0].[1:2] = f => setpath(["foo",0,{"start":1,"end":2}]; f) + if xs := l.toIndices(nil); xs != nil { + // ref: compileCall + v := c.newVariable() + c.append(&code{op: opstore, v: v}) + c.append(&code{op: opload, v: v}) + if err := c.compileQuery(r); err != nil { + return err + } + c.append(&code{op: oppush, v: xs}) + c.append(&code{op: opload, v: v}) + c.append(&code{op: opcall, v: [3]any{internalFuncs["setpath"].callback, 2, "setpath"}}) + return nil + } + fallthrough + case OpModify: + return c.compileFunc( + &Func{ + Name: op.getFunc(), + Args: []*Query{l, r}, + }, + ) + default: + name := "$%0" + c.append(&code{op: opdup}) + if err := c.compileQuery(r); err != nil { + return err + } + c.append(&code{op: opstore, v: c.pushVariable(name)}) + return c.compileFunc( + &Func{ + Name: "_modify", + Args: []*Query{ + l, + {Term: &Term{ + Type: TermTypeFunc, + Func: &Func{ + Name: op.getFunc(), + Args: []*Query{ + {Term: &Term{Type: TermTypeIdentity}}, + {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: name}}}, + }, + }, + }}, + }, + }, + ) + } +} + +func (c *compiler) compileBind(l, r *Query, patterns []*Pattern) error { + defer c.newScopeDepth()() + c.append(&code{op: opdup}) + c.append(&code{op: opexpbegin}) + if err := c.compileQuery(l); err != nil { + return err + } + var pc int + var vs [][2]int + for i, p := range patterns { + var pcc int + var err error + if i < len(patterns)-1 { + defer c.lazy(func() *code { + return &code{op: opforkalt, v: pcc} + })() + } + if 0 < i { + for _, v := range vs { + c.append(&code{op: oppush, v: nil}) + c.append(&code{op: opstore, v: v}) + } + } + if vs, err = c.compilePattern(vs[:0], p); err != nil { + return err + } + if i < len(patterns)-1 { + defer c.lazy(func() *code { + return &code{op: opjump, v: pc} + })() + pcc = len(c.codes) + } + } + if len(patterns) > 1 { + pc = len(c.codes) + } + if len(patterns) == 1 && c.codes[len(c.codes)-2].op == opexpbegin { + c.codes[len(c.codes)-2].op = opnop + } else { + c.append(&code{op: opexpend}) + } + return c.compileQuery(r) +} + +func (c *compiler) compilePattern(vs [][2]int, p *Pattern) ([][2]int, error) { + var err error + c.appendCodeInfo(p) + if p.Name != "" { + v := c.pushVariable(p.Name) + c.append(&code{op: opstore, v: v}) + return append(vs, v), nil + } else if len(p.Array) > 0 { + v := c.newVariable() + c.append(&code{op: opstore, v: v}) + for i, p := range p.Array { + c.append(&code{op: opload, v: v}) + c.append(&code{op: opindexarray, v: i}) + if vs, err = c.compilePattern(vs, p); err != nil { + return nil, err + } + } + return vs, nil + } else if len(p.Object) > 0 { + v := c.newVariable() + c.append(&code{op: opstore, v: v}) + for _, kv := range p.Object { + var key, name string + c.append(&code{op: opload, v: v}) + if key = kv.Key; key != "" { + if key[0] == '$' { + key, name = key[1:], key + } + } else if kv.KeyString != nil { + if key = kv.KeyString.Str; key == "" { + if err := c.compileString(kv.KeyString, nil); err != nil { + return nil, err + } + } + } else if kv.KeyQuery != nil { + if err := c.compileQuery(kv.KeyQuery); err != nil { + return nil, err + } + } + if key != "" { + c.append(&code{op: opindex, v: key}) + } else { + c.append(&code{op: opload, v: v}) + c.append(&code{op: oppush, v: nil}) + // ref: compileCall + c.append(&code{op: opcall, v: [3]any{internalFuncs["_index"].callback, 2, "_index"}}) + } + if name != "" { + if kv.Val != nil { + c.append(&code{op: opdup}) + } + if vs, err = c.compilePattern(vs, &Pattern{Name: name}); err != nil { + return nil, err + } + } + if kv.Val != nil { + if vs, err = c.compilePattern(vs, kv.Val); err != nil { + return nil, err + } + } + } + return vs, nil + } else { + return nil, fmt.Errorf("invalid pattern: %s", p) + } +} + +func (c *compiler) compileIf(e *If) error { + c.appendCodeInfo(e) + c.append(&code{op: opdup}) // duplicate the value for then or else clause + c.append(&code{op: opexpbegin}) + pc := len(c.codes) + f := c.newScopeDepth() + if err := c.compileQuery(e.Cond); err != nil { + return err + } + f() + if pc == len(c.codes) { + c.codes = c.codes[:pc-1] + } else { + c.append(&code{op: opexpend}) + } + pcc := len(c.codes) + setjumpifnot := c.lazy(func() *code { + return &code{op: opjumpifnot, v: len(c.codes)} // skip then clause + }) + f = c.newScopeDepth() + if err := c.compileQuery(e.Then); err != nil { + return err + } + f() + defer c.lazy(func() *code { + return &code{op: opjump, v: len(c.codes)} + })() + setjumpifnot() + if len(e.Elif) > 0 { + return c.compileIf(&If{e.Elif[0].Cond, e.Elif[0].Then, e.Elif[1:], e.Else}) + } + if e.Else != nil { + defer c.newScopeDepth()() + defer func() { + // optimize constant results + // opdup, ..., opjumpifnot, opconst, opjump, opconst + // => opnop, ..., opjumpifnot, oppush, opjump, oppush + if pcc+4 == len(c.codes) && + c.codes[pcc+1] != nil && c.codes[pcc+1].op == opconst && + c.codes[pcc+3] != nil && c.codes[pcc+3].op == opconst { + c.codes[pc-2].op = opnop + c.codes[pcc+1].op = oppush + c.codes[pcc+3].op = oppush + } + }() + return c.compileQuery(e.Else) + } + return nil +} + +func (c *compiler) compileTry(e *Try) error { + c.appendCodeInfo(e) + setforktrybegin := c.lazy(func() *code { + return &code{op: opforktrybegin, v: len(c.codes)} + }) + f := c.newScopeDepth() + if err := c.compileQuery(e.Body); err != nil { + return err + } + f() + c.append(&code{op: opforktryend}) + defer c.lazy(func() *code { + return &code{op: opjump, v: len(c.codes)} + })() + setforktrybegin() + if e.Catch != nil { + defer c.newScopeDepth()() + return c.compileQuery(e.Catch) + } + c.append(&code{op: opbacktrack}) + return nil +} + +func (c *compiler) compileReduce(e *Reduce) error { + c.appendCodeInfo(e) + defer c.newScopeDepth()() + c.append(&code{op: opdup}) + v := c.newVariable() + f := c.newScopeDepth() + if err := c.compileQuery(e.Start); err != nil { + return err + } + f() + c.append(&code{op: opstore, v: v}) + setfork := c.lazy(func() *code { + return &code{op: opfork, v: len(c.codes)} + }) + if err := c.compileQuery(e.Query); err != nil { + return err + } + if _, err := c.compilePattern(nil, e.Pattern); err != nil { + return err + } + c.append(&code{op: opload, v: v}) + f = c.newScopeDepth() + if err := c.compileQuery(e.Update); err != nil { + return err + } + f() + c.append(&code{op: opstore, v: v}) + c.append(&code{op: opbacktrack}) + setfork() + c.append(&code{op: oppop}) + c.append(&code{op: opload, v: v}) + return nil +} + +func (c *compiler) compileForeach(e *Foreach) error { + c.appendCodeInfo(e) + defer c.newScopeDepth()() + c.append(&code{op: opdup}) + v := c.newVariable() + f := c.newScopeDepth() + if err := c.compileQuery(e.Start); err != nil { + return err + } + f() + c.append(&code{op: opstore, v: v}) + if err := c.compileQuery(e.Query); err != nil { + return err + } + if _, err := c.compilePattern(nil, e.Pattern); err != nil { + return err + } + c.append(&code{op: opload, v: v}) + f = c.newScopeDepth() + if err := c.compileQuery(e.Update); err != nil { + return err + } + f() + c.append(&code{op: opdup}) + c.append(&code{op: opstore, v: v}) + if e.Extract != nil { + defer c.newScopeDepth()() + return c.compileQuery(e.Extract) + } + return nil +} + +func (c *compiler) compileLabel(e *Label) error { + c.appendCodeInfo(e) + v := c.pushVariable("$%" + e.Ident[1:]) + c.append(&code{op: opforklabel, v: v}) + return c.compileQuery(e.Body) +} + +func (c *compiler) compileBreak(label string) error { + v, err := c.lookupVariable("$%" + label[1:]) + if err != nil { + return &breakError{label, nil} + } + c.append(&code{op: oppop}) + c.append(&code{op: opload, v: v}) + c.append(&code{op: opcall, v: [3]any{funcBreak(label), 0, "_break"}}) + return nil +} + +func funcBreak(label string) func(any, []any) any { + return func(v any, _ []any) any { + return &breakError{label, v} + } +} + +func (c *compiler) compileTerm(e *Term) error { + if len(e.SuffixList) > 0 { + s := e.SuffixList[len(e.SuffixList)-1] + t := *e // clone without changing e + t.SuffixList = t.SuffixList[:len(e.SuffixList)-1] + return c.compileTermSuffix(&t, s) + } + switch e.Type { + case TermTypeIdentity: + return nil + case TermTypeRecurse: + return c.compileFunc(&Func{Name: "recurse"}) + case TermTypeNull: + c.append(&code{op: opconst, v: nil}) + return nil + case TermTypeTrue: + c.append(&code{op: opconst, v: true}) + return nil + case TermTypeFalse: + c.append(&code{op: opconst, v: false}) + return nil + case TermTypeIndex: + return c.compileIndex(&Term{Type: TermTypeIdentity}, e.Index) + case TermTypeFunc: + return c.compileFunc(e.Func) + case TermTypeObject: + return c.compileObject(e.Object) + case TermTypeArray: + return c.compileArray(e.Array) + case TermTypeNumber: + c.append(&code{op: opconst, v: toNumber(e.Number)}) + return nil + case TermTypeUnary: + return c.compileUnary(e.Unary) + case TermTypeFormat: + return c.compileFormat(e.Format, e.Str) + case TermTypeString: + return c.compileString(e.Str, nil) + case TermTypeIf: + return c.compileIf(e.If) + case TermTypeTry: + return c.compileTry(e.Try) + case TermTypeReduce: + return c.compileReduce(e.Reduce) + case TermTypeForeach: + return c.compileForeach(e.Foreach) + case TermTypeLabel: + return c.compileLabel(e.Label) + case TermTypeBreak: + return c.compileBreak(e.Break) + case TermTypeQuery: + defer c.newScopeDepth()() + return c.compileQuery(e.Query) + default: + panic("invalid term: " + e.String()) + } +} + +func (c *compiler) compileIndex(e *Term, x *Index) error { + if k := x.toIndexKey(); k != nil { + if err := c.compileTerm(e); err != nil { + return err + } + c.appendCodeInfo(x) + c.append(&code{op: opindex, v: k}) + return nil + } + c.appendCodeInfo(x) + if x.Str != nil { + return c.compileCall("_index", []*Query{{Term: e}, {Term: &Term{Type: TermTypeString, Str: x.Str}}}) + } + if !x.IsSlice { + return c.compileCall("_index", []*Query{{Term: e}, x.Start}) + } + if x.Start == nil { + return c.compileCall("_slice", []*Query{{Term: e}, x.End, {Term: &Term{Type: TermTypeNull}}}) + } + if x.End == nil { + return c.compileCall("_slice", []*Query{{Term: e}, {Term: &Term{Type: TermTypeNull}}, x.Start}) + } + return c.compileCall("_slice", []*Query{{Term: e}, x.End, x.Start}) +} + +func (c *compiler) compileFunc(e *Func) error { + if len(e.Args) == 0 { + if f, v := c.lookupFuncOrVariable(e.Name); f != nil { + return c.compileCallPc(f, e.Args) + } else if v != nil { + if e.Name[0] == '$' { + c.append(&code{op: oppop}) + c.append(&code{op: opload, v: v.index}) + } else { + c.append(&code{op: opload, v: v.index}) + c.append(&code{op: opcallpc}) + } + return nil + } else if e.Name == "$ENV" || e.Name == "env" { + env := make(map[string]any) + if c.environLoader != nil { + for _, kv := range c.environLoader() { + if k, v, ok := strings.Cut(kv, "="); ok && k != "" { + env[k] = v + } + } + } + c.append(&code{op: opconst, v: env}) + return nil + } else if e.Name[0] == '$' { + return &variableNotFoundError{e.Name} + } + } else { + for i := len(c.scopes) - 1; i >= 0; i-- { + s := c.scopes[i] + for j := len(s.funcs) - 1; j >= 0; j-- { + if f := s.funcs[j]; f.name == e.Name && f.argcnt == len(e.Args) { + return c.compileCallPc(f, e.Args) + } + } + } + } + if f := c.lookupBuiltin(e.Name, len(e.Args)); f != nil { + return c.compileCallPc(f, e.Args) + } + if fds, ok := builtinFuncDefs[e.Name]; ok { + var compiled bool + for _, fd := range fds { + if len(fd.Args) == len(e.Args) { + if err := c.compileFuncDef(fd, true); err != nil { + return err + } + compiled = true + break + } + } + if !compiled { + switch e.Name { + case "_assign": + c.compileAssign() + case "_modify": + c.compileModify() + case "_last": + c.compileLast() + } + } + if f := c.lookupBuiltin(e.Name, len(e.Args)); f != nil { + return c.compileCallPc(f, e.Args) + } + } + if fn, ok := internalFuncs[e.Name]; ok && fn.accept(len(e.Args)) { + switch e.Name { + case "empty": + c.append(&code{op: opbacktrack}) + return nil + case "path": + c.append(&code{op: oppathbegin}) + if err := c.compileCall(e.Name, e.Args); err != nil { + return err + } + c.codes[len(c.codes)-1] = &code{op: oppathend} + return nil + case "builtins": + return c.compileCallInternal( + [3]any{c.funcBuiltins, 0, e.Name}, + e.Args, + true, + -1, + ) + case "input": + if c.inputIter == nil { + return &inputNotAllowedError{} + } + return c.compileCallInternal( + [3]any{c.funcInput, 0, e.Name}, + e.Args, + true, + -1, + ) + case "modulemeta": + return c.compileCallInternal( + [3]any{c.funcModulemeta, 0, e.Name}, + e.Args, + true, + -1, + ) + case "debug": + setfork := c.lazy(func() *code { + return &code{op: opfork, v: len(c.codes)} + }) + if err := c.compileQuery(e.Args[0]); err != nil { + return err + } + if err := c.compileFunc(&Func{Name: "debug"}); err != nil { + if _, ok := err.(*funcNotFoundError); ok { + err = &funcNotFoundError{e} + } + return err + } + c.append(&code{op: opbacktrack}) + setfork() + return nil + case "_match": + return c.compileCallInternal( + [3]any{c.funcMatch, len(e.Args), e.Name}, + e.Args, + true, + -1, + ) + default: + return c.compileCall(e.Name, e.Args) + } + } + if fn, ok := c.customFuncs[e.Name]; ok && fn.accept(len(e.Args)) { + if err := c.compileCallInternal( + [3]any{fn.callback, len(e.Args), e.Name}, + e.Args, + true, + -1, + ); err != nil { + return err + } + if fn.iter { + c.append(&code{op: opiter}) + } + return nil + } + return &funcNotFoundError{e} +} + +// Appends the compiled code for the assignment operator (`=`) to maximize +// performance. Originally the operator was implemented as follows. +// +// def _assign(p; $x): reduce path(p) as $q (.; setpath($q; $x)); +// +// To overcome the difficulty of reducing allocations on `setpath`, we use the +// `allocator` type and track the allocated addresses during the reduction. +func (c *compiler) compileAssign() { + defer c.appendBuiltin("_assign", 2)() + scope := c.newScope() + v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1} + x, a := [2]int{scope.id, 2}, [2]int{scope.id, 3} + // Cannot reuse v, p due to backtracking in x. + w, q := [2]int{scope.id, 4}, [2]int{scope.id, 5} + c.appends( + &code{op: opscope, v: [3]int{scope.id, 6, 2}}, + &code{op: opstore, v: v}, // def _assign(p; $x): + &code{op: opstore, v: p}, + &code{op: opstore, v: x}, + &code{op: opload, v: v}, + &code{op: opexpbegin}, + &code{op: opload, v: x}, + &code{op: opcallpc}, + &code{op: opstore, v: x}, + &code{op: opexpend}, + &code{op: oppush, v: nil}, + &code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}}, + &code{op: opstore, v: a}, + &code{op: opload, v: v}, + &code{op: opfork, v: len(c.codes) + 30}, // reduce [L1] + &code{op: opdup}, + &code{op: opstore, v: w}, + &code{op: oppathbegin}, // path(p) + &code{op: opload, v: p}, + &code{op: opcallpc}, + &code{op: opload, v: w}, + &code{op: oppathend}, + &code{op: opstore, v: q}, // as $q (.; + &code{op: opload, v: a}, // setpath($q; $x) + &code{op: opload, v: x}, + &code{op: opload, v: q}, + &code{op: opload, v: w}, + &code{op: opcall, v: [3]any{funcSetpathWithAllocator, 3, "_setpath"}}, + &code{op: opstore, v: w}, + &code{op: opbacktrack}, // ); + &code{op: oppop}, // [L1] + &code{op: opload, v: w}, + &code{op: opret}, + ) +} + +// Appends the compiled code for the update-assignment operator (`|=`) to +// maximize performance. We use the `allocator` type, just like `_assign/2`. +func (c *compiler) compileModify() { + defer c.appendBuiltin("_modify", 2)() + scope := c.newScope() + v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1} + f, d := [2]int{scope.id, 2}, [2]int{scope.id, 3} + a, l := [2]int{scope.id, 4}, [2]int{scope.id, 5} + c.appends( + &code{op: opscope, v: [3]int{scope.id, 6, 2}}, + &code{op: opstore, v: v}, // def _modify(p; f): + &code{op: opstore, v: p}, + &code{op: opstore, v: f}, + &code{op: oppush, v: []any{}}, + &code{op: opstore, v: d}, + &code{op: oppush, v: nil}, + &code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}}, + &code{op: opstore, v: a}, + &code{op: opload, v: v}, + &code{op: opfork, v: len(c.codes) + 39}, // reduce [L1] + &code{op: oppathbegin}, // path(p) + &code{op: opload, v: p}, + &code{op: opcallpc}, + &code{op: opload, v: v}, + &code{op: oppathend}, + &code{op: opstore, v: p}, // as $p (.; + &code{op: opforklabel, v: l}, // label $l | + &code{op: opload, v: v}, // + &code{op: opfork, v: len(c.codes) + 36}, // [L2] + &code{op: oppop}, // (getpath($p) | + &code{op: opload, v: a}, + &code{op: opload, v: p}, + &code{op: opload, v: v}, + &code{op: opcall, v: [3]any{internalFuncs["getpath"].callback, 1, "getpath"}}, + &code{op: opload, v: f}, // f) + &code{op: opcallpc}, + &code{op: opload, v: p}, // setpath($p; ...) + &code{op: opload, v: v}, + &code{op: opcall, v: [3]any{funcSetpathWithAllocator, 3, "_setpath"}}, + &code{op: opstore, v: v}, + &code{op: opload, v: v}, // ., break $l + &code{op: opfork, v: len(c.codes) + 34}, // [L4] + &code{op: opjump, v: len(c.codes) + 38}, // [L3] + &code{op: opload, v: l}, // [L4] + &code{op: opcall, v: [3]any{funcBreak(""), 0, "_break"}}, + &code{op: opload, v: p}, // append $p to $d [L2] + &code{op: opappend, v: d}, // + &code{op: opbacktrack}, // ) | [L3] + &code{op: oppop}, // delpaths($d); [L1] + &code{op: opload, v: a}, + &code{op: opload, v: d}, + &code{op: opload, v: v}, + &code{op: opcall, v: [3]any{funcDelpathsWithAllocator, 2, "_delpaths"}}, + &code{op: opret}, + ) +} + +// Appends the compiled code for the `last/1` function to +// maximize performance avoiding unnecessary boxing. +func (c *compiler) compileLast() { + defer c.appendBuiltin("_last", 1)() + scope := c.newScope() + v, g, x := [2]int{scope.id, 0}, [2]int{scope.id, 1}, [2]int{scope.id, 2} + c.appends( + &code{op: opscope, v: [3]int{scope.id, 3, 1}}, + &code{op: opstore, v: v}, + &code{op: opstore, v: g}, + &code{op: oppush, v: true}, // $x = true + &code{op: opstore, v: x}, + &code{op: opload, v: v}, + &code{op: opfork, v: len(c.codes) + 13}, // reduce [L1] + &code{op: opload, v: g}, // g + &code{op: opcallpc}, + &code{op: opstore, v: v}, // as $v ( + &code{op: oppush, v: false}, // $x = false + &code{op: opstore, v: x}, + &code{op: opbacktrack}, // ); + &code{op: oppop}, // [L1] + &code{op: opload, v: x}, // if $x then $v else empty end + &code{op: opjumpifnot, v: len(c.codes) + 17}, + &code{op: opbacktrack}, + &code{op: opload, v: v}, + &code{op: opret}, + ) +} + +func (c *compiler) funcBuiltins(any, []any) any { + type funcNameArity struct { + name string + arity int + } + var xs []*funcNameArity + for _, fds := range builtinFuncDefs { + for _, fd := range fds { + if fd.Name[0] != '_' { + xs = append(xs, &funcNameArity{fd.Name, len(fd.Args)}) + } + } + } + for name, fn := range internalFuncs { + if name[0] != '_' { + for i, cnt := 0, fn.argcount; cnt > 0; i, cnt = i+1, cnt>>1 { + if cnt&1 > 0 { + xs = append(xs, &funcNameArity{name, i}) + } + } + } + } + for name, fn := range c.customFuncs { + if name[0] != '_' { + for i, cnt := 0, fn.argcount; cnt > 0; i, cnt = i+1, cnt>>1 { + if cnt&1 > 0 { + xs = append(xs, &funcNameArity{name, i}) + } + } + } + } + slices.SortFunc(xs, func(x, y *funcNameArity) int { + return cmp.Or(cmp.Compare(x.name, y.name), cmp.Compare(x.arity, y.arity)) + }) + ys := make([]any, len(xs)) + for i, x := range xs { + ys[i] = x.name + "/" + strconv.Itoa(x.arity) + } + return ys +} + +func (c *compiler) funcInput(any, []any) any { + v, ok := c.inputIter.Next() + if !ok { + return errors.New("break") + } + return v +} + +func (c *compiler) funcModulemeta(v any, _ []any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"modulemeta", v} + } + if c.moduleLoader == nil { + return fmt.Errorf("cannot load module: %q", s) + } + var q *Query + var err error + if moduleLoader, ok := c.moduleLoader.(interface { + LoadModuleWithMeta(string, map[string]any) (*Query, error) + }); ok { + if q, err = moduleLoader.LoadModuleWithMeta(s, nil); err != nil { + return err + } + } else if moduleLoader, ok := c.moduleLoader.(interface { + LoadModule(string) (*Query, error) + }); ok { + if q, err = moduleLoader.LoadModule(s); err != nil { + return err + } + } + meta := q.Meta.ToValue() + if meta == nil { + meta = make(map[string]any) + } + meta["defs"] = listModuleDefs(q) + meta["deps"] = listModuleDeps(q) + return meta +} + +func listModuleDefs(q *Query) []any { + type funcNameArity struct { + name string + arity int + } + var xs []*funcNameArity + for _, fd := range q.FuncDefs { + if fd.Name[0] != '_' { + xs = append(xs, &funcNameArity{fd.Name, len(fd.Args)}) + } + } + slices.SortFunc(xs, func(x, y *funcNameArity) int { + return cmp.Or(cmp.Compare(x.name, y.name), cmp.Compare(x.arity, y.arity)) + }) + defs := make([]any, len(xs)) + for i, x := range xs { + defs[i] = x.name + "/" + strconv.Itoa(x.arity) + } + return defs +} + +func listModuleDeps(q *Query) []any { + deps := make([]any, len(q.Imports)) + for j, i := range q.Imports { + v := i.Meta.ToValue() + if v == nil { + v = make(map[string]any) + } + v["relpath"] = cmp.Or(i.ImportPath, i.IncludePath) + if i.ImportAlias != "" { + v["as"] = strings.TrimPrefix(i.ImportAlias, "$") + } + v["is_data"] = strings.HasPrefix(i.ImportAlias, "$") + deps[j] = v + } + return deps +} + +func (c *compiler) funcMatch(v any, args []any) any { + return funcMatch(v, args[0], args[1], args[2], &c.regexpCache) +} + +func (c *compiler) compileObject(e *Object) error { + c.appendCodeInfo(e) + if len(e.KeyVals) == 0 { + c.append(&code{op: opconst, v: map[string]any{}}) + return nil + } + defer c.newScopeDepth()() + v := c.newVariable() + c.append(&code{op: opstore, v: v}) + pc := len(c.codes) + for _, kv := range e.KeyVals { + if err := c.compileObjectKeyVal(v, kv); err != nil { + return err + } + } + c.append(&code{op: opobject, v: len(e.KeyVals)}) + // optimize constant objects + l := len(e.KeyVals) + if pc+l*3+1 != len(c.codes) { + return nil + } + for i := range l { + if c.codes[pc+i*3].op != oppush || + c.codes[pc+i*3+1].op != opload || + c.codes[pc+i*3+2].op != opconst { + return nil + } + } + w := make(map[string]any, l) + for i := range l { + w[c.codes[pc+i*3].v.(string)] = c.codes[pc+i*3+2].v + } + c.codes[pc-1] = &code{op: opconst, v: w} + c.codes = c.codes[:pc] + return nil +} + +func (c *compiler) compileObjectKeyVal(v [2]int, kv *ObjectKeyVal) error { + if key := kv.Key; key != "" { + if key[0] == '$' { + if kv.Val == nil { // {$foo} == {foo:$foo} + c.append(&code{op: oppush, v: key[1:]}) + } + c.append(&code{op: opload, v: v}) + if err := c.compileFunc(&Func{Name: key}); err != nil { + return err + } + } else { + c.append(&code{op: oppush, v: key}) + if kv.Val == nil { // {foo} == {foo:.foo} + c.append(&code{op: opload, v: v}) + c.append(&code{op: opindex, v: key}) + } + } + } else if key := kv.KeyString; key != nil { + if key.Queries == nil { + c.append(&code{op: oppush, v: key.Str}) + if kv.Val == nil { // {"foo"} == {"foo":.["foo"]} + c.append(&code{op: opload, v: v}) + c.append(&code{op: opindex, v: key.Str}) + } + } else { + c.append(&code{op: opload, v: v}) + if err := c.compileString(key, nil); err != nil { + return err + } + if kv.Val == nil { + c.append(&code{op: opdup}) + c.append(&code{op: opload, v: v}) + c.append(&code{op: oppush, v: nil}) + // ref: compileCall + c.append(&code{op: opcall, v: [3]any{internalFuncs["_index"].callback, 2, "_index"}}) + } + } + } else if kv.KeyQuery != nil { + c.append(&code{op: opload, v: v}) + f := c.newScopeDepth() + if err := c.compileQuery(kv.KeyQuery); err != nil { + return err + } + f() + } + if kv.Val != nil { + c.append(&code{op: opload, v: v}) + if err := c.compileQuery(kv.Val); err != nil { + return err + } + } + return nil +} + +func (c *compiler) compileArray(e *Array) error { + c.appendCodeInfo(e) + if e.Query == nil { + c.append(&code{op: opconst, v: []any{}}) + return nil + } + c.append(&code{op: oppush, v: []any{}}) + arr := c.newVariable() + c.append(&code{op: opstore, v: arr}) + pc := len(c.codes) + setfork := c.lazy(func() *code { + return &code{op: opfork, v: len(c.codes)} + }) + defer c.newScopeDepth()() + if err := c.compileQuery(e.Query); err != nil { + return err + } + c.append(&code{op: opappend, v: arr}) + c.append(&code{op: opbacktrack}) + setfork() + c.append(&code{op: oppop}) + c.append(&code{op: opload, v: arr}) + if e.Query.Op == OpPipe { + return nil + } + // optimize constant arrays + if (len(c.codes)-pc)%3 != 0 { + return nil + } + l := (len(c.codes) - pc - 3) / 3 + for i := range l { + if c.codes[pc+i].op != opfork || + c.codes[pc+i*2+l].op != opconst || + (i < l-1 && c.codes[pc+i*2+l+1].op != opjump) { + return nil + } + } + v := make([]any, l) + for i := range l { + v[i] = c.codes[pc+i*2+l].v + } + c.codes[pc-2] = &code{op: opconst, v: v} + c.codes = c.codes[:pc-1] + return nil +} + +func (c *compiler) compileUnary(e *Unary) error { + c.appendCodeInfo(e) + if v := e.toNumber(); v != nil { + c.append(&code{op: opconst, v: v}) + return nil + } + if err := c.compileTerm(e.Term); err != nil { + return err + } + switch e.Op { + case OpAdd: + return c.compileCall("_plus", nil) + case OpSub: + return c.compileCall("_negate", nil) + default: + return fmt.Errorf("unexpected operator in Unary: %s", e.Op) + } +} + +func (c *compiler) compileFormat(format string, str *String) error { + f := formatToFunc(format) + if f == nil { + f = &Func{ + Name: "format", + Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: format[1:]}}}}, + } + } + if str == nil { + return c.compileFunc(f) + } + return c.compileString(str, f) +} + +func formatToFunc(format string) *Func { + switch format { + case "@text": + return &Func{Name: "tostring"} + case "@json": + return &Func{Name: "tojson"} + case "@html": + return &Func{Name: "_tohtml"} + case "@uri": + return &Func{Name: "_touri"} + case "@urid": + return &Func{Name: "_tourid"} + case "@csv": + return &Func{Name: "_tocsv"} + case "@tsv": + return &Func{Name: "_totsv"} + case "@sh": + return &Func{Name: "_tosh"} + case "@base64": + return &Func{Name: "_tobase64"} + case "@base64d": + return &Func{Name: "_tobase64d"} + default: + return nil + } +} + +func (c *compiler) compileString(s *String, f *Func) error { + if s.Queries == nil { + c.append(&code{op: opconst, v: s.Str}) + return nil + } + if f == nil { + f = &Func{Name: "tostring"} + } + var q *Query + for _, e := range s.Queries { + if e.Term.Str == nil { + e = &Query{Left: e, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: f}}} + } + if q == nil { + q = e + } else { + q = &Query{Left: q, Op: OpAdd, Right: e} + } + } + return c.compileQuery(q) +} + +func (c *compiler) compileTermSuffix(e *Term, s *Suffix) error { + if s.Index != nil { + return c.compileIndex(e, s.Index) + } else if s.Iter { + if err := c.compileTerm(e); err != nil { + return err + } + c.append(&code{op: opiter}) + return nil + } else if s.Optional { + if len(e.SuffixList) > 0 { + if u := e.SuffixList[len(e.SuffixList)-1].toTerm(); u != nil { + // no need to clone (ref: compileTerm) + e.SuffixList = e.SuffixList[:len(e.SuffixList)-1] + if err := c.compileTerm(e); err != nil { + return err + } + e = u + } + } + return c.compileTry(&Try{Body: &Query{Term: e}}) + } else { + return fmt.Errorf("invalid suffix: %s", s) + } +} + +func (c *compiler) compileCall(name string, args []*Query) error { + fn := internalFuncs[name] + var indexing int + switch name { + case "_index", "_slice": + indexing = 1 + case "getpath": + indexing = 0 + default: + indexing = -1 + } + if err := c.compileCallInternal( + [3]any{fn.callback, len(args), name}, + args, + true, + indexing, + ); err != nil { + return err + } + if fn.iter { + c.append(&code{op: opiter}) + } + return nil +} + +func (c *compiler) compileCallPc(fn *funcinfo, args []*Query) error { + return c.compileCallInternal(fn.pc, args, false, -1) +} + +func (c *compiler) compileCallInternal( + fn any, args []*Query, internal bool, indexing int, +) error { + if len(args) == 0 { + c.append(&code{op: opcall, v: fn}) + return nil + } + v := c.newVariable() + c.append(&code{op: opstore, v: v}) + if indexing >= 0 { + c.append(&code{op: opexpbegin}) + } + for i := len(args) - 1; i >= 0; i-- { + pc := len(c.codes) + 1 // skip opjump (ref: compileFuncDef) + name := "lambda:" + strconv.Itoa(pc) + if err := c.compileFuncDef(&FuncDef{Name: name, Body: args[i]}, false); err != nil { + return err + } + if internal { + switch len(c.codes) - pc { + case 2: // optimize identity argument (opscope, opret) + j := len(c.codes) - 3 + c.codes[j] = &code{op: opload, v: v} + c.codes = c.codes[:j+1] + s := c.scopes[len(c.scopes)-1] + s.funcs = s.funcs[:len(s.funcs)-1] + c.deleteCodeInfo(name) + case 3: // optimize one instruction argument (opscope, opX, opret) + j := len(c.codes) - 4 + if c.codes[j+2].op == opconst { + c.codes[j] = &code{op: oppush, v: c.codes[j+2].v} + c.codes = c.codes[:j+1] + } else { + c.codes[j] = &code{op: opload, v: v} + c.codes[j+1] = c.codes[j+2] + c.codes = c.codes[:j+2] + } + s := c.scopes[len(c.scopes)-1] + s.funcs = s.funcs[:len(s.funcs)-1] + c.deleteCodeInfo(name) + default: + c.append(&code{op: opload, v: v}) + c.append(&code{op: oppushpc, v: pc}) + c.append(&code{op: opcallpc}) + } + } else { + c.append(&code{op: oppushpc, v: pc}) + } + if i == indexing { + if c.codes[len(c.codes)-2].op == opexpbegin { + c.codes[len(c.codes)-2] = c.codes[len(c.codes)-1] + c.codes = c.codes[:len(c.codes)-1] + } else { + c.append(&code{op: opexpend}) + } + } + } + if indexing > 0 { + c.append(&code{op: oppush, v: nil}) + } else { + c.append(&code{op: opload, v: v}) + } + c.append(&code{op: opcall, v: fn}) + return nil +} + +func (c *compiler) append(code *code) { + c.codes = append(c.codes, code) +} + +func (c *compiler) appends(codes ...*code) { + c.codes = append(c.codes, codes...) +} + +func (c *compiler) lazy(f func() *code) func() { + i := len(c.codes) + c.codes = append(c.codes, nil) + return func() { c.codes[i] = f() } +} + +func (c *compiler) optimizeTailRec() { + var pcs []int + scopes := map[int]bool{} +L: + for i, code := range c.codes { + switch code.op { + case opscope: + pcs = append(pcs, i) + if v := code.v.([3]int); v[2] == 0 { + scopes[i] = v[1] == 0 + } + case opcall: + var canjump bool + if j, ok := code.v.(int); !ok || + len(pcs) == 0 || pcs[len(pcs)-1] != j { + break + } else if canjump, ok = scopes[j]; !ok { + break + } + for j := i + 1; j < len(c.codes); { + switch c.codes[j].op { + case opjump: + j = c.codes[j].v.(int) + case opret: + if canjump { + code.op = opjump + code.v = pcs[len(pcs)-1] + 1 + } else { + code.op = opcallrec + } + fallthrough + default: + continue L + } + } + case opret: + if len(pcs) == 0 { + break L + } + pcs = pcs[:len(pcs)-1] + } + } +} + +func (c *compiler) optimizeCodeOps() { + for i, next := len(c.codes)-1, (*code)(nil); i >= 0; i-- { + code := c.codes[i] + switch code.op { + case oppush, opdup, opload: + switch next.op { + case oppop: + code.op = opnop + next.op = opnop + case opconst: + code.op = opnop + next.op = oppush + } + case opjump, opjumpifnot: + if j := code.v.(int); j-1 == i { + code.op = opnop + } else if next = c.codes[j]; next.op == opjump { + code.v = next.v + } + } + next = code + } +} diff --git a/func.go b/func.go new file mode 100644 index 0000000..81dbe31 --- /dev/null +++ b/func.go @@ -0,0 +1,2187 @@ +package gojq + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "iter" + "maps" + "math" + "math/big" + "net/url" + "reflect" + "regexp" + "slices" + "strconv" + "strings" + "sync" + "time" + "unicode" + "unicode/utf8" + + "github.com/itchyny/timefmt-go" +) + +//go:generate go run -modfile=go.dev.mod _tools/gen_builtin.go -i builtin.jq -o builtin.go +var builtinFuncDefs map[string][]*FuncDef + +const ( + argcount0 = 1 << iota + argcount1 + argcount2 + argcount3 +) + +type function struct { + argcount int + iter bool + callback func(any, []any) any +} + +func (fn function) accept(cnt int) bool { + return fn.argcount&(1<= 0 { + return v + } + return negate(v) + case float64: + return math.Abs(v) + case *big.Int: + if v.Sign() >= 0 { + return v + } + return new(big.Int).Abs(v) + case json.Number: + if !strings.HasPrefix(v.String(), "-") { + return v + } + return v[1:] + default: + return &func0TypeError{"abs", v} + } +} + +func funcLength(v any) any { + switch v := v.(type) { + case nil: + return 0 + case int: + if v >= 0 { + return v + } + return negate(v) + case float64: + return math.Abs(v) + case *big.Int: + if v.Sign() >= 0 { + return v + } + return new(big.Int).Abs(v) + case json.Number: + if !strings.HasPrefix(v.String(), "-") { + return v + } + return v[1:] + case string: + return len([]rune(v)) + case []any: + return len(v) + case map[string]any: + return len(v) + default: + return &func0TypeError{"length", v} + } +} + +func funcUtf8ByteLength(v any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"utf8bytelength", v} + } + return len(s) +} + +func funcKeys(v any) any { + switch v := v.(type) { + case []any: + w := make([]any, len(v)) + for i := range v { + w[i] = i + } + return w + case map[string]any: + w := make([]any, len(v)) + for i, k := range keys(v) { + w[i] = k + } + return w + default: + return &func0TypeError{"keys", v} + } +} + +func keys(v map[string]any) []string { + w := make([]string, len(v)) + var i int + for k := range v { + w[i] = k + i++ + } + slices.Sort(w) + return w +} + +func values(v any) ([]any, bool) { + switch v := v.(type) { + case []any: + return v, true + case map[string]any: + vs := make([]any, len(v)) + for i, k := range keys(v) { + vs[i] = v[k] + } + return vs, true + default: + return nil, false + } +} + +func funcHas(v, x any) any { + switch v := v.(type) { + case []any: + if x, ok := toInt(x); ok { + return 0 <= x && x < len(v) + } + case map[string]any: + if x, ok := x.(string); ok { + _, ok := v[x] + return ok + } + case nil: + return false + } + return &func1TypeError{"has", v, x} +} + +func funcAdd(v any) any { + vs, ok := values(v) + if !ok { + return &func0TypeError{"add", v} + } + return add(slices.Values(vs)) +} + +func add(xs iter.Seq[any]) any { + var v any + for x := range xs { + switch x := x.(type) { + case nil: + continue + case string: + switch w := v.(type) { + case nil: + var sb strings.Builder + sb.WriteString(x) + v = &sb + continue + case *strings.Builder: + w.WriteString(x) + continue + } + case []any: + switch w := v.(type) { + case nil: + v = slices.Clone(x) + continue + case []any: + v = append(w, x...) + continue + } + case map[string]any: + switch w := v.(type) { + case nil: + v = maps.Clone(x) + continue + case map[string]any: + maps.Copy(w, x) + continue + } + } + if sb, ok := v.(*strings.Builder); ok { + v = sb.String() + } + v = funcOpAdd(nil, v, x) + if err, ok := v.(error); ok { + return err + } + } + if sb, ok := v.(*strings.Builder); ok { + v = sb.String() + } + return v +} + +func funcToBoolean(v any) any { + switch v := v.(type) { + case bool: + return v + case string: + switch v { + case "true": + return true + case "false": + return false + default: + return &func0WrapError{"toboolean", v, errors.New("invalid boolean")} + } + default: + return &func0TypeError{"toboolean", v} + } +} + +func funcToNumber(v any) any { + switch v := v.(type) { + case int, float64, *big.Int, json.Number: + return v + case string: + if !newLexer(v).validNumber() { + return &func0WrapError{"tonumber", v, errors.New("invalid number")} + } + return toNumber(v) + default: + return &func0TypeError{"tonumber", v} + } +} + +func toNumber(v string) any { + return parseNumber(json.Number(v)) +} + +func funcToString(v any) any { + if s, ok := v.(string); ok { + return s + } + return funcToJSON(v) +} + +func funcType(v any) any { + return TypeOf(v) +} + +func funcReverse(v any) any { + vs, ok := v.([]any) + if !ok { + return &func0TypeError{"reverse", v} + } + ws := make([]any, len(vs)) + for i, v := range vs { + ws[len(ws)-i-1] = v + } + return ws +} + +func funcContains(v, x any) any { + return binopTypeSwitch(v, x, + func(l, r int) any { return l == r }, + func(l, r float64) any { return l == r }, + func(l, r *big.Int) any { return l.Cmp(r) == 0 }, + func(l, r string) any { return strings.Contains(l, r) }, + func(l, r []any) any { + R: + for _, r := range r { + for _, l := range l { + if funcContains(l, r) == true { + continue R + } + } + return false + } + return true + }, + func(l, r map[string]any) any { + if len(l) < len(r) { + return false + } + for k, r := range r { + if l, ok := l[k]; !ok || funcContains(l, r) != true { + return false + } + } + return true + }, + func(l, r any) any { + if l == r { + return true + } + return &func1TypeError{"contains", l, r} + }, + ) +} + +func funcInside(v, x any) any { + return funcContains(x, v) +} + +func funcIndices(v, x any) any { + return indexFunc("indices", v, x, indices) +} + +func indices(vs, xs []any) any { + rs := []any{} + if len(xs) == 0 { + return rs + } + for i := range len(vs) - len(xs) + 1 { + if Compare(vs[i:i+len(xs)], xs) == 0 { + rs = append(rs, i) + } + } + return rs +} + +func funcIndex(v, x any) any { + return indexFunc("index", v, x, func(vs, xs []any) any { + if len(xs) == 0 { + return nil + } + for i := range len(vs) - len(xs) + 1 { + if Compare(vs[i:i+len(xs)], xs) == 0 { + return i + } + } + return nil + }) +} + +func funcRindex(v, x any) any { + return indexFunc("rindex", v, x, func(vs, xs []any) any { + if len(xs) == 0 { + return nil + } + for i := len(vs) - len(xs); i >= 0; i-- { + if Compare(vs[i:i+len(xs)], xs) == 0 { + return i + } + } + return nil + }) +} + +func indexFunc(name string, v, x any, f func(_, _ []any) any) any { + switch v := v.(type) { + case nil: + return nil + case []any: + switch x := x.(type) { + case []any: + return f(v, x) + default: + return f(v, []any{x}) + } + case string: + if x, ok := x.(string); ok { + return f(explode(v), explode(x)) + } + return &func1TypeError{name, v, x} + default: + return &func1TypeError{name, v, x} + } +} + +func funcStartsWith(v, x any) any { + s, ok := v.(string) + if !ok { + return &func1TypeError{"startswith", v, x} + } + t, ok := x.(string) + if !ok { + return &func1TypeError{"startswith", v, x} + } + return strings.HasPrefix(s, t) +} + +func funcEndsWith(v, x any) any { + s, ok := v.(string) + if !ok { + return &func1TypeError{"endswith", v, x} + } + t, ok := x.(string) + if !ok { + return &func1TypeError{"endswith", v, x} + } + return strings.HasSuffix(s, t) +} + +func funcLtrimstr(v, x any) any { + s, ok := v.(string) + if !ok { + return &func1TypeError{"ltrimstr", v, x} + } + t, ok := x.(string) + if !ok { + return &func1TypeError{"ltrimstr", v, x} + } + return strings.TrimPrefix(s, t) +} + +func funcRtrimstr(v, x any) any { + s, ok := v.(string) + if !ok { + return &func1TypeError{"rtrimstr", v, x} + } + t, ok := x.(string) + if !ok { + return &func1TypeError{"rtrimstr", v, x} + } + return strings.TrimSuffix(s, t) +} + +func funcTrimstr(v, x any) any { + s, ok := v.(string) + if !ok { + return &func1TypeError{"trimstr", v, x} + } + t, ok := x.(string) + if !ok { + return &func1TypeError{"trimstr", v, x} + } + return strings.TrimSuffix(strings.TrimPrefix(s, t), t) +} + +func funcLtrim(v any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"ltrim", v} + } + return strings.TrimLeftFunc(s, unicode.IsSpace) +} + +func funcRtrim(v any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"rtrim", v} + } + return strings.TrimRightFunc(s, unicode.IsSpace) +} + +func funcTrim(v any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"trim", v} + } + return strings.TrimSpace(s) +} + +func funcExplode(v any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"explode", v} + } + return explode(s) +} + +func explode(s string) []any { + xs := make([]any, len([]rune(s))) + var i int + for _, r := range s { + xs[i] = int(r) + i++ + } + return xs +} + +func funcImplode(v any) any { + vs, ok := v.([]any) + if !ok { + return &func0TypeError{"implode", v} + } + var sb strings.Builder + sb.Grow(len(vs)) + for _, v := range vs { + if r, ok := toInt(v); ok { + if 0 <= r && r <= utf8.MaxRune { + sb.WriteRune(rune(r)) + } else { + sb.WriteRune(utf8.RuneError) + } + } else { + return &func0TypeError{"implode", vs} + } + } + return sb.String() +} + +func funcSplit(v, x any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"split", v} + } + t, ok := x.(string) + if !ok { + return &func0TypeError{"split", x} + } + return splitString(s, t) +} + +func splitString(s, t string) []any { + if s == "" { + return []any{} + } + ss := strings.Split(s, t) + xs := make([]any, len(ss)) + for i, s := range ss { + xs[i] = s + } + return xs +} + +func funcJoin(v, x any) any { + vs, ok := values(v) + if !ok { + return &func1TypeError{"join", v, x} + } + if len(vs) == 0 { + return "" + } + return add(func(yield func(any) bool) { + for i, v := range vs { + s := x + if i == 0 { + s = "" + } + if !yield(s) { + return + } + switch w := v.(type) { + case bool, int, float64, *big.Int, json.Number: + v = jsonMarshal(w) + } + if !yield(v) { + return + } + } + }) +} + +func funcASCIIDowncase(v any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"ascii_downcase", v} + } + return strings.Map(func(r rune) rune { + if 'A' <= r && r <= 'Z' { + return r + ('a' - 'A') + } + return r + }, s) +} + +func funcASCIIUpcase(v any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"ascii_upcase", v} + } + return strings.Map(func(r rune) rune { + if 'a' <= r && r <= 'z' { + return r - ('a' - 'A') + } + return r + }, s) +} + +func funcToJSON(v any) any { + return jsonMarshal(v) +} + +func funcFromJSON(v any) any { + s, ok := v.(string) + if !ok { + return &func0TypeError{"fromjson", v} + } + var w any + dec := json.NewDecoder(strings.NewReader(s)) + dec.UseNumber() + if err := dec.Decode(&w); err != nil { + return &func0WrapError{"fromjson", v, err} + } + if _, err := dec.Token(); err != io.EOF { + return &func0TypeError{"fromjson", v} + } + return w +} + +func funcFormat(v, x any) any { + s, ok := x.(string) + if !ok { + return &func0TypeError{"format", x} + } + format := "@" + s + f := formatToFunc(format) + if f == nil { + return &formatNotFoundError{format} + } + return internalFuncs[f.Name].callback(v, nil) +} + +var htmlEscaper = strings.NewReplacer( + `<`, "<", + `>`, ">", + `&`, "&", + `'`, "'", + `"`, """, +) + +func funcToHTML(v any) any { + switch x := funcToString(v).(type) { + case string: + return htmlEscaper.Replace(x) + default: + return x + } +} + +func funcToURI(v any) any { + switch x := funcToString(v).(type) { + case string: + return strings.ReplaceAll(url.QueryEscape(x), "+", "%20") + default: + return x + } +} + +func funcToURId(v any) any { + switch x := funcToString(v).(type) { + case string: + x, err := url.QueryUnescape(strings.ReplaceAll(x, "+", "%2B")) + if err != nil { + return &func0WrapError{"@urid", v, err} + } + return x + default: + return x + } +} + +var csvEscaper = strings.NewReplacer( + `"`, `""`, + "\x00", `\0`, +) + +func funcToCSV(v any) any { + return formatJoin("csv", v, ",", func(s string) string { + return `"` + csvEscaper.Replace(s) + `"` + }) +} + +var tsvEscaper = strings.NewReplacer( + "\t", `\t`, + "\r", `\r`, + "\n", `\n`, + "\\", `\\`, + "\x00", `\0`, +) + +func funcToTSV(v any) any { + return formatJoin("tsv", v, "\t", tsvEscaper.Replace) +} + +var shEscaper = strings.NewReplacer( + "'", `'\''`, + "\x00", `\0`, +) + +func funcToSh(v any) any { + if _, ok := v.([]any); !ok { + v = []any{v} + } + return formatJoin("sh", v, " ", func(s string) string { + return "'" + shEscaper.Replace(s) + "'" + }) +} + +func formatJoin(typ string, v any, sep string, escape func(string) string) any { + vs, ok := v.([]any) + if !ok { + return &func0TypeError{"@" + typ, v} + } + ss := make([]string, len(vs)) + for i, v := range vs { + switch v := v.(type) { + case []any, map[string]any: + return &formatRowError{typ, v} + case string: + ss[i] = escape(v) + default: + if s := jsonMarshal(v); s != "null" || typ == "sh" { + ss[i] = s + } + } + } + return strings.Join(ss, sep) +} + +func funcToBase64(v any) any { + switch x := funcToString(v).(type) { + case string: + return base64.StdEncoding.EncodeToString([]byte(x)) + default: + return x + } +} + +func funcToBase64d(v any) any { + switch x := funcToString(v).(type) { + case string: + if i := strings.IndexRune(x, base64.StdPadding); i >= 0 { + x = x[:i] + } + y, err := base64.RawStdEncoding.DecodeString(x) + if err != nil { + return &func0WrapError{"@base64d", v, err} + } + return string(y) + default: + return x + } +} + +func funcIndex2(_, v, x any) any { + switch x := x.(type) { + case string: + switch v := v.(type) { + case nil: + return nil + case map[string]any: + return v[x] + default: + return &expectedObjectError{v} + } + case int, float64, *big.Int, json.Number: + i, _ := toInt(x) + switch v := v.(type) { + case nil: + return nil + case []any: + return index(v, i) + case string: + return indexString(v, i) + default: + return &expectedArrayError{v} + } + case []any: + switch v := v.(type) { + case nil: + return nil + case []any: + return indices(v, x) + default: + return &expectedArrayError{v} + } + case map[string]any: + if v == nil { + return nil + } + start, ok := x["start"] + if !ok { + return &expectedStartEndError{x} + } + end, ok := x["end"] + if !ok { + return &expectedStartEndError{x} + } + return funcSlice(nil, v, end, start) + default: + switch v.(type) { + case []any: + return &arrayIndexNotNumberError{x} + case string: + return &stringIndexNotNumberError{x} + default: + return &objectKeyNotStringError{x} + } + } +} + +func index(vs []any, i int) any { + i = clampIndex(i, -1, len(vs)) + if 0 <= i && i < len(vs) { + return vs[i] + } + return nil +} + +func indexString(s string, i int) any { + l := len([]rune(s)) + i = clampIndex(i, -1, l) + if 0 <= i && i < l { + for _, r := range s { + if i--; i < 0 { + return string(r) + } + } + } + return nil +} + +func funcSlice(_, v, e, s any) (r any) { + switch v := v.(type) { + case nil: + return nil + case []any: + return slice(v, e, s) + case string: + return sliceString(v, e, s) + default: + return &expectedArrayError{v} + } +} + +func slice(vs []any, e, s any) any { + var start, end int + if s != nil { + if i, ok := toInt(s); ok { + start = clampIndex(i, 0, len(vs)) + } else { + return &arrayIndexNotNumberError{s} + } + } + if e != nil { + if i, ok := toIntCeil(e); ok { + end = clampIndex(i, start, len(vs)) + } else { + return &arrayIndexNotNumberError{e} + } + } else { + end = len(vs) + } + return vs[start:end] +} + +func sliceString(v string, e, s any) any { + var start, end int + l := len([]rune(v)) + if s != nil { + if i, ok := toInt(s); ok { + start = clampIndex(i, 0, l) + } else { + return &stringIndexNotNumberError{s} + } + } + if e != nil { + if i, ok := toIntCeil(e); ok { + end = clampIndex(i, start, l) + } else { + return &stringIndexNotNumberError{e} + } + } else { + end = l + } + if start < l { + for i := range v { + if start--; start < 0 { + start = i + break + } + } + } else { + start = len(v) + } + if end < l { + for i := range v { + if end--; end < 0 { + end = i + break + } + } + } else { + end = len(v) + } + return v[start:end] +} + +func clampIndex(i, minimum, maximum int) int { + if i < 0 { + i += maximum + } + if i < minimum { + return minimum + } else if i < maximum { + return i + } else { + return maximum + } +} + +func funcFlatten(v any, args []any) any { + vs, ok := values(v) + if !ok { + return &func0TypeError{"flatten", v} + } + var depth float64 + if len(args) == 0 { + depth = -1 + } else { + depth, ok = toFloat(args[0]) + if !ok { + return &func0TypeError{"flatten", args[0]} + } + if lt(depth, 0) { + return &flattenDepthError{depth} + } + } + return flatten([]any{}, vs, depth) +} + +func flatten(xs, vs []any, depth float64) []any { + for _, v := range vs { + if vs, ok := v.([]any); ok && depth != 0 { + xs = flatten(xs, vs, depth-1) + } else { + xs = append(xs, v) + } + } + return xs +} + +type rangeIter struct { + value, end, step any +} + +func (iter *rangeIter) Next() (any, bool) { + if Compare(iter.step, 0)*Compare(iter.value, iter.end) >= 0 { + return nil, false + } + v := iter.value + iter.value = funcOpAdd(nil, v, iter.step) + return v, true +} + +func funcRange(_ any, xs []any) any { + for _, x := range xs { + switch x.(type) { + case int, float64, *big.Int, json.Number: + default: + return &func0TypeError{"range", x} + } + } + return &rangeIter{xs[0], xs[1], xs[2]} +} + +func funcMin(v any) any { + vs, ok := v.([]any) + if !ok { + return &func0TypeError{"min", v} + } + return minMaxBy(vs, vs, true) +} + +func funcMinBy(v, x any) any { + vs, ok := v.([]any) + if !ok { + return &func1TypeError{"min_by", v, x} + } + xs, ok := x.([]any) + if !ok { + return &func1TypeError{"min_by", v, x} + } + if len(vs) != len(xs) { + return &func1WrapError{"min_by", v, x, &lengthMismatchError{}} + } + return minMaxBy(vs, xs, true) +} + +func funcMax(v any) any { + vs, ok := v.([]any) + if !ok { + return &func0TypeError{"max", v} + } + return minMaxBy(vs, vs, false) +} + +func funcMaxBy(v, x any) any { + vs, ok := v.([]any) + if !ok { + return &func1TypeError{"max_by", v, x} + } + xs, ok := x.([]any) + if !ok { + return &func1TypeError{"max_by", v, x} + } + if len(vs) != len(xs) { + return &func1WrapError{"max_by", v, x, &lengthMismatchError{}} + } + return minMaxBy(vs, xs, false) +} + +func minMaxBy(vs, xs []any, isMin bool) any { + if len(vs) == 0 { + return nil + } + i, j, x := 0, 0, xs[0] + for i++; i < len(xs); i++ { + if Compare(x, xs[i]) > 0 == isMin { + j, x = i, xs[i] + } + } + return vs[j] +} + +type sortItem struct { + value, key any +} + +func sortItems(name string, v, x any) ([]*sortItem, error) { + vs, ok := v.([]any) + if !ok { + if strings.HasSuffix(name, "_by") { + return nil, &func1TypeError{name, v, x} + } + return nil, &func0TypeError{name, v} + } + xs, ok := x.([]any) + if !ok { + return nil, &func1TypeError{name, v, x} + } + if len(vs) != len(xs) { + return nil, &func1WrapError{name, v, x, &lengthMismatchError{}} + } + items := make([]*sortItem, len(vs)) + for i, v := range vs { + items[i] = &sortItem{v, xs[i]} + } + slices.SortStableFunc(items, func(x, y *sortItem) int { + return Compare(x.key, y.key) + }) + return items, nil +} + +func funcSort(v any) any { + return sortBy("sort", v, v) +} + +func funcSortBy(v, x any) any { + return sortBy("sort_by", v, x) +} + +func sortBy(name string, v, x any) any { + items, err := sortItems(name, v, x) + if err != nil { + return err + } + rs := make([]any, len(items)) + for i, x := range items { + rs[i] = x.value + } + return rs +} + +func funcGroupBy(v, x any) any { + items, err := sortItems("group_by", v, x) + if err != nil { + return err + } + rs := []any{} + var last any + for i, r := range items { + if i == 0 || Compare(last, r.key) != 0 { + rs, last = append(rs, []any{r.value}), r.key + } else { + rs[len(rs)-1] = append(rs[len(rs)-1].([]any), r.value) + } + } + return rs +} + +func funcUnique(v any) any { + return uniqueBy("unique", v, v) +} + +func funcUniqueBy(v, x any) any { + return uniqueBy("unique_by", v, x) +} + +func uniqueBy(name string, v, x any) any { + items, err := sortItems(name, v, x) + if err != nil { + return err + } + rs := []any{} + var last any + for i, r := range items { + if i == 0 || Compare(last, r.key) != 0 { + rs, last = append(rs, r.value), r.key + } + } + return rs +} + +func funcSignificand(v float64) float64 { + frac, _ := math.Frexp(v) + return frac * 2 +} + +func funcExp10(v float64) float64 { + return math.Pow(10, v) +} + +func funcFrexp(v any) any { + x, ok := toFloat(v) + if !ok { + return &func0TypeError{"frexp", v} + } + f, e := math.Frexp(x) + return []any{f, e} +} + +func funcModf(v any) any { + x, ok := toFloat(v) + if !ok { + return &func0TypeError{"modf", v} + } + if math.IsInf(x, 0) { + return []any{math.Copysign(0, x), x} + } + i, f := math.Modf(x) + return []any{f, i} +} + +func funcLgamma(v float64) float64 { + v, _ = math.Lgamma(v) + return v +} + +func funcDrem(l, r float64) float64 { + x := math.Remainder(l, r) + if x == 0.0 { + return math.Copysign(x, l) + } + return x +} + +func funcFmax(l, r float64) float64 { + if math.IsNaN(l) { + return r + } + if math.IsNaN(r) { + return l + } + return max(l, r) +} + +func funcFmin(l, r float64) float64 { + if math.IsNaN(l) { + return r + } + if math.IsNaN(r) { + return l + } + return min(l, r) +} + +func funcJn(l, r float64) float64 { + return math.Jn(int(l), r) +} + +func funcLdexp(l, r float64) float64 { + return math.Ldexp(l, int(r)) +} + +func funcYn(l, r float64) float64 { + return math.Yn(int(l), r) +} + +func funcInfinite(any) any { + return math.Inf(1) +} + +func funcIsfinite(v any) any { + x, ok := toFloat(v) + return ok && !math.IsInf(x, 0) +} + +func funcIsinfinite(v any) any { + x, ok := toFloat(v) + return ok && math.IsInf(x, 0) +} + +func funcNan(any) any { + return math.NaN() +} + +func funcIsnan(v any) any { + x, ok := toFloat(v) + if !ok { + if v == nil { + return false + } + return &func0TypeError{"isnan", v} + } + return math.IsNaN(x) +} + +func funcIsnormal(v any) any { + if v, ok := toFloat(v); ok { + e := (math.Float64bits(v) & 0x7ff0000000000000) >> 52 + return 0 < e && e < 0x7ff + } + return false +} + +// An `allocator` creates new maps and slices, stores the allocated addresses. +// This allocator is used to reduce allocations on assignment operator (`=`), +// update-assignment operator (`|=`), and the `map_values`, `del`, `delpaths` +// functions. +type allocator map[uintptr]struct{} + +func funcAllocator(any, []any) any { + return allocator{} +} + +func (a allocator) allocated(v any) bool { + _, ok := a[reflect.ValueOf(v).Pointer()] + return ok +} + +func (a allocator) makeObject(l int) map[string]any { + v := make(map[string]any, l) + if a != nil { + a[reflect.ValueOf(v).Pointer()] = struct{}{} + } + return v +} + +func (a allocator) makeArray(l, c int) []any { + v := make([]any, l, max(l, c)) + if a != nil { + a[reflect.ValueOf(v).Pointer()] = struct{}{} + } + return v +} + +func funcSetpath(v, p, n any) any { + // There is no need to use an allocator on a single update. + return setpath(v, p, n, nil) +} + +// Used in compiler#compileAssign and compiler#compileModify. +func funcSetpathWithAllocator(v any, args []any) any { + return setpath(v, args[0], args[1], args[2].(allocator)) +} + +func setpath(v, p, n any, a allocator) any { + path, ok := p.([]any) + if !ok { + return &func1TypeError{"setpath", v, p} + } + u, err := update(v, path, n, a) + if err != nil { + return &func2WrapError{"setpath", v, p, n, err} + } + return u +} + +func funcDelpaths(v, p any) any { + return delpaths(v, p, allocator{}) +} + +// Used in compiler#compileAssign and compiler#compileModify. +func funcDelpathsWithAllocator(v any, args []any) any { + return delpaths(v, args[0], args[1].(allocator)) +} + +func delpaths(v, p any, a allocator) any { + paths, ok := p.([]any) + if !ok { + return &func1TypeError{"delpaths", v, p} + } + if len(paths) == 0 { + return v + } + // Fills the paths with an empty value and then delete them. We cannot delete + // in each loop because array indices should not change. For example, + // jq -n "[0, 1, 2, 3] | delpaths([[1], [2]])" #=> [0, 3]. + var empty struct{} + var err error + u := v + for _, q := range paths { + path, ok := q.([]any) + if !ok { + return &func1WrapError{"delpaths", v, p, &expectedArrayError{q}} + } + u, err = update(u, path, empty, a) + if err != nil { + return &func1WrapError{"delpaths", v, p, err} + } + } + return deleteEmpty(u) +} + +func update(v any, path []any, n any, a allocator) (any, error) { + if len(path) == 0 { + return n, nil + } + switch p := path[0].(type) { + case string: + switch v := v.(type) { + case nil: + return updateObject(nil, p, path[1:], n, a) + case map[string]any: + return updateObject(v, p, path[1:], n, a) + case struct{}: + return v, nil + default: + return nil, &expectedObjectError{v} + } + case int, float64, *big.Int, json.Number: + i, _ := toInt(p) + switch v := v.(type) { + case nil: + return updateArrayIndex(nil, i, path[1:], n, a) + case []any: + return updateArrayIndex(v, i, path[1:], n, a) + case struct{}: + return v, nil + default: + return nil, &expectedArrayError{v} + } + case map[string]any: + switch v := v.(type) { + case nil: + return updateArraySlice(nil, p, path[1:], n, a) + case []any: + return updateArraySlice(v, p, path[1:], n, a) + case struct{}: + return v, nil + default: + return nil, &expectedArrayError{v} + } + default: + switch v.(type) { + case []any: + return nil, &arrayIndexNotNumberError{p} + default: + return nil, &objectKeyNotStringError{p} + } + } +} + +func updateObject(v map[string]any, k string, path []any, n any, a allocator) (any, error) { + x, ok := v[k] + if !ok && n == struct{}{} { + if v == nil { + return nil, nil + } + return v, nil + } + u, err := update(x, path, n, a) + if err != nil { + return nil, err + } + if a.allocated(v) { + v[k] = u + return v, nil + } + w := a.makeObject(len(v) + 1) + maps.Copy(w, v) + w[k] = u + return w, nil +} + +func updateArrayIndex(v []any, i int, path []any, n any, a allocator) (any, error) { + var x any + if j := clampIndex(i, -1, len(v)); j < 0 { + if n == struct{}{} { + if v == nil { + return nil, nil + } + return v, nil + } + return nil, &arrayIndexNegativeError{i} + } else if j < len(v) { + i = j + x = v[i] + } else { + if n == struct{}{} { + if v == nil { + return nil, nil + } + return v, nil + } + if i >= 0x20000000 { + return nil, &arrayIndexTooLargeError{i} + } + } + u, err := update(x, path, n, a) + if err != nil { + return nil, err + } + l, c := len(v), cap(v) + if a.allocated(v) { + if i < c { + if i >= l { + v = v[:i+1] + } + v[i] = u + return v, nil + } + c *= 2 + } + if i >= l { + l = i + 1 + } + w := a.makeArray(l, c) + copy(w, v) + w[i] = u + return w, nil +} + +func updateArraySlice(v []any, m map[string]any, path []any, n any, a allocator) (any, error) { + s, ok := m["start"] + if !ok { + return nil, &expectedStartEndError{m} + } + e, ok := m["end"] + if !ok { + return nil, &expectedStartEndError{m} + } + var start, end int + if s != nil { + if i, ok := toInt(s); ok { + start = clampIndex(i, 0, len(v)) + } else { + return nil, &arrayIndexNotNumberError{s} + } + } + if e != nil { + if i, ok := toIntCeil(e); ok { + end = clampIndex(i, start, len(v)) + } else { + return nil, &arrayIndexNotNumberError{e} + } + } else { + end = len(v) + } + if start == end && n == struct{}{} { + if v == nil { + return nil, nil + } + return v, nil + } + u, err := update(v[start:end], path, n, a) + if err != nil { + return nil, err + } + switch u := u.(type) { + case []any: + var w []any + if len(u) == end-start && a.allocated(v) { + w = v + } else { + w = a.makeArray(len(v)-(end-start)+len(u), 0) + copy(w, v[:start]) + copy(w[start+len(u):], v[end:]) + } + copy(w[start:], u) + return w, nil + case struct{}: + var w []any + if a.allocated(v) { + w = v + } else { + w = a.makeArray(len(v), 0) + copy(w, v) + } + for i := start; i < end; i++ { + w[i] = u + } + return w, nil + default: + return nil, &expectedArrayError{u} + } +} + +func deleteEmpty(v any) any { + switch v := v.(type) { + case struct{}: + return nil + case map[string]any: + for k, w := range v { + if w == struct{}{} { + delete(v, k) + } else { + v[k] = deleteEmpty(w) + } + } + return v + case []any: + var j int + for _, w := range v { + if w != struct{}{} { + v[j] = deleteEmpty(w) + j++ + } + } + for i := j; i < len(v); i++ { + v[i] = nil + } + return v[:j] + default: + return v + } +} + +func funcGetpath(v, p any) any { + path, ok := p.([]any) + if !ok { + return &func1TypeError{"getpath", v, p} + } + u := v + for _, x := range path { + v = funcIndex2(nil, v, x) + if err, ok := v.(error); ok { + return &func1WrapError{"getpath", u, p, err} + } + } + return v +} + +func funcTranspose(v any) any { + vss, ok := v.([]any) + if !ok { + return &func0TypeError{"transpose", v} + } + if len(vss) == 0 { + return []any{} + } + var l int + for _, vs := range vss { + vs, ok := vs.([]any) + if !ok { + return &func0TypeError{"transpose", v} + } + if k := len(vs); l < k { + l = k + } + } + wss := make([][]any, l) + xs := make([]any, l) + for i, k := 0, len(vss); i < l; i++ { + s := make([]any, k) + wss[i] = s + xs[i] = s + } + for i, vs := range vss { + for j, v := range vs.([]any) { + wss[j][i] = v + } + } + return xs +} + +func funcBsearch(v, t any) any { + vs, ok := v.([]any) + if !ok { + return &func1TypeError{"bsearch", v, t} + } + i, ok := slices.BinarySearchFunc(vs, t, Compare) + if ok { + return i + } + return -i - 1 +} + +func funcGmtime(v any) any { + if v, ok := toFloat(v); ok { + return epochToArray(v, time.UTC) + } + return &func0TypeError{"gmtime", v} +} + +func funcLocaltime(v any) any { + if v, ok := toFloat(v); ok { + return epochToArray(v, time.Local) + } + return &func0TypeError{"localtime", v} +} + +func epochToArray(v float64, loc *time.Location) []any { + t := time.Unix(int64(v), int64((v-math.Floor(v))*1e9)).In(loc) + return []any{ + t.Year(), + int(t.Month()) - 1, + t.Day(), + t.Hour(), + t.Minute(), + float64(t.Second()) + float64(t.Nanosecond())/1e9, + int(t.Weekday()), + t.YearDay() - 1, + } +} + +func funcMktime(v any) any { + a, ok := v.([]any) + if !ok { + return &func0TypeError{"mktime", v} + } + t, err := arrayToTime(a, time.UTC) + if err != nil { + return &func0WrapError{"mktime", v, err} + } + return timeToEpoch(t) +} + +func timeToEpoch(t time.Time) float64 { + return float64(t.Unix()) + float64(t.Nanosecond())/1e9 +} + +func funcStrftime(v, x any) any { + if w, ok := toFloat(v); ok { + v = epochToArray(w, time.UTC) + } + a, ok := v.([]any) + if !ok { + return &func1TypeError{"strftime", v, x} + } + format, ok := x.(string) + if !ok { + return &func1TypeError{"strftime", v, x} + } + t, err := arrayToTime(a, time.UTC) + if err != nil { + return &func1WrapError{"strftime", v, x, err} + } + return timefmt.Format(t, format) +} + +func funcStrflocaltime(v, x any) any { + if w, ok := toFloat(v); ok { + v = epochToArray(w, time.Local) + } + a, ok := v.([]any) + if !ok { + return &func1TypeError{"strflocaltime", v, x} + } + format, ok := x.(string) + if !ok { + return &func1TypeError{"strflocaltime", v, x} + } + t, err := arrayToTime(a, time.Local) + if err != nil { + return &func1WrapError{"strflocaltime", v, x, err} + } + return timefmt.Format(t, format) +} + +func funcStrptime(v, x any) any { + s, ok := v.(string) + if !ok { + return &func1TypeError{"strptime", v, x} + } + format, ok := x.(string) + if !ok { + return &func1TypeError{"strptime", v, x} + } + t, err := timefmt.Parse(s, format) + if err != nil { + return &func1WrapError{"strptime", v, x, err} + } + if t.Equal(time.Time{}) { + return &func1TypeError{"strptime", v, x} + } + return epochToArray(timeToEpoch(t), time.UTC) +} + +func arrayToTime(a []any, loc *time.Location) (time.Time, error) { + var t time.Time + var year, month, day, hour, minute, + second, nanosecond, weekday, yearday int + for i, p := range []*int{ + &year, &month, &day, &hour, &minute, + &second, &weekday, &yearday, + } { + if i >= len(a) { + break + } + if i == 5 { + if v, ok := toFloat(a[i]); ok { + *p = int(v) + nanosecond = int((v - math.Floor(v)) * 1e9) + } else { + return t, &timeArrayError{} + } + } else if v, ok := toInt(a[i]); ok { + *p = v + } else { + return t, &timeArrayError{} + } + } + return time.Date(year, time.Month(month+1), day, + hour, minute, second, nanosecond, loc), nil +} + +func funcNow(any) any { + return timeToEpoch(time.Now()) +} + +func funcMatch(v, re, fs, testing any, cache *sync.Map) any { + var name string + if testing == true { + name = "test" + } else { + name = "match" + } + var flags string + if fs != nil { + var ok bool + flags, ok = fs.(string) + if !ok { + return &func2TypeError{name, v, re, fs} + } + } + s, ok := v.(string) + if !ok { + return &func2TypeError{name, v, re, fs} + } + restr, ok := re.(string) + if !ok { + return &func2TypeError{name, v, re, fs} + } + r, err := compileRegexp(restr, flags, cache) + if err != nil { + return err + } + if testing == true { + return r.MatchString(s) + } + var n int + if strings.ContainsRune(flags, 'g') { + n = -1 + } else { + n = 1 + } + xs := r.FindAllStringSubmatchIndex(s, n) + res, names := make([]any, len(xs)), r.SubexpNames() + for i, x := range xs { + captures := make([]any, (len(x)-2)/2) + for j := 1; j < len(x)/2; j++ { + var name any + if n := names[j]; n != "" { + name = n + } + if x[j*2] < 0 { + captures[j-1] = map[string]any{ + "name": name, + "offset": -1, + "length": 0, + "string": nil, + } + continue + } + captures[j-1] = map[string]any{ + "name": name, + "offset": len([]rune(s[:x[j*2]])), + "length": len([]rune(s[:x[j*2+1]])) - len([]rune(s[:x[j*2]])), + "string": s[x[j*2]:x[j*2+1]], + } + } + res[i] = map[string]any{ + "offset": len([]rune(s[:x[0]])), + "length": len([]rune(s[:x[1]])) - len([]rune(s[:x[0]])), + "string": s[x[0]:x[1]], + "captures": captures, + } + } + return res +} + +func compileRegexp(re, flags string, cache *sync.Map) (*regexp.Regexp, error) { + key := [2]string{re, flags} + if r, ok := cache.Load(key); ok { + return r.(*regexp.Regexp), nil + } + if strings.IndexFunc(flags, func(r rune) bool { + return r != 'g' && r != 'i' && r != 'm' + }) >= 0 { + return nil, fmt.Errorf("unsupported regular expression flag: %q", flags) + } + if strings.ContainsRune(flags, 'i') { + re = "(?i)" + re + } + if strings.ContainsRune(flags, 'm') { + re = "(?s)" + re + } + r, err := regexp.Compile(re) + if err != nil { + return nil, fmt.Errorf("invalid regular expression %q: %s", re, err) + } + cache.Store(key, r) + return r, nil +} + +func funcCaptures(v any) any { + captures, ok := v.([]any) + if !ok { + return &expectedArrayError{v} + } + w := make(map[string]any, len(captures)) + for _, capture := range captures { + if capture, ok := capture.(map[string]any); ok { + if name, ok := capture["name"].(string); ok { + w[name] = capture["string"] + } + } + } + return w +} + +func funcError(v any, args []any) any { + if len(args) > 0 { + v = args[0] + } + return &exitCodeError{v, 5} +} + +func funcHalt(any) any { + return &HaltError{nil, 0} +} + +func funcHaltError(v any, args []any) any { + code := 5 + if len(args) > 0 { + var ok bool + if code, ok = toInt(args[0]); !ok { + return &func0TypeError{"halt_error", args[0]} + } + } + return &HaltError{v, code} +} + +func toInt(x any) (int, bool) { + switch x := x.(type) { + case int: + return x, true + case float64: + return floatToInt(x), true + case *big.Int: + if x.IsInt64() { + if i := x.Int64(); math.MinInt <= i && i <= math.MaxInt { + return int(i), true + } + } + if x.Sign() > 0 { + return math.MaxInt, true + } + return math.MinInt, true + case json.Number: + return toInt(parseNumber(x)) + default: + return 0, false + } +} + +func toIntCeil(x any) (int, bool) { + if f, ok := x.(float64); ok { + x = math.Ceil(f) + } + return toInt(x) +} + +func floatToInt(x float64) int { + if math.MinInt <= x && x < math.MaxInt { + return int(x) + } + if x > 0 { + return math.MaxInt + } + return math.MinInt +} + +func toFloat(x any) (float64, bool) { + switch x := x.(type) { + case int: + return float64(x), true + case float64: + return x, true + case *big.Int: + return bigToFloat(x), true + case json.Number: + v, err := x.Float64() + return v, err == nil + default: + return 0.0, false + } +} + +func bigToFloat(x *big.Int) float64 { + if x.IsInt64() { + return float64(x.Int64()) + } + if f, err := strconv.ParseFloat(x.String(), 64); err == nil { + return f + } + return math.Inf(x.Sign()) +} + +func parseNumber(v json.Number) any { + if i, err := v.Int64(); err == nil && math.MinInt <= i && i <= math.MaxInt { + return int(i) + } + if strings.ContainsAny(v.String(), ".eE") { + if f, err := v.Float64(); err == nil { + return f + } + } + if bi, ok := new(big.Int).SetString(v.String(), 10); ok { + return bi + } + if strings.HasPrefix(v.String(), "-") { + return math.Inf(-1) + } + return math.Inf(1) +}