Попробуйте это:
s = 'A +4, B +6, C (hello, goodbye) +5, D +3'
tokens = s.scan(/(?:\(.*?\)|[^,])+/)
tokens.each {|t| puts t.strip}
Выход:
A +4
B +6
C (hello, goodbye) +5
D +3
Краткое объяснение:
(?: # open non-capturing group 1
\( # match '('
.*? # reluctatly match zero or more character other than line breaks
\) # match ')'
| # OR
[^,] # match something other than a comma
)+ # close non-capturing group 1 and repeat it one or more times
Другим вариантом является разделение на запятую, за которой следуют некоторые пробелы, только если первая круглая скобка, которая видна при взгляде вперед, является открывающей круглой скобкой (или вообще без скобок: т.е. конец строки):
s = 'A +4, B +6, C (hello, goodbye) +5, D +3'
tokens = s.split(/,\s*(?=[^()]*(?:\(|$))/)
tokens.each {|t| puts t}
выдаст тот же результат, но я считаю, что метод очистки scan