Bagaimana cara menulis interpreter / parser perintah?

22

Masalah: Jalankan perintah dalam bentuk string.

  • contoh perintah:

    /user/files/ list all; setara dengan: /user/files/ ls -la;

  • yang lainnya:

    post tw fb "HOW DO YOU STOP THE TICKLE MONSTER?;"

setara dengan: post -tf "HOW DO YOU STOP THE TICKLE MONSTER?;"

Solusi saat ini:

tokenize string(string, array);

switch(first item in array) {
    case "command":
        if ( argument1 > stuff) {
           // do the actual work;
        }
}

Masalah yang saya lihat dalam solusi ini adalah:

  • Tidak ada kesalahan memeriksa selain jika bersarang-lain di dalam setiap kasus. Naskah menjadi sangat besar dan sulit untuk dipertahankan.
  • Perintah dan tanggapan dikodekan dengan keras.
  • Tidak ada cara untuk mengetahui apakah flag adalah parameter yang benar atau tidak ada.
  • Kurangnya kecerdasan untuk menyarankan "Anda mungkin ingin menjalankan $ command".

Dan hal terakhir yang tidak bisa saya sampaikan adalah sinonim dalam pengkodean yang berbeda, contoh:

case command:
case command_in_hebrew:
    do stuff;
break;

Yang terakhir mungkin sepele, tapi yah, yang ingin saya lihat adalah dana yang kuat dari program semacam ini.

Saya sedang memprogram ini dalam PHP tetapi mungkin melakukannya dalam PERL.

alfa64
sumber
Saya tidak melihat sama sekali bagaimana ini berhubungan khusus dengan PHP. Ada banyak utas pada topik juru bahasa / kompiler ini pada SO dan SE.
Raffael
3
Tidak ada yang menyebutkan getopt?
Anton Barkovsky
@AntonBarkovsky: Ya. Lihat tautan saya. Saya pikir jawaban seperti Ubermensch hanya terlalu rumit untuk apa yang OP coba lakukan.
quentin-starin
1
Saya juga mengutip pendekatan sederhana menggunakan RegExp. Jawaban juga diperbarui
Ubermensch
Tidak menyebutkan progr tertentu. lang. Anda dapat menambahkan tag "c", "ruby" tag, "php", mungkin ada lib opensource, lib standar., atau "yang umum digunakan, belum lib standar." untuk progr Anda. lang.
umlcat

Jawaban:

14

Biar saya akui terus terang, membangun parser adalah pekerjaan yang membosankan dan mendekati teknologi kompiler, tetapi membangunnya akan menjadi petualangan yang baik. Dan pengurai dilengkapi dengan juru bahasa. Jadi, Anda harus membangun keduanya.

Pengantar cepat untuk parser dan penerjemah

Ini tidak terlalu teknis. Jadi para ahli tidak khawatir pada saya.

Saat Anda memasukkan input ke terminal, terminal membagi input menjadi beberapa unit. Input disebut ekspresi dan beberapa unit disebut token. Token ini dapat berupa operator atau simbol. Jadi, jika Anda memasukkan 4 + 5 dalam kalkulator, ungkapan ini akan dibagi menjadi tiga token 4, +, 5. Nilai tambah dianggap sebagai operator sementara 4 dan 5 simbol. Ini diteruskan ke program (menganggap ini sebagai juru bahasa) yang berisi definisi untuk operator. Berdasarkan definisi (dalam kasus kami, tambahkan), ia menambahkan dua simbol dan mengembalikan hasilnya ke terminal. Semua kompiler didasarkan pada teknologi ini. Program yang membagi ekspresi menjadi beberapa token disebut lexer dan program yang mengubah token ini menjadi tag untuk diproses dan dieksekusi lebih lanjut disebut parser.

Lex dan Yacc adalah bentuk kanonik untuk membangun lexer dan parser berdasarkan tata bahasa BNF di bawah C dan itu adalah opsi yang disarankan. Sebagian besar parser adalah tiruan dari Lex dan Yacc.

Langkah-langkah dalam membangun parser / intrepreter

  1. Klasifikasi token Anda menjadi simbol, operator, dan kata kunci (kata kunci adalah operator)
  2. Bangun tata bahasa Anda menggunakan formulir BNF
  3. Tulis fungsi pengurai untuk operasi Anda
  4. Kompilasikan prosesnya sebagai program

