Pegjs: Terapkan cara yang lebih sederhana untuk mengekspresikan daftar dengan pemisah

Dibuat pada 21 Sep 2012  ·  25Komentar  ·  Sumber: pegjs/pegjs

Ketika kita memiliki rekursi yang benar, kita harus melakukan sesuatu seperti ini:

Statements
  = head:Statement tail:(__ Statement)* {
      var result = [head];
      for (var i = 0; i < tail.length; i++) {
        result.push(tail[i][1]);
      }
      return result;
    }

Saya dapat menemukan berguna untuk dapat melakukan hal yang sama sebagai berikut:

Statements
  = st:Statement (__ st:Statement)* { return st; /*where st is an array*/ }
feature

Semua 25 komentar

Terkait: #69

Ini sebenarnya bukan tentang pembantu rekursi yang benar, tetapi tentang daftar item yang dipisahkan oleh pemisah. Ini adalah pola yang cukup umum dalam tata bahasa dan karena itu mungkin perlu disederhanakan. Pertanyaannya adalah bagaimana melakukannya.

Saya tidak suka solusi yang diusulkan dalam deskripsi masalah. Ini hanya ilmu hitam dan tidak sejalan dengan aturan ruang lingkup label seperti yang dijelaskan di #69. Sebagai gantinya, saya sedang memikirkan dua solusi berikut:

Sintaks khusus

Bayangkan sesuatu seperti ini:

Args = args:Arg % ","

Artinya adalah "daftar Arg s dipisahkan oleh "," s. Variabel args akan berisi larik apa pun yang dihasilkan Arg , pemisah akan dilupakan.

Satu pertanyaan adalah bagaimana membedakan daftar terpisah yang memungkinkan nol atau lebih item dari yang memungkinkan satu atau lebih item. Pengalaman menunjukkan keduanya dibutuhkan. Jawaban yang mungkin adalah menambahkan atau menambahkan * atau + ke operator % :

Args0 = args:Arg %* ","
Args1 = args:Arg %+ ","
Args0 = args:Arg *% ","
Args1 = args:Arg +% ","

Operator % bahkan dapat diimplementasikan sebagai pengubah dari operator * dan + yang ada:

Args0 = args:Arg* % ","
Args1 = args:Arg+ % ","

Kelebihan: Sederhana untuk diterapkan, tidak memperkenalkan konsep baru.
Kontra: Solusi spesifik untuk masalah spesifik, bukan generik.

Aturan Parametrik

Solusi kedua adalah menggunakan aturan parametrik, yang sudah diusulkan di #45. Ide saya saat ini tentang sintaks:

// Template definition
List<item, separator> = head:item tail:(separator item)* { ... boilerplate ... }

// Template use
Args = List<Arg, ",">

Dengan cara ini kode boilerplate akan diulang paling banyak satu kali dalam tata bahasa. Masalah dengan dua jenis daftar dapat diselesaikan dengan dua templat. Template ini bahkan bisa built-in.

Kelebihan: Generik, dapat menghilangkan jenis boilerplate lainnya juga.
Cons: Kompleks untuk diterapkan, memperkenalkan konsep baru.


Saya belum memutuskan jalan mana yang harus saya tempuh. Saya ingin mendengar pemikiran/saran/usulan alternatif.

Definisi template sepertinya cara untuk melakukannya, terutama jika ada beberapa template bawaan (opsional), seperti list.

