- TRRE adalah ekstensi bahasa yang menambahkan operator
:untuk mengekspresikan transformasi teks langsung di dalam ekspresi reguler, dan disediakan sebagai alat CLItrremiripgrep -Euntuk bereksperimen dengannya - Bentuk dasarnya adalah pasangan transduktif yang mengubah pola input menjadi pola output, seperti
a:b; penghapusan diekspresikan sebagai transformasi dengan string kosong sepertix:, dan penyisipan seperti:x - Seperti ekspresi reguler biasa, TRRE mendukung alternatif, repetisi, dan transformasi rentang karakter, dengan contoh seperti
cat:dog,[a:A-z:Z], dan Caesar cipher - Implementasi internalnya membangun Finite State Transducer(FST) yang menangani pasangan input-output, alih-alih FSA pada ekspresi reguler biasa, serta mendukung determinisasi on-the-fly yang eksperimental
- Saat ini belum ada biner prabangun dan harus dibangun sendiri; stabilisasi DFT, dukungan Unicode penuh, penyelesaian fitur ERE, dan pemrosesan rentang yang efisien masih tersisa sebagai TODO
Masalah yang ingin diselesaikan TRRE
- Ekspresi reguler biasa berguna untuk menemukan pola dalam teks, tetapi dalam penyuntingan teks, logika pemrosesan grup dapat menjadi rumit karena terasa seperti pascapemrosesan
- TRRE memperluas bahasa ekspresi reguler agar pencocokan pola dan modifikasi teks dapat dimasukkan ke dalam ekspresi yang sama
- Sintaks intinya berbentuk
pattern-to-match:pattern-to-generate, dan contoh paling sederhana adalaha:b, yang mengubahamenjadib - Alat CLI
trreadalah implementasi yang menunjukkan konsep ini dan bekerja dengan nuansa yang miripgrep -E
Sintaks transformasi dasar
- Substitusi string ditulis seperti
cat:dogecho 'cat' | ./trre 'cat:dog'mencetakdog- Hasil yang sama juga bisa dibuat sebagai transformasi per karakter seperti
(c:d)(a:o)(t:g)
- Dapat digunakan seperti
seduntuk mengganti semua kecocokan di dalam string- Jika menerapkan
lamb:catpadaMary had a little lamb., hasilnya menjadiMary had a little cat.
- Jika menerapkan
- Penghapusan diekspresikan dengan mengosongkan sisi kanan, dalam bentuk
string_to_delete:(x:)ormenghapusxdarixordan menghasilkanora:dalam scan mode default mengganti semuaadengan simbol kosong sehingga menghapusnya- Beberapa karakter dapat dihapus menggunakan ekspresi kurung siku seperti
[aie]:
- Penyisipan diekspresikan dengan mengosongkan sisi kiri, dalam bentuk
:string_to_insert(:x)ormenyisipkanxdi depanordan menghasilkanxorhad a (:little )lambmenyisipkanlittledi dalam konteks
Transformasi di atas ekspresi reguler
- TRRE mendukung alternatif menggunakan
|seperti ekspresi reguler biasa(c:b)at|(d:h)ogmengubahcat dogmenjadibat hog
- Operator repetisi juga dapat diterapkan pada transformasi
(cat:dog)*mengubahcatcatcatmenjadidogdogdog- Dalam scan mode default,
cat:dogsaja juga diterapkan berulang dan menghasilkan hasil yang sama
- Jika repetisi digunakan pada pola kiri, beberapa input dapat dikonsumsi dan diubah menjadi satu output
(cat)*:dogmengubahcatcatcatmenjadidog
- Menggunakan
*atau+pada pola kanan dapat menyebabkan loop tak hingga- Ekspresi seperti
:a*sebaiknya dihindari - Jika perlu repetisi terbatas, tentukan jumlahnya seperti
:(repeat-10-times){10}
- Ekspresi seperti
Transformasi rentang dan generator
- Transformasi rentang karakter ditulis seperti
[a:A-z:Z]regular expressionsdapat diubah menjadiREGULAR EXPRESSIONS
- Contoh Caesar cipher disertakan
[a:b-y:zz:a]mengubahcaesar ciphermenjadidbftbs djqifs[a:zb:a-z:y]mengembalikannya lagi menjadicaesar cipher
- TRRE juga dapat membuat beberapa output dari satu input, seperti generator
- Default-nya menggunakan kecocokan pertama yang mungkin
- Jika opsi
-adigunakan, semua output yang mungkin akan dihasilkan
- Sebagai contoh, menerapkan
:(0|1){3}pada input kosong dapat menghasilkan urutan biner 3-bit dari000hingga111 - Menggunakan
:(0|1){,3}?bersama-mamenghasilkan output berbentuk subset dengan panjang 3 atau kurang
Spesifikasi bahasa dan prioritas operator
- Secara informal, TRRE didefinisikan sebagai pasangan
pattern-to-match:pattern-to-generate pattern-to-matchdi sisi kiri dapat berupa string atau ekspresi regulerpattern-to-generatedi sisi kanan biasanya berupa string, tetapi juga dapat berupa ekspresi reguler- Operator
:saat ini diperlakukan sebagai non-asosiatif, dan bentukTRRE:TRREtidak diizinkan secara sintaksis- Bentuk ini memiliki makna alami sebagai komposisi relasi yang didefinisikan TRRE, tetapi belum disertakan karena kompleksitasnya dapat meningkat
- Prioritas operator dari yang tertinggi adalah sebagai berikut
- Karakter escape
\ - Ekspresi kurung siku
[] - Pengelompokan
() - Repetisi
* + ? {m,n} - Konkatenasi
- Transduksi
: - Alternatif
|
- Karakter escape
Mode dan greediness
trremendukung dua mode- Scan Mode: mode default, menerapkan transformasi secara berurutan
- Match Mode: menggunakan flag
-m, memeriksa apakah seluruh string cocok dengan ekspresi
- Opsi
-amenghasilkan semua output yang mungkin - Modifier
?membuat operator*,+,{,}menjadi non-greedy<(.:)*>mencetak<>dari<cat><dog><(.:)*?>mencetak<><>dari input yang sama
- Contoh untuk mengubah isi di dalam tag atau tanda kurung juga disertakan
<(.*?:cat)>mengubah<dog> <mouse>menjadi<cat> <cat>
Implementasi berbasis FST dan determinisasi
- TRRE secara internal membangun Finite State Transducer(FST)
- FST mirip dengan Finite State Automaton(FSA) yang digunakan pada ekspresi reguler biasa, tetapi menangani pasangan input-output alih-alih string sederhana
- Perbedaan utama TRRE adalah sebagai berikut
- Mendefinisikan relasi biner antara dua bahasa reguler
- Menggunakan FST, bukan FSA, untuk inferensi
- Mendukung determinisasi on-the-fly yang eksperimental demi performa
- Dalam mesin ekspresi reguler biasa, determinisasi mengubah automata nondeterministik menjadi automata deterministik sehingga memungkinkan inferensi waktu linear terhadap panjang string input
- Pendekatan serupa juga dimungkinkan di TRRE, tetapi tidak semua transducer nondeterministik NFT dapat diubah menjadi transducer deterministik DFT
- Jika ada dua cycle “bad” dengan label input yang sama, pembuatan state dapat terjebak dalam loop tak hingga
- Ada cara untuk mendeteksi loop seperti ini, tetapi biayanya besar
Performa dan status instalasi
- Versi nondeterministik default disajikan dengan contoh yang sedikit lebih lambat daripada
seddalam substitusi sederhana./trre '(vodka):(VODKA)': real0m0.046ssed 's/vodka/VODKA/': real0m0.024s
- Untuk tugas kompleks, ada contoh versi deterministik
trre_dftyang lebih cepat daripadasedsed -e 's/\(.*\)/\U\1/': real0m0.508s./trre_dft '[a:A-z:Z]': real0m0.131s
- Biner prabangun belum tersedia
- Instalasi dilakukan dengan meng-clone repositori lalu membangun dan menguji dengan
make && sh test.sh - TODO masih mencakup hal-hal berikut
- Versi DFT yang stabil
- Dukungan Unicode penuh
- Penyelesaian fitur ERE
- Negasi
^di dalam[] - Kelas karakter
- Simbol anchor
$^
- Negasi
- Pemrosesan rentang yang efisien
Pendekatan yang dirujuk
- Pendekatan pencocokan ekspresi reguler sangat terinspirasi oleh Regular Expression Matching Can Be Simple And Fast dari Russ Cox
- Ide determinisasi transducer diambil dari Finitely Subsequential Transducers karya Cyril Allauzen dan Mehryar Mohri
- Pendekatan parsing menggunakan Double-E algorithm dari Erik Eidt, dan dekat dengan Shunting Yard algorithm klasik
1 komentar
Komentar Hacker News
Menarik melihat proyek ini akan berkembang ke mana. Namun prioritas operator terasa tidak alami, dan tampaknya orang lain di thread ini juga merasakan hal yang sama
cat:dogsecara alami lebih mudah diasumsikan setara dengan(cat):(dog)daripadaca(t:d)ogAku juga sempat bingung karena
cat:dogditafsirkan sepertica(t:d)og, bukan(cat):(dog), tetapi lalu teringat bahwa semua orang sedikit banyak memakai regex dengan “keliru”, jadi masuk akal. Regex “pada dasarnya” lebih tepat dipandang sebagai generator string, bukan matcher, sehinggacat|dogsecara formal bisa dianggap berkembang menjadi himpunan seperti{catog,cadog}Untuk matching, himpunan string ini lalu cukup dicocokkan sebagai substring terhadap teks yang lebih besar. Masalahnya, kebanyakan regex engine nyata tidak bekerja seperti ini, dan demi memenuhi ekspektasi atau efisiensi, mereka melakukan berbagai perilaku aneh
Kalau mencoba beberapa alat regex, akan muncul variasi seperti
(cat)|(dog)atau(cat)|(dog)|(ca[td]og). Jadi dari sudut pandang yang lebih formal, menurutku benar jikacat:dogmenghasilkanca(t:d)og, bukan(cat):(dog). Namun karena selama puluhan tahun regex disalahgunakan sebagai alat matching yang disesuaikan dengan ekspektasi pengguna, sekarang semua orang cenderung menambahkan tanda kurung di sekitar ekspresi yang ingin digantiUsulan ini menarik dan dirancang dengan baik, tetapi pada akhirnya terasa seperti upaya mengembalikan regex ke model generator aslinya. Masalahnya lebih dekat ke alatnya daripada ke tata bahasanya
Dulu aku pernah mengerjakan hal yang dekat dengan bidang ini, dan kalau kamu belum pernah memikirkan regex sebagai generator himpunan string, kamu bisa bermain-main di sini: https://onlinestringtools.com/generate-string-from-regex
Namun perilaku alat generator seperti ini juga sangat spesifik. Alat yang dulu kupakai punya berbagai cara untuk membatasi generator, misalnya dengan memberi kendala pada closure dan semacamnya
Kalau diturunkan ke bawah konkatenasi, bisa muncul masalah lain. Misalnya pada
:yang tidak asosiatif,cat:dog:mousemungkin seharusnya ilegal, dan aku belum yakin bagaimana menanganinyaPada versi saat ini, epsilon, yaitu string kosong, disisipkan. Misalnya untuk menghapus dengan melompati satu karakter setiap kali, secara teknis bisa menjalankan
..:yang merupakan.(.:eps)Hasil
echo 'abcde' | ./trre '..:'adalah'ace'Sebenarnya pengikatan
:juga bisa diberi makna sebagai komposisi relasi reguler, tetapi untuk saat ini menurutku itu terlalu rumit[a:A-z:Z]lebih baik ditulis[a-z:A-Z], dan alih-alih[a:b-y:zz:a], aku ingin mengusulkan bentuk seperti[a-y:b-z;z:a]Jika tertarik pada finite-state transducer dan alat terkait, XFST (Xerox Finite-State Transducer) layak dilihat. Ini sudah dipakai lebih dari 20 tahun dalam aplikasi linguistik komputasional
Seorang peneliti Finlandia dari PARC pernah datang ke kelas UT dan menunjukkan cara menangani morfologi bahasa Finlandia dengan FST, dan bahkan dari luar pun itu tampak sangat mengesankan
Menjelaskan pekerjaan yang dilakukan di PARC
OpenFst benar-benar pustaka yang hebat untuk transduser. Tutorial use case Pynini yang dibuat dalam bentuk tugas oleh Johns Hopkins dan lainnya juga cukup bagus
[1] https://www.openfst.org/twiki/bin/view/GRM/Pynini
[2] https://www.openfst.org/
Jika sedang mencari alternatif untuk regex standar, terutama bila logika grup terasa sulit atau ingin ekspresi yang lebih mudah dirawat, Rosie Pattern Language mungkin cocok
https://gitlab.com/rosie-pattern-language/rosie/-/blob/maste...
https://rosie-lang.org/about/
Keren. Sekitar tahun 1997 aku menulis tesis Diplom ilmu komputer dengan topik finite-state transducer, dan ternyata jauh kurang sepele daripada yang kukira
Tugasnya adalah mengimplementasikan komposisi dan DFA jika memungkinkan, termasuk transduser hasil komposisi. Judulnya “aljabar finite-state transducer” dan use case-nya adalah morfologi. Topik ini sangat diremehkan sehingga aku harus berhenti di tengah jalan. Jadi salut
Soal tata bahasa, aku penasaran apakah benar
:ingin diikat lebih kuat daripada konkatenasiabSenang melihat proyek itu masih berlanjut 20 tahun kemudian: https://www.openfst.org/twiki/bin/view/FST/WebHome
Aku masih belum yakin apakah
:memang harus diikat lebih kuat daripada konkatenasi. Setelah melihat sekitar 100 contoh, cara sekarang—yakni:lebih rendah daripada.—terasa lebih alami, tetapi di kode perubahan itu harfiah hanya butuh mengganti satu angka. Itulah kenapa aku mempostingnya di sini; aku butuh umpan balik nyata.Begitu ingin melakukan substitusi terstruktur jenis tertentu, pendekatan ini terasa kurang memadai. Misalnya, ada kalanya kita ingin melakukan sesuatu seperti
s/\"([^\"]*)\"/'$1'/Selain itu, akan lebih berguna jika bagian yang cocok dengan
[']di dalam[^\"]bisa diubah menjadi\\'Secara lebih umum, regex pada dasarnya mendefinisikan parse tree terhadap hasil pencocokan, jadi akan berguna jika kita bisa melakukan transformasi yang lebih umum pada tree tersebut
\":'(':(\\\\')|[^\"'])*\":'\"...\"dan mengganti tanda kutipnya menjadi kutip tunggal'Ini bisa dilakukan dengan ekspresi berikut:
echo '\"hello world\" \"hello again!\"' | ./trre "\\\":'.+?:-\\\":'"Hasilnya adalah
'-' '-'Artinya, dengan ekspresi
\".+?:-\", teks di dalam\"\"diganti dengan simbol-sambil sekaligus mengubah tanda kutip di sekelilingnya. Tanda tanya berarti mode non-greedySeluruh proyek ini tampaknya bertumpu pada klaim bahwa “regex adalah alat yang hebat untuk menemukan pola dalam teks, tetapi selalu terasa tidak alami untuk penyuntingan teks”, tetapi justru tidak ada satu pun contohnya
Saya tidak paham kenapa regex terasa tidak alami untuk penyuntingan. Saya juga tidak tahu apa yang dimaksud dengan penyuntingan di sini, dan kenapa orang kesulitan dengan grup
Contoh sintaks proyek ini banyak, tetapi saya tidak paham kenapa ini lebih baik daripada regex biasa. Kalau ada beberapa contoh seperti “versi regex biasa seperti ini, versi saya seperti ini, jadi lebih mudah”, saya rasa proyeknya akan lebih mudah dipahami
Misalnya, jika ingin mengubah hanya
yyang berada di antaraxdanzmenjadiY, di Python kira-kira ditulis seperti ini:pattern = r'(x)y(z)'replacement = r'\\1Y\\2'result = re.sub(pattern, replacement, text)Saya ingin menggantinya dengan pola
xy:Yz:result = re.trre('xy:Yz', text)Jika
xdanzadalah pola yang lebih kompleks atau regex itu sendiri, pendekatan ini bisa terasa lebih nyamanProyek yang bagus
Kode C-nya benar-benar menyenangkan untuk dibaca. Sangat bagus, dan saya sedang membacanya sekarang
Satu komentar kecil saja: tautan
theory.pdfdi README rusak. Karena PDF-nya ada di direktoridocs/, URL-nya cukup ditambahkandocs/Disebutkan bahwa memakai
*atau+di sisi kanan bisa menyebabkan loop tak hingga, jadi sebaiknya dihindari. Kenapa tidak dilarang saja?Saya paham kalau itu membuat spesifikasi sintaks lebih sulit, tetapi rasanya tidak ada alasan kuat untuk mempertahankannya
Alasan awalnya adalah karena saya ingin mengimplementasikan operasi menarik berupa komposisi transduser. Kita bisa melakukan operasi sederhana pada string dan mengomposisikan trre seperti filter, tetapi itu belum sempat saya selesaikan. Jadi, ya, masukan itu memang valid
Eksplorasinya menarik, tetapi masih kurang contoh yang menunjukkan kenapa ini benar-benar lebih baik. Tentu saja, mungkin karena saya sudah terlalu lama terbiasa dengan regex
Misalnya, saya tidak tahu kenapa
(cat):(dog)di trre lebih baik daripadas/cat/dog, atau apa keunggulan(x:)ordibandings/xor/or. Hampir semua contoh terlihat bisa dipetakan di kepala saya ke regex yang relatif mudahKalau ada keunggulan inti, sepertinya itu ada di sisi logika grup, jadi akan bagus kalau contohnya lebih fokus ke sana. Bahkan sebelum menjelaskan sintaks dasarnya, mungkin lebih baik dijelaskan dulu kenapa ini pilihan yang lebih baik
Contoh sandi Caesar terasa sangat membutuhkan fitur “terapkan ini secara terbalik”. Itu permintaan yang umum dalam banyak substitusi teks, dan pada contoh ini kebutuhan itu terlihat sangat jelas. Otak programmer langsung berteriak, “kenapa logika yang sama harus dinyatakan dua kali?”
Saya masih belum tahu apakah ini akan berguna, tetapi mengeksplorasi alternatif terhadap status quo yang sudah lama mengakar adalah hal yang hebat. Biasanya upaya seperti itu memang punya kemungkinan besar untuk tidak berhasil, tetapi eksplorasinya sendiri tetap menyenangkan untuk dilihat
Spesifikasinya tampak cukup kurang. Contoh pertama saja sudah aneh:
$ echo 'cat' | trre 'c:da:ot:g'dogSaya tidak paham apa yang terjadi di sini. Sintaksnya ditulis seperti ini:
TRRE <- TRRE* TRRE|TRRE TRRE.TRRETRRE <- REGEX REGEX:REGEXDi sini parse tree-nya seperti apa? Kenapa
ctidak berubah menjadida? Atau kenapactidak dihapus laludaberubah menjadiot?Idenya bagus, yaitu memiliki makna cari/ganti yang lebih intuitif daripada operator pengelompokan. Pada era MS-DOS, kita bisa melakukan hal seperti
ren .log .txtdan itu bekerja; dari sudut pikir bash modern itu terdengar tidak masuk akal, tetapi sekali lihat maksudnya sangat jelasIni masalah prioritas operator dan tokenisasi. Dalam bahasa ini, token adalah karakter tunggal, dan ada operator tak terlihat di antara karakter-karakter tersebut
Jika operator itu kita sebut secara eksplisit sebagai
~, contohnya jadi terlihat seperti ini:$ echo 'cat' | trre 'c:d~a:o~t:g'dogJika ditambahkan tanda kurung yang tidak perlu, hasilnya menjadi seperti ini:
$ echo 'cat' | trre '(c:d)~(a:o)~(t:g)'dogTata bahasanya memang kurang spesifik. Tata bahasa lengkapnya lebih rumit. Versi saat ini sepertinya harus dihapus dari dokumentasi, karena sekarang justru benar-benar membingungkan
Kenapa
ctidak berubah menjadidasepenuhnya karena prioritas. Dari diskusi ini, saya jadi merasa saya memilih prioritas yang salah, dan itu menimbulkan kebingunganTabel prioritas saat ini adalah sebagai berikut:
| 1 | karakter escape | \\ || 2 | ekspresi tanda kurung siku | [] || 3 | pengelompokan | () || 4 | pengulangan ERE karakter tunggal | * + ? {m,n} || 5 | transformasi | : || 6 | konkatenasi | . (implisit) || 8 | pilihan | | |Jadi
:mengikat lebih kuat daripada.yaitu konkatenasi implisitBenar, spesifikasinya kurang. Contoh penghapusan menunjukkan bahwa string kosong juga bisa menjadi REGEX. Kalau begitu, pada dasarnya bisa dianggap ada regex string kosong sebanyak yang diinginkan di posisi mana pun, sehingga jumlah parse menjadi tak terbatas
Sebaliknya, jika diwajibkan bahwa regex tidak boleh kosong, contoh penghapusan jadi rusak, tetapi ambiguitasnya berpindah ke sisi konkatenasi. Artinya, ambigu apakah
(((c:d)(a:o))(t:g))atau((c:d)((a:o)(d:g))). Jika kita mengasumsikan asosiativitas, perbedaan ini seharusnya tidak pentingDari kesan perilakunya, ini terlihat seperti
c:d,a:yaitu tidak ada, laluot:gTetapi setelah saya baca lagi, memang jelas membingungkan, dan secara teoritis keberatan itu masuk akal. Setelah membaca repositorinya, saya juga jadi percaya bahwa
cseharusnya berubah menjadida, tetapi saya tidak yakin