Jadi dalam kasus di atas token tambahan Anda akan berupa digit dan tanda tambah apa pun yang harus dilakukan dengan tanda tambah di lexer

Catatan dan Tip

  • Pilih teknik parser yang mengevaluasi dari LALR kiri ke kanan
  • Baca buku naga ini di Compiler untuk merasakannya. Saya pribadi belum menyelesaikan buku itu
  • Tautan ini akan memberikan wawasan super cepat ke dalam Lex dan Yacc di bawah Python

Pendekatan sederhana

Jika Anda hanya perlu mekanisme parsing sederhana dengan fungsi terbatas, ubah persyaratan Anda menjadi Ekspresi Reguler dan buat saja sejumlah fungsi. Untuk menggambarkan, asumsikan pengurai sederhana untuk empat fungsi aritmatika. Jadi Anda akan menjadi operator panggilan pertama dan kemudian daftar fungsi (mirip dengan lisp) dalam gaya (+ 4 5)atau (add [4,5])kemudian Anda bisa menggunakan RegExp sederhana untuk mendapatkan daftar operator dan simbol untuk dioperasikan.

Sebagian besar kasus umum dapat dengan mudah diselesaikan dengan pendekatan ini. Kelemahannya adalah Anda tidak dapat memiliki banyak ekspresi bersarang dengan sintaks yang jelas dan Anda tidak dapat memiliki fungsi urutan lebih tinggi yang mudah.

Ubermensch
sumber
2
Ini adalah salah satu cara tersulit yang mungkin. Memisahkan lexing dan parsing pass, dll. - mungkin berguna untuk mengimplementasikan parser berkinerja tinggi untuk bahasa yang sangat kompleks namun kuno. Dalam dunia modern, parsing lexerless adalah opsi default yang paling sederhana. Combinator parsing atau eDSL lebih mudah digunakan daripada preprosesor khusus seperti Yacc.
SK-logic
Saya setuju dengan SK-logika tetapi karena diperlukan jawaban terperinci secara umum, saya menyarankan Lex dan Yacc serta beberapa dasar parser. getopts yang disarankan oleh Anton juga merupakan pilihan yang lebih sederhana.
Ubermensch
itulah yang saya katakan - lex dan yacc adalah salah satu cara parsing yang paling sulit, dan bahkan tidak cukup generik. Parsing tanpa Lexer (mis., Packrat, atau seperti Parsec sederhana) jauh lebih sederhana untuk kasus umum. Dan buku Naga bukanlah pengantar yang sangat berguna untuk mem-parsing lagi - ini terlalu ketinggalan zaman.
SK-logic
@ SK-logic Bisakah Anda merekomendasikan buku yang diperbarui dengan lebih baik. Tampaknya mencakup semua dasar-dasar bagi seseorang yang mencoba memahami parsing (setidaknya dalam persepsi saya). Mengenai lex dan yacc, meskipun sulit, ini banyak digunakan dan banyak bahasa pemrograman menyediakan implementasinya.
Ubermensch
1
@ alfa64: pastikan untuk memberi tahu kami saat Anda benar-benar membuat kode solusi berdasarkan jawaban ini
quentin-starin
7

Pertama, ketika berbicara tentang tata bahasa, atau bagaimana menentukan argumen, jangan buat argumen Anda sendiri. Standar gaya GNU sudah sangat populer dan terkenal.

Kedua, karena Anda menggunakan standar yang diterima, jangan menemukan kembali kemudi. Gunakan perpustakaan yang ada untuk melakukannya untuk Anda. Jika Anda menggunakan argumen gaya GNU, sudah hampir pasti ada perpustakaan yang sudah matang dalam bahasa pilihan Anda. Misalnya: c # , php , c .

Pilihan parsing library yang baik bahkan akan mencetak bantuan yang diformat pada opsi yang tersedia untuk Anda.

EDIT 12/27

Sepertinya Anda membuat ini menjadi lebih rumit dari itu.

Ketika Anda melihat baris perintah, itu sangat sederhana. Itu hanya opsi dan argumen untuk opsi itu. Ada beberapa masalah rumit. Opsi dapat memiliki alias. Argumen dapat berupa daftar argumen.

Satu masalah dengan pertanyaan Anda adalah bahwa Anda belum benar-benar menentukan aturan apa pun untuk jenis baris perintah yang ingin Anda tangani. Saya telah menyarankan standar GNU, dan contoh Anda mendekati itu (meskipun saya tidak benar-benar memahami contoh pertama Anda dengan path sebagai item pertama?).