Saya memiliki proyek saudara untuk peg.js (otac0n/pegasus, pada dasarnya ini adalah port untuk C#) yang sudah menggunakan tanda kurung sudut di posisi itu untuk tipe data aturan, tetapi sepertinya saya dapat menemukan sesuatu jika Anda pergi dengan ini.

Hanya dua sen saya:

Sintaks khusus

Sintaks ini juga harus dapat membedakan daftar yang memungkinkan dua atau lebih item. Ini adalah pola yang cukup umum bahwa ketika daftar memiliki dua atau lebih item, Anda ingin membungkusnya di dalam simpul wadah, sedangkan hanya memiliki satu item, Anda cukup mengembalikan item itu.

Juga, pemisah mungkin tidak selalu dijatuhkan:

complexSelector = simpleSelectors:simpleSelector % (ws* [>+~] ws* / ws+)

Ini contoh pemilih CSS, di mana Anda mungkin ingin tahu kombinator mana yang digunakan.

Aturan Parametrik

Saya menyukai ide ini, tetapi tampaknya template yang ditawarkan masih sangat terbatas: hanya ekspresi yang dapat diparameterisasi. Jika Anda hanya ingin menukar * dengan + , Anda harus menggunakan template yang berbeda. Anda tentu saja dapat membuat sarang template seperti ini:

AbstractList<head, tail> = head:head tail:tail { tail.unshift(head); return tail;  }
List<item, separator> = AbstractList<item, (separator item)*>
List2<item, separator> = AbstractList<item, (separator item)+>

tetapi:

  • templat penamaan itu sulit

    • nama List2 mengatakan sedikit tentang karakteristiknya, dan dengan definisi yang diabstraksikan, situasinya menjadi lebih buruk

    • Anda pasti tidak ingin menggunakan ListWithTwoOrMoreItems

  • apakah itu benar-benar lebih baik daripada:

{ var list = function(head, tail) { tail.unshift(head); return tail; } } args = head:arg tail:(',' a:arg {return a})* { return list(head, tail) } args2 = head:arg tail:(',' a:arg {return a})+ { return list(head, tail) }

Saya pribadi menemukan ini lebih eksplisit dan karenanya lebih mudah dibaca.

  • meskipun ekspresi dapat diparameterisasi, tindakan cenderung menganggap mereka memiliki properti tertentu. Misalnya, jika head adalah array, AbstractList akan gagal. Jadi, meskipun ekspresi dalam template cocok dengan aturan saat ini, Anda tidak akan benar-benar menggunakannya.

Masalah sebenarnya

Masalah ini sebenarnya adalah bahwa pengguna ingin pegjs menggabungkan nilai ekspresi untuk mereka, sehingga mereka tidak harus melakukannya secara manual dalam tindakan.

Saya bertanya-tanya apakah label digunakan kembali seperti ini, apakah ini memperjelas bahwa pengguna ingin nilai-nilai tertentu digabungkan?

args = args:arg args:(',' a:arg {return a})* { // args is an array of "arg"s }
args2 = args:arg args:((',' / ';') a:arg)* { //args is an array of "arg"s and separators "," or ";"

Perhatikan aturan kedua meratakan nilai kedua dari args

Aturan pertama menyiratkan pegjs perlu menguji jenis nilai

items = items:item1 items:item2

Jika tidak satu pun dari item1 dan item2 adalah array, items adalah [item1, item2] , jika tidak items adalah gabungan dari keduanya.

Aturan kedua, bagaimanapun, menyiratkan beberapa perilaku aneh yang mungkin perlu diubah.

items = items:item1 items:item2

Jika salah satu dari item1 dan item2 adalah array dari array, itu perlu diratakan, tetapi tetap seperti itu ketika labelnya unik

items = items:item1 other:item2

Tetapi karena ketika pengguna menggunakan satu label untuk dua ekspresi, pikiran mereka kemungkinan besar telah mengaktifkan "mode gabungan", jadi itu mungkin tidak terlalu membingungkan daripada yang terlihat.

curvemark, saya tidak setuju dengan poin kontra Anda terhadap aturan parametrik. Tampaknya dua poin pertama Anda berasal dari gagasan bahwa modularitas dan abstraksi entah bagaimana membuat segalanya lebih sulit untuk dibaca atau dipahami. Ini jelas salah seperti yang ditunjukkan oleh ilmu komputer selama 50 tahun. Abstraksi adalah akar dari semua kekuatan dalam pemrograman

Jika Anda memiliki masalah penamaan variabel, itu bukan kesalahan bahasa. Mengharapkan seseorang untuk membaca internal aturan Anda untuk memahami apa yang dilakukan variabel Anda yang bernama buruk bukanlah solusi, itu adalah kludge. Saya berpendapat bahwa Anda tentu menginginkan nama seperti "ListWithTwoOrMoreItems" - ini mendokumentasikan kode Anda yang berarti Anda tidak perlu menulis komentar yang mengatakan apa arti "List2".

"apakah ini benar-benar lebih baik daripada ..." - ya benar, jauh lebih bersih, lebih mudah dibaca, dan lebih mudah dirawat. Situasi menjadi lebih jelas bahkan dengan _sedikit_ aturan yang lebih rumit

David, saya tidak mengerti perlunya sintaks khusus di sini. Ini:
Args = args:Arg % ","
bisa dilakukan seperti ini:
Args = args:(Arg ",")* { return args.map(function(v){v[0]}) }

Peta (dan tentu saja pengurangan juga) adalah fungsi yang sangat berguna yang telah saya gunakan di beberapa tempat saat menggunakan PEG.js . Ya, ini sedikit lebih panjang dari sintaks khusus, tetapi A. Jauh jauh lebih fleksibel, dan B. tidak memerlukan siapa pun untuk mempelajari sintaks PEG baru. Tentu saja tidak perlu membuat loop pendek dan jelek untuk perilaku seperti ini.

Tampaknya dua poin pertama Anda berasal dari gagasan bahwa modularitas dan abstraksi entah bagaimana membuat segalanya lebih sulit untuk dibaca atau dipahami.

tidak, bukan itu yang saya maksud. PEG sudah berisi mekanisme abstraksi fantastis yang disebut aturan

    = number operator number

dalam hal ini, number dan operator keduanya adalah abstraksi, dan saya sangat menyukainya.

Sintaks template, di sisi lain, mencoba mengabstraksi aturan ini dengan parameterisasi ekspresi. Tapi ingat, PEG adalah bahasa deklaratif, bagian tata bahasa tidak memiliki pengetahuan tentang sifat ekspresi dan bagian tata bahasa tidak mengizinkan sintaks bersyarat. Parameterisasi berarti mengganti di sini. Dibandingkan dengan aturan, ini hampir tidak mengabstraksikan apa pun.

Jika tujuannya adalah untuk menggunakan kembali struktur aturan, Anda sebaiknya menulis aturan yang lebih umum dan gagal dalam tindakan.

Situasinya menjadi lebih jelas dengan aturan yang sedikit lebih rumit

Bisakah Anda memberikan beberapa kasus penggunaan dunia nyata di mana sintaks template dapat berguna, kecuali untuk daftar dengan pemisah atau string dengan tanda kutip berbeda, yang penting berhubungan dengan penggabungan ekspresi dan harus memiliki sintaks yang lebih bertarget?

David, saya tidak mengerti perlunya sintaks khusus di sini.

Args = args:(Arg ",")* berbeda dari Args = args:Arg % "," . Yang pertama mengizinkan aturan diakhiri dengan , , yang terakhir tidak.

Parameterisasi berarti mengganti di sini.

Tidak bisakah Anda mengatakan hal yang sama untuk fungsi dalam bahasa pemrograman apa pun? Saya yakin kita berdua setuju bahwa fungsi berguna, jadi mengapa tidak di parser?

Bisakah Anda memberikan beberapa kasus penggunaan dunia nyata?

Saya sudah menulis beberapa contoh dalam masalah utama di sini: https://github.com/dmajda/pegjs/issues/45

Yang pertama mengizinkan aturan diakhiri dengan ,

Ah aku mengerti, kamu benar. Bagaimanapun poin saya masih tetap bahwa itu bisa dilakukan seperti ini:
Args = first:Arg rest:("," Arg)* { return [first].concat(rest.map(function(v){v[0]})) }

Hal yang menyenangkan tentang fungsi adalah jika Anda sering melakukan ini, Anda dapat membuat fungsi untuknya dan merobohkannya menjadi:
Args = first:Arg rest:("," Arg)* { return yourFancyFunction(first,rest) }

Dan jika Anda memiliki templat aturan, Anda bisa menjadi lebih sederhana: Args = list<Arg,",">

Sepertinya Anda berbicara tentang fitur yang sama sekali berbeda.

Sintaks yang diusulkan oleh David adalah menggunakan parameter di bagian tata bahasa, dan seperti yang saya katakan, itu hanya mengganti sesuatu. Anda tidak dapat menguji nilainya, Anda tidak dapat menentukan struktur tata bahasa yang berbeda untuk nilai parameter yang berbeda.

Apa yang Anda sarankan adalah menggunakannya dalam tindakan (jika saya memahaminya dengan benar), tetapi saya tidak yakin cara kerjanya. Contoh "hitungan" pertama di #45 tidak mengatakan dari mana nilai awal untuk count berasal, atau Anda hanya berasumsi semua parameter memiliki nilai default 0 ?

Mungkin Anda harus membuka masalah baru untuk itu.

@dmajda , saya sedang memikirkan sintaks lain untuk menyelesaikan masalah penggabungan, yang berperilaku seperti sintaks $ expression .

Sintaks $ expression mengembalikan string yang cocok, tidak peduli bagaimana ekspresi terstruktur. Demikian juga, bagaimana dengan memperkenalkan, katakanlah sintaks # expression (atau apa pun semacam itu), yang mengembalikan larik sub-ekspresi yang cocok, tidak peduli bagaimana struktur ekspresinya:

args = #(arg (',' a:arg {return a})*) // args is [arg, arg...]

args = #(arg (',' arg)*) // args is [arg, ',', arg, ',', ...]

Namun, jika Anda menulisnya seperti ini

args = #(arg restArgs) // args is [arg, [',', arg, ',', arg, ...]]

restArgs = #(',' arg)* // restArgs is [',', arg, ',', arg, ...]

Tidak berperilaku persis seperti $ expression

@curvedmark , dia menyebutkan dalam email kepada saya bahwa proposalnya di sini cocok dengan apa yang saya pahami tentang proposal saya. Tapi mungkin Anda benar. Terlepas dari itu, proposal saya tidak "sama sekali berbeda" - ini adalah generalisasi dari apa yang menurut Anda dia usulkan. Mungkin @dmajda bisa mengklarifikasi sendiri. Haruskah saya membuka masalah baru untuk ini, David?

Anda benar tentang contoh hitungan saya. Saya memperbarui komentar saya untuk memiliki (semoga) struktur yang benar. Terima kasih.

Untuk apa nilainya, saya akan senang untuk salah satu dari dua saran David. Aturan parametrik sangat kuat dan akan berguna untuk banyak hal, jadi saya pikir saya akan condong ke solusi itu. Meskipun saya setuju dengan @otac0n pada prinsipnya bahwa akan menyenangkan untuk memiliki abstraksi bawaan atau bahkan agar orang dapat berbagi abstraksi secara modular, saya akan Tetap Sederhana dan baru mulai dengan fasilitas abstraksi. Anda dapat memecahkan masalah tambahan tersebut di masa mendatang. Hanya menyediakan abstraksi template akan menjadi peningkatan bersih dalam keringkasan dan menghilangkan duplikasi kode.

Regexp::Grammars Perl melakukan ini dengan operator modulus juga:

# a list of one or more "item", separated by "separator", returning an array:
<rule: list>
        <[item]>+ % <separator>

Ini semacam penggunaan modulus yang dapat diperdebatkan. Modulus floating point mendefinisikan grup hasil bagi, interval real setengah terbuka (IEEE 754 benar-benar mengapung). Ini analogi yang ceroboh karena item yang dikembalikan oleh modulus Regexp::Grammar hanya identik hingga suatu pola (dan yang lebih penting karena string bukan grup) tetapi cukup dekat.

Alih-alih menjadikannya sebagai operator bawaan, saya baru saja membuat parser yang dapat diparameterisasi oleh parser.
Metagram di sini.

@futagoza Apakah ada tiket untuk parser berparameter? Saya pikir ada satu, tetapi saya tidak dapat menemukannya.

Yang paling terkait yang dapat saya temukan adalah #45 tetapi OP dari masalah itu mengusulkan sintaks yang berbeda (mirip dengan apa yang telah Anda terapkan dalam metagrammar Anda @polkovnikov-ph), tetapi saya berencana untuk menggunakan aturan parametrik (seperti yang disarankan @dmajda di atas) yang menggunakan sintaks yang lebih umum dari < .. > (templat, generik, dll).

Sintaks @futagoza tidak terlalu penting. Apa pun yang Anda lakukan dengan masalah ini, saya sangat menghargai itu.

Pilihan simbol sangat penting, karena ini dapat mengganggu fitur prioritas tinggi lainnya, seperti patch jangkauan @Mingun

Tidak ada alternatif yang sangat masuk akal untuk rentang, jadi kecenderungan saya adalah untuk mempertahankan kurung kurawal untuk itu

Sejujurnya, saya adalah tipe orang yang parser noob. Solusi saya untuk ini tampaknya sederhana dan saya sedikit khawatir itu mungkin tidak berhasil dalam skala besar, tetapi mengapa tidak melakukan sesuatu seperti

fname = "bob" / "dan" / "charlie"
namelist = (WS? fname ","?)+

Jika ada kebutuhan aktual di sini, saya mendukungnya - daftar adalah salah satu hal paling umum yang harus dilakukan parser, dan sepertinya mungkin ada kebutuhan aktual di sini, karena beberapa pengguna kuat ada di utas ini dan tidak mengatakan itu

Jadi solusi saya mungkin tidak cukup baik, tetapi saya ingin mengetahui alasannya

Jadi solusi saya mungkin tidak cukup baik, tetapi saya ingin mengetahui alasannya

Sebagian besar karena menerima beberapa masukan, yang dalam kehidupan nyata pasti tidak dapat diterima. Misalnya, ia mem-parsing bobdan .

Adil. Itu naif dari saya.

Ini memungkinkan bob , dan , dan bob,dan , tetapi tidak bobdan . Apa yang saya lewatkan di sini?

Document = names
WS = [ \r\n]+

fname = "bob" / "dan" / "charlie"

nameitem = (WS? fname ",")+ 
namelastitem = (WS? fname)

namelist = nameitem? namelastitem

@polkovnikov-ph - ada juga #36, yang mirip dengan #45 tetapi tidak identik

Penjelasan penulis tampaknya menunjukkan bahwa 36 lebih dekat dengan apa yang Anda maksud

Sekarang hanya mem-parsing apa yang diharapkan, tetapi hasilnya bukan array tunggal. Ini adalah motivasi utama untuk memiliki konstruksi khusus untuk mengurai data yang dibatasi

Oke, ini mulai terlihat seperti sesuatu yang benar-benar harus dimiliki operator, sebagai hal yang mudah digunakan. Sebagian besar pengguna tidak akan memiliki pakar bahasa Rusia untuk mengajukan pertanyaan bodoh 😁

Bagaimana dengan

Document = names

WS = [ \r\n]+

fname = "bob" / "dan" / "charlie"

namelist = nl:(namelast ",")+ { return nl[0][0]; }
namelast = WS? fn:fname       { return fn; }

names = nl:namelist? na:namelast { return [].concat(nl, na); } 

Atau Ukraina atau apa pun. Saya minta maaf: Saya melihat nama Cyrillic. Aku tidak seharusnya menebak seperti itu. Mendapatkan yang salah bisa ofensif.

Berhasil, tentu saja, tetapi di sini kita mendekati pertanyaan yang dimasukkan ke dalam masalah utama -- _cara yang lebih sederhana_. Data terpisah digunakan cukup sering untuk memiliki sintaks terpisah untuk mereka. Dalam praktik saya, ini adalah tempat kedua yang saya gunakan rentang, yang pertama adalah pengulangan dengan data variabel ( peg = len:number someData|len|; dalam sintaks rentang saya)

Dan ya, saya dari Rusia. Jangan khawatir, jangan tersinggung

Oke, ini mulai terlihat seperti sesuatu yang benar-benar harus memiliki operator, sebagai kemudahan penggunaan

di sini kita mendekati pertanyaan yang dimasukkan ke dalam isu utama -- dengan cara yang lebih sederhana .

Ya, saya setuju sekarang. Hanya saja upaya salah pertama saya terlihat sangat sederhana, dan jika itu tidak salah, itu akan cukup sederhana untuk tidak mengganggu

tetapi sekarang setelah saya melihat apa yang diperlukan, saya setuju

Apakah halaman ini membantu?
0 / 5 - 0 peringkat