Hi Luc,
I've asked exactly the same question many moons ago... Someone responded that it was a super trivial exercise you would give to students (ca. 10 lines of code!). Unfortunately, the 10 lines of code were not included back then.
Most operators can be implemented by m*n, where m is the min number of consecutive rule applications and n is the max number of consecutive rule applications. n may be inf. You also want the number of times the rule is applied back, ideally. It should non-deterministically generate all strings. It has to work for parsing and generating. It has to be able to take arguments that are given or returned in a list.
The core API would look like this:
'm*n'//3, % ?Low, ?High, -Count, :Dcg_0
'm*n'//4, % ?Low, ?High, -Count, :Dcg_1, -Args1
'm*n'//5, % ?Low, ?High, -Count, :Dcg_2, -Args1, -Args2
You can then define m*, *n, ?, +, #, *.
Implementing this in a naive way, my DCGs still look crappy when compared to the ABNF definition:
[ABNF] field-body = *(continuation 1*character)
[DCG] field_body(S) --> parsing, continuation, +(character, Cs), {string_codes(S, Cs)}.
Here I am at the end of my PhD, still scratching my head as to how to do this properly :-)