Jika kita berbicara GNU, setiap opsi tunggal hanya dapat memiliki bentuk panjang dan pendek (karakter tunggal) sebagai alias. Setiap argumen yang mengandung spasi harus dikelilingi dalam tanda kutip. Beberapa opsi formulir pendek dapat dirantai. Opsi formulir pendek harus diproses dengan satu tanda hubung, bentuk panjang dengan dua tanda hubung. Hanya opsi form pendek berantai terakhir yang dapat memiliki argumen.

Semua sangat mudah. Semuanya sangat umum. Juga telah diterapkan dalam setiap bahasa yang dapat Anda temukan, mungkin lima kali lipat.

Jangan tulis itu. Gunakan apa yang sudah ditulis.

Kecuali Anda memiliki sesuatu dalam pikiran selain argumen baris perintah standar, cukup gunakan salah satu dari BANYAK perpustakaan yang sudah ada dan teruji yang melakukan ini.

Apa komplikasinya?

quentin-starin
sumber
3
Selalu, selalu manfaatkan komunitas sumber terbuka.
Spencer Rathbun
Sudahkah Anda mencoba getoptionkit?
alfa64
Tidak, saya belum bekerja di php dalam beberapa tahun. Mungkin juga ada pustaka php lainnya. Saya telah menggunakan perpustakaan parser baris perintah c # yang saya tautkan.
quentin-starin
4

Sudahkah Anda mencoba sesuatu seperti http://qntm.org/loco ? Pendekatan ini jauh lebih bersih daripada ad hoc tulisan tangan apa pun, tetapi tidak memerlukan alat pembuat kode mandiri seperti Lemon.

EDIT: Dan trik umum untuk menangani baris perintah dengan sintaks yang kompleks adalah menggabungkan argumen kembali menjadi string yang dipisahkan dengan spasi putih dan kemudian menguraikannya dengan baik seolah-olah itu adalah ekspresi dari beberapa bahasa khusus domain.

Logika SK
sumber
+1 tautan yang bagus, saya ingin tahu apakah itu tersedia di github atau yang lainnya. Dan bagaimana dengan ketentuan penggunaannya?
hakre
1

Anda belum memberikan banyak spesifik tentang tata bahasa Anda, hanya beberapa contoh. Apa yang dapat saya lihat adalah bahwa ada beberapa string, spasi putih dan (mungkin, contoh Anda tidak acuh dalam pertanyaan Anda) string yang dikutip ganda dan kemudian satu ";" pada akhirnya.

Sepertinya ini bisa mirip dengan sintaks PHP. Jika demikian, PHP dilengkapi dengan parser, Anda dapat menggunakan kembali dan kemudian memvalidasi lebih konkret. Akhirnya Anda harus berurusan dengan token, tetapi sepertinya ini hanya dari kiri ke kanan sehingga sebenarnya hanya iterasi dari semua token.

Beberapa contoh untuk menggunakan kembali parser token PHP ( token_get_all) diberikan dalam jawaban atas pertanyaan berikut:

Kedua contoh mengandung parser sederhana juga, mungkin sesuatu seperti itu cocok untuk skenario Anda.

hakre
sumber
ya, saya mempercepat hal-hal tata bahasa, saya akan menambahkannya sekarang.
alfa64
1

Jika kebutuhan Anda sederhana, dan Anda berdua punya waktu dan tertarik, saya akan menentangnya di sini dan mengatakan jangan malu untuk menulis parser Anda sendiri. Ini pengalaman belajar yang baik, jika tidak ada yang lain. Jika Anda memiliki persyaratan yang lebih kompleks - panggilan fungsi bersarang, array, dll - hanya perlu diketahui bahwa melakukan hal itu bisa memakan banyak waktu. Salah satu hal positif dari menggulirkan milik Anda sendiri adalah tidak akan ada masalah untuk mengintegrasikan dengan sistem Anda. Kelemahannya, tentu saja, semua gangguan adalah kesalahan Anda.

Namun, bekerja melawan token, jangan gunakan perintah kode keras. Kemudian masalah dengan perintah terdengar serupa hilang.

Semua orang selalu merekomendasikan buku naga, tetapi saya selalu menemukan "Menulis Kompiler dan Penerjemah" oleh Ronald Mak menjadi pengantar yang lebih baik.

GrandmasterB
sumber
0

