Since the language isn't fully specified in the question, I'm assuming a language with the grammar:
root = phrase | phrase "," root
phrase = clause [ "und" clause ]
clause = number conj (numbers | number conj number)
conj = "Abs." | "Satz" | "Nr."
numbers = number | number "und" number | number "," numbers
Note: this grammar isn't necessarily unambiguous. Proof or disproof left as an exercise.
The start of a phrase is simple: /\d+ (Abs\.|Nr\.|Satz)/. The complex parts are the rest of a phrase (i.e. the pattern following the first abbreviation in a phrase) and separating the phrases. The latter seems to be presenting the most trouble.
The end of a phrase can be recognized with one observation: it's always a number followed by either a comma, "und", or the end of the string, and never an abbreviation. If it's possible to write a regex matching the tail of a phrase that doesn't consume the phrase-separator, a simple (, | und |$) should match the separator, as it won't match an abbreviation that would indicate the preceding number is part of the next phrase.
The phrase tail broadly has three forms:
- single number:
number
- conjoined pair:
number abbr. number
- number list:
number(, number)*( und number)?
An alternative, more permissive option for 3. would be to allow either commas or "und" as item separators in the list: number((,| und) number)*. Since this won't impact correctness of results and is more robust, this alternative form is used below.
Combining the branches, this gives the pattern: number( abbr. number|((,| und) number)*)?. As a regex, this is:
\d+( (Abs\.|Nr\.|Satz) \d+|((,| und) \d+)*)?
Note: this assumes there will always be exactly one space character between tokens. A more robust regex would replace all spaces with \s+; this should be done for a production regex, but it's not done in this answer for readability.
The regex for a full phrase is the phrase-start and phrase-tail regexes concatenated and then grouped:
(\d+ (Abs\.|Nr\.|Satz) \d+( (Abs\.|Nr\.|Satz) \d+|((,| und) \d+)*)?)
This contains capturing groups that aren't needed, so turn them into non-capturing groups:
(\d+ (?:Abs\.|Nr\.|Satz) \d+(?: (?:Abs\.|Nr\.|Satz) \d+|(?:(?:,| und) \d+)*)?)
Append the phrase separator pattern, and it's complete. In Python (also available on regex101):
import re
pattern = re.compile(r'(\d+ (?:Abs\.|Nr\.|Satz) \d+(?: (?:Abs\.|Nr\.|Satz) \d+|(?:(?:,| und) \d+)*)?)(?:, | und |$)');
text = "32 Abs. 8 und 10, 37 Abs. 5 Satz 3, 39 Abs. 3 und 40 Abs. 7, 8 und 10"
pattern.findall(text)
# result
['32 Abs. 8 und 10', '37 Abs. 5 Satz 3', '39 Abs. 3', '40 Abs. 7, 8 und 10']
How does this handle other cases? A few examples:
# another und
pattern.findall("39 Abs. 3 und 40 Abs. 7, 8 und 10 und 11 Nr. 12")
# result
['39 Abs. 3', '40 Abs. 7, 8 und 10', '11 Nr. 12']
# number list without ending "und"
pattern.findall("1 Abs. 2 und 3 Abs. 4, 5 und 6 Nr. 7")
# result
['1 Abs. 2', '3 Abs. 4, 5', '6 Nr. 7']
# und after und
pattern.findall("1 Abs. 2 und 3 Abs. 4, 5 Abs. 6 und 7 und 8 und 9 Nr. 10 und 11")
pattern.findall("1 Abs. 2 und 3 Abs. 4 und 5 Abs. 6 und 7 und 8 und 9 Nr. 10 und 11")
# result of both
['1 Abs. 2', '3 Abs. 4', '5 Abs. 6 und 7 und 8', '9 Nr. 10 und 11']
# all commas
pattern.findall("1 Abs. 2, 3 Abs. 4, 5 Abs. 6, 7, 8, 9 Nr. 10, 11")
# result
['1 Abs. 2', '3 Abs. 4', '5 Abs. 6, 7, 8', '9 Nr. 10, 11']
Suppose you wanted to know whether the pattern is complete. For it to be complete, it would need to fully match every string if the pattern were repeated (i.e. /^(pattern)*$/). In Python:
re.match(f"^({pattern.pattern})*$", text)
If you can find text for which the match fails, then the pattern is incomplete. Given the available sample data, there's no way to determine this.