Match balanced groups and what they hold
Match a bracket group whole however deeply it nests, keep the groups that hold something, and bind a name only inside its own group (pattern syntax).
$ cat calls.py
log(fetch(url, retries=3), level="info")
save(record)
retry(fetch(url, retries=5), backoff=2.5)
$ cat keys.json
[{"id": 1, "name": "ann", "id": 7}, {"id": 2, "name": "bob"}, {"name": "cy", "id": 3}]
A call, whatever its arguments nest
\B(...) is a balanced paren group whose interior matches the pattern inside it, and @call an
identifier followed by one. A call nested in an argument stays inside the outer match:
$ trex scan '@call' calls.py
[0..40] "log(fetch(url, retries=3), level=\"info\")"
[41..53] "save(record)"
[54..95] "retry(fetch(url, retries=5), backoff=2.5)"
$ trex scan '"fetch" \B(.*)' calls.py
[4..25] "fetch(url, retries=3)"
[60..81] "fetch(url, retries=5)"
\B[...] and \B{...} are the other bracket kinds, @block a balanced brace group and
@nesting a group of any kind.
The groups that hold something
~#(A), written where a group opens, holds when A matches somewhere inside that group, and
!~#(A) when it matches nowhere, however deep:
$ trex scan '\W ~#(\N{>=5}) \B(.*)' calls.py
[54..95] "retry(fetch(url, retries=5), backoff=2.5)"
$ trex scan '\W !~#("retries") \B(.*)' calls.py
[41..53] "save(record)"
~#{m,n}(A) asks for between m and n places inside the group where A starts a match.
A name bound inside one group
A::name binds a register for the rest of the enclosing balanced group and drops it at the
group’s close, so a back-reference to it never reaches past the group. A key repeated inside one
object:
$ trex scan '\B{ .* \Q::k ":" .* =k ":" .* }' keys.json
[1..34] "{\"id\": 1, \"name\": \"ann\", \"id\": 7}" captures: k=""
The binding is dropped when its group closes, so the match reports k empty. A plain \Q:k
outside a group reaches across objects instead, from the first "id" to one in the third
object:
$ trex scan '\Q:k ":" .* =k ":"' keys.json
[2..82] "\"id\": 1, \"name\": \"ann\", \"id\": 7}, {\"id\": 2, \"name\": \"bob\"}, {\"name\": \"cy\", \"id\":" captures: k="\"id\""