Saya punya program tertulis yang berfungsi seperti itu. Salah satunya adalah bot IRC yang memiliki sintaks perintah serupa. Ada file besar yang merupakan pernyataan saklar besar. Ini bekerja - bekerja cepat - tetapi agak sulit untuk mempertahankannya.

Pilihan lain, yang memiliki putaran OOP lebih banyak, adalah menggunakan event handler. Anda membuat array nilai kunci dengan perintah dan fungsi khusus mereka. Ketika perintah diberikan, Anda memeriksa apakah array memiliki kunci yang diberikan. Jika ya, panggil fungsi tersebut. Ini akan menjadi rekomendasi saya untuk kode baru.

Perampok
sumber
Saya telah membaca kode Anda dan skema yang persis sama dengan kode saya, tetapi seperti yang saya nyatakan, jika Anda ingin orang lain menggunakan, Anda perlu menambahkan pengecekan kesalahan dan yang lainnya
alfa64
1
@ alfa64 Silakan tambahkan klarifikasi untuk pertanyaan, bukan komentar. Tidak terlalu jelas apa yang sebenarnya Anda minta, meskipun agak jelas bahwa Anda mencari sesuatu yang sangat spesifik. Jika demikian, memberitahu kami persis apa itu. Saya tidak berpikir itu sangat mudah untuk pergi dari I think my implementation is very crude and faultyke but as i stated, if you want other people to use, you need to add error checking and stuff... Beritahu kami apa yang kasar tentang itu dan apa yang salah, itu akan membantu Anda mendapatkan jawaban yang lebih baik.
yannis
tentu, saya akan mengerjakan ulang pertanyaan
alfa64
0

Saya sarankan menggunakan alat, alih-alih menerapkan kompiler atau penerjemah sendiri. Irony menggunakan C # untuk mengekspresikan tata bahasa bahasa target (tata bahasa dari baris perintah Anda). Deskripsi tentang CodePlex mengatakan: "Irony adalah kit pengembangan untuk mengimplementasikan bahasa pada platform .NET."

Lihat beranda resmi Irony di CodePlex: Irony - .NET Language Implementation Kit .

Olivier Jacot-Descombes
sumber
Bagaimana Anda menggunakannya dengan PHP?
SK-logic
Saya tidak melihat tag PHP atau referensi ke PHP dalam pertanyaan.
Olivier Jacot-Descombes
Begitu ya, dulu tentang PHP awalnya, tapi sekarang ditulis ulang.
SK-logic
0

Saran saya adalah google untuk perpustakaan yang memecahkan masalah Anda.

Saya telah menggunakan NodeJS banyak akhir-akhir ini, dan Optimist adalah apa yang saya gunakan untuk pemrosesan baris perintah. Saya mendorong Anda untuk mencari yang dapat Anda gunakan untuk bahasa pilihan Anda sendiri. Jika tidak .. tulis satu dan buka sumbernya: D Anda bahkan dapat membaca kode sumber Optimist dan mengirimkannya ke bahasa pilihan Anda.

ming_codes
sumber
0

Mengapa Anda tidak menyederhanakan sedikit, persyaratan Anda?

Jangan gunakan parser penuh, terlalu rumit, dan bahkan tidak perlu untuk kasus Anda.

Buat perulangan, tulis pesan yang mewakili "prompt" Anda, bisa menjadi jalur Anda saat ini.

Tunggu string, "parsing" string, dan lakukan sesuatu tergantung pada isi string.

String dapat "parse" seperti mengharapkan garis, di mana spasi adalah pemisah ("tokenizer"), dan karakter lainnya dikelompokkan.

Contoh.

Output program (dan tetap di baris yang sama): / user / files / Pengguna menulis (di baris yang sama) daftar semua;

Program Anda akan menghasilkan daftar, koleksi, atau susunan seperti

list

all;

atau jika ";" dianggap sebagai pemisah seperti spasi

/user/files/

list

all

Program Anda bisa mulai dengan mengharapkan satu instruksi tunggal, tanpa "pipa" unix-style, tidak ada redirection gaya windowze.

Program Anda dapat membuat kamus instruksi, setiap instruksi, mungkin memiliki daftar parameter.

Pola desain perintah berlaku untuk kasus Anda:

http://en.wikipedia.org/wiki/Command_pattern

Ini pseudocode "c polos", tidak diuji atau selesai, hanya sebuah gagasan tentang bagaimana bisa dilakukan.

Anda juga bisa membuatnya lebih berorientasi objek, dan dalam bahasa pemrograman, Anda suka.

