This is arguably a nice use case for .NET balancing groups to ensure all round and angle brackets are balanced, see also What are regular expression Balancing Groups?
The first pattern does just that
(?<B>\()+[^()]+(?<-B>\))(?(B)(?!))|(?<A><)+[^<>]+(?<-A>>)+(?(A)(?!))
matching <tags>
and (everything between brackets)
The second regex then is used to tokenize the perfectly balanced parts.
\(|<.*?>|\)
Sample code:
using System;
using System.Text.RegularExpressions;
public class Example
{
public static void Main()
{
string input = @"<expr><op><expr>
(<expr><op><expr>)
<pre_op>(<expr>)
(<expr>)<pre_op>(<expr>)";
Regex rxBalanced = new Regex(
@"(?<B>\()+[^()]+(?<-B>\))(?(B)(?!))|(?<A><)+[^<>]+(?<-A>>)+(?(A)(?!))",
RegexOptions.Multiline
| RegexOptions.CultureInvariant
| RegexOptions.IgnorePatternWhitespace
| RegexOptions.Compiled
);
Regex rxTokens = new Regex(
@"\(|<.*?>|\)",
RegexOptions.Multiline
| RegexOptions.CultureInvariant
| RegexOptions.IgnorePatternWhitespace
| RegexOptions.Compiled
);
foreach (Match match in rxBalanced.Matches(input))
{
foreach (Match token in rxTokens.Matches(match.Value))
{
Console.WriteLine(token.Value);
}
}
}
}
An alternative pattern that would allow you to check both balancing groups at the same time could look like this
(?<B>(\())*((?<A><)+[^<>]+(?<-A>>)+(?(A)(?!)))+(?<-B>(\)))*(?(B)(?!))
Unfortunately, it's much harder to get all the needed values from the resulting nested collection. However, I found this problem interesting enough to create a LinQ-query that does all the black magic:
var regex = new Regex("(?<B>(\\())*((?<A><)+[^<>]+(?<-A>>)+(?(A)(?!)))+(?<-B>(\\)))*(?(B)(?!))",
RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
var x = (from Match m in regex.Matches("(<x><y><z>)<expr>(<a><b><c>)<d>")
select new
{
result = m.Groups[1].Value.StartsWith("(") ?
(new List<string> { "(" }
.Concat(m.Groups[2].Captures.Count > 1 ?
(from Capture c in m.Groups[2].Captures select c.Value).ToList()
: new List<string> { m.Groups[2].Value }
)
.Concat(new List<string> { ")" })
)
: new List<string> { m.Value }
}).SelectMany(r => r.result);