Frage

Ich habe eine Anwendung, die Benutzer braucht, damit Ausdrücke zu schreiben, ähnlich zu übertreffen:

(H1 + (D1 / C3)) * I8

und komplexere Dinge wie

Wenn (H1 = 'True', D3 * 0,2, D3 * 0,5)

Ich kann nur mit regulären Ausdrücken so viel tun. Irgendwelche Vorschläge in Bezug auf die richtige Ansatz, um dies zu tun sowie alle Ressourcen, die ich lernen kann würde sehr geschätzt werden.

Danke!

War es hilfreich?

Andere Tipps

Wenn Sie mit einer ähnlichen Situation konfrontiert - die Notwendigkeit zu handhaben kurzen einzeiligen Ausdrücke - Ich schrieb einen Parser. Die Ausdrücke waren Booleschen Logik der Form

n1 = y and n2 > z
n2 != x or (n3 > y and n4 = z) 

und so weiter. In Englisch kann man sagen, dass es Atome durch AND und OR verbunden, und jedes Atom hat drei Elemente - eine linke seitige Attribut, einen Operator und einen Wert. Weil es so succint war glaube ich das Parsen war einfacher. Die Menge der möglichen Attribute ist bekannt und beschränkt (zB Name, Größe, Zeit). Die Betreiber ändern sich je nach Attribut: verschiedene Attribute verschiedene Sätze von Operatoren. Und der Bereich und das Format der möglichen Werte variieren je auch zuzuschreiben.

Um zu analysieren, spaltete ich die Zeichenfolge auf Leer mit String.Split (). Ich später erkennen, dass vor () nach Split, musste ich die Eingabezeichenfolge normalisieren - Einfügen von Leerzeichen vor und nach Pars. Ich habe das mit einem Regex.Replace ().

Der Ausgang des Split ist ein Array von Tokens. Dann tritt das Parsen in einem großen for-Schleife mit einem Schalter auf dem linken Seite-Attributwert. Mit jedem go-round der Schleife wurde ich gesetzt in einer Gruppe von Token schlürfen. Wenn das erste Token eine Open-paren war, dann war die Gruppe nur ein Token in der Länge: die selbst PAREN. Für Token, die bekannte Namen waren - meine Attributwerte - der Parser in einer Gruppe von 3 Tokens schlürfen hatte, je einen für den Namen, den Operator und den Wert. Wenn zu irgendeinem Zeitpunkt nicht genügend Token sind, wirft der Parser eine Ausnahme. Basierend auf dem Strom von Tokens, würde der Parser-Zustand ändern. Eine Verbindung (AND, OR, XOR) gemeint, das Standes Atom auf einen Stapel zu schieben, und wenn das nächste Atom beendet war, würde ich der vorherigen Atom und kommen die beiden Atome, in eine Verbindung Atom Pop. Und so weiter. Die staatliche Verwaltung am Ende jeder Schleife des Parsers passiert ist.

Atom current;
for (int i=0; i < tokens.Length; i++) 
{
  switch (tokens[i].ToLower())
  {
    case "name":
        if (tokens.Length <= i + 2)
            throw new ArgumentException();
        Comparison o = (Comparison) EnumUtil.Parse(typeof(Comparison), tokens[i+1]);
        current = new NameAtom { Operator = o, Value = tokens[i+2] };
        i+=2;
        stateStack.Push(ParseState.AtomDone);
        break;
    case "and": 
    case "or":
        if (tokens.Length <= i + 3) 
          throw new ArgumentException();
        pendingConjunction = (LogicalConjunction)Enum.Parse(typeof(LogicalConjunction), tokens[i].ToUpper());
        current = new CompoundAtom { Left = current, Right = null, Conjunction = pendingConjunction };
        atomStack.Push(current);
        break;

    case "(":
        state = stateStack.Peek();
        if (state != ParseState.Start && state != ParseState.ConjunctionPending && state != ParseState.OpenParen)
          throw new ArgumentException();
        if (tokens.Length <= i + 4)
          throw new ArgumentException();
        stateStack.Push(ParseState.OpenParen);
        break;

    case ")":
        state = stateStack.Pop();
        if (stateStack.Peek() != ParseState.OpenParen)
            throw new ArgumentException();
        stateStack.Pop();
        stateStack.Push(ParseState.AtomDone);
        break;

    // more like that...
    case "":
       // do nothing in the case of whitespace
       break;
    default:
        throw new ArgumentException(tokens[i]);
  }

  // insert housekeeping for parse states here

}

Das ist vereinfacht, nur ein wenig. Aber die Idee ist, dass jeder Fall Aussage ist ziemlich einfach. Es ist einfach, in einer atomaren Einheit des Ausdrucks zu analysieren. Der schwierige Teil war Verbinden sie alle zusammen in geeigneter Weise.