Contoh:


// "global function" pointer type declaration
typedef
  void (*ActionProc) ();

struct Command
{
  char[512] Identifier;
  ActionProc Action; 
};

// global var declarations

list<char*> CommandList = new list<char*>();
list<char*> Tokens = new list<char*>();

void Action_ListDirectory()
{
  // code to list directory
} // Action_ListDirectory()

void Action_ChangeDirectory()
{
  // code to change directory
} // Action_ChangeDirectory()

void Action_CreateDirectory()
{
  // code to create new directory
} // Action_CreateDirectory()

void PrepareCommandList()
{
  CommandList->Add("ls", &Action_ListDirectory);
  CommandList->Add("cd", &Action_ChangeDirectory);
  CommandList->Add("mkdir", &Action_CreateDirectory);

  // register more commands
} // void PrepareCommandList()

void interpret(char* args, int *ArgIndex)
{
  char* Separator = " ";
  Tokens = YourSeparateInTokensFunction(args, Separator);

  // "LocateCommand" may be case sensitive
  int AIndex = LocateCommand(CommandList, args[ArgIndex]);
  if (AIndex >= 0)
  {
    // the command

    move to the next parameter
    *ArgIndex = (*ArgIndex + 1);

    // obtain already registered command
    Command = CommandList[AIndex];

    // execute action
    Command.Action();
  }
  else
  {
    puts("some kind of command not found error, or, error syntax");
  }  
} // void interpret()

void main(...)
{
  bool CanContinue = false;
  char* Prompt = "c\:>";

  char Buffer[512];

  // which command line parameter string is been processed
  int ArgsIndex = 0;

  PrepareCommandList();

  do
  {
    // display "prompt"
    puts(Prompt);
    // wait for user input
      fgets(Buffer, sizeof(Buffer), stdin);

    interpret(buffer, &ArgsIndex);

  } while (CanContinue);

} // void main()

Anda tidak menyebutkan bahasa pemrograman Anda. Anda juga dapat menyebutkan bahasa pemrograman apa pun, tetapi lebih disukai "XYZ".

umlcat
sumber
0

Anda memiliki beberapa tugas di depan Anda.

melihat kebutuhan Anda ...

  • Anda perlu menguraikan perintah. Itu tugas yang cukup mudah
  • Anda harus memiliki bahasa perintah yang dapat diperluas.
  • Anda perlu memeriksa kesalahan dan saran.

Bahasa perintah extensible menunjukkan bahwa DSL diperlukan. Saya sarankan tidak menggulung sendiri tetapi menggunakan JSON jika ekstensi Anda sederhana. Jika mereka kompleks, sintaks s-ekspresi bagus.

Pemeriksaan kesalahan menyiratkan bahwa sistem Anda juga tahu tentang perintah yang mungkin. Itu akan menjadi bagian dari sistem pasca-perintah.

Jika saya menerapkan sistem seperti itu dari awal, saya akan menggunakan Common Lisp dengan pembaca yang tidak benar. Setiap token perintah akan memetakan ke simbol, yang akan ditentukan dalam file RC s-ekspresi. Setelah tokenization, itu akan dievaluasi / diperluas dalam konteks terbatas, menjebak kesalahan, dan pola kesalahan yang dikenali akan mengembalikan saran. Setelah itu, perintah yang sebenarnya akan dikirim ke OS.

Paul Nathan
sumber
0

Ada fitur bagus dalam pemrograman fungsional yang mungkin Anda tertarik untuk melihatnya.

Ini disebut pencocokan pola .

Berikut adalah dua tautan untuk beberapa contoh pencocokan pola di Scala dan di F # .

Saya setuju dengan Anda bahwa menggunakan switchstruktur agak membosankan, dan saya sangat menikmati menggunakan pencocokan pola selama implementasi kompiler di Scala.

Secara khusus, saya akan merekomendasikan Anda untuk melihat contoh kalkulus lambda dari situs web Scala.

Itu, menurut saya, cara paling cerdas untuk melanjutkan, tetapi jika Anda harus tetap menggunakan PHP, maka Anda terjebak dengan "old-school" switch.

SRKX
sumber
0

Lihat Apache CLI , seluruh tujuan tampaknya melakukan persis apa yang ingin Anda lakukan, jadi bahkan jika Anda tidak dapat menggunakannya, Anda dapat memeriksa arsitekturnya dan menyalinnya.

Stephen Rudolph
sumber