web-dev-qa-db-ja.com

コマンドインタープリター/パーサーの記述方法

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

  • コマンドの例:

    /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;

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

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

22
alfa64

率直に言って、パーサーの作成は退屈な仕事であり、コンパイラー技術に近づきますが、1つを作成することは良い冒険になることがわかります。パーサーにはインタープリターが付属しています。両方をビルドする必要があります。

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

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

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

LexとYacc は、 [〜#〜] bnf [〜#〜] Cに基づく文法に基づいてレクサーとパーサーを構築するための標準的な形式であり、推奨されるオプションです。ほとんどのパーサーは、LexとYaccのクローンです。

パーサー/インタプリタを構築するステップ

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

したがって、上記の場合の追加トークンは、任意の数字と正符号であり、レクサー内の正符号の処理方法が定義されています。

注意事項とヒント

  • 左から右に評価するパーサーテクニックを選択 [〜#〜] lalr [〜#〜]
  • コンパイラ でこのドラゴンの本を読んで、感じてみてください。私は個人的には本を書き終えていません
  • この link は、PythonでのLexとYaccに関する超高速の洞察を提供します

シンプルなアプローチ

機能が制限された単純な解析メカニズムが必要な場合は、要件を正規表現に変えて、一連の関数全体を作成します。説明のために、4つの算術関数の単純なパーサーを想定します。したがって、最初に演算子を呼び出し、次に(+ 4 5)または(add [4,5])の関数のリスト(LISPと同様)を呼び出します。次に、単純なRegExpを使用して、演算子と記号のリストを取得します。手術する。

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

14
Ubermensch

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

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

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

編集12/27

これは、実際よりも複雑になっているようです。

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

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

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

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

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

標準のコマンドライン引数以外のことを考えているのでない限り、これを行う既存のテスト済みライブラリの多くを使用してください。

合併症は何ですか?

7
quentin-starin

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

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

4
SK-logic

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

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

PHPトークンパーサー( token_get_all )は、次の質問への回答に記載されています。

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

1
hakre

あなたのニーズが単純で、時間もあり、それに興味がある場合は、ここでは細かいことに反対し、独自のパーサーを書くのをためらわないでください。他にないにしても、それは良い学習体験です。ネストされた関数呼び出し、配列など、より複雑な要件がある場合は、そうするのにかなりの時間がかかることに注意してください。独自のローリングの大きな利点の1つは、システムとの統合の問題がないことです。欠点は、もちろん、すべてのねじ上げはあなたのせいです。

ただし、トークンに対抗するには、ハードコードされたコマンドを使用しないでください。次に、同様のサウンドコマンドの問題は解消されます。

誰もがドラゴンブックを常に推奨していますが、私は常にロナルドマックの "Writing Compilers and Interpreters" をより良いイントロとして見つけました。

1
GrandmasterB

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

CodePlexのIronyの公式ホームページを参照してください: Irony-.NET言語実装キット

関数型プログラミングには、調べてみたいと思われる素晴らしい機能があります。

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

Scala および F# でのパターンマッチングのいくつかの例の2つのリンクを次に示します。

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

特に、Scala Webサイトの lambda calculus の例を確認することをお勧めします。

私の意見では、これが最も賢い方法ですが、PHPに厳密に従う必要がある場合は、「古い学校」のswitchに悩まされます。

0
SRKX

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

より多くのOOPスピンがあるもう1つのオプションは、イベントハンドラーを使用することです。コマンドとその専用関数を含むKey-Value-Arrayを作成します。コマンドが指定されると、配列には指定されたキーが含まれています。含まれている場合は、関数を呼び出します。これは、新しいコードの推奨事項です。

0
Brigand

私のアドバイスは、あなたの問題を解決するライブラリについてグーグルであるでしょう。

私は最近NodeJSを頻繁に使用しており、 Optimist はコマンドライン処理に使用しています。自分の選択した言語で使用できるものを検索することをお勧めします。そうでない場合は、1つ記述してオープンソースにしてください。DOptimistのソースコードを読んで、選択した言語に移植することもできます。

0
ming_codes

チェックアウト Apache CLI 、それはすべての目的がまさにあなたがやりたいことをしているように思われるので、それを使用できない場合でも、そのアーキテクチャをチェックアウトしてそれをコピーできます。

0
Stephen Rudolph

要件を少し単純化してみませんか?

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

ループを作成し、現在のパスである可能性があることを示す「プロンプト」を表すメッセージを書き込みます。

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

文字列は、スペースがセパレータ(「トークン化」)である行を期待するように「解析」でき、残りの文字はグループ化されます。

例。

プログラムは出力します(同じ行に留まります):/ 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
umlcat

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

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

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

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

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

[〜#〜] i [〜#〜]がそのようなシステムを最初から実装している場合は、一般的なLISPとストリップリーダーを使用します。各コマンドトークンは、s-expression RCファイルで指定されるシンボルにマップされます。トークン化後、限られたコンテキストで評価/展開され、エラーがトラップされます。認識可能なエラーパターンがあれば提案が返されます。その後、実際のコマンドがOSにディスパッチされます。

0
Paul Nathan