Here is what I came up with, either one has to run RegEx over the data in a multi-pass scenario or have the match groups have a boolean indicator that the proceeding match group is actually a sub group. Here is the regex:
(?<=X)(?<Data>\d{1,3})(?<SubMatch>\[ )?
(Note there is a space after the \[ in the submatch for easier reading otherwise it should be spaceless as \]))
Here is a description from Expresso
Match a prefix but exclude it from the capture. [X]
X
[Data]: A named capture group. [\d{1,3}]
Any digit, between 1 and 3 repetitions
[SubMatch]: A named capture group. [\[], zero or one repetitions
[
Basically the consumer of the expression can check for the group Submatch to exist and have data. If it does, it suggest that the group is a parent to the next proceeding match group.
Output on X123[X456[X789]]
123[
Data: 123 Submatch: [
456[
Data: 456 Submatch: [
789
Data: 789 Submatch: