2012-02-14 15 views
6

SQL文をビルドして実行するif-else型構造用の非常に単純なパーサを作成しようとしています。if-else if文の解析アルゴリズム

文を実行するための条件をテストするのではなく、文字列を作成する条件をテストします。

なステートメントの例は次のようになります。

select column1 
from 
#if(VariableA = Case1) 
table1 
#else if(VariableA = Case2) 
table2 
#else 
defaultTable 
#end 

VariableAがケース1に等しい場合、結果の文字列があるべき:select column1 from table1

if文より複雑な例では、入れ子になったであろう:

select column1 
from 
#if(VariableA = Case1) 
#if(VariableB = Case3) 
    table3 
#else 
    table4 
#else if(VariableA = Case2) 
table2 
#else 
defaultTable 
#end 

これは私が本当に問題を抱えているところで、if-else-endグループを正しく識別する良い方法を考えることができません。

また、「else」節の文字列がtrueに評価されるかどうかを把握するための良い方法がわかりません。

私はさまざまな種類の解析アルゴリズムでネットを見てきましたが、それらはすべて非常に抽象的で複雑なようです。

このコンピュータ以外の専攻では、どのような場所から始めればよいでしょうか?

+0

お待ちください。文字列を解析するか、文字列を作成していますか? – Jay

+0

文字列を解析して文字列を作成しています。構文解析の出力は、SQLクエリの目的の部分を含む新しい文字列になります。 – ChandlerPelhams

+4

あなた自身の言語を作成し、C#のように既存の言語を使用しない理由はありますか? – svick

答えて

8

私は単純なパーサーを作成しました。私はあなたが提供した例に対してテストしました。解析についてもっと知りたいのであれば、Niklaus WirthからCompiler Constructionを読むことをお勧めします。

最初の手順は、言語の構文を適切な方法で書き留めることです。私はEBNFを選択しました。これは非常に理解しやすいです。

|は代替案を分けます。

[および]オプションを囲みます。

{および}は、繰り返し(ゼロ、1つ以上)を示します。

(および)グループ式(ここでは使用しません)。

この説明は完全ではありませんが、私が提供したリンクで詳しく説明しています。

EBNF構文

 
LineSequence = { TextLine | IfStatement }. 
TextLine  = <string>. 
IfStatement = IfLine LineSequence { ElseIfLine LineSequence } [ ElseLine LineSequence ] EndLine. 
IfLine  = "#if" "(" Condition ")". 
ElseLine  = "#else". 
ElseIfLine = "#else" "if" "(" Condition ")". 
EndLine  = "#end". 
Condition = Identifier "=" Identifier. 
Identifier = <letter_or_underline> { <letter_or_underline> | <digit> }. 

パーサが密接構文に従い、すなわち繰り返しがそうであれば、else文に代わり、ループに翻訳され、。

using System; 
using System.Collections.Generic; 
using System.Text.RegularExpressions; 
using System.Windows.Forms; 

namespace Example.SqlPreprocessor 
{ 
    class Parser 
    { 
     enum Symbol 
     { 
      None, 
      LPar, 
      RPar, 
      Equals, 
      Text, 
      NumberIf, 
      If, 
      NumberElse, 
      NumberEnd, 
      Identifier 
     } 

     List<string> _input; // Raw SQL with preprocessor directives. 
     int _currentLineIndex = 0; 

     // Simulates variables used in conditions 
     Dictionary<string, string> _variableValues = new Dictionary<string, string> { 
      { "VariableA", "Case1" }, 
      { "VariableB", "CaseX" } 
     }; 

     Symbol _sy; // Current symbol. 
     string _string; // Identifier or text line; 
     Queue<string> _textQueue = new Queue<string>(); // Buffered text parts of a single line. 
     int _lineNo; // Current line number for error messages. 
     string _line; // Current line for error messages. 

     /// <summary> 
     /// Get the next line from the input. 
     /// </summary> 
     /// <returns>Input line or null if no more lines are available.</returns> 
     string GetLine() 
     { 
      if (_currentLineIndex >= _input.Count) { 
       return null; 
      } 
      _line = _input[_currentLineIndex++]; 
      _lineNo = _currentLineIndex; 
      return _line; 
     } 

     /// <summary> 
     /// Get the next symbol from the input stream and stores it in _sy. 
     /// </summary> 
     void GetSy() 
     { 
      string s; 
      if (_textQueue.Count > 0) { // Buffered text parts available, use one from these. 
       s = _textQueue.Dequeue(); 
       switch (s.ToLower()) { 
        case "(": 
         _sy = Symbol.LPar; 
         break; 
        case ")": 
         _sy = Symbol.RPar; 
         break; 
        case "=": 
         _sy = Symbol.Equals; 
         break; 
        case "if": 
         _sy = Symbol.If; 
         break; 
        default: 
         _sy = Symbol.Identifier; 
         _string = s; 
         break; 
       } 
       return; 
      } 

      // Get next line from input. 
      s = GetLine(); 
      if (s == null) { 
       _sy = Symbol.None; 
       return; 
      } 

      s = s.Trim(' ', '\t'); 
      if (s[0] == '#') { // We have a preprocessor directive. 
       // Split the line in order to be able get its symbols. 
       string[] parts = Regex.Split(s, @"\b|[^#_a-zA-Z0-9()=]"); 
       // parts[0] = # 
       // parts[1] = if, else, end 
       switch (parts[1].ToLower()) { 
        case "if": 
         _sy = Symbol.NumberIf; 
         break; 
        case "else": 
         _sy = Symbol.NumberElse; 
         break; 
        case "end": 
         _sy = Symbol.NumberEnd; 
         break; 
        default: 
         Error("Invalid symbol #{0}", parts[1]); 
         break; 
       } 

       // Store the remaining parts for later. 
       for (int i = 2; i < parts.Length; i++) { 
        string part = parts[i].Trim(' ', '\t'); 
        if (part != "") { 
         _textQueue.Enqueue(part); 
        } 
       } 
      } else { // We have an ordinary SQL text line. 
       _sy = Symbol.Text; 
       _string = s; 
      } 
     } 

     void Error(string message, params object[] args) 
     { 
      // Make sure parsing stops here 
      _sy = Symbol.None; 
      _textQueue.Clear(); 
      _input.Clear(); 

      message = String.Format(message, args) + 
         String.Format(" in line {0}\r\n\r\n{1}", _lineNo, _line); 
      Output("------"); 
      Output(message); 
      MessageBox.Show(message, "Error"); 
     } 

     /// <summary> 
     /// Writes the processed line to a (simulated) output stream. 
     /// </summary> 
     /// <param name="line">Line to be written to output</param> 
     void Output(string line) 
     { 
      Console.WriteLine(line); 
     } 

     /// <summary> 
     /// Starts the parsing process. 
     /// </summary> 
     public void Parse() 
     { 
      // Simulate an input stream. 
      _input = new List<string> { 
       "select column1", 
       "from", 
       "#if(VariableA = Case1)", 
       " #if(VariableB = Case3)", 
       "  table3", 
       " #else", 
       "  table4", 
       " #end", 
       "#else if(VariableA = Case2)", 
       " table2", 
       "#else", 
       " defaultTable", 
       "#end" 
      }; 

      // Clear previous parsing 
      _textQueue.Clear(); 
      _currentLineIndex = 0; 

      // Get first symbol and start parsing 
      GetSy(); 
      if (LineSequence(true)) { // Finished parsing successfully. 
       //TODO: Do something with the generated SQL 
      } else { // Error encountered. 
       Output("*** ABORTED ***"); 
      } 
     } 

     // The following methods parse according the the EBNF syntax. 

     bool LineSequence(bool writeOutput) 
     { 
      // EBNF: LineSequence = { TextLine | IfStatement }. 
      while (_sy == Symbol.Text || _sy == Symbol.NumberIf) { 
       if (_sy == Symbol.Text) { 
        if (!TextLine(writeOutput)) { 
         return false; 
        } 
       } else { // _sy == Symbol.NumberIf 
        if (!IfStatement(writeOutput)) { 
         return false; 
        } 
       } 
      } 
      return true; 
     } 

     bool TextLine(bool writeOutput) 
     { 
      // EBNF: TextLine = <string>. 
      if (writeOutput) { 
       Output(_string); 
      } 
      GetSy(); 
      return true; 
     } 

     bool IfStatement(bool writeOutput) 
     { 
      // EBNF: IfStatement = IfLine LineSequence { ElseIfLine LineSequence } [ ElseLine LineSequence ] EndLine. 
      bool result; 
      if (IfLine(out result) && LineSequence(writeOutput && result)) { 
       writeOutput &= !result; // Only one section can produce an output. 
       while (_sy == Symbol.NumberElse) { 
        GetSy(); 
        if (_sy == Symbol.If) { // We have an #else if 
         if (!ElseIfLine(out result)) { 
          return false; 
         } 
         if (!LineSequence(writeOutput && result)) { 
          return false; 
         } 
         writeOutput &= !result; // Only one section can produce an output. 
        } else { // We have a simple #else 
         if (!LineSequence(writeOutput)) { 
          return false; 
         } 
         break; // We can have only one #else statement. 
        } 
       } 
       if (_sy != Symbol.NumberEnd) { 
        Error("'#end' expected"); 
        return false; 
       } 
       GetSy(); 
       return true; 
      } 
      return false; 
     } 

     bool IfLine(out bool result) 
     { 
      // EBNF: IfLine = "#if" "(" Condition ")". 
      result = false; 
      GetSy(); 
      if (_sy != Symbol.LPar) { 
       Error("'(' expected"); 
       return false; 
      } 
      GetSy(); 
      if (!Condition(out result)) { 
       return false; 
      } 
      if (_sy != Symbol.RPar) { 
       Error("')' expected"); 
       return false; 
      } 
      GetSy(); 
      return true; 
     } 

     private bool Condition(out bool result) 
     { 
      // EBNF: Condition = Identifier "=" Identifier. 
      string variable; 
      string expectedValue; 
      string variableValue; 

      result = false; 
      // Identifier "=" Identifier 
      if (_sy != Symbol.Identifier) { 
       Error("Identifier expected"); 
       return false; 
      } 
      variable = _string; // The first identifier is a variable. 
      GetSy(); 
      if (_sy != Symbol.Equals) { 
       Error("'=' expected"); 
       return false; 
      } 
      GetSy(); 
      if (_sy != Symbol.Identifier) { 
       Error("Value expected"); 
       return false; 
      } 
      expectedValue = _string; // The second identifier is a value. 

      // Search the variable 
      if (_variableValues.TryGetValue(variable, out variableValue)) { 
       result = variableValue == expectedValue; // Perform the comparison. 
      } else { 
       Error("Variable '{0}' not found", variable); 
       return false; 
      } 

      GetSy(); 
      return true; 
     } 

     bool ElseIfLine(out bool result) 
     { 
      // EBNF: ElseIfLine = "#else" "if" "(" Condition ")". 
      result = false; 
      GetSy(); // "#else" already processed here, we are only called if the symbol is "if" 
      if (_sy != Symbol.LPar) { 
       Error("'(' expected"); 
       return false; 
      } 
      GetSy(); 
      if (!Condition(out result)) { 
       return false; 
      } 
      if (_sy != Symbol.RPar) { 
       Error("')' expected"); 
       return false; 
      } 
      GetSy(); 
      return true; 
     } 
    } 
} 

ネストされた文があれば、非常に自然な方法で自動的に処理されていることに注意してください。まず、文法は再帰的に表現される。 LineSequenceは、を含有することができ、そしては、LineSequenceを含有することができる。第2に、これは、再帰的な方法でお互いを呼び出す構文処理方法をもたらす。したがって、構文要素のネストは再帰的メソッド呼び出しに変換されます。

+0

<3 Olivier、このような完全かつ有益な解決に感謝します! – ChandlerPelhams

3

Ironyを見てみましょう:

アイロニーは、.NET プラットフォーム上で言語を実装するための開発キットです。ほとんどの既存のyacc/lexスタイルのソリューションと異なり、Ironyは特殊なメタ言語で書かれた文法 仕様のスキャナまたはパーサーコード生成を採用していません。アイロニーでは、 ターゲット言語文法は、演算子 を使用してC#で直接コーディングされ、文法構成を表現します。アイロニーのスキャナとパーサ モジュールは、構文解析を制御するためにc#クラスとしてコード化された文法を使用します。 プロセス。 C#クラスの文法 定義の例については、式文法サンプルを参照してください。また、これを作業パーサーで使用します。

1

... C#またはT4テンプレートやASP.NET MVCの部分表示などの既存のコードジェネレータを使用することをお勧めします。

しかし、これを自分でやりたいのであれば、ある種の再帰(または同等のスタック)が必要です。

string BuildCode(string str) 
{ 
foreach(Match ifMatch in Regex.Matches("#if(?<condition>[^\n\r]*)[\r\n]*(?<body>.*?)#endif) 
{ 
    var condition = ifMatch.Groups["condition"].Value; 
    return EvaluateCondition(condition) ? BuildCode(ifMatch.Value) : null; 
} 
} 

これは擬似コードです。あなたはこれを通して自分自身を考える必要があります。これはelseブランチをサポートしていませんが、簡単に追加することができます。

ここに新しい答えがあります。CodeDomを使用してC#関数をコンパイルします。あなたはC#の能力をフルに活用することができますが、C#コードはデータベースに格納されています。そうすれば、再デプロイする必要はありません。

+1

その正規表現はelseステートメントを考慮しませんでした。すべての条件がフォームx = yの場合は、C#.NETで最初に使用する方が簡単です。 正規表現ifElse =新しい正規表現(@ "#if \ s * [(] \ s *(? [^ =)] * (?<##*の場合は#?)*)* s * = \ s *(? [^]] *)\ s * )(?:#else(? [^#] *))?(?:#end) ")。 次に、else else ifsを#else ifに似た正規表現で処理し、単純な.Matches文を発行します。結果の配列を返します。各エントリは "全体一致"と一致した配列ですグループ(すなわち、変数、価値など)。 –

関連する問題