2012-02-14 33 views
6

Tôi đang cố gắng tạo một trình phân tích cú pháp rất đơn giản cho một cấu trúc kiểu if-else sẽ xây dựng và thực hiện một câu lệnh SQL.Phân tích cú pháp if-else if algorithm statement

Thay vì kiểm tra điều kiện để thực hiện các câu lệnh, tôi sẽ kiểm tra các điều kiện để tạo chuỗi.

Một ví dụ về Statment sẽ là:

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

Nếu VariableA bằng hợp 1 chuỗi kết quả nên là: select column1 from table1

Một ví dụ phức tạp hơn sẽ có lồng nhau nếu phát biểu:

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

Đây là nơi tôi thực sự gặp sự cố, tôi không thể nghĩ ra một cách hay để xác định từng nhóm if-else-end chính xác.

Ngoài ra, tôi không chắc chắn cách tốt nhất để theo dõi xem chuỗi trong mệnh đề "else" có nên được đánh giá là đúng hay không.

Tôi đã tìm kiếm trên mạng ở các loại thuật toán phân tích cú pháp khác nhau, tất cả chúng dường như rất trừu tượng và phức tạp.

Có đề xuất nào về địa điểm tốt để bắt đầu cho chuyên ngành khoa học phi máy tính này không?

+0

Chờ, bạn đang phân tích chuỗi hoặc tạo chuỗi? – Jay

+0

Tôi đang phân tích chuỗi và tạo chuỗi. Đầu ra của phân tích cú pháp sẽ là một chuỗi mới chứa các phần mong muốn của truy vấn SQL. – ChandlerPelhams

+4

Có lý do nào khiến bạn tự tạo ngôn ngữ của mình và không sử dụng ngôn ngữ hiện có, như C#? – svick

Trả lời

8

Tôi đã viết một trình phân tích cú pháp đơn giản, mà tôi đã thử nghiệm dựa trên ví dụ bạn đã cung cấp. Nếu bạn muốn biết thêm về phân tích cú pháp tôi đề nghị bạn đọc Compiler Construction từ Niklaus Wirth.

Bước đầu tiên là luôn viết ra cú pháp ngôn ngữ của bạn theo cách thích hợp. Tôi đã chọn EBNF, điều này rất đơn giản để hiểu.

| tách các lựa chọn thay thế.

[] tùy chọn kèm theo.

{} biểu thị sự lặp lại (số không, một hoặc nhiều).

() cụm từ nhóm (không được sử dụng tại đây).

Mô tả này chưa hoàn chỉnh nhưng liên kết tôi đã cung cấp mô tả chi tiết hơn.

Các EBNF cú pháp

 
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> }. 

Các phân tích cú pháp sau chặt chẽ cú pháp, tức là một sự lặp lại được dịch sang một vòng, một sự thay thế vào một tuyên bố if-else, và vân vân.

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

Lưu ý rằng lồng if-báo cáo được xử lý tự động theo một cách hoàn toàn tự nhiên. Đầu tiên, ngữ pháp được biểu diễn đệ quy. A LineSequence có thể chứa IfStatment s và IfStatment s chứa LineSequence s. Thứ hai, điều này dẫn đến các phương thức xử lý cú pháp gọi cho nhau theo cách đệ quy. Do đó, việc lồng ghép các phần tử cú pháp được dịch thành các cuộc gọi phương thức đệ quy.

+0

<3 Olivier, cảm ơn bạn rất nhiều vì một giải pháp hoàn chỉnh và đầy đủ thông tin! – ChandlerPelhams

3

Hãy xem Irony:

Irony là một bộ phát triển để thực hiện các ngôn ngữ trên nền tảng .NET . Không giống như hầu hết các giải pháp yacc/lex kiểu hiện có khác, Irony không sử dụng bất kỳ máy quét hoặc trình tạo mã phân tích cú pháp nào từ ngữ pháp thông số kỹ thuật được viết bằng một ngôn ngữ meta chuyên dụng. Trong Irony, ngữ pháp ngôn ngữ đích được mã hóa trực tiếp trong C# sử dụng toán tử quá tải để diễn tả cấu trúc ngữ pháp. Máy quét và phân tích cú pháp của Irony mô-đun sử dụng ngữ pháp được mã hóa dưới dạng lớp C# để kiểm soát quá trình phân tích cú pháp . Xem mẫu ngữ pháp biểu hiện cho một ví dụ về ngữ pháp định nghĩa trong lớp C# và sử dụng nó trong một trình phân tích cú pháp làm việc.

1

Tôi khuyên bạn nên sử dụng trình tạo mã hiện tại như ... Mẫu C# hoặc T4 hoặc chế độ xem một phần ASP.NET MVC.

Nhưng nếu bạn muốn tự mình làm điều này, bạn cần một số loại đệ quy (hoặc ngăn xếp tương đương). Nó có thể hoạt động như sau:

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

Đây là mã giả. Bạn cần phải suy nghĩ về điều này cho mình. Điều này cũng không hỗ trợ một chi nhánh khác nhưng bạn có thể thêm một cách dễ dàng.

Đây là câu trả lời mới: Sử dụng CodeDom để biên dịch hàm C#. Bạn có thể sử dụng sức mạnh ful của C# nhưng có mã C# được lưu trữ trong cơ sở dữ liệu hoặc như vậy. Bằng cách đó bạn không phải triển khai lại.

+1

Regex đó không tính đến tuyên bố khác. Nếu tất cả các điều kiện của bạn có dạng x = y, trong C# .NET, dễ sử dụng hơn: Regex ifElse = new Regex (@ "# if \ s * [(] \ s * (? [^ =)] *) \ s * = \ s * (? [^)] *) \ s * [)] (? [^ #] *) (? (?: # else if [^ #] *) *) (?: # else (? [^ #] *))? (?: # end) "). Sau đó, xử lý bên trong "else ifs" với một regex tương tự cho #else nếu, và phát hành một câu lệnh đơn giản .Matches - nó sẽ trả về mảng kết quả, trong đó mỗi mục là một mảng chứa "toàn bộ kết quả" và khớp nhóm (tức là biến, giá trị, v.v.). –

Các vấn đề liên quan