Skip to content
Match balanced groups

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\""