Jadi masalah Anda adalah memvalidasi regex, Anda memilih regex untuk menyelesaikannya. Saya bertanya-tanya apakah properti regex yang bertambah banyak adalah additive atau multiplicative. Rasanya seperti 4 masalah, bukan 2 :)
abesto
15
Ada banyak notasi untuk ekspresi reguler - beberapa fitur dan ejaannya umum bagi sebagian besar, beberapa dieja secara berbeda atau hanya tersedia dalam satu notasi tertentu. Sebagian besar dari notasi tersebut tidak "biasa" dalam arti tata bahasa biasa - Anda akan memerlukan parser bebas konteks untuk menangani penyatuan subekspresi yang tidak terbatas - meskipun banyak notasi "ekspresi reguler" modern memiliki ekstensi yang melampaui definisi formal asli dan mungkin memungkinkan notasi mereka sendiri diakui. Bagaimanapun, mengapa tidak bertanya pada perpustakaan regex Anda apakah setiap regex valid?
Steve314
1
@ bevacqua saya perlu memvalidasi regexp dalam skema XML. Bagaimana saya bisa melakukannya tanpa regexp lain?
zenden2k
3
Sebenarnya kompilasi / jalankan regex (pola) untuk diperiksa, di bawah mekanisme penanganan pengecualian yang dimiliki bahasa Anda. Jadi mesin regex bahasa / kompiler itu sendiri akan memeriksanya. (Ini mengasumsikan sintaksis dasar yang benar sehingga program berjalan, tetapi itu dapat dimasukkan dalam cek dengan menggunakan fasilitas bahasa Anda untuk mengevaluasi string untuk regex sebagai (mungkin secara sintaksis salah) kode, atau semacamnya.)
/^# start of string(# first group start(?:(?:[^?+*{}()[\]\\|]+# literals and ^, $| \\.# escaped characters| \[ (?: \^?\\.| \^[^\\]|[^\\^])# character classes(?:[^\]\\]+| \\.)* \]| \( (?:\?[:=!]|\?<[=!]|\?>)?(?1)?? \) # parenthesis, with recursive content| \(\? (?:R|[+-]?\d+) \) # recursive matching)(?:(?:[?+*]|\{\d+(?:,\d*)?\})[?+]?)?# quantifiers| \| # alternative)*# repeat content)# end first group
$ # end of string/
Ini adalah regex rekursif, dan tidak didukung oleh banyak mesin regex. Yang berbasis PCRE harus mendukungnya.
Apakah ini akan memvalidasi substitusi dan terjemahan?
Ini akan memvalidasi hanya bagian regex dari substitusi dan terjemahan. s/<this part>/.../
Secara teori tidak mungkin untuk mencocokkan semua tata bahasa regex yang valid dengan suatu regex.
Mungkin saja jika mesin regex mendukung rekursi, seperti PCRE, tetapi itu tidak bisa benar-benar disebut ekspresi reguler lagi.
Memang, "ekspresi reguler rekursif" bukanlah ekspresi reguler. Tapi ini ekstensi yang sering diterima untuk mesin regex ... Ironisnya, regex yang diperluas ini tidak cocok dengan regex yang diperluas.
"Secara teori, teori dan praktik adalah sama. Dalam praktik, mereka tidak sama." Hampir setiap orang yang mengetahui ekspresi reguler tahu bahwa ekspresi reguler tidak mendukung rekursi. Tetapi PCRE dan kebanyakan implementasi lainnya mendukung lebih dari sekadar ekspresi reguler dasar.
menggunakan ini dengan skrip shell dalam perintah grep, ini menunjukkan kepada saya beberapa kesalahan .. grep: Konten {} yang tidak valid. Saya membuat skrip yang dapat menangkap basis kode untuk menemukan semua file yang berisi ekspresi reguler
Pola ini mengeksploitasi ekstensi yang disebut ekspresi reguler rekursif. Ini tidak didukung oleh rasa POSIX dari regex. Anda bisa mencoba dengan sakelar -P, untuk mengaktifkan rasa regre PCRE.
Regex sendiri "bukan bahasa biasa dan karenanya tidak dapat diuraikan dengan ekspresi reguler ..."
Ini berlaku untuk ekspresi reguler klasik. Beberapa implementasi modern memungkinkan rekursi, yang membuatnya menjadi bahasa Bebas Konteks, meskipun agak bertele-tele untuk tugas ini.
Saya melihat di mana Anda cocok []()/\. dan karakter regex khusus lainnya. Di mana Anda mengizinkan karakter non-khusus? Sepertinya ini akan cocok ^(?:[\.]+)$, tetapi tidak ^abcdefg$. Itu regex yang valid.
[^?+*{}()[\]\\|]akan cocok dengan karakter tunggal mana pun, bukan bagian dari konstruksi lainnya. Ini termasuk kedua literal ( a- z), dan karakter khusus tertentu ( ^, $, .).
Jawaban ini mengirim orang ke arah yang sepenuhnya salah. Mereka seharusnya tidak pernah menggunakan regEx untuk menemukan ekspresi reguler, karena itu tidak dapat bekerja dengan benar dalam semua kasus. Lihat jawaban saya ditambahkan.
vitaly-t
1
.{,1}tidak tertandingi. Ubah ke ^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$pertandingan. Ubah \d+ke\d*
yunzen
4
regex oleh def seharusnya tidak memiliki rekursi, setidaknya mengatakan sesuatu seperti itu dalam jawaban Anda, mesin regex Anda mungkin "terlalu kuat" dan bukan benar-benar mesin regex.
Charlie Parker
Hanya sebuah catatan Anda lupa bendera x
RedClover
Validator ini tampaknya dibuat untuk ekspresi PCRE, tetapi akan melewati banyak ERE POSIX yang tidak valid. Khususnya, mereka adalah pilih-pilih sedikit dalam rentang kelas karakter, misalnya ini berlaku di PCRE tapi tidak di ERE: [a-b-c].
Pedro Gimeno
321
Tidak sepertinya.
Evaluasilah dalam try..catchatau apa pun bahasa Anda sediakan.
Tidak, jika Anda benar-benar berbicara tentang ekspresi reguler dan tidak termasuk beberapa implementasi ekspresi reguler yang sebenarnya adalah tata bahasa bebas konteks.
Ada satu batasan ekspresi reguler yang membuatnya mustahil untuk menulis sebuah regex yang cocok dengan semua dan hanya regex. Anda tidak dapat mencocokkan implementasi seperti kawat gigi yang dipasangkan. Regex menggunakan banyak konstruksi seperti itu, mari kita ambil []sebagai contoh. Setiap kali ada [harus ada yang cocok ], yang cukup sederhana untuk sebuah regex "\[.*\]".
Apa yang membuatnya tidak mungkin bagi regex adalah mereka bisa disarangkan. Bagaimana Anda bisa menulis regex yang cocok dengan tanda kurung bersarang? Jawabannya adalah Anda tidak bisa tanpa regex yang sangat panjang. Anda dapat mencocokkan sejumlah kurung bersarang melalui brute force tetapi Anda tidak dapat mencocokkan set kurung bersarang panjang yang sewenang-wenang.
Kemampuan ini sering disebut sebagai penghitungan, karena Anda menghitung kedalaman sarang. Regex menurut definisi tidak memiliki kemampuan untuk menghitung.
Bahasa reguler yang sebenarnya tidak dapat memutuskan tanda kurung yang terbentuk dengan kuat. Jika alfabet Anda berisi '('dan ')'tujuannya adalah untuk memutuskan apakah untaian ini memiliki tanda kurung yang cocok. Karena ini adalah persyaratan yang diperlukan untuk ekspresi reguler, jawabannya adalah tidak.
Namun, jika Anda melonggarkan persyaratan dan menambahkan rekursi Anda mungkin dapat melakukannya. Alasannya adalah bahwa rekursi dapat bertindak sebagai tumpukan yang memungkinkan Anda "menghitung" kedalaman sarang saat ini dengan mendorong ke tumpukan ini.
Tidak, jika Anda menggunakan ekspresi reguler standar.
Alasannya adalah bahwa Anda tidak dapat memenuhi lemma pemompaan untuk bahasa reguler. Negara-negara memompa lemma bahwa string milik bahasa "L" adalah biasa jika ada sejumlah "N" seperti itu, setelah membagi string menjadi tiga substring x, y, z, sehingga |x|>=1 && |xy|<=N, Anda dapat mengulangi ysebanyak yang Anda inginkan dan seluruh string masih akan menjadi milik L.
Konsekuensi dari lemma pemompaan adalah bahwa Anda tidak dapat memiliki string reguler dalam bentuk a^Nb^Mc^N, yaitu, dua substring memiliki panjang yang sama dipisahkan oleh string lain. Dengan cara apa pun Anda memisahkan string seperti itu ke dalam x, ydan z, Anda tidak dapat "memompa" ytanpa mendapatkan string dengan jumlah berbeda dari "a" dan "c", sehingga meninggalkan bahasa aslinya. Itu yang terjadi, misalnya, dengan tanda kurung dalam ekspresi reguler.
Itu bukan deskripsi yang sangat tepat tentang lemma pemompaan. Pertama, itu adalah seluruh bahasa yang bisa teratur atau tidak, bukan string tunggal. Kedua, ini adalah kondisi yang diperlukan, bukan cukup, untuk keteraturan. Akhirnya, hanya string yang cukup panjang yang dapat dipompa.
darij grinberg
13
Meskipun sangat mungkin untuk menggunakan regex rekursif seperti yang diposting MizardX, untuk hal-hal semacam ini jauh lebih berguna sebagai parser. Regex pada awalnya dimaksudkan untuk digunakan dengan bahasa biasa, menjadi rekursif atau memiliki kelompok penyeimbang hanyalah sebuah tambalan.
Bahasa yang mendefinisikan regex yang valid sebenarnya adalah tata bahasa bebas konteks, dan Anda harus menggunakan parser yang sesuai untuk menanganinya. Berikut ini adalah contoh untuk proyek universitas untuk penguraian regex sederhana (tanpa sebagian konstruksi). Ini menggunakan JavaCC. Dan ya, komentar dalam bahasa Spanyol, meskipun nama metode cukup jelas.
Contoh berikut oleh Paul McGuire, berasal dari wiki pyparsing, tetapi sekarang hanya tersedia melalui Wayback Machine , memberikan tata bahasa untuk parsing beberapa regex, untuk tujuan mengembalikan rangkaian string yang cocok. Dengan demikian, ia menolak mereka yang menyertakan istilah pengulangan tanpa batas, seperti '+' dan '*'. Tapi itu harus memberi Anda ide tentang bagaimana menyusun parser yang akan memproses ulang.
# # invRegex.py## Copyright 2008, Paul McGuire## pyparsing script to expand a regular expression into all possible matching strings# Supports:# - {n} and {m,n} repetition, but not unbounded + or * repetition# - ? optional elements# - [] character ranges# - () grouping# - | alternation#
__all__ =["count","invert"]from pyparsing import(Literal, oneOf, printables,ParserElement,Combine,SkipTo, operatorPrecedence,ParseFatalException,Word, nums, opAssoc,Suppress,ParseResults, srange)classCharacterRangeEmitter(object):def __init__(self,chars):# remove duplicate chars in character range, but preserve original order
seen =set()self.charset ="".join( seen.add(c)or c for c in chars if c notin seen )def __str__(self):return'['+self.charset+']'def __repr__(self):return'['+self.charset+']'def makeGenerator(self):def genChars():for s inself.charset:yield s
return genChars
classOptionalEmitter(object):def __init__(self,expr):self.expr = expr
def makeGenerator(self):def optionalGen():yield""for s inself.expr.makeGenerator()():yield s
return optionalGen
classDotEmitter(object):def makeGenerator(self):def dotGen():for c in printables:yield c
return dotGen
classGroupEmitter(object):def __init__(self,exprs):self.exprs =ParseResults(exprs)def makeGenerator(self):def groupGen():def recurseList(elist):if len(elist)==1:for s in elist[0].makeGenerator()():yield s
else:for s in elist[0].makeGenerator()():for s2 in recurseList(elist[1:]):yield s + s2
ifself.exprs:for s in recurseList(self.exprs):yield s
return groupGen
classAlternativeEmitter(object):def __init__(self,exprs):self.exprs = exprs
def makeGenerator(self):def altGen():for e inself.exprs:for s in e.makeGenerator()():yield s
return altGen
classLiteralEmitter(object):def __init__(self,lit):self.lit = lit
def __str__(self):return"Lit:"+self.lit
def __repr__(self):return"Lit:"+self.lit
def makeGenerator(self):def litGen():yieldself.lit
return litGen
def handleRange(toks):returnCharacterRangeEmitter(srange(toks[0]))def handleRepetition(toks):
toks=toks[0]if toks[1]in"*+":raiseParseFatalException("",0,"unbounded repetition operators not supported")if toks[1]=="?":returnOptionalEmitter(toks[0])if"count"in toks:returnGroupEmitter([toks[0]]*int(toks.count))if"minCount"in toks:
mincount =int(toks.minCount)
maxcount =int(toks.maxCount)
optcount = maxcount - mincount
if optcount:
opt =OptionalEmitter(toks[0])for i in range(1,optcount):
opt =OptionalEmitter(GroupEmitter([toks[0],opt]))returnGroupEmitter([toks[0]]* mincount +[opt])else:return[toks[0]]* mincount
def handleLiteral(toks):
lit =""for t in toks:if t[0]=="\\":if t[1]=="t":
lit +='\t'else:
lit += t[1]else:
lit += t
returnLiteralEmitter(lit)def handleMacro(toks):
macroChar = toks[0][1]if macroChar =="d":returnCharacterRangeEmitter("0123456789")elif macroChar =="w":returnCharacterRangeEmitter(srange("[A-Za-z0-9_]"))elif macroChar =="s":returnLiteralEmitter(" ")else:raiseParseFatalException("",0,"unsupported macro character ("+ macroChar +")")def handleSequence(toks):returnGroupEmitter(toks[0])def handleDot():returnCharacterRangeEmitter(printables)def handleAlternative(toks):returnAlternativeEmitter(toks[0])
_parser =Nonedef parser():global _parser
if _parser isNone:ParserElement.setDefaultWhitespaceChars("")
lbrack,rbrack,lbrace,rbrace,lparen,rparen = map(Literal,"[]{}()")
reMacro =Combine("\\"+ oneOf(list("dws")))
escapedChar =~reMacro +Combine("\\"+ oneOf(list(printables)))
reLiteralChar ="".join(c for c in printables if c notin r"\[]{}().*?+|")+" \t"
reRange =Combine(lbrack +SkipTo(rbrack,ignore=escapedChar)+ rbrack)
reLiteral =( escapedChar | oneOf(list(reLiteralChar)))
reDot =Literal(".")
repetition =(( lbrace +Word(nums).setResultsName("count")+ rbrace )|( lbrace +Word(nums).setResultsName("minCount")+","+Word(nums).setResultsName("maxCount")+ rbrace )|
oneOf(list("*+?")))
reRange.setParseAction(handleRange)
reLiteral.setParseAction(handleLiteral)
reMacro.setParseAction(handleMacro)
reDot.setParseAction(handleDot)
reTerm =( reLiteral | reRange | reMacro | reDot )
reExpr = operatorPrecedence( reTerm,[(repetition,1, opAssoc.LEFT, handleRepetition),(None,2, opAssoc.LEFT, handleSequence),(Suppress('|'),2, opAssoc.LEFT, handleAlternative),])
_parser = reExpr
return _parser
def count(gen):"""Simple function to count the number of elements returned by a generator."""
i =0for s in gen:
i +=1return i
def invert(regex):"""Call this routine as a generator to return all the strings that
match the input regular expression.
for s in invert("[A-Z]{3}\d{3}"):
print s
"""
invReGenerator =GroupEmitter(parser().parseString(regex)).makeGenerator()return invReGenerator()def main():
tests = r"""
[A-EA]
[A-D]*
[A-D]{3}
X[A-C]{3}Y
X[A-C]{3}\(
X\d
foobar\d\d
foobar{2}
foobar{2,9}
fooba[rz]{2}
(foobar){2}
([01]\d)|(2[0-5])
([01]\d\d)|(2[0-4]\d)|(25[0-5])
[A-C]{1,2}
[A-C]{0,3}
[A-C]\s[A-C]\s[A-C]
[A-C]\s?[A-C][A-C]
[A-C]\s([A-C][A-C])
[A-C]\s([A-C][A-C])?
[A-C]{2}\d{2}
@|TH[12]
@(@|TH[12])?
@(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9]))?
@(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9])|OH(1[0-9]?|2[0-9]?|30?|[4-9]))?
(([ECMP]|HA|AK)[SD]|HS)T
[A-CV]{2}
A[cglmrstu]|B[aehikr]?|C[adeflmorsu]?|D[bsy]|E[rsu]|F[emr]?|G[ade]|H[efgos]?|I[nr]?|Kr?|L[airu]|M[dgnot]|N[abdeiop]?|Os?|P[abdmortu]?|R[abefghnu]|S[bcegimnr]?|T[abcehilm]|Uu[bhopqst]|U|V|W|Xe|Yb?|Z[nr]
(a|b)|(x|y)
(a|b) (x|y)
""".split('\n')for t in tests:
t = t.strip()ifnot t:continueprint'-'*50print t
try:print count(invert(t))for s in invert(t):print s
exceptParseFatalException,pfe:print pfe.msg
printcontinueprintif __name__ =="__main__":
main()
Jawaban:
Ini adalah regex rekursif, dan tidak didukung oleh banyak mesin regex. Yang berbasis PCRE harus mendukungnya.
Tanpa spasi dan komentar:
.NET tidak mendukung rekursi secara langsung. ( Konstruksi
(?1)
dan(?R)
konstruksi.) Rekursi harus dikonversi menjadi penghitungan kelompok berimbang:Kompak:
Dari komentar:
Ini akan memvalidasi hanya bagian regex dari substitusi dan terjemahan.
s/<this part>/.../
Mungkin saja jika mesin regex mendukung rekursi, seperti PCRE, tetapi itu tidak bisa benar-benar disebut ekspresi reguler lagi.
"Secara teori, teori dan praktik adalah sama. Dalam praktik, mereka tidak sama." Hampir setiap orang yang mengetahui ekspresi reguler tahu bahwa ekspresi reguler tidak mendukung rekursi. Tetapi PCRE dan kebanyakan implementasi lainnya mendukung lebih dari sekadar ekspresi reguler dasar.
Pola ini mengeksploitasi ekstensi yang disebut ekspresi reguler rekursif. Ini tidak didukung oleh rasa POSIX dari regex. Anda bisa mencoba dengan sakelar -P, untuk mengaktifkan rasa regre PCRE.
Ini berlaku untuk ekspresi reguler klasik. Beberapa implementasi modern memungkinkan rekursi, yang membuatnya menjadi bahasa Bebas Konteks, meskipun agak bertele-tele untuk tugas ini.
[^?+*{}()[\]\\|]
akan cocok dengan karakter tunggal mana pun, bukan bagian dari konstruksi lainnya. Ini termasuk kedua literal (a
-z
), dan karakter khusus tertentu (^
,$
,.
).sumber
.{,1}
tidak tertandingi. Ubah ke^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$
pertandingan. Ubah\d+
ke\d*
[a-b-c]
.Tidak sepertinya.
Evaluasilah dalam
try..catch
atau apa pun bahasa Anda sediakan.sumber
Tidak, jika Anda benar-benar berbicara tentang ekspresi reguler dan tidak termasuk beberapa implementasi ekspresi reguler yang sebenarnya adalah tata bahasa bebas konteks.
Ada satu batasan ekspresi reguler yang membuatnya mustahil untuk menulis sebuah regex yang cocok dengan semua dan hanya regex. Anda tidak dapat mencocokkan implementasi seperti kawat gigi yang dipasangkan. Regex menggunakan banyak konstruksi seperti itu, mari kita ambil
[]
sebagai contoh. Setiap kali ada[
harus ada yang cocok]
, yang cukup sederhana untuk sebuah regex"\[.*\]"
.Apa yang membuatnya tidak mungkin bagi regex adalah mereka bisa disarangkan. Bagaimana Anda bisa menulis regex yang cocok dengan tanda kurung bersarang? Jawabannya adalah Anda tidak bisa tanpa regex yang sangat panjang. Anda dapat mencocokkan sejumlah kurung bersarang melalui brute force tetapi Anda tidak dapat mencocokkan set kurung bersarang panjang yang sewenang-wenang.
Kemampuan ini sering disebut sebagai penghitungan, karena Anda menghitung kedalaman sarang. Regex menurut definisi tidak memiliki kemampuan untuk menghitung.
Saya akhirnya menulis " Batasan Ekspresi Reguler " tentang ini.
sumber
Pertanyaan bagus.
Bahasa reguler yang sebenarnya tidak dapat memutuskan tanda kurung yang terbentuk dengan kuat. Jika alfabet Anda berisi
'('
dan')'
tujuannya adalah untuk memutuskan apakah untaian ini memiliki tanda kurung yang cocok. Karena ini adalah persyaratan yang diperlukan untuk ekspresi reguler, jawabannya adalah tidak.Namun, jika Anda melonggarkan persyaratan dan menambahkan rekursi Anda mungkin dapat melakukannya. Alasannya adalah bahwa rekursi dapat bertindak sebagai tumpukan yang memungkinkan Anda "menghitung" kedalaman sarang saat ini dengan mendorong ke tumpukan ini.
Russ Cox menulis " Pencocokan Ekspresi Reguler Dapat Sederhana dan Cepat " yang merupakan risalah yang luar biasa pada implementasi mesin regex.
sumber
Tidak, jika Anda menggunakan ekspresi reguler standar.
Alasannya adalah bahwa Anda tidak dapat memenuhi lemma pemompaan untuk bahasa reguler. Negara-negara memompa lemma bahwa string milik bahasa "L" adalah biasa jika ada sejumlah "N" seperti itu, setelah membagi string menjadi tiga substring
x
,y
,z
, sehingga|x|>=1 && |xy|<=N
, Anda dapat mengulangiy
sebanyak yang Anda inginkan dan seluruh string masih akan menjadi milikL
.Konsekuensi dari lemma pemompaan adalah bahwa Anda tidak dapat memiliki string reguler dalam bentuk
a^Nb^Mc^N
, yaitu, dua substring memiliki panjang yang sama dipisahkan oleh string lain. Dengan cara apa pun Anda memisahkan string seperti itu ke dalamx
,y
danz
, Anda tidak dapat "memompa"y
tanpa mendapatkan string dengan jumlah berbeda dari "a" dan "c", sehingga meninggalkan bahasa aslinya. Itu yang terjadi, misalnya, dengan tanda kurung dalam ekspresi reguler.sumber
Meskipun sangat mungkin untuk menggunakan regex rekursif seperti yang diposting MizardX, untuk hal-hal semacam ini jauh lebih berguna sebagai parser. Regex pada awalnya dimaksudkan untuk digunakan dengan bahasa biasa, menjadi rekursif atau memiliki kelompok penyeimbang hanyalah sebuah tambalan.
Bahasa yang mendefinisikan regex yang valid sebenarnya adalah tata bahasa bebas konteks, dan Anda harus menggunakan parser yang sesuai untuk menanganinya. Berikut ini adalah contoh untuk proyek universitas untuk penguraian regex sederhana (tanpa sebagian konstruksi). Ini menggunakan JavaCC. Dan ya, komentar dalam bahasa Spanyol, meskipun nama metode cukup jelas.
sumber
Anda dapat mengirimkan regex
preg_match
yang akan mengembalikan false jika regex tidak valid. Jangan lupa gunakan@
untuk menekan pesan kesalahan://
.sumber
Contoh berikut oleh Paul McGuire, berasal dari wiki pyparsing, tetapi sekarang hanya tersedia melalui Wayback Machine , memberikan tata bahasa untuk parsing beberapa regex, untuk tujuan mengembalikan rangkaian string yang cocok. Dengan demikian, ia menolak mereka yang menyertakan istilah pengulangan tanpa batas, seperti '+' dan '*'. Tapi itu harus memberi Anda ide tentang bagaimana menyusun parser yang akan memproses ulang.
sumber