Komut yorumlayıcı / çözümleyici nasıl yazılır?


22

Sorun: Bir dizge şeklinde komutları çalıştırın.

  • komut örneği:

    /user/files/ list all; eşittir: /user/files/ ls -la;

  • bir diğeri:

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

eşittir: post -tf "HOW DO YOU STOP THE TICKLE MONSTER?;"

Güncel çözüm:

tokenize string(string, array);

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

Bu çözümde gördüğüm sorunlar:

  • Her durumun içinde iç içe ifs-else dışında hata denetimi yapılmaz. Senaryo yönetimi çok büyük ve zorlaşır.
  • Komutlar ve cevaplar kodlanmıştır.
  • Bayrakların doğru ya da eksik parametreleri olup olmadığını bilmenin bir yolu yoktur.
  • "$ Komutunu çalıştırmak isteyebilirsin" diyecek istihbarat eksikliği.

Ve ele alamadığım son şey, farklı kodlamalardaki eşanlamlılar, örneğin:

case command:
case command_in_hebrew:
    do stuff;
break;

Sonuncusu önemsiz olabilir, ama görmek istediğim, bu tür bir programın sağlam temelleri.

Şu anda bunu PHP'de programlıyorum ancak PERL'de yapabilir.


Bunun özellikle PHP ile nasıl bir ilişkisi olduğunu hiç anlamıyorum. SO ve SE'de bu tercüman / derleyici-konu hakkında çok fazla konu var.
Raffael

3
Getopt'tan kimse bahsetmedi mi?
Anton Barkovsky

@AntonBarkovsky: Yaptım. Bağlantılarıma bak. Ubermensch'inki gibi cevapların OP'nin yapmaya çalıştığı şey için aşırı derecede karmaşık olduğunu düşünüyorum.
quentin-starin

1
Ayrıca RegExp kullanarak basit bir yaklaşım alıntı yaptım. Cevap ayrıca güncellendi
Ubermensch

Belirli bir programdan bahsetmedim. Lang. "c" etiketini, "ruby" etiketini, "php" etiketini ekleyebilirsiniz, belki bir opensource kütüphanesi, standart kütüphanesi veya "genel olarak kullanılmış, henüz standart kütüphanesi" yoktur. progrunuz için. Lang.
umlcat

Yanıtlar:


14

Açıkça kabul etmeme izin verin, bina ayrıştırıcı sıkıcı bir iştir ve derleyici teknolojisine yaklaşır, ancak bina inşa etmek iyi bir maceraya dönüşür. Ve bir ayrıştırıcı tercüman ile birlikte gelir. Yani ikisini de inşa etmelisin.

Ayrıştırıcı ve tercümanlara hızlı bir giriş

Bu çok teknik değil. Yani uzmanlar bana endişelenmiyor.

Bir girişi bir terminale beslediğinizde, terminal girişi birden fazla birime böler. Girdi ifadesi ve çoklu birimler belirteçler olarak adlandırılır. Bu belirteçler operatör veya sembol olabilir. Bir hesap makinesine 4 + 5 girerseniz , bu ifade üç, 4, +, 5 belirteçlerine bölünür. Artı, 4 ve 5 semboller iken operatör olarak kabul edilir. Bu, operatörlerin tanımını içeren bir programa (bunu tercüman olarak kabul edin) aktarılır. Tanımı temel alarak (bizim durumumuzda, ekle), iki sembolü ekler ve sonucu terminale döndürür. Tüm derleyiciler bu teknolojiye dayanmaktadır. Bir ifadeyi çoklu belirteçlere bölen programa lexer denir ve bu belirteçleri daha fazla işlem ve yürütme için etiketlere dönüştüren program ayrıştırıcı olarak adlandırılır.

Lex ve Yacc , C altındaki BNF gramerini temel alan lexers ve parser'leri oluşturmak için kullanılan kanonik formlardır ve önerilen seçenektir. Çoğu ayrıştırıcı Lex ve Yacc klonudur.

Ayrıştırıcı / davetsiz misafir oluşturmanın adımları

  1. Belirteçlerinizi sembollere, operatörlere ve anahtar kelimelere göre sınıflandırın (anahtar kelimeler operatördür)
  2. Dilbilginizi BNF formunu kullanarak oluşturun
  3. İşlemleriniz için ayrıştırıcı işlevler yazın
  4. Bir program olarak çalıştırmayı derlemek

Bu yüzden yukarıdaki durumda belirteçleri eklemeniz herhangi bir rakam olacaktır ve lexerdaki artı işaretiyle ne yapılacağının tanımını içeren bir artı işareti olacaktır.

Notlar ve İpuçları

  • Soldan sağa LALR'yi değerlendiren bir ayrıştırıcı teknik seçin
  • Bunu öğrenmek için Derleyiciler'deki bu ejderha kitabı okuyun . Ben şahsen kitabı bitirmedim
  • Bu bağlantı Python altında Lex ve Yacc ile ilgili çok hızlı bir fikir verecektir.

Basit bir yaklaşım

Yalnızca sınırlı işlevlere sahip basit bir ayrıştırma mekanizmasına ihtiyacınız varsa, gereksiniminizi Düzenli İfadeye çevirin ve sadece çok sayıda işlev oluşturun. Göstermek için, dört aritmetik fonksiyon için basit bir ayrıştırıcı varsayalım. Böylece önce operatörü arayacak, sonra da tarzdaki fonksiyonların listesini (lisp'a benzer) (+ 4 5)veya (add [4,5])daha sonra operatörleri ve çalıştırılacak sembolleri almak için basit bir RegExp kullanabilirsiniz.

En yaygın vakalar bu yaklaşımla kolayca çözülebilir. Dezavantajı ise, net bir sözdizimi ile çok fazla iç içe ifadeye sahip olamayacağınız ve daha yüksek dereceli fonksiyonlara sahip olamayacağınızdır.


2
Bu mümkün olan en zor yollardan biridir. Ayrıştırma ve ayrıştırma geçişleri vb. Ayırma - çok karmaşık ancak arkaik bir dil için yüksek performanslı bir ayrıştırıcı uygulamak için yararlıdır. Modern dünyada lexerless ayrıştırma en basit varsayılan seçenektir. Ayrıştırma birleştiricileri veya eDSL'lerin kullanımı, Yacc gibi özel ön işlemcilerden daha kolaydır.
SK-mantık

SK-mantığa katılıyorum, ancak genel bir ayrıntılı cevap gerektiğinden, Lex ve Yacc ve bazı çözümleyici temellerini önerdim. Anton tarafından önerilen getopts da daha basit bir seçenektir.
Ubermensch

Ben de öyle söyledim - lex ve yacc, ayrıştırmanın en zor yollarından biri ve hatta yeterince genel değil. Lexerless ayrıştırma (örneğin, packrat veya Parsec benzeri basit) genel bir durum için çok daha kolaydır. Ve Dragon kitabı artık ayrıştırma için çok kullanışlı bir giriş değil - güncel değil.
SK-mantık

@ SK-mantık Daha iyi güncellenmiş bir kitap tavsiye eder misiniz. Ayrıştırmayı anlamaya çalışan bir insanın tüm temellerini (en azından benim görüşüme göre) ele alıyor gibi görünüyor. Lex ve yacc ile ilgili olarak, zor olsa da, yaygın olarak kullanılır ve birçok programlama dili uygulanmasını sağlar.
Ubermensch

1
@ alfa64: bu cevabı temel alan bir çözümü gerçekten kodladığınızda bize haber verdiğinizden emin olun
quentin-starin

7

İlk olarak, dilbilgisi söz konusu olduğunda veya bağımsız değişkenlerin nasıl belirtileceği söz konusu olduğunda, kendi dilinizi icat etmeyin. GNU tarzı standart zaten çok popüler ve iyi bilinir.

İkincisi, kabul edilmiş bir standart kullandığınızdan, tekerleği yeniden icat etmeyin. Bunu sizin için yapmak için mevcut bir kütüphaneyi kullanın. GNU tarzı argümanlar kullanırsanız, seçim dilinizde neredeyse kesinlikle olgun bir kütüphane bulunmaktadır. Örneğin: c # , php , c .

İyi bir seçenek ayrıştırma kütüphanesi, sizin için mevcut seçeneklere biçimlendirilmiş yardım bile basacaktır.

12/27 DÜZENLEME

Görünüşe göre bunu olduğundan daha karmaşık hale getirdin.

Bir komut satırına baktığınızda, gerçekten oldukça basit. Bu sadece seçenekler ve bu seçeneklerin argümanları. Çok az karmaşık konu var. Seçenek takma adlara sahip olabilir. Bağımsız değişkenler, bağımsız değişkenlerin listesi olabilir.

Sorunuzla ilgili bir sorun, ne tür bir komut satırıyla uğraşmak istediğinize ilişkin gerçekten herhangi bir kural belirtmemiş olmanızdır. GNU standardını önerdim ve örnekleriniz buna yaklaşıyor (ilk örnek olarak yolu ilk madde olarak anlamıyor olmama rağmen?).

GNU’dan bahsediyorsak, herhangi bir seçenek, takma ad olarak yalnızca uzun bir forma ve kısa forma (tek karakter) sahip olabilir. Boşluk içeren tüm argümanlar tırnak içine alınmalıdır. Birden çok kısa form seçeneği zincirlenebilir. Kısa form seçeneklerine tek bir çizgi, uzun form iki çizgi ile devam etmelidir. Yalnızca son zincirleme kısa form seçenekleri tartışılabilir.

Hepsi çok basit. Hepsi çok yaygın. Ayrıca bulabileceğiniz her dilde, muhtemelen beş kez kullanıldı.

Yazma onu. Zaten yazılı olanı kullanın.

Standart komut satırı argümanları dışında başka bir şey aklınızda bulunmadıkça, bunu yapan, zaten mevcut olan MANY testlerinden birini kullanın.

Komplikasyon nedir?


3
Her zaman, her zaman açık kaynak topluluğundan yararlanın.
Spencer Rathbun

getoptionkit denedin mi?
alfa64

Hayır, php'da epeydir çalışmıyorum. Diğer php kütüphaneleri de olabilir. Bağlandığım c # komut satırı çözümleyici kütüphanesini kullandım.
quentin-starin

4

Zaten http://qntm.org/loco gibi bir şey denediniz mi? Bu yaklaşım el yazısıyla yazılmış herhangi bir adresten çok daha temizdir, ancak Lemon gibi bağımsız bir kod oluşturma aracı gerektirmez.

EDIT: Komut satırlarını karmaşık sözdizimi ile ele almanın genel bir püf noktası argümanları boşluklarla ayrılmış tek bir dizgede tekrar birleştirmek ve daha sonra etki alanına özgü bir dilin ifadesi gibi düzgün bir şekilde ayrıştırmaktır.


+1 güzel bağlantı, github veya başka bir şey için müsait olup olmadığını merak ediyorum. Peki ya kullanım şartları?
hakre

1

Dilbilginizle ilgili birçok özellik vermediniz, sadece bazı örnekler. Görebildiğim, bazı dizeler, boşluklar ve bir (muhtemelen, örneğinizin sorunuza kayıtsız) çift alıntılı dize ve sonra bir ";" olduğu. sonunda.

Bunun PHP sözdizimine benzer olabileceği görülüyor. Öyleyse, PHP bir çözümleyici ile birlikte gelir, yeniden kullanabilir ve daha somut olarak doğrulayabilirsiniz. Sonunda belirteçlerle başa çıkmanız gerekir, ancak görünen o ki bu sadece soldan sağa, yani aslında tüm belirteçler üzerinde bir yineleme.

PHP token ayrıştırıcısını ( token_get_all) yeniden kullanmak için bazı örnekler aşağıdaki soruların cevaplarında verilmiştir:

Her iki örnek de basit bir ayrıştırıcı içerir, muhtemelen senaryo için uygun olan bir şeydir.


Evet, gramer şeylerini koştum, şimdi ekleyeceğim.
alfa64

1

İhtiyaçlarınız basitse ve hem vaktiniz varsa hem de buna ilgi duyuyorsanız, burada tahılın üzerine gidip kendi ayrıştırıcınızı yazmaktan çekinmeyin diyeceğim. Başka bir şey değilse, iyi bir öğrenme deneyimi. Daha karmaşık gereksinimleriniz varsa - iç içe geçmiş işlev çağrıları, diziler, vb. - bunu yapmanın iyi zaman alabileceğini unutmayın. Kendinizinkini yuvarlamanın en büyük pozitiflerinden biri, sisteminizle bütünleşmenin bir sorunu olmayacağıdır. Dezavantajı elbette, tüm vidalar sizin suçunuz.

Belirteçlere karşı çalışsanız da, kodlanmış komutları kullanmayın. Ardından benzer sesli komutlarla ilgili bu sorun ortadan kalkar.

Herkes her zaman ejderha kitabı önerir, ama ben her zaman Ronald Mak tarafından daha iyi bir intro olarak "Derleyiciler ve Tercümanlar Yazma" buldum .


0

Böyle çalışan programlar yazdım. Biri, benzer komut sözdizimi olan bir IRC botuydu. Bir Orada büyük dosya büyük bir switch deyimidir. Çalışıyor - hızlı çalışıyor - ama bakımı biraz zor.

Daha fazla OOP döndürmesi olan başka bir seçenek, olay işleyicileri kullanmaktır. Komutları ve atanmış işlevleri olan bir anahtar / değer dizisi yaratırsınız. Bir komut verildiğinde, dizinin verilen tuşa sahip olup olmadığını kontrol edersiniz. Varsa, işlevi çağırın. Bu yeni kod için benim tavsiyem olur.


kodunuzu okudum ve
kodumla

1
@ alfa64 Lütfen yorumlarınız yerine soruya herhangi bir açıklama ekleyin. Tam olarak ne istediğin çok net değil, gerçekte belirli bir şeyi aradığın belli. Eğer öyleyse, tam olarak ne olduğunu bize söyleyin . Ben gitmek çok kolay sanmıyorum I think my implementation is very crude and faultyiçin but as i stated, if you want other people to use, you need to add error checking and stufftam olarak ne bu konuda ham neyin hatalı olduğunu, bu daha iyi cevap almak yardımcı olacağını ... bize bildirin.
yannis

Elbette, soruyu
elden geçireceğim

0

Derleyici veya tercüman kullanmak yerine bir araç kullanmanızı öneririm. İroni, hedef dili gramerini (komut satırının gramerini) ifade etmek için C # kullanır. CodePlex'teki açıklama şöyle diyor: "İroni, .NET platformundaki dilleri uygulamak için bir geliştirme setidir."

CodePlex'teki Irony resmi ana sayfasına bakın: Irony - .NET Dil Uygulama Seti .


PHP ile nasıl kullanırsınız?
SK-mantık

Herhangi bir PHP etiketi veya soruda PHP referansı göremiyorum.
Olivier Jacot-Descombes

Görüyorum ki eskiden PHP ile ilgiliydi ama şimdi yeniden yazıldı.
SK-mantık

0

Tavsiyem sorununuzu çözen bir kütüphane için google olacaktır.

NodeJS'i son zamanlarda çok kullanıyorum ve iyimser komut satırı işlemlerinde kullandığım şey. Kendi seçtiğiniz dilde kullanabileceğiniz birini aramanızı tavsiye ederim. Değilse .. bir tane yazın ve kaynağı açın: D Optimist'in kaynak kodunu bile okuyabilir ve dilediğiniz dile getirebilirsiniz.


0

Neden gereksinimlerinizi biraz kolaylaştırmıyorsunuz?

Tam bir çözümleyici kullanmayın, çok karmaşık ve davanız için gereksiz.

Bir döngü yapın, "isteminizi" temsil eden bir mesaj yazın, bulunduğunuz yol olabilir.

Bir dizgiyi bekleyin, dizgiyi "ayrıştırın" ve dizginin içeriğine bağlı olarak bir şeyler yapın.

Dize, boşlukların ayırıcı ("tokenizer") olduğu ve diğer karakterlerin gruplandığı bir satır beklemek gibi "ayrıştırılabilir".

Örnek.

Program çıktı verir (ve aynı satırda kalır): / user / files / Kullanıcı yazar (aynı satırda) hepsini listeler;

Programınız bir liste, koleksiyon veya benzeri bir dizi oluşturacak

list

all;

ya da eğer ";" boşluk gibi bir ayırıcı olarak kabul edilir

/user/files/

list

all

Programınız, ne windowze tarzı yönlendirme, unix tarzı "borular" olmadan tek bir komut bekleyerek başlayabilir.

Programınız bir talimatlar sözlüğü yapabilir, her komutta bir parametre listesi bulunabilir.

Komut tasarım deseni sizin durumunuz için geçerlidir:

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

Bu bir "düz c" sözde kodu, test edilmedi veya bitmedi, sadece nasıl yapılabileceğine dair bir fikir.

Daha fazla nesne yönelimli de yapabilirsiniz ve programlama dilinde de hoşunuza gider.

Örnek:


// "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()

Programlama dilinden bahsetmedin. Ayrıca herhangi bir programlama dilinden ancak tercihen "XYZ" den bahsedebilirsiniz.


0

önünüzde birkaç görev var.

gereksinimlerinize bakarken ...

  • Komutu ayrıştırman gerekiyor. Bu oldukça kolay bir iş
  • Genişletilebilir bir komut diline sahip olmanız gerekir.
  • Hata kontrolü ve önerileri olması gerekir.

Genişletilebilir komut dili, bir DSL'nin gerekli olduğunu gösterir. Kendi uzantınızı değil, uzantılarınız basitse JSON kullanmanızı öneririm. Onlar karmaşıksa, bir s ifadesi sözdizimi güzeldir.

Hata kontrolü, sisteminizin olası komutları da bildiği anlamına gelir. Bu komut sonrası sistemin bir parçası olurdu.

Eğer ben sıfırdan böyle bir sistemi uygulayan, ben sadeleştirilmiş okuyucu ile Common Lisp kullanmak. Her komut belirteci, bir s ifadesi RC dosyasında belirtilecek bir sembole eşlenir. Belirleme işleminden sonra, sınırlı bir bağlamda değerlendirilecek / genişletilecek, hataları hapsedecek ve tanınabilir herhangi bir hata paterni önerileri getirecektir. Bundan sonra, gerçek komut işletim sistemine gönderilir.


0

Güzel bir özellik var İşlevsel programlamada , ilginizi çekebilecek .

Denir desen eşleştirme .

Burada, Scala’da ve F # 'da .

Seninle aynı fikirdeyim switchYapıları biraz sıkıcı olduğu ve özellikle Scala'da bir derleyicinin uygulanması sırasında eşleştirme eşleştirmesini kullanmaktan keyif aldım.

Özellikle, içine bakmak tavsiye ederim Scala web sitesinin lambda matematiği örneğine .

Bu, benim görüşüme göre, devam etmenin en akıllı yoludur, ancak PHP'ye sıkı sıkıya bağlı kalmanız gerekiyorsa, “eski okul” ile sıkışmışsınız demektir switch.


0

Apache CLI'ye göz atın , tüm amacı tam olarak ne yapmak istediğinizi yapmak gibi görünüyor, bu yüzden kullanmasanız bile mimarisini kontrol edebilir ve kopyalayabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.