dieser Trick wurde im housekeeping Abschnitt erreicht, am Ende jeder slurp-Schleife, die den Zustand Stack und den Atom-Stack. Verschiedene Sachen können nach dem Parser-Zustand geschehen. Wie gesagt, in jedem Fall Aussage könnte der Parser-Zustand ändern, mit dem vorherigen Zustand auf einen Stapel geschoben zu werden. Dann am Ende der switch-Anweisung, wenn der Staat, sagt ich ein Atom Parsen gerade fertig war, und es gab eine ausstehende Verbindung, würde ich bewege die gerade analysiert Atom in die CompoundAtom. Der Code sieht wie folgt aus:

            state = stateStack.Peek();
            if (state == ParseState.AtomDone)
            {
                stateStack.Pop();
                if (stateStack.Peek() == ParseState.ConjunctionPending)
                {
                    while (stateStack.Peek() == ParseState.ConjunctionPending)
                    {
                        var cc = critStack.Pop() as CompoundAtom;
                        cc.Right = current;
                        current = cc; // mark the parent as current (walk up the tree)
                        stateStack.Pop();   // the conjunction is no longer pending 

                        state = stateStack.Pop();
                        if (state != ParseState.AtomDone)
                            throw new ArgumentException();
                    }
                }
                else stateStack.Push(ParseState.AtomDone); 
            }

Die eine andere wenig Magie war die EnumUtil.Parse. Das ermöglicht es mir, die Dinge zu analysieren, wie „<“ in einen Enum-Wert. Angenommen, Sie definieren Ihr Aufzählungen wie folgt aus:

internal enum Operator
{
    [Description(">")]   GreaterThan,
    [Description(">=")]  GreaterThanOrEqualTo,
    [Description("<")]   LesserThan,
    [Description("<=")]  LesserThanOrEqualTo,
    [Description("=")]   EqualTo,
    [Description("!=")]  NotEqualTo
}

Normalerweise Enum.Parse sucht nach dem symbolischen Namen des ENUM-Wertes und

internal sealed class EnumUtil
{
    /// <summary>
    /// Returns the value of the DescriptionAttribute if the specified Enum value has one.
    /// If not, returns the ToString() representation of the Enum value.
    /// </summary>
    /// <param name="value">The Enum to get the description for</param>
    /// <returns></returns>
    internal static string GetDescription(System.Enum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());
        var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attributes.Length > 0)
            return attributes[0].Description;
        else
            return value.ToString();
    }

    /// <summary>
    /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivilant enumerated object.
    /// Note: Utilised the DescriptionAttribute for values that use it.
    /// </summary>
    /// <param name="enumType">The System.Type of the enumeration.</param>
    /// <param name="value">A string containing the name or value to convert.</param>
    /// <returns></returns>
    internal static object Parse(Type enumType, string value)
    {
        return Parse(enumType, value, false);
    }

    /// <summary>
    /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivilant enumerated object.
    /// A parameter specified whether the operation is case-sensitive.
    /// Note: Utilised the DescriptionAttribute for values that use it.
    /// </summary>
    /// <param name="enumType">The System.Type of the enumeration.</param>
    /// <param name="value">A string containing the name or value to convert.</param>
    /// <param name="ignoreCase">Whether the operation is case-sensitive or not.</param>
    /// <returns></returns>
    internal static object Parse(Type enumType, string stringValue, bool ignoreCase)
    {
        if (ignoreCase)
            stringValue = stringValue.ToLower();

        foreach (System.Enum enumVal in System.Enum.GetValues(enumType))
        {
            string description = GetDescription(enumVal);
            if (ignoreCase)
                description = description.ToLower();
            if (description == stringValue)
                return enumVal;
        }

        return System.Enum.Parse(enumType, stringValue, ignoreCase);
    }

}

Ich habe das EnumUtil.Parse () Sache von woanders. Vielleicht hier?

Ein wenig rekursiv absteigende Parser ist für dieses perfekt. Sie wahrscheinlich nicht einmal einen Parse-Baum bauen -. Sie die Auswertung tun können, wie Sie analysieren

 /* here's a teeny one in C++ */
void ScanWhite(const char* &p){
  while (*p==' ') p++;
}

bool ParseNum(const char* &p, double &v){
  ScanWhite(p);
  if (!DIGIT(*p)) return false;
  const char* p0 = p;
  while(DIGIT(*p)) p++;
  if (*p == '.'){
    p++;
    while(DIGIT(*p)) p++;
  }
  v = /* value of characters p0 up to p */;
  return true;
}

bool ParseId(const char* &p, double &v){
  ScanWhite(p);
  if (ALPHA(p[0]) && DIGIT(p[1])){
    v = /* value of cell whose name is p[0], p[1] */;
    p += 2;
    return true;
  }
  return false;
}

bool ParseChar(const char* &p, char c){
  ScanWhite(p);
  if (*p != c) return false;
  p++;
  return true;
}

void ParseExpr(const char* &p, double &v); /* forward declaration */

void ParsePrimitive(const char* &p, double &v){
  if (ParseNum(p, v));
  else if (ParseId(p, v));
  else if (ParseChar(p, '(')){
    ParseExpr(p, v);
    if (!ParseChar(p, ')'){/* throw syntax error */}
  }
  else {/* throw syntax error */}
}
#define PARSE_HIGHER ParsePrimitive

void ParseUnary(const char* &p, double &v){
  if (ParseChar(p, '-')){
    ParseUnary(p, v);
    v = -v;
  }
  else {
    PARSE_HIGHER(p, v);
  }
}
#undef  PARSE_HIGHER
#define PARSE_HIGHER ParseUnary

void ParseProduct(const char* &p, double &v){
  double v2;
  PARSE_HIGHER(p, v);
  while(true){
    if (ParseChar(p, '*')){
      PARSE_HIGHER(p, v2);
      v *= v2;
    }
    else if (ParseChar(p, '/')){
      PARSE_HIGHER(p, v2);
      v /= v2;
    }
    else break;
  }
}
#undef  PARSE_HIGHER
#define PARSE_HIGHER ParseProduct

void ParseSum(const char* &p, double &v){
  double v2;
  PARSE_HIGHER(p, v);
  while(true){
    if (ParseChar(p, '+')){
      PARSE_HIGHER(p, v2);
      v += v2;
    }
    else if (ParseChar(p, '-')){
      PARSE_HIGHER(p, v2);
      v -= v2;
    }
    else break;
  }
}
#undef  PARSE_HIGHER
#define PARSE_HIGHER ParseSum

void ParseExpr(const char* &p, double &v){
  PARSE_HIGHER(p, v);
}

double ParseTopLevel(const char* buf){
  const char* p = buf;
  double v;
  ParseExpr(p, v);
  return v;
}

Nun, wenn Sie nur ParseTop nennen, wird es den Wert eines Ausdrucks für Sie berechnen.

Der Grund für den PARSE_HIGHER Makro ist es einfacher zu machen Betreiber auf Zwischenebenen Vorrang hinzuzufügen.

das zu tun „wenn“ Aussage ein wenig mehr beteiligt ist. Jede analysiert Routine muss ein zusätzliches „enable“ Argument, so dass es keine Berechnung tut, wenn er aktiviert wird. Dann analysieren Sie das Wort „wenn“, analysieren den Testausdruck, und dann die beide Ergebnis Ausdrücke analysieren, mit dem inaktiven deaktiviert.

Sie können die .NET JScript-Compiler, oder Schnittstelle mit Ironpython, IronRuby oder Ironscheme verwenden (mit dem Namen alphabetisch, nicht bevorzugt; p).

Ich habe ein Gegenbeispiel dafür, wie bekam nicht , es zu tun: Will o‘the Wisp (da dies mein eigener Code ist es ich bin zuversichtlich, kritisiert).

Was ist gut über den Code?

  1. Es verwendet ein Entwurfsmuster folglich: Das Interpreter Muster
  2. Es hat ein ziemlich klares Design
  3. Es nutzt Attribute auf eine nette Art.
  4. Es produziert schöne Grafik. ; -)

Turtle Grafiken http://i3.codeplex.com /Project/Download/FileDownload.aspx?ProjectName=wisp&DownloadId=34823

Was ist schlecht über den Code?

  1. Es ist langsam
  2. Die Sprache ist schlecht definiert in Bezug auf Listen (Daten vs. Code).

Schauen Sie sich ANTLR . Sie definieren eine Sprachsyntax, testen Sie es ein GUI-Tool und den Quellcode in einer Vielzahl von Sprachen generieren. Open Source.

Ich würde das Buch empfehlen Constructing Kleine Sprachen . Sie führt durch viele Compiler Grundlagen für den Abschluss dieser Aufgabe richtig benötigt.

Sie haben die Tatsache, dass reguläre Ausdrücke erzogen wird nicht funktionieren, es sei denn, Sie einige strenge Grenzen für Ihre Sprache. Wie andere gesagt haben, ein Rekursiver Abstieg den Trick tun wird.

Die Wahl nächste wäre, ob eine Parser Generator wie ANTLR oder von Grund auf neu zu schreiben.

Haben Sie einen Blick auf diesem Open-Source-Projekt:

Excel Finanzfunktionen

Ich empfehle bei CoreCalc / FunCalc Arbeit suchen: http://www.itu.dk/people/sestoft/funcalc/

Ich habe ihre Grammatik für COCO \ R Parser-Generator in der Produktion verwendet und es funktioniert wirklich schnell.

Alles, was Sie tun müssen, ist: 1. erhalten treffen Grammatik von corecalc 2. Lauf coco.exe darauf (erzeugt Parser für Excel-ähnliche Ausdrücke) 3. übersetzen Ausdrucksbaum polnische Notation zu umkehren 4. einfache formel

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top