Upstream itchyny/gojq state of the touched files before a0fd150fb8
This commit is contained in:
1 parent
af32cf4c58
commit
33d02f2fdb
4 files changed
+47
-864
No files matched your search
+3
-397
@@ -710,27 +710,6 @@
|
|||||||
error: |
|
error: |
|
||||||
expected a number for indexing an array but got: boolean (true)
|
expected a number for indexing an array but got: boolean (true)
|
||||||
|
|
||||||
- name: indexing number literal error
|
|
||||||
args:
|
|
||||||
- '-1[0]'
|
|
||||||
input: 'null'
|
|
||||||
error: |
|
|
||||||
expected an array but got: number (1)
|
|
||||||
|
|
||||||
- name: slicing number literal error
|
|
||||||
args:
|
|
||||||
- '1 - -1[1:]'
|
|
||||||
input: 'null'
|
|
||||||
error: |
|
|
||||||
expected an array but got: number (1)
|
|
||||||
|
|
||||||
- name: indexing by indexed number literal error
|
|
||||||
args:
|
|
||||||
- '.[1[0]]'
|
|
||||||
input: '[0, 1]'
|
|
||||||
error: |
|
|
||||||
expected an array but got: number (1)
|
|
||||||
|
|
||||||
- name: array slicing start error
|
- name: array slicing start error
|
||||||
args:
|
args:
|
||||||
- '.[[]:0]'
|
- '.[[]:0]'
|
||||||
@@ -1346,33 +1325,6 @@
|
|||||||
expected: |
|
expected: |
|
||||||
9223372036854775808
|
9223372036854775808
|
||||||
|
|
||||||
- name: abs function against non-numbers
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'abs'
|
|
||||||
input: |
|
|
||||||
""
|
|
||||||
"-42"
|
|
||||||
[]
|
|
||||||
[1,-2]
|
|
||||||
{}
|
|
||||||
{"a":-1}
|
|
||||||
expected: |
|
|
||||||
""
|
|
||||||
"-42"
|
|
||||||
[]
|
|
||||||
[1,-2]
|
|
||||||
{}
|
|
||||||
{"a":-1}
|
|
||||||
|
|
||||||
- name: abs function error
|
|
||||||
args:
|
|
||||||
- 'abs'
|
|
||||||
input: 'null true'
|
|
||||||
error: |
|
|
||||||
cannot negate: null
|
|
||||||
cannot negate: boolean (true)
|
|
||||||
|
|
||||||
- name: length function
|
- name: length function
|
||||||
args:
|
args:
|
||||||
- 'length'
|
- 'length'
|
||||||
@@ -2478,17 +2430,6 @@
|
|||||||
["b"]
|
["b"]
|
||||||
[]
|
[]
|
||||||
|
|
||||||
- name: split/1 function error
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'try split(",") catch ., try split(1) catch .'
|
|
||||||
input: '1 "a"'
|
|
||||||
expected: |
|
|
||||||
"split(\",\") cannot be applied to: number (1)"
|
|
||||||
"split(1) cannot be applied to: number (1)"
|
|
||||||
["a"]
|
|
||||||
"split(1) cannot be applied to: string (\"a\")"
|
|
||||||
|
|
||||||
- name: join function
|
- name: join function
|
||||||
args:
|
args:
|
||||||
- 'join(",")'
|
- 'join(",")'
|
||||||
@@ -3408,13 +3349,6 @@
|
|||||||
expected: |
|
expected: |
|
||||||
[5,5,false,3,3,[null,false],null,false]
|
[5,5,false,3,3,[null,false],null,false]
|
||||||
|
|
||||||
- name: optional operator with alternative operator
|
|
||||||
args:
|
|
||||||
- '.a ?// 1'
|
|
||||||
input: 'null'
|
|
||||||
expected: |
|
|
||||||
1
|
|
||||||
|
|
||||||
- name: condition
|
- name: condition
|
||||||
args:
|
args:
|
||||||
- 'if . then . else [.] end'
|
- 'if . then . else [.] end'
|
||||||
@@ -3630,29 +3564,6 @@
|
|||||||
expected: |
|
expected: |
|
||||||
5
|
5
|
||||||
|
|
||||||
- name: reduce with destructuring alternative operator
|
|
||||||
args:
|
|
||||||
- 'reduce .[] as [$i,$j] ?// {$i,$j} (0; . + $i * $j)'
|
|
||||||
input: '[[1,2], {"i":3,"j":4}, [5,6]]'
|
|
||||||
expected: |
|
|
||||||
44
|
|
||||||
|
|
||||||
- name: reduce with destructuring alternative operator resetting variables
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'reduce .[] as [$a] ?// {$b} ?// $c ([]; . + [[$a, $b, $c]])'
|
|
||||||
input: '[{"b":1}, [2], 3, [4]]'
|
|
||||||
expected: |
|
|
||||||
[[null,1,null],[2,null,null],[null,null,3],[4,null,null]]
|
|
||||||
|
|
||||||
- name: reduce with destructuring alternative operator and error backtrack
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'reduce .[] as [$a] ?// $a ([]; . + [if $a == 2 then error else $a end])'
|
|
||||||
input: '[[1], [2], [3]]'
|
|
||||||
expected: |
|
|
||||||
[1,[2],3]
|
|
||||||
|
|
||||||
- name: reduce null
|
- name: reduce null
|
||||||
args:
|
args:
|
||||||
- 'reduce . as $n (.; .)'
|
- 'reduce . as $n (.; .)'
|
||||||
@@ -3731,68 +3642,6 @@
|
|||||||
expected: |
|
expected: |
|
||||||
[[1,1],[1]]
|
[[1,1],[1]]
|
||||||
|
|
||||||
- name: reduce with assignment in update
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'reduce range(3) as $i ({}; .[$i|tostring] = {}),
|
|
||||||
reduce range(3) as $i ({}; .a[$i|tostring] = $i),
|
|
||||||
reduce range(3) as $i ([]; .[$i] = $i),
|
|
||||||
reduce range(3) as $i ({}; .[$i|tostring] += 1)'
|
|
||||||
input: 'null'
|
|
||||||
expected: |
|
|
||||||
{"0":{},"1":{},"2":{}}
|
|
||||||
{"a":{"0":0,"1":1,"2":2}}
|
|
||||||
[0,1,2]
|
|
||||||
{"0":1,"1":1,"2":1}
|
|
||||||
|
|
||||||
- name: reduce with assignment of the accumulated value
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'reduce range(3) as $i ({}; .a = .),
|
|
||||||
reduce range(3) as $i ({}; .a = {v: .}),
|
|
||||||
reduce range(3) as $i ({}; . as $p | .a = $i | .b = $p)'
|
|
||||||
input: 'null'
|
|
||||||
expected: |
|
|
||||||
{"a":{"a":{"a":{}}}}
|
|
||||||
{"a":{"v":{"a":{"v":{"a":{"v":{}}}}}}}
|
|
||||||
{"a":2,"b":{"a":1,"b":{"a":0,"b":{}}}}
|
|
||||||
|
|
||||||
- name: reduce with assignment of multiple values
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'reduce range(2) as $i ({"k":"k"}; .[.k] = ("x","y")),
|
|
||||||
reduce range(2) as $i ({}; (.a = 1), (.b = 2))'
|
|
||||||
input: 'null'
|
|
||||||
expected: |
|
|
||||||
{"k":"y","y":"y"}
|
|
||||||
{"b":2}
|
|
||||||
|
|
||||||
- name: reduce with assignment of a shared value
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- '{"a":1} as $x | reduce range(2) as $i ({}; .[$i|tostring] = $x) |
|
|
||||||
[(.["0"].a = 9), $x]'
|
|
||||||
input: 'null'
|
|
||||||
expected: |
|
|
||||||
[{"0":{"a":9},"1":{"a":1}},{"a":1}]
|
|
||||||
|
|
||||||
- name: reduce with assignment result reused
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'reduce range(3) as $i ({}; .[$i|tostring] = $i) as $r |
|
|
||||||
[($r | .["0"] = 9), $r]'
|
|
||||||
input: 'null'
|
|
||||||
expected: |
|
|
||||||
[{"0":9,"1":1,"2":2},{"0":0,"1":1,"2":2}]
|
|
||||||
|
|
||||||
- name: foreach with assignment in update
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- '[foreach range(3) as $i ({}; .[$i|tostring] = $i)]'
|
|
||||||
input: 'null'
|
|
||||||
expected: |
|
|
||||||
[{"0":0},{"0":0,"1":1},{"0":0,"1":1,"2":2}]
|
|
||||||
|
|
||||||
- name: foreach iterator
|
- name: foreach iterator
|
||||||
args:
|
args:
|
||||||
- 'foreach range(5) as $item (0; $item)'
|
- 'foreach range(5) as $item (0; $item)'
|
||||||
@@ -3822,37 +3671,6 @@
|
|||||||
-1
|
-1
|
||||||
-4
|
-4
|
||||||
|
|
||||||
- name: foreach with destructuring alternative operator
|
|
||||||
args:
|
|
||||||
- 'foreach .[] as [$i,$j] ?// {$i,$j} (0; . + $i * $j)'
|
|
||||||
input: '[[1,2], {"i":3,"j":4}, [5,6]]'
|
|
||||||
expected: |
|
|
||||||
2
|
|
||||||
14
|
|
||||||
44
|
|
||||||
|
|
||||||
- name: foreach with destructuring alternative operator resetting variables
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'foreach .[] as [$a] ?// {$b} ?// $c (null; [$a, $b, $c])'
|
|
||||||
input: '[{"b":1}, [2], 3, [4]]'
|
|
||||||
expected: |
|
|
||||||
[null,1,null]
|
|
||||||
[2,null,null]
|
|
||||||
[null,null,3]
|
|
||||||
[4,null,null]
|
|
||||||
|
|
||||||
- name: foreach with destructuring alternative operator and error backtrack in extract
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'foreach .[] as {$a} ?// [$a] ?// $a (null; $a; if 1 < . and . < 4 then error else . end)'
|
|
||||||
input: '[[1], [2], {"a":3}, 4]'
|
|
||||||
expected: |
|
|
||||||
1
|
|
||||||
[2]
|
|
||||||
{"a":3}
|
|
||||||
4
|
|
||||||
|
|
||||||
- name: foreach with iterator in update
|
- name: foreach with iterator in update
|
||||||
args:
|
args:
|
||||||
- 'foreach .[] as $i (1; ., . + $i, range(.))'
|
- 'foreach .[] as $i (1; ., . + $i, range(.))'
|
||||||
@@ -4677,17 +4495,6 @@
|
|||||||
[-3,-3,-2,-1,-1,-0,-0,0,0,0,0,1,1,2,3,3]
|
[-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]
|
[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: math functions against large number literals
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- '[floor, ceil, sqrt, exp10]'
|
|
||||||
input: |
|
|
||||||
1e1000
|
|
||||||
-1e1000
|
|
||||||
expected: |
|
|
||||||
[1.7976931348623157e+308,1.7976931348623157e+308,1.7976931348623157e+308,1.7976931348623157e+308]
|
|
||||||
[-1.7976931348623157e+308,-1.7976931348623157e+308,null,0]
|
|
||||||
|
|
||||||
- name: round, nearbyint, rint functions
|
- name: round, nearbyint, rint functions
|
||||||
args:
|
args:
|
||||||
- -c
|
- -c
|
||||||
@@ -6068,7 +5875,7 @@
|
|||||||
args:
|
args:
|
||||||
- -c
|
- -c
|
||||||
- 'gmtime, localtime | map(. * 1000000 | floor / 1000000)'
|
- 'gmtime, localtime | map(. * 1000000 | floor / 1000000)'
|
||||||
input: '1500000000 1567890123.456 1600000000.111 -1 -0.5 0.5'
|
input: '1500000000 1567890123.456 1600000000.111'
|
||||||
expected: | # UTC-7 (TZ=Etc/GMT+7)
|
expected: | # UTC-7 (TZ=Etc/GMT+7)
|
||||||
[2017,6,14,2,40,0,5,194]
|
[2017,6,14,2,40,0,5,194]
|
||||||
[2017,6,13,19,40,0,4,193]
|
[2017,6,13,19,40,0,4,193]
|
||||||
@@ -6076,12 +5883,6 @@
|
|||||||
[2019,8,7,14,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,12,26,40.111,0,256]
|
||||||
[2020,8,13,5,26,40.111,0,256]
|
[2020,8,13,5,26,40.111,0,256]
|
||||||
[1969,11,31,23,59,59,3,364]
|
|
||||||
[1969,11,31,16,59,59,3,364]
|
|
||||||
[1969,11,31,23,59,59.5,3,364]
|
|
||||||
[1969,11,31,16,59,59.5,3,364]
|
|
||||||
[1970,0,1,0,0,0.5,4,0]
|
|
||||||
[1969,11,31,17,0,0.5,3,364]
|
|
||||||
|
|
||||||
- name: mktime, strftime, strflocaltime, todate functions
|
- name: mktime, strftime, strflocaltime, todate functions
|
||||||
args:
|
args:
|
||||||
@@ -6093,7 +5894,6 @@
|
|||||||
[2020,8,13,12,26,40.111,0,256]
|
[2020,8,13,12,26,40.111,0,256]
|
||||||
[2024,7,20]
|
[2024,7,20]
|
||||||
[2025]
|
[2025]
|
||||||
[1969,11,31,23,59,59.5,3,364]
|
|
||||||
expected: |
|
expected: |
|
||||||
[2017,6,14,2,40,0,5,194]
|
[2017,6,14,2,40,0,5,194]
|
||||||
2017-07-14T02:40:00Z
|
2017-07-14T02:40:00Z
|
||||||
@@ -6130,105 +5930,15 @@
|
|||||||
2024-12-31T00:00:00Z
|
2024-12-31T00:00:00Z
|
||||||
2024-12-30T17:00:00-0700
|
2024-12-30T17:00:00-0700
|
||||||
2024-12-31T00:00:00Z
|
2024-12-31T00:00:00Z
|
||||||
[1969,11,31,23,59,59.5,3,364]
|
|
||||||
1969-12-31T23:59:59Z
|
|
||||||
1969-12-31T23:59:59-0700
|
|
||||||
-0.5
|
|
||||||
1969-12-31T23:59:59Z
|
|
||||||
1969-12-31T16:59:59-0700
|
|
||||||
1969-12-31T23:59:59Z
|
|
||||||
|
|
||||||
- name: date functions with fractional seconds
|
|
||||||
args:
|
|
||||||
- -r
|
|
||||||
- 'mktime, strftime("%Y-%m-%dT%H:%M:%S.%fZ"), strflocaltime("%Y-%m-%dT%H:%M:%S.%f%z")'
|
|
||||||
input: |
|
|
||||||
[1970,0,1,0,0,-0.5]
|
|
||||||
[1970,0,1,0,0,-1.25]
|
|
||||||
[1970,0,1,0,0,-60.25]
|
|
||||||
[1970,0,1,0,0,-1]
|
|
||||||
[1970,0,1,0,0,0]
|
|
||||||
[1970,0,1,0,0,0.5]
|
|
||||||
[1970,0,1,0,0,60.25]
|
|
||||||
expected: | # UTC-7 (TZ=Etc/GMT+7)
|
|
||||||
-0.5
|
|
||||||
1969-12-31T23:59:59.500000Z
|
|
||||||
1969-12-31T23:59:59.500000-0700
|
|
||||||
-1.25
|
|
||||||
1969-12-31T23:59:58.750000Z
|
|
||||||
1969-12-31T23:59:58.750000-0700
|
|
||||||
-60.25
|
|
||||||
1969-12-31T23:58:59.750000Z
|
|
||||||
1969-12-31T23:58:59.750000-0700
|
|
||||||
-1
|
|
||||||
1969-12-31T23:59:59.000000Z
|
|
||||||
1969-12-31T23:59:59.000000-0700
|
|
||||||
0
|
|
||||||
1970-01-01T00:00:00.000000Z
|
|
||||||
1970-01-01T00:00:00.000000-0700
|
|
||||||
0.5
|
|
||||||
1970-01-01T00:00:00.500000Z
|
|
||||||
1970-01-01T00:00:00.500000-0700
|
|
||||||
60.25
|
|
||||||
1970-01-01T00:01:00.250000Z
|
|
||||||
1970-01-01T00:01:00.250000-0700
|
|
||||||
|
|
||||||
- name: mktime after subtracting fractional seconds
|
|
||||||
args:
|
|
||||||
- 'gmtime | .[5] -= 0.5 | mktime'
|
|
||||||
input: '0 60 86400 -60'
|
|
||||||
expected: |
|
|
||||||
-0.5
|
|
||||||
59.5
|
|
||||||
86399.5
|
|
||||||
-60.5
|
|
||||||
|
|
||||||
- name: date functions with non-finite or out of range epoch timestamps
|
|
||||||
args:
|
|
||||||
- -n
|
|
||||||
- '[infinite, -infinite, nan, 1e19, -1e19, 9223372036854775807, -9223372036854775808] | .[] | try gmtime catch .'
|
|
||||||
expected: |
|
|
||||||
"gmtime cannot be applied to 1.7976931348623157e+308: number of seconds is out of range"
|
|
||||||
"gmtime cannot be applied to -1.7976931348623157e+308: number of seconds is out of range"
|
|
||||||
"gmtime cannot be applied to null: number of seconds is out of range"
|
|
||||||
"gmtime cannot be applied to 10000000000000000000: number of seconds is out of range"
|
|
||||||
"gmtime cannot be applied to -10000000000000000000: number of seconds is out of range"
|
|
||||||
"gmtime cannot be applied to 9223372036854775807: number of seconds is out of range"
|
|
||||||
"gmtime cannot be applied to -9223372036854775808: number of seconds is out of range"
|
|
||||||
|
|
||||||
- name: date functions with out of range numbers in the array
|
|
||||||
args:
|
|
||||||
- -n
|
|
||||||
- '[[infinite,0,1], [nan,0,1], [1e19,0,1], [0,1e19,1], [2001,0,1,0,0,infinite], [2001,0,1,0,0,nan], [2001,0,1,0,0,1e19], [9223372036854775807,0,1], ["x",0,1]] | .[] | try mktime catch .'
|
|
||||||
expected: |
|
|
||||||
"mktime cannot be applied to [1.7976931348623157e+308,0,1]: number is out of range"
|
|
||||||
"mktime cannot be applied to [null,0,1]: number is out of range"
|
|
||||||
"mktime cannot be applied to [10000000000000000000,0,1]: number is out of range"
|
|
||||||
"mktime cannot be applied to [0,10000000000000000000,1]: number is out of range"
|
|
||||||
"mktime cannot be applied to [2001,0,1,0,0,1.797693134 ...]: number is out of range"
|
|
||||||
"mktime cannot be applied to [2001,0,1,0,0,null]: number is out of range"
|
|
||||||
"mktime cannot be applied to [2001,0,1,0,0,10000000000 ...]: number is out of range"
|
|
||||||
"mktime cannot be applied to [9223372036854775807,0,1]: number is out of range"
|
|
||||||
"mktime cannot be applied to [\"x\",0,1]: expected an array of 8 numbers"
|
|
||||||
|
|
||||||
- name: strftime function with out of range datetime
|
|
||||||
args:
|
|
||||||
- -n
|
|
||||||
- 'try (infinite | todate) catch ., try ([1e19,0,1] | strftime("%Y")) catch .'
|
|
||||||
expected: |
|
|
||||||
"strftime(\"%Y-%m-%dT%H:%M:%SZ\") cannot be applied to 1.7976931348623157e+308: number of seconds is out of range"
|
|
||||||
"strftime(\"%Y\") cannot be applied to [10000000000000000000,0,1]: number is out of range"
|
|
||||||
|
|
||||||
- name: strptime, fromdate functions
|
- name: strptime, fromdate functions
|
||||||
args:
|
args:
|
||||||
- -c
|
- -c
|
||||||
- 'strptime("%Y-%m-%dT%H:%M:%S%z")? // strptime("%Y-%m-%dT%H:%M:%S.%f%z"), fromdate?'
|
- 'strptime("%Y-%m-%dT%H:%M:%S%z"), fromdate'
|
||||||
input: |
|
input: |
|
||||||
"2017-07-14T02:40:00Z"
|
"2017-07-14T02:40:00Z"
|
||||||
"2019-09-08T06:02:03+0900"
|
"2019-09-08T06:02:03+0900"
|
||||||
"2020-09-13T07:26:40-05:00"
|
"2020-09-13T07:26:40-05:00"
|
||||||
"1969-12-31T23:59:59.5+0000"
|
|
||||||
"2345-01-02T03:04:05.678901+0000"
|
|
||||||
expected: |
|
expected: |
|
||||||
[2017,6,14,2,40,0,5,194]
|
[2017,6,14,2,40,0,5,194]
|
||||||
1500000000
|
1500000000
|
||||||
@@ -6236,8 +5946,6 @@
|
|||||||
1567890123
|
1567890123
|
||||||
[2020,8,13,12,26,40,0,256]
|
[2020,8,13,12,26,40,0,256]
|
||||||
1600000000
|
1600000000
|
||||||
[1969,11,31,23,59,59.5,3,364]
|
|
||||||
[2345,0,2,3,4,5.678901,2,1]
|
|
||||||
|
|
||||||
- name: strftime, strptime functions
|
- name: strftime, strptime functions
|
||||||
args:
|
args:
|
||||||
@@ -6953,49 +6661,6 @@
|
|||||||
"aabcABC☆★☆ABCabc"
|
"aabcABC☆★☆ABCabc"
|
||||||
"abcABC☆★☆ABCabc"
|
"abcABC☆★☆ABCabc"
|
||||||
|
|
||||||
- name: match function with empty matches
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- '[match("a*"; "g").offset], [scan("[a-z]*")], gsub("[0-9]*"; "#"), split("-*"; ""), [splits("\\s*")]'
|
|
||||||
input: '"a1b-c d"'
|
|
||||||
expected: |
|
|
||||||
[0,1,2,3,4,5,6,7]
|
|
||||||
["a","","b","","c","","d",""]
|
|
||||||
"#a##b#-#c# #d#"
|
|
||||||
["","a","1","b","","c"," ","d",""]
|
|
||||||
["","a","1","b","-","c","","d",""]
|
|
||||||
|
|
||||||
- name: match function with empty matches and assertions
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'gsub("(?m)^a*"; "#"), gsub("\\ba*"; "#"), gsub("(^|-)a*"; "#"), gsub("[a-z]*\\b"; "#")'
|
|
||||||
input: '"a-a b"'
|
|
||||||
expected: |
|
|
||||||
"#-a b"
|
|
||||||
"##-## #b#"
|
|
||||||
"## b"
|
|
||||||
"##-## ##"
|
|
||||||
|
|
||||||
- name: match function with empty matches and captures
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- '[scan("(x)?a*")], [match("(a|)"; "g") | [.offset, .captures[0].offset]], [scan("[^a]*"; "i")]'
|
|
||||||
input: '"1a-A"'
|
|
||||||
expected: |
|
|
||||||
[[null],[null],[null],[null],[null]]
|
|
||||||
[[0,0],[1,1],[2,2],[3,3],[4,4]]
|
|
||||||
["1","","-","",""]
|
|
||||||
|
|
||||||
- name: match function with empty matches against multibyte characters
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- '[scan("[☆★]*")], [match("[☆★]*"; "g").offset], gsub("[☆★]*"; "#")'
|
|
||||||
input: '"☆★1☆★"'
|
|
||||||
expected: |
|
|
||||||
["☆★","","☆★",""]
|
|
||||||
[0,2,3,5]
|
|
||||||
"##1##"
|
|
||||||
|
|
||||||
- name: INDEX function
|
- name: INDEX function
|
||||||
args:
|
args:
|
||||||
- -c
|
- -c
|
||||||
@@ -7293,17 +6958,6 @@
|
|||||||
{"a":3}
|
{"a":3}
|
||||||
{"a":4}
|
{"a":4}
|
||||||
|
|
||||||
- name: destructuring alternative operator with break in try
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'first((. as {$a} ?// [$a] ?// $a | {$a})?)'
|
|
||||||
input: '[1] [2] {"a":3} 4'
|
|
||||||
expected: |
|
|
||||||
{"a":1}
|
|
||||||
{"a":2}
|
|
||||||
{"a":3}
|
|
||||||
{"a":4}
|
|
||||||
|
|
||||||
- name: destructuring alternative operator with halt
|
- name: destructuring alternative operator with halt
|
||||||
args:
|
args:
|
||||||
- -c
|
- -c
|
||||||
@@ -7323,28 +6977,6 @@
|
|||||||
[1]
|
[1]
|
||||||
exit_code: 5
|
exit_code: 5
|
||||||
|
|
||||||
- name: destructuring alternative operator with halt_error in try
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- 'try (. as {$a} ?// [$a] ?// $a | {$a}, halt_error)'
|
|
||||||
input: '[1]'
|
|
||||||
expected: |
|
|
||||||
{"a":1}
|
|
||||||
error: |
|
|
||||||
[1]
|
|
||||||
exit_code: 5
|
|
||||||
|
|
||||||
- name: destructuring alternative operator resets variables of later alternatives
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- '.[] as [$a] ?// {$b} ?// $c | [$a, $b, $c]'
|
|
||||||
input: '[{"b":1}, [2], 3, [4]]'
|
|
||||||
expected: |
|
|
||||||
[null,1,null]
|
|
||||||
[2,null,null]
|
|
||||||
[null,null,3]
|
|
||||||
[4,null,null]
|
|
||||||
|
|
||||||
- name: destructuring alternative operator with multiple variables
|
- name: destructuring alternative operator with multiple variables
|
||||||
args:
|
args:
|
||||||
- -c
|
- -c
|
||||||
@@ -8008,7 +7640,7 @@
|
|||||||
[["c"],3]
|
[["c"],3]
|
||||||
[["c"]]
|
[["c"]]
|
||||||
|
|
||||||
- name: stream option with unterminated input after a comma
|
- name: stream option with unterminated input
|
||||||
args:
|
args:
|
||||||
- -c
|
- -c
|
||||||
- --stream
|
- --stream
|
||||||
@@ -8021,21 +7653,6 @@
|
|||||||
{"a":1,
|
{"a":1,
|
||||||
^ unexpected EOF
|
^ unexpected EOF
|
||||||
|
|
||||||
- name: stream option with unterminated input after a value
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- --stream
|
|
||||||
- '.'
|
|
||||||
input: '[1,[2,3'
|
|
||||||
expected: |
|
|
||||||
[[0],1]
|
|
||||||
[[1,0],2]
|
|
||||||
[[1,1],3]
|
|
||||||
error: |
|
|
||||||
invalid json: <stdin>
|
|
||||||
[1,[2,3
|
|
||||||
^ unexpected EOF
|
|
||||||
|
|
||||||
- name: yaml input option
|
- name: yaml input option
|
||||||
args:
|
args:
|
||||||
- --yaml-input
|
- --yaml-input
|
||||||
@@ -8205,17 +7822,6 @@
|
|||||||
2 | bar: ]
|
2 | bar: ]
|
||||||
^ did not find expected node content
|
^ did not find expected node content
|
||||||
|
|
||||||
- name: yaml input option error with multibyte characters
|
|
||||||
args:
|
|
||||||
- --yaml-input
|
|
||||||
- '.'
|
|
||||||
input: |
|
|
||||||
{123: 4, 123: 5}
|
|
||||||
error: |
|
|
||||||
invalid yaml: <stdin>:1
|
|
||||||
1 | {123: 4, 123: 5}
|
|
||||||
^ mapping key "123" already defined at line 1
|
|
||||||
|
|
||||||
- name: yaml input option multiple errors
|
- name: yaml input option multiple errors
|
||||||
args:
|
args:
|
||||||
- --yaml-input
|
- --yaml-input
|
||||||
|
|||||||
+44
-173
@@ -17,7 +17,6 @@ type compiler struct {
|
|||||||
variables []string
|
variables []string
|
||||||
customFuncs map[string]function
|
customFuncs map[string]function
|
||||||
inputIter Iter
|
inputIter Iter
|
||||||
inputMu sync.Mutex
|
|
||||||
codes []*code
|
codes []*code
|
||||||
codeinfos []codeinfo
|
codeinfos []codeinfo
|
||||||
builtinScope *scopeinfo
|
builtinScope *scopeinfo
|
||||||
@@ -394,7 +393,7 @@ func (c *compiler) compileQuery(e *Query) error {
|
|||||||
return c.compileAlt(e.Left, e.Right)
|
return c.compileAlt(e.Left, e.Right)
|
||||||
case OpAssign, OpModify, OpUpdateAdd, OpUpdateSub,
|
case OpAssign, OpModify, OpUpdateAdd, OpUpdateSub,
|
||||||
OpUpdateMul, OpUpdateDiv, OpUpdateMod, OpUpdateAlt:
|
OpUpdateMul, OpUpdateDiv, OpUpdateMod, OpUpdateAlt:
|
||||||
return c.compileQueryUpdate(e.Left, e.Right, e.Op, nil)
|
return c.compileQueryUpdate(e.Left, e.Right, e.Op)
|
||||||
case OpOr:
|
case OpOr:
|
||||||
return c.compileIf(
|
return c.compileIf(
|
||||||
&If{
|
&If{
|
||||||
@@ -468,10 +467,7 @@ func (c *compiler) compileAlt(l, r *Query) error {
|
|||||||
return c.compileQuery(r)
|
return c.compileQuery(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// compileQueryUpdate compiles an assignment operator. alloc is nil unless the
|
func (c *compiler) compileQueryUpdate(l, r *Query, op Operator) error {
|
||||||
// assignment may update the input value in place, in which case it yields the
|
|
||||||
// allocator of the enclosing reduce expression.
|
|
||||||
func (c *compiler) compileQueryUpdate(l, r *Query, op Operator, alloc *Query) error {
|
|
||||||
switch op {
|
switch op {
|
||||||
case OpAssign:
|
case OpAssign:
|
||||||
// optimize assignment operator with constant indexing and slicing
|
// optimize assignment operator with constant indexing and slicing
|
||||||
@@ -491,7 +487,12 @@ func (c *compiler) compileQueryUpdate(l, r *Query, op Operator, alloc *Query) er
|
|||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
case OpModify:
|
case OpModify:
|
||||||
return c.compileUpdateFunc(op.getFunc(), []*Query{l, r}, alloc)
|
return c.compileFunc(
|
||||||
|
&Func{
|
||||||
|
Name: op.getFunc(),
|
||||||
|
Args: []*Query{l, r},
|
||||||
|
},
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
name := "$%0"
|
name := "$%0"
|
||||||
c.append(&code{op: opdup})
|
c.append(&code{op: opdup})
|
||||||
@@ -499,9 +500,10 @@ func (c *compiler) compileQueryUpdate(l, r *Query, op Operator, alloc *Query) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.append(&code{op: opstore, v: c.pushVariable(name)})
|
c.append(&code{op: opstore, v: c.pushVariable(name)})
|
||||||
return c.compileUpdateFunc(
|
return c.compileFunc(
|
||||||
"_modify",
|
&Func{
|
||||||
[]*Query{
|
Name: "_modify",
|
||||||
|
Args: []*Query{
|
||||||
l,
|
l,
|
||||||
{Term: &Term{
|
{Term: &Term{
|
||||||
Type: TermTypeFunc,
|
Type: TermTypeFunc,
|
||||||
@@ -514,38 +516,11 @@ func (c *compiler) compileQueryUpdate(l, r *Query, op Operator, alloc *Query) er
|
|||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
alloc,
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
// Both names are deliberately invalid identifiers, so that a query cannot
|
|
||||||
// reach the allocator variants directly.
|
|
||||||
allocatorArgSuffix = "/3"
|
|
||||||
allocatorName = "$%allocator"
|
|
||||||
)
|
|
||||||
|
|
||||||
// compileUpdateFunc calls _assign or _modify, or the variant taking an
|
|
||||||
// allocator as its third argument.
|
|
||||||
func (c *compiler) compileUpdateFunc(name string, args []*Query, alloc *Query) error {
|
|
||||||
if alloc == nil {
|
|
||||||
return c.compileFunc(&Func{Name: name, Args: args})
|
|
||||||
}
|
|
||||||
args = append(args, alloc)
|
|
||||||
fn := c.lookupBuiltin(name+allocatorArgSuffix, len(args))
|
|
||||||
if fn == nil {
|
|
||||||
switch name {
|
|
||||||
case "_assign":
|
|
||||||
c.compileAssign(true)
|
|
||||||
case "_modify":
|
|
||||||
c.compileModify(true)
|
|
||||||
}
|
|
||||||
fn = c.lookupBuiltin(name+allocatorArgSuffix, len(args))
|
|
||||||
}
|
|
||||||
return c.compileCallPc(fn, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *compiler) compileBind(l, r *Query, patterns []*Pattern) error {
|
func (c *compiler) compileBind(l, r *Query, patterns []*Pattern) error {
|
||||||
defer c.newScopeDepth()()
|
defer c.newScopeDepth()()
|
||||||
c.append(&code{op: opdup})
|
c.append(&code{op: opdup})
|
||||||
@@ -553,36 +528,23 @@ func (c *compiler) compileBind(l, r *Query, patterns []*Pattern) error {
|
|||||||
if err := c.compileQuery(l); err != nil {
|
if err := c.compileQuery(l); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.compilePatterns(patterns, func() error {
|
|
||||||
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) compilePatterns(patterns []*Pattern, body func() error) error {
|
|
||||||
var pc int
|
var pc int
|
||||||
var vs [][2]int
|
var vs [][2]int
|
||||||
if len(patterns) > 1 {
|
|
||||||
for _, p := range patterns {
|
|
||||||
vs = c.pushPatternVariables(vs, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, p := range patterns {
|
for i, p := range patterns {
|
||||||
var pcc int
|
var pcc int
|
||||||
|
var err error
|
||||||
if i < len(patterns)-1 {
|
if i < len(patterns)-1 {
|
||||||
defer c.lazy(func() *code {
|
defer c.lazy(func() *code {
|
||||||
return &code{op: opforkalt, v: pcc}
|
return &code{op: opforkalt, v: pcc}
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
|
if 0 < i {
|
||||||
for _, v := range vs {
|
for _, v := range vs {
|
||||||
c.append(&code{op: oppush, v: nil})
|
c.append(&code{op: oppush, v: nil})
|
||||||
c.append(&code{op: opstore, v: v})
|
c.append(&code{op: opstore, v: v})
|
||||||
}
|
}
|
||||||
if _, err := c.compilePattern(nil, p); err != nil {
|
}
|
||||||
|
if vs, err = c.compilePattern(vs[:0], p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if i < len(patterns)-1 {
|
if i < len(patterns)-1 {
|
||||||
@@ -595,29 +557,12 @@ func (c *compiler) compilePatterns(patterns []*Pattern, body func() error) error
|
|||||||
if len(patterns) > 1 {
|
if len(patterns) > 1 {
|
||||||
pc = len(c.codes)
|
pc = len(c.codes)
|
||||||
}
|
}
|
||||||
return body()
|
if len(patterns) == 1 && c.codes[len(c.codes)-2].op == opexpbegin {
|
||||||
}
|
c.codes[len(c.codes)-2].op = opnop
|
||||||
|
} else {
|
||||||
func (c *compiler) pushPatternVariables(vs [][2]int, p *Pattern) [][2]int {
|
c.append(&code{op: opexpend})
|
||||||
if p.Name != "" {
|
|
||||||
if v := c.pushVariable(p.Name); !slices.Contains(vs, v) {
|
|
||||||
vs = append(vs, v)
|
|
||||||
}
|
}
|
||||||
} else if len(p.Array) > 0 {
|
return c.compileQuery(r)
|
||||||
for _, p := range p.Array {
|
|
||||||
vs = c.pushPatternVariables(vs, p)
|
|
||||||
}
|
|
||||||
} else if len(p.Object) > 0 {
|
|
||||||
for _, kv := range p.Object {
|
|
||||||
if kv.Key != "" && kv.Key[0] == '$' {
|
|
||||||
vs = c.pushPatternVariables(vs, &Pattern{Name: kv.Key})
|
|
||||||
}
|
|
||||||
if kv.Val != nil {
|
|
||||||
vs = c.pushPatternVariables(vs, kv.Val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *compiler) compilePattern(vs [][2]int, p *Pattern) ([][2]int, error) {
|
func (c *compiler) compilePattern(vs [][2]int, p *Pattern) ([][2]int, error) {
|
||||||
@@ -771,46 +716,23 @@ func (c *compiler) compileReduce(e *Reduce) error {
|
|||||||
}
|
}
|
||||||
f()
|
f()
|
||||||
c.append(&code{op: opstore, v: v})
|
c.append(&code{op: opstore, v: v})
|
||||||
// An assignment forming the whole update query is the only thing that
|
|
||||||
// observes the accumulated value, so it may modify it in place; see
|
|
||||||
// Query#inPlaceUpdate. The allocator lives across the whole reduction.
|
|
||||||
l, r, op, inPlace := e.Update.inPlaceUpdate()
|
|
||||||
var alloc *Query
|
|
||||||
if inPlace {
|
|
||||||
c.appends(
|
|
||||||
&code{op: oppush, v: nil},
|
|
||||||
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
|
||||||
&code{op: opstore, v: c.pushVariable(allocatorName)},
|
|
||||||
)
|
|
||||||
alloc = &Query{Term: &Term{
|
|
||||||
Type: TermTypeFunc, Func: &Func{Name: allocatorName},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
setfork := c.lazy(func() *code {
|
setfork := c.lazy(func() *code {
|
||||||
return &code{op: opfork, v: len(c.codes)}
|
return &code{op: opfork, v: len(c.codes)}
|
||||||
})
|
})
|
||||||
if err := c.compileQuery(e.Query); err != nil {
|
if err := c.compileQuery(e.Query); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.compilePatterns(e.Patterns, func() error {
|
if _, err := c.compilePattern(nil, e.Pattern); err != nil {
|
||||||
c.append(&code{op: opload, v: v})
|
return err
|
||||||
f := c.newScopeDepth()
|
|
||||||
var err error
|
|
||||||
if inPlace {
|
|
||||||
err = c.compileQueryUpdate(l, r, op, alloc)
|
|
||||||
} else {
|
|
||||||
err = c.compileQuery(e.Update)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
c.append(&code{op: opload, v: v})
|
||||||
|
f = c.newScopeDepth()
|
||||||
|
if err := c.compileQuery(e.Update); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f()
|
f()
|
||||||
c.append(&code{op: opstore, v: v})
|
c.append(&code{op: opstore, v: v})
|
||||||
c.append(&code{op: opbacktrack})
|
c.append(&code{op: opbacktrack})
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setfork()
|
setfork()
|
||||||
c.append(&code{op: oppop})
|
c.append(&code{op: oppop})
|
||||||
c.append(&code{op: opload, v: v})
|
c.append(&code{op: opload, v: v})
|
||||||
@@ -831,9 +753,11 @@ func (c *compiler) compileForeach(e *Foreach) error {
|
|||||||
if err := c.compileQuery(e.Query); err != nil {
|
if err := c.compileQuery(e.Query); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.compilePatterns(e.Patterns, func() error {
|
if _, err := c.compilePattern(nil, e.Pattern); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
c.append(&code{op: opload, v: v})
|
c.append(&code{op: opload, v: v})
|
||||||
f := c.newScopeDepth()
|
f = c.newScopeDepth()
|
||||||
if err := c.compileQuery(e.Update); err != nil {
|
if err := c.compileQuery(e.Update); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -845,7 +769,6 @@ func (c *compiler) compileForeach(e *Foreach) error {
|
|||||||
return c.compileQuery(e.Extract)
|
return c.compileQuery(e.Extract)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *compiler) compileLabel(e *Label) error {
|
func (c *compiler) compileLabel(e *Label) error {
|
||||||
@@ -1009,9 +932,9 @@ func (c *compiler) compileFunc(e *Func) error {
|
|||||||
if !compiled {
|
if !compiled {
|
||||||
switch e.Name {
|
switch e.Name {
|
||||||
case "_assign":
|
case "_assign":
|
||||||
c.compileAssign(false)
|
c.compileAssign()
|
||||||
case "_modify":
|
case "_modify":
|
||||||
c.compileModify(false)
|
c.compileModify()
|
||||||
case "_last":
|
case "_last":
|
||||||
c.compileLast()
|
c.compileLast()
|
||||||
}
|
}
|
||||||
@@ -1107,54 +1030,29 @@ func (c *compiler) compileFunc(e *Func) error {
|
|||||||
//
|
//
|
||||||
// To overcome the difficulty of reducing allocations on `setpath`, we use the
|
// To overcome the difficulty of reducing allocations on `setpath`, we use the
|
||||||
// `allocator` type and track the allocated addresses during the reduction.
|
// `allocator` type and track the allocated addresses during the reduction.
|
||||||
//
|
func (c *compiler) compileAssign() {
|
||||||
// The shared variant reuses the allocator of the enclosing reduce expression
|
defer c.appendBuiltin("_assign", 2)()
|
||||||
// instead of creating a new one, modifying the value in place.
|
|
||||||
func (c *compiler) compileAssign(shared bool) {
|
|
||||||
name, argcnt := "_assign", 2
|
|
||||||
if shared {
|
|
||||||
name, argcnt = name+allocatorArgSuffix, 3
|
|
||||||
}
|
|
||||||
defer c.appendBuiltin(name, argcnt)()
|
|
||||||
scope := c.newScope()
|
scope := c.newScope()
|
||||||
v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1}
|
v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1}
|
||||||
x, a := [2]int{scope.id, 2}, [2]int{scope.id, 3}
|
x, a := [2]int{scope.id, 2}, [2]int{scope.id, 3}
|
||||||
// Cannot reuse v, p due to backtracking in x.
|
// Cannot reuse v, p due to backtracking in x.
|
||||||
w, q := [2]int{scope.id, 4}, [2]int{scope.id, 5}
|
w, q := [2]int{scope.id, 4}, [2]int{scope.id, 5}
|
||||||
c.appends(
|
c.appends(
|
||||||
&code{op: opscope, v: [3]int{scope.id, 6, argcnt}},
|
&code{op: opscope, v: [3]int{scope.id, 6, 2}},
|
||||||
&code{op: opstore, v: v}, // def _assign(p; $x):
|
&code{op: opstore, v: v}, // def _assign(p; $x):
|
||||||
&code{op: opstore, v: p},
|
&code{op: opstore, v: p},
|
||||||
&code{op: opstore, v: x},
|
&code{op: opstore, v: x},
|
||||||
)
|
|
||||||
if shared {
|
|
||||||
c.append(&code{op: opstore, v: a}) // the allocator argument
|
|
||||||
}
|
|
||||||
c.appends(
|
|
||||||
&code{op: opload, v: v},
|
&code{op: opload, v: v},
|
||||||
&code{op: opexpbegin},
|
&code{op: opexpbegin},
|
||||||
&code{op: opload, v: x},
|
&code{op: opload, v: x},
|
||||||
&code{op: opcallpc},
|
&code{op: opcallpc},
|
||||||
&code{op: opstore, v: x},
|
&code{op: opstore, v: x},
|
||||||
&code{op: opexpend},
|
&code{op: opexpend},
|
||||||
)
|
|
||||||
if shared {
|
|
||||||
c.appends(
|
|
||||||
&code{op: opload, v: v},
|
|
||||||
&code{op: opload, v: a},
|
|
||||||
&code{op: opcallpc},
|
|
||||||
&code{op: opstore, v: a},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
c.appends(
|
|
||||||
&code{op: oppush, v: nil},
|
&code{op: oppush, v: nil},
|
||||||
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
||||||
&code{op: opstore, v: a},
|
&code{op: opstore, v: a},
|
||||||
)
|
|
||||||
}
|
|
||||||
c.appends(
|
|
||||||
&code{op: opload, v: v},
|
&code{op: opload, v: v},
|
||||||
&code{op: opfork, v: len(c.codes) + 17}, // reduce [L1]
|
&code{op: opfork, v: len(c.codes) + 30}, // reduce [L1]
|
||||||
&code{op: opdup},
|
&code{op: opdup},
|
||||||
&code{op: opstore, v: w},
|
&code{op: opstore, v: w},
|
||||||
&code{op: oppathbegin}, // path(p)
|
&code{op: oppathbegin}, // path(p)
|
||||||
@@ -1178,49 +1076,24 @@ func (c *compiler) compileAssign(shared bool) {
|
|||||||
|
|
||||||
// Appends the compiled code for the update-assignment operator (`|=`) to
|
// Appends the compiled code for the update-assignment operator (`|=`) to
|
||||||
// maximize performance. We use the `allocator` type, just like `_assign/2`.
|
// maximize performance. We use the `allocator` type, just like `_assign/2`.
|
||||||
//
|
func (c *compiler) compileModify() {
|
||||||
// The shared variant reuses the allocator of the enclosing reduce expression;
|
defer c.appendBuiltin("_modify", 2)()
|
||||||
// see compiler#compileAssign.
|
|
||||||
func (c *compiler) compileModify(shared bool) {
|
|
||||||
name, argcnt := "_modify", 2
|
|
||||||
if shared {
|
|
||||||
name, argcnt = name+allocatorArgSuffix, 3
|
|
||||||
}
|
|
||||||
defer c.appendBuiltin(name, argcnt)()
|
|
||||||
scope := c.newScope()
|
scope := c.newScope()
|
||||||
v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1}
|
v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1}
|
||||||
f, d := [2]int{scope.id, 2}, [2]int{scope.id, 3}
|
f, d := [2]int{scope.id, 2}, [2]int{scope.id, 3}
|
||||||
a, l := [2]int{scope.id, 4}, [2]int{scope.id, 5}
|
a, l := [2]int{scope.id, 4}, [2]int{scope.id, 5}
|
||||||
c.appends(
|
c.appends(
|
||||||
&code{op: opscope, v: [3]int{scope.id, 6, argcnt}},
|
&code{op: opscope, v: [3]int{scope.id, 6, 2}},
|
||||||
&code{op: opstore, v: v}, // def _modify(p; f):
|
&code{op: opstore, v: v}, // def _modify(p; f):
|
||||||
&code{op: opstore, v: p},
|
&code{op: opstore, v: p},
|
||||||
&code{op: opstore, v: f},
|
&code{op: opstore, v: f},
|
||||||
)
|
|
||||||
if shared {
|
|
||||||
c.append(&code{op: opstore, v: a}) // the allocator argument
|
|
||||||
}
|
|
||||||
c.appends(
|
|
||||||
&code{op: oppush, v: []any{}},
|
&code{op: oppush, v: []any{}},
|
||||||
&code{op: opstore, v: d},
|
&code{op: opstore, v: d},
|
||||||
)
|
|
||||||
if shared {
|
|
||||||
c.appends(
|
|
||||||
&code{op: opload, v: v},
|
|
||||||
&code{op: opload, v: a},
|
|
||||||
&code{op: opcallpc},
|
|
||||||
&code{op: opstore, v: a},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
c.appends(
|
|
||||||
&code{op: oppush, v: nil},
|
&code{op: oppush, v: nil},
|
||||||
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
&code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}},
|
||||||
&code{op: opstore, v: a},
|
&code{op: opstore, v: a},
|
||||||
)
|
|
||||||
}
|
|
||||||
c.appends(
|
|
||||||
&code{op: opload, v: v},
|
&code{op: opload, v: v},
|
||||||
&code{op: opfork, v: len(c.codes) + 31}, // reduce [L1]
|
&code{op: opfork, v: len(c.codes) + 40}, // reduce [L1]
|
||||||
&code{op: oppathbegin}, // path(p)
|
&code{op: oppathbegin}, // path(p)
|
||||||
&code{op: opload, v: p},
|
&code{op: opload, v: p},
|
||||||
&code{op: opcallpc},
|
&code{op: opcallpc},
|
||||||
@@ -1229,7 +1102,7 @@ func (c *compiler) compileModify(shared bool) {
|
|||||||
&code{op: opstore, v: p}, // as $p (.;
|
&code{op: opstore, v: p}, // as $p (.;
|
||||||
&code{op: opforklabel, v: l}, // label $l |
|
&code{op: opforklabel, v: l}, // label $l |
|
||||||
&code{op: opload, v: v}, //
|
&code{op: opload, v: v}, //
|
||||||
&code{op: opfork, v: len(c.codes) + 28}, // [L2]
|
&code{op: opfork, v: len(c.codes) + 37}, // [L2]
|
||||||
&code{op: oppop}, // (getpath($p) |
|
&code{op: oppop}, // (getpath($p) |
|
||||||
&code{op: opload, v: a},
|
&code{op: opload, v: a},
|
||||||
&code{op: opload, v: a},
|
&code{op: opload, v: a},
|
||||||
@@ -1243,8 +1116,8 @@ func (c *compiler) compileModify(shared bool) {
|
|||||||
&code{op: opcall, v: [3]any{funcSetpathWithAllocator, 3, "_setpath"}},
|
&code{op: opcall, v: [3]any{funcSetpathWithAllocator, 3, "_setpath"}},
|
||||||
&code{op: opstore, v: v},
|
&code{op: opstore, v: v},
|
||||||
&code{op: opload, v: v}, // ., break $l
|
&code{op: opload, v: v}, // ., break $l
|
||||||
&code{op: opfork, v: len(c.codes) + 26}, // [L4]
|
&code{op: opfork, v: len(c.codes) + 35}, // [L4]
|
||||||
&code{op: opjump, v: len(c.codes) + 30}, // [L3]
|
&code{op: opjump, v: len(c.codes) + 39}, // [L3]
|
||||||
&code{op: opload, v: l}, // [L4]
|
&code{op: opload, v: l}, // [L4]
|
||||||
&code{op: opcall, v: [3]any{funcBreak(""), 0, "_break"}},
|
&code{op: opcall, v: [3]any{funcBreak(""), 0, "_break"}},
|
||||||
&code{op: opload, v: p}, // append $p to $d [L2]
|
&code{op: opload, v: p}, // append $p to $d [L2]
|
||||||
@@ -1330,8 +1203,6 @@ func (c *compiler) funcBuiltins(any, []any) any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *compiler) funcInput(any, []any) any {
|
func (c *compiler) funcInput(any, []any) any {
|
||||||
c.inputMu.Lock()
|
|
||||||
defer c.inputMu.Unlock()
|
|
||||||
v, ok := c.inputIter.Next()
|
v, ok := c.inputIter.Next()
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("break")
|
return errors.New("break")
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -375,95 +374,6 @@ func TestCodeRun_RaceRegexp(t *testing.T) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodeRun_RaceInputIter(t *testing.T) {
|
|
||||||
const n = 10
|
|
||||||
values := make([]int, n)
|
|
||||||
for i := range values {
|
|
||||||
values[i] = i
|
|
||||||
}
|
|
||||||
query, err := gojq.Parse("input")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
code, err := gojq.Compile(query, gojq.WithInputIter(gojq.NewIter(values...)))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var seen sync.Map
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for range n {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
v, ok := code.Run(nil).Next()
|
|
||||||
if !ok {
|
|
||||||
t.Error("expected a value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, loaded := seen.LoadOrStore(v, struct{}{}); loaded {
|
|
||||||
t.Errorf("input %v was consumed more than once", v)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
for i := range n {
|
|
||||||
if _, ok := seen.Load(i); !ok {
|
|
||||||
t.Errorf("expected input %d to be consumed", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Building an object or an array of n elements must allocate memory
|
|
||||||
// proportional to n, not to n squared.
|
|
||||||
func TestCodeRun_ReduceAssignmentAllocation(t *testing.T) {
|
|
||||||
for _, src := range []string{
|
|
||||||
`reduce .[] as $x ({}; .[$x|tostring] = $x)`,
|
|
||||||
`reduce .[] as $x ({}; .[$x|tostring] |= $x)`,
|
|
||||||
`reduce .[] as $x ({}; .[$x|tostring] += 1)`,
|
|
||||||
`reduce .[] as $x ([]; .[$x] = $x)`,
|
|
||||||
} {
|
|
||||||
t.Run(src, func(t *testing.T) {
|
|
||||||
query, err := gojq.Parse(src)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
code, err := gojq.Compile(query)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
small, large := allocatedBytes(t, code, 1000), allocatedBytes(t, code, 4000)
|
|
||||||
// Quadratic growth would be about sixteen times as much.
|
|
||||||
if expected := small * 8; large > expected {
|
|
||||||
t.Errorf("expected at most %v bytes for 4000 elements, got %v (%v bytes for 1000 elements)",
|
|
||||||
expected, large, small)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func allocatedBytes(t *testing.T, code *gojq.Code, n int) uint64 {
|
|
||||||
t.Helper()
|
|
||||||
input := make([]any, n)
|
|
||||||
for i := range input {
|
|
||||||
input[i] = i
|
|
||||||
}
|
|
||||||
var before, after runtime.MemStats
|
|
||||||
runtime.GC()
|
|
||||||
runtime.ReadMemStats(&before)
|
|
||||||
iter := code.Run(input)
|
|
||||||
for {
|
|
||||||
v, ok := iter.Next()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err, ok := v.(error); ok {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runtime.ReadMemStats(&after)
|
|
||||||
return after.TotalAlloc - before.TotalAlloc
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCompile(b *testing.B) {
|
func BenchmarkCompile(b *testing.B) {
|
||||||
cnt, err := os.ReadFile("builtin.jq")
|
cnt, err := os.ReadFile("builtin.jq")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
-204
@@ -1,204 +0,0 @@
|
|||||||
package gojq
|
|
||||||
|
|
||||||
// Static analysis deciding whether the accumulated value of a reduce
|
|
||||||
// expression may be updated in place; see compiler#compileReduce. The checks
|
|
||||||
// are conservative: failing them only keeps the previous copying behavior.
|
|
||||||
|
|
||||||
// inPlaceUpdate reports whether the query is a single assignment applied to
|
|
||||||
// the input value, and returns its operands.
|
|
||||||
func (e *Query) inPlaceUpdate() (l, r *Query, op Operator, ok bool) {
|
|
||||||
if e.Term != nil || len(e.FuncDefs) > 0 {
|
|
||||||
return nil, nil, 0, false
|
|
||||||
}
|
|
||||||
switch e.Op {
|
|
||||||
case OpModify:
|
|
||||||
// _modify releases the allocator before calling the update function.
|
|
||||||
return e.Left, e.Right, e.Op, true
|
|
||||||
case OpAssign:
|
|
||||||
// A constant path compiles to setpath, which uses no allocator.
|
|
||||||
if e.Left.toIndices(nil) != nil {
|
|
||||||
return nil, nil, 0, false
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case OpUpdateAdd, OpUpdateSub, OpUpdateMul,
|
|
||||||
OpUpdateDiv, OpUpdateMod, OpUpdateAlt:
|
|
||||||
// The right-hand side must neither observe the input value, which it
|
|
||||||
// could otherwise alias, nor yield multiple values, which would run
|
|
||||||
// the assignment against an already modified input.
|
|
||||||
if !e.Right.readsInput() && e.Right.singleValued() {
|
|
||||||
return e.Left, e.Right, e.Op, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil, 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// readsInput reports whether evaluating the query may observe its input value.
|
|
||||||
// It answers true unless it can prove otherwise.
|
|
||||||
func (e *Query) readsInput() bool {
|
|
||||||
if len(e.FuncDefs) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if e.Term != nil {
|
|
||||||
return e.Term.readsInput()
|
|
||||||
}
|
|
||||||
switch e.Op {
|
|
||||||
case OpPipe:
|
|
||||||
// Binding variables also exposes the input to the right-hand side.
|
|
||||||
if len(e.Patterns) > 0 {
|
|
||||||
return e.Left.readsInput() || e.Right.readsInput()
|
|
||||||
}
|
|
||||||
return e.Left.readsInput()
|
|
||||||
case OpComma, OpAdd, OpSub, OpMul, OpDiv, OpMod, OpEq, OpNe,
|
|
||||||
OpGt, OpLt, OpGe, OpLe, OpAnd, OpOr, OpAlt:
|
|
||||||
return e.Left.readsInput() || e.Right.readsInput()
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// singleValued reports whether the query yields at most one value.
|
|
||||||
// It answers false unless it can prove otherwise.
|
|
||||||
func (e *Query) singleValued() bool {
|
|
||||||
if len(e.FuncDefs) > 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e.Term != nil {
|
|
||||||
return e.Term.singleValued()
|
|
||||||
}
|
|
||||||
switch e.Op {
|
|
||||||
case OpPipe:
|
|
||||||
return len(e.Patterns) == 0 &&
|
|
||||||
e.Left.singleValued() && e.Right.singleValued()
|
|
||||||
case OpAdd, OpSub, OpMul, OpDiv, OpMod, OpEq, OpNe,
|
|
||||||
OpGt, OpLt, OpGe, OpLe, OpAnd, OpOr:
|
|
||||||
return e.Left.singleValued() && e.Right.singleValued()
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Term) readsInput() bool {
|
|
||||||
for _, s := range e.SuffixList {
|
|
||||||
if s.readsInput() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch e.Type {
|
|
||||||
case TermTypeNull, TermTypeTrue, TermTypeFalse, TermTypeNumber:
|
|
||||||
return false
|
|
||||||
case TermTypeString:
|
|
||||||
return e.Str.readsInput()
|
|
||||||
case TermTypeObject:
|
|
||||||
for _, kv := range e.Object.KeyVals {
|
|
||||||
if kv.readsInput() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case TermTypeArray:
|
|
||||||
return e.Array.Query != nil && e.Array.Query.readsInput()
|
|
||||||
case TermTypeUnary:
|
|
||||||
return e.Unary.Term.readsInput()
|
|
||||||
case TermTypeFunc:
|
|
||||||
// Any function may be redefined, so only $x is known not to read it.
|
|
||||||
return e.Func.Name[0] != '$' || len(e.Func.Args) > 0
|
|
||||||
case TermTypeQuery:
|
|
||||||
return e.Query.readsInput()
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Term) singleValued() bool {
|
|
||||||
for _, s := range e.SuffixList {
|
|
||||||
if !s.singleValued() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch e.Type {
|
|
||||||
case TermTypeNull, TermTypeTrue, TermTypeFalse, TermTypeNumber:
|
|
||||||
return true
|
|
||||||
case TermTypeString:
|
|
||||||
return e.Str.singleValued()
|
|
||||||
case TermTypeObject:
|
|
||||||
for _, kv := range e.Object.KeyVals {
|
|
||||||
if !kv.singleValued() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case TermTypeArray:
|
|
||||||
return true
|
|
||||||
case TermTypeUnary:
|
|
||||||
return e.Unary.Term.singleValued()
|
|
||||||
case TermTypeFunc:
|
|
||||||
return e.Func.Name[0] == '$' && len(e.Func.Args) == 0
|
|
||||||
case TermTypeQuery:
|
|
||||||
return e.Query.singleValued()
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Suffix) readsInput() bool {
|
|
||||||
// Only an index is evaluated against the input value.
|
|
||||||
return e.Index != nil && e.Index.readsInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Suffix) singleValued() bool {
|
|
||||||
return !e.Iter && (e.Index == nil || e.Index.singleValued())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Index) readsInput() bool {
|
|
||||||
if e.Str != nil {
|
|
||||||
return e.Str.readsInput()
|
|
||||||
}
|
|
||||||
return e.Start != nil && e.Start.readsInput() ||
|
|
||||||
e.End != nil && e.End.readsInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Index) singleValued() bool {
|
|
||||||
if e.Str != nil {
|
|
||||||
return e.Str.singleValued()
|
|
||||||
}
|
|
||||||
return (e.Start == nil || e.Start.singleValued()) &&
|
|
||||||
(e.End == nil || e.End.singleValued())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *String) readsInput() bool {
|
|
||||||
for _, q := range e.Queries {
|
|
||||||
if q.readsInput() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *String) singleValued() bool {
|
|
||||||
for _, q := range e.Queries {
|
|
||||||
if !q.singleValued() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ObjectKeyVal) readsInput() bool {
|
|
||||||
if e.Val == nil {
|
|
||||||
// {foo} reads the input value, {$foo} does not.
|
|
||||||
return e.Key == "" || e.Key[0] != '$'
|
|
||||||
}
|
|
||||||
if e.KeyString != nil && e.KeyString.readsInput() ||
|
|
||||||
e.KeyQuery != nil && e.KeyQuery.readsInput() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return e.Val.readsInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ObjectKeyVal) singleValued() bool {
|
|
||||||
if e.KeyString != nil && !e.KeyString.singleValued() ||
|
|
||||||
e.KeyQuery != nil && !e.KeyQuery.singleValued() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return e.Val == nil || e.Val.singleValued()
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user