コマンドインタープリター/パーサーの書き方


22

問題:コマンドを文字列の形式で実行します。

  • コマンド例:

    /user/files/ list all; に相当: /user/files/ ls -la;

  • 別のもの:

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

に相当: post -tf "HOW DO YOU STOP THE TICKLE MONSTER?;"

現在のソリューション:

tokenize string(string, array);

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

このソリューションで見られる問題は次のとおりです。

  • 各ケース内にネストされたifs-else以外のエラーチェックはありません。スクリプトは非常に大きくなり、管理が難しくなります。
  • コマンドと応答はハードコードされています。
  • フラグが正しいか、パラメータが欠落しているかを知る方法はありません。
  • 「$ commandを実行したいかもしれない」ことを示唆する知性の欠如。

そして最後に対処できないのは、異なるエンコーディングの同義語です、例:

case command:
case command_in_hebrew:
    do stuff;
break;

最後のものは些細なことかもしれませんが、まあ、私が見たいのは、この種のプログラムの強固な基金です。

私は現在これをPHPでプログラミングしていますが、PERLでプログラミングするかもしれません。


これが特にPHPにどのように関係しているかはまったくわかりません。SOおよびSEのこのインタープリター/コンパイラートピックには、すでに多くのスレッドがあります。
ラファエル

3
誰もgetoptに言及していませんか?
アントンバルコフスキー

@AntonBarkovsky:やりました。リンクをご覧ください。Ubermenschのような答えは、OPがやろうとしていることに対して、非常に複雑すぎると思います。
クエンティン・スターリン

1
RegExpを使用した簡単なアプローチも引用しました。回答も更新されました
Ubermensch

特定のプログラムについては言及しませんでした。ラング。「c」タグ、「ruby」タグ、「php」タグを追加できます。オープンソースライブラリ、標準ライブラリ、または「一般的に使用されているが、標準ライブラリではない」があります。あなたのプログラムのために。ラング。
umlcat

回答:


14

率直に言って、パーサーの構築は退屈な仕事であり、コンパイラーテクノロジーに近づきますが、1つを構築することは良い冒険であることが判明します。また、パーサーにはインタープリターが付属しています。したがって、両方を構築する必要があります。

パーサーとインタープリターの簡単な紹介

これはあまり技術的ではありません。だから専門家は私を心配しない。

何らかの入力を端末に入力すると、端末は入力を複数のユニットに分割します。入力は式と呼ばれ、複数のユニットはトークンと呼ばれます。これらのトークンは、演算子またはシンボルにすることができます。したがって、電卓に4 + 5を入力すると、この式は3つのトークン4、+、5に分割されます。プラス記号は演算子と見なされますが、4つと5つの記号です。これは、オペレーターの定義を含むプログラムに渡されます(これをインタープリターと見なしてください)。定義(この例ではadd)に基づいて、2つのシンボルを追加し、結果をターミナルに返します。すべてのコンパイラはこの技術に基づいています。式を複数のトークンに分割するプログラムはレクサーと呼ばれ、これらのトークンをさらに処理および実行するためにタグに変換するプログラムはパーサーと呼ばれます。

LexとYaccは、Cの下でBNF文法に基づいてレクサーとパーサーを構築するための標準形式であり、推奨オプションです。ほとんどのパーサーは、LexとYaccのクローンです。

パーサー/インタプリタを構築する手順

  1. トークンを記号、演算子、キーワードに分類します(キーワードは演算子です)
  2. BNFフォームを使用して文法を構築する
  3. 操作用のパーサー関数を作成する
  4. コンパイルしてプログラムとして実行する

したがって、上記の場合、追加トークンは、レクサーのプラス記号で何をするかを定義した任意の数字とプラス記号になります

メモとヒント

  • 左から右にLALRを評価するパーサー手法を選択します
  • コンパイラに関するこのドラゴンの本を読んで、それを感じてください。私は個人的に本を終えていません
  • このリンクは、PythonでのLexとYaccについての超高速な洞察を提供します

シンプルなアプローチ

機能が制限された単純な解析メカニズムが必要な場合は、要件を正規表現に変えて、機能全体を作成します。説明のために、4つの算術関数の単純なパーサーを想定します。したがって、最初に演算子を呼び出し、次にスタイル内の関数のリスト(lispに類似)を呼び出す(+ 4 5)(add [4,5])、単純なRegExpを使用して演算子のリストと操作対象のシンボルを取得できます。

最も一般的なケースは、このアプローチで簡単に解決できます。欠点は、明確な構文を持つネストされた式を多く持つことができず、簡単な高階関数を持たないことです。


2
これは最も困難な方法の1つです。字句解析パスと解析パスの分離など-これは、非常に複雑ではあるが古風な言語の高性能パーサーを実装するのにおそらく役立つでしょう。現代の世界では、レクサーレス解析は最も単純なデフォルトオプションです。コンビネータまたはeDSLの解析は、Yaccなどの専用プリプロセッサよりも使いやすいです。
SKロジック

SKロジックに同意しましたが、一般的な詳細な回答が必要なので、LexとYaccといくつかのパーサーの基本を提案しました。Antonが推奨するgetoptsもより簡単なオプションです。
超人

それは私が言ったことです-lexとyaccは解析の最も難しい方法の1つであり、十分に一般的でもありません。レクサーレス解析(例:packrat、または単純なParsecのような)は、一般的なケースでははるかに簡単です。また、Dragonの本は、これ以上構文解析の有用な紹介ではありません-古すぎます。
SKロジック

@ SK-logicより良い最新の本をお勧めしますか。構文解析を理解しようとしている人のすべての基本をカバーしているようです(少なくとも私の認識では)。lexとyaccについては、難しいですが、広く使用されており、多くのプログラミング言語がその実装を提供しています。
超人

1
@ alfa64:あなたが実際にこの回答に基づくソリューションをコーディングするとき、私たちはそれから知っているようにしてください
クエンティン・スターアン

7

まず、文法や引数の指定方法に関しては、独自に発明しないでください。GNUスタイルの標準は、すでに非常に人気があり、よく知られています。

第二に、受け入れられている標準を使用しているので、車輪を再発明しないでください。既存のライブラリを使用してそれを行います。GNUスタイルの引数を使用する場合、選択した言語には既にほぼ確実に成熟したライブラリがあります。例:c#phpc

優れたオプション解析ライブラリは、利用可能なオプションに関するフォーマットされたヘルプも印刷します。

編集12/27

これを実際よりも複雑にしようとしているようです。

コマンドラインを見ると、非常に簡単です。それは、オプションとそれらのオプションの引数にすぎません。複雑な問題はほとんどありません。オプションにはエイリアスを含めることができます。引数は引数のリストにすることができます。

質問の1つの問題は、どの種類のコマンドラインを処理するかについて、実際にルールを指定していないことです。GNU標準を提案しましたが、あなたの例はそれに近づいています(ただし、パスを最初の項目とする最初の例を本当に理解できませんか?)。

GNUについて話している場合、単一のオプションはエイリアスとして長い形式と短い形式(1文字)のみを持つことができます。スペースを含む引数は引用符で囲む必要があります。複数の短い形式のオプションを連鎖させることができます。短い形式のオプションは、ダッシュを1つ、長い形式を2つのダッシュで進める必要があります。引数を持つことができるのは、連鎖した短い形式のオプションの最後のみです。

すべて非常に簡単です。すべて非常に一般的です。また、見つけることができるすべての言語で実装されており、おそらく5倍以上です。

書かないでください。すでに書かれているものを使用します。

標準のコマンドライン引数以外のことを念頭に置いていない限り、これを行う既存のテスト済みライブラリのいずれかを使用してください。

合併症は何ですか?


3
常に、常にオープンソースコミュニティを活用してください。
スペンサーラスブン

getoptionkitを試しましたか?
アルファ64

いいえ、私はかなりの数年間PHPで働いていません。他のphpライブラリも存在する可能性があります。リンクしたC#コマンドラインパーサーライブラリを使用しました。
クエンティン・スターリン

4

すでにhttp://qntm.org/locoのようなものを試しましたか?このアプローチは、手書きのアドホックよりもはるかにクリーンですが、Lemonのようなスタンドアロンのコード生成ツールを必要としません。

編集:そして、複雑な構文でコマンドラインを処理するための一般的なトリックは、引数を単一の空白で区切られた文字列に結合し、ドメイン固有の言語の式であるかのように適切に解析することです。


ナイスリンクを+1します。githubで利用できるのか、それとも何か他のものですか。そして、使用条件はどうですか?
ハクレ

1

あなたはあなたの文法について多くの詳細を与えておらず、ほんのいくつかの例を挙げました。私が見ることができるのは、いくつかの文字列、空白、(おそらく、あなたの例はあなたの質問に無関心です)二重引用符で囲まれた文字列と1つの「;」があるということです 最後に。

これはPHP構文に似ているようです。その場合、PHPにはパーサーが付属しており、再利用してより具体的に検証できます。最後に、トークンを処理する必要がありますが、これは単に左から右に向かっているように見えるため、実際にはすべてのトークンに対する単なる反復です。

PHPトークンパーサー(token_get_all)を再利用するためのいくつかの例は、次の質問に対する回答に記載されています。

どちらの例にも単純なパーサーが含まれており、おそらくこれらのようなものがシナリオに適しています。


はい、文法を急ぎました。今から追加します。
alfa64

1

あなたのニーズが単純で、時間があるので興味があるなら、私はここで穀物に反対し、あなた自身のパーサーを書くことを恥ずかしがらないと言います。その良い学習体験は、他に何もありません。より複雑な要件(ネストされた関数呼び出し、配列など)がある場合は、そのためにかなりの時間がかかることに注意してください。独自のローリングの大きな利点の1つは、システムとの統合の問題が発生しないことです。欠点は、もちろん、すべてのネジアップはあなたのせいです。

ただし、トークンに対しては、ハードコードされたコマンドを使用しないでください。その後、同様のサウンドコマンドの問題はなくなります。

誰もが常にドラゴンの本をお勧めしますが、私は常に、Ronald Makによる"Writing Compilers and Interpreters"がより良いイントロであることを発見しました。


0

そのように動作するプログラムを作成しました。1つは、同様のコマンド構文を持つIRCボットでした。大きなswitchステートメントである巨大なファイルがあります。それは動作します-それは高速に動作します-しかし、それは維持するのがいくらか難しいです。

OOPスピンを増やすもう1つのオプションは、イベントハンドラーを使用することです。コマンドと専用機能を使用して、キーと値の配列を作成します。コマンドが与えられたら、配列に与えられたキーがあるかどうかを確認します。存在する場合は、関数を呼び出します。これは、新しいコードの推奨事項です。


私はあなたが使用する他の人々をしたい場合、あなたはエラーチェックやものを追加する必要があり、あなたのコードを読んで、それは私のコードと全く同じ仕組みだが、私が述べたようにしました
alfa64

1
@ alfa64コメントではなく、質問に説明を追加してください。あなたが本当に何を求めているのかは明確ではありませんが、本当に具体的なものを探していることはいくぶん明確です。もしそうなら、それが何であるかを正確に教えてください。私はそれから行くのは非常に簡単だとは思わないI think my implementation is very crude and faultybut as i stated, if you want other people to use, you need to add error checking and stuff、まさにそれについての粗だと何の不良だ、それはあなたがより良い答えを得るに役立つだろう...教えてください。
ヤニス

確かに、私は質問を手直します
alfa64

0

自分でコンパイラーやインタープリターを実装する代わりに、ツールを使用することをお勧めします。IronyはC#を使用して、ターゲット言語の文法(コマンドラインの文法)を表現します。CodePlexの説明では、「Ironyは.NETプラットフォームで言語を実装するための開発キットです。」

CodePlexに関するIronyの公式ホームページ:Irony-.NET Language Implementation Kitを参照してください。


PHPでどのように使用しますか?
SKロジック

質問にPHPタグまたはPHPへの参照が表示されません。
オリビエジャコット-デスコンベス

以前はPHPについてでしたが、今は書き直されています。
SKロジック

0

私のアドバイスは、あなたの問題を解決するライブラリのグーグルです。

私は最近NodeJSを多く使用していますが、Optimistはコマンドライン処理に使用しています。選択した言語で使用できるものを検索することをお勧めします。そうでなければ、それを書いて、それをオープンソースにしてください:D


0

なぜあなたの要件を少し簡素化しないのですか?

完全なパーサーを使用しないでください。複雑すぎて、場合によっては不要です。

ループを作成し、youtの「プロンプト」を表すメッセージを記述して、現在のパスにすることができます。

文字列を待ち、文字列を「解析」し、文字列の内容に応じて何かを行います。

文字列は、行が期待されるように「解析」できます。この場合、スペースが区切り文字(「トークン化機能」)であり、残りの文字はグループ化されます。

例。

プログラムは出力します(そして同じ行にとどまります):/ user / files /ユーザーは(同じ行に)すべてをリストします。

プログラムは、リスト、コレクション、または配列を生成します

list

all;

または、「;」の場合 スペースのようなセパレータと見なされます

/user/files/

list

all

あなたのプログラムは、unixスタイルの「パイプ」なしで、windowzeスタイルのリダイレクトもせずに、単一の命令を期待することから始めることができます。

プログラムは、命令の辞書を作成できます。各命令には、パラメータのリストを含めることができます。

コマンド設計パターンは、ケースに適用されます。

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

これは「プレーンc」の擬似コードであり、テストも完成もされていません。どのようにすればよいかというアイデアにすぎません。

さらにオブジェクト指向にすることもできますし、プログラミング言語で好きなことをすることもできます。

例:


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

プログラミング言語については言及しませんでした。任意のプログラミング言語に言及することもできますが、「XYZ」が望ましいです。


0

あなたの前にいくつかのタスクがあります。

要件を確認しています...

  • コマンドを解析する必要があります。それはかなり簡単な作業です
  • 拡張可能なコマンド言語が必要です。
  • エラーチェックと提案が必要です。

拡張可能なコマンド言語は、DSLが必要であることを示しています。拡張機能が単純な場合は、独自に展開するのではなく、JSONを使用することをお勧めします。複雑な場合は、s-expression構文が適しています。

エラーチェックは、システムが可能なコマンドについても知っていることを意味します。それはコマンド後システムの一部です。

もしがそのようなシステムをゼロから実装していたなら、私はCommon Lispを使いきったリーダーで使うでしょう。各コマンドトークンは、s-expression RCファイルで指定されるシンボルにマップされます。トークン化後、限られたコンテキストで評価/拡張され、エラーをトラップし、認識可能なエラーパターンがあれば提案を返します。その後、実際のコマンドがOSにディスパッチされます。


0

関数型プログラミングには、興味があるかもしれないすばらしい機能があります。

パターンマッチングと呼ばれます

ScalaF#でのパターンマッチングの例の2つのリンクを次に示します。

switch構造体を使用するのは少し面倒であり、Scalaでのコンパイラーの実装にパターンマッチングを使用することは特に楽しかったことに同意します。

特に、Scala Webサイトのラムダ計算の例を調べることをお勧めします。

私の意見では、これが最も賢い方法ですが、PHPに厳密に固執する必要がある場合は、「古い学校」に固執していますswitch


0

Apache CLIをチェックしてください。全体の目的はあなたがやりたいことを正確にやっているようですので、たとえ使用できなくても、そのアーキテクチャをチェックアウトしてコピーすることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.