7
votes

Analysant si, d'autre si l'algorithme de déclaration

J'essaie de créer un analyseur très simple pour une structure de type If-else qui construira et exécutera une instruction SQL.

plutôt que des conditions de test pour exécuter des déclarations, je serais des conditions de test pour construire une chaîne.

Un exemple de déclaration serait le suivant: p>

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


6 commentaires

Attendez-vous une chaîne ou construisez-vous une chaîne?


Je mesure une chaîne et je construis une chaîne. La sortie de l'analyse sera une nouvelle chaîne contenant les pièces souhaitées de la requête SQL.


Y a-t-il une raison pour laquelle vous créez une langue de votre choix et que vous n'utilisez pas déjà la langue existante, comme c #?


De plus, si vous construisez votre propre langue et un analyseur pour cela, vous devez connaître au moins les bases de la théorie sur les grammaires et les langues formelles.


Oui - Ceci est pour une demande de rapport conçue pour être entièrement dynamique. Je ne veux pas réécrire et ré-déployer mon application à chaque fois que j'écris un nouveau rapport.


@svick - Les commentaires constructifs seraient appréciés. Je veux seulement avoir la possibilité de construire conditionnellement des chaînes SQL basées sur une évaluation d'expression simple, ne pas développer une nouvelle langue complexe.


3 Réponses :


3
votes

Jetez un coup d'œil à Irony :

L'ironie est un kit de développement pour la mise en œuvre de langages sur .NET Plate-forme. Contrairement à la plupart des solutions de style YACC / LEX existantes, l'ironie ne fait pas Employer n'importe quel scanner ou génération de code d'analyseur de grammaire Spécifications écrites dans une méta-langueur spécialisée. En ironie la La grammaire de langue cible est codée directement dans C # à l'aide de l'opérateur surcharge pour exprimer des constructions de grammaire. Scanner et analyseur d'ironie Les modules utilisent la grammaire codée comme classe C # pour contrôler l'analyse traiter. Voir l'échantillon d'expression grammaire pour un exemple de grammaire Définition en classe C #, et l'utiliser dans un analyseur de travail.


0 commentaires

1
votes

Je vous recommande d'utiliser un générateur de code existant comme ... C # ou des templats C # ou T4 ou des vues partielles ASP.NET MVC.

Mais si vous voulez le faire vous-même, vous avez besoin d'une sorte de récursivité (ou d'une pile qui est une pile qui est équivalent). Cela pourrait fonctionner comme ceci: xxx

C'est pseudo-code. Vous devez réfléchir à cela vous-même. Cela ne supporte également pas une succursale d'autre, mais vous pouvez l'ajouter facilement.

Voici une nouvelle réponse: utilisez le CodeDom pour compiler une fonction C #. Vous pouvez utiliser la puissance complète de C # mais avoir le code C # stocké dans une base de données ou. De cette façon, vous n'avez pas à redéployer.


1 commentaires

La regex de cette regex n'a pas tenu compte de la déclaration d'autre. Si toutes vos conditions ont la forme x = y, en C # .NET, plus facile à utiliser: Regex ifelse = nouvelle regex (@ "# si \ s * [(? [^ =)] *) \ s * = \ s * (? [^)] *) \ s * [)] (? <^ ^ # #] *) (? (?: [^ #] *) *) (?: # Autre (? [^ #] *))? (?: # Fin) "). Ensuite, traitez l'intérieur "Sinon IFS" avec une regex similaire pour #ELLSE si, et émettez une déclaration simple .Matches - il retournera la matrice de résultats, où chaque entrée est un tableau contenant le "match entier" et les groupes de matchs (c.-à-d. Variable, valeur, etc.).



10
votes

J'ai écrit un analyseur simple, que j'ai testé contre l'exemple que vous avez fourni. Si vous voulez en savoir plus sur l'analyse, je vous suggère de lire Construction de compilateur a > De Niklaus Wirth.

La première étape consiste toujours à écrire la syntaxe de votre langue de manière appropriée. J'ai choisi EBNF, qui est très simple à comprendre. P>

| code> sépare des alternatives. P>

[ code> et et et code> enfermez les options. p>

{ code> et } code> désigne des répétitions (zéro, un ou plusieurs). p>

( code> et ) code> expressions de groupe (non utilisé ici). p>

Cette description n'est pas complète mais le lien que j'ai fourni le décrit plus en détail. P>

La syntaxe Ebnf p> xxx pré>

L'analyseur suit de près la syntaxe, c'est-à-dire qu'une répétition est traduite en boucle, une alternative dans une déclaration if-else, etc. P>

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


1 commentaires

<3 Olivier, merci beaucoup pour une solution aussi complète et informative!