35 votes

Le lexer du pauvre pour C #

Je suis en train d'écrire une simple analyseur en C#.

J'ai besoin d'un analyseur lexical -- quelque chose qui me permet d'associer des expressions régulières avec des jetons, de sorte qu'il lit dans les regexs et me donne de nouveau des symboles.

Il me semble que je devrais être en mesure d'utiliser les Regex pour faire le gros du travail, mais je ne peux pas voir un moyen facile de le faire. Pour une chose, la Regex ne semble que le travail sur les cordes, pas de flux (pourquoi est-ce que!?!?).

En fait, je cherche une implémentation de l'interface suivante:

interface ILexer : IDisposable
{
    /// <summary>
    /// Return true if there are more tokens to read
    /// </summary>
    bool HasMoreTokens { get; }
    /// <summary>
    /// The actual contents that matched the token
    /// </summary>
    string TokenContents { get; }
    /// <summary>
    /// The particular token in "tokenDefinitions" that was matched (e.g. "STRING", "NUMBER", "OPEN PARENS", "CLOSE PARENS"
    /// </summary>
    object Token { get; }
    /// <summary>
    /// Move to the next token
    /// </summary>
    void Next();
}

interface ILexerFactory
{
    /// <summary>
    /// Create a Lexer for converting a stream of characters into tokens
    /// </summary>
    /// <param name="reader">TextReader that supplies the underlying stream</param>
    /// <param name="tokenDefinitions">A dictionary from regular expressions to their "token identifers"</param>
    /// <returns>The lexer</returns>
    ILexer CreateLexer(TextReader reader, IDictionary<string, object> tokenDefinitions);
}

Donc, pluz envoyer le codz...
Non, sérieusement, je suis sur le point de commencer à écrire une implémentation de l'interface ci-dessus mais j'ai du mal à croire qu'il n'existe pas une façon simple de le faire dans .NET (2.0) déjà.

Ainsi, des suggestions pour une façon simple de le faire ci-dessus? (Aussi, je ne veux pas de "générateurs de code". La Performance n'est pas important pour cette chose et je ne veux pas introduire de la complexité dans le processus de construction.)

29voto

Paul Hollingsworth Points 4257

La version originale que j'ai posté ici comme une réponse a un problème en ce qu'il n'a travaillé que pendant qu'il y avait plus d'un "Regex" qui correspondent à l'expression actuelle. C'est, dès qu'une seule Regex appariés, il serait de retour d'un jeton - alors que la plupart des gens veulent la Regex pour être "gourmand". Ce fut particulièrement le cas pour des choses telles que "chaînes entre guillemets".

La seule solution qui se trouve sur le dessus de la Regex est à lire l'entrée ligne par ligne (ce qui signifie que vous ne pouvez pas avoir des jetons qui s'étendent sur plusieurs lignes). Je peux vivre avec cela - c'est, après tout, un homme pauvre lexer! En outre, il est généralement utile pour obtenir le numéro de ligne de l'analyseur lexical, en tout cas.

Donc, voici une nouvelle version qui corrige ces problèmes. Le mérite en revient aussi à cette

public interface IMatcher
{
    /// <summary>
    /// Return the number of characters that this "regex" or equivalent
    /// matches.
    /// </summary>
    /// <param name="text">The text to be matched</param>
    /// <returns>The number of characters that matched</returns>
    int Match(string text);
}

sealed class RegexMatcher : IMatcher
{
    private readonly Regex regex;
    public RegexMatcher(string regex)
    {
        this.regex = new Regex(string.Format("^{0}", regex));
    }

    public int Match(string text)
    {
        var m = regex.Match(text);
        return m.Success ? m.Length : 0;
    }

    public override string ToString()
    {
        return regex.ToString();
    }
}

public sealed class TokenDefinition
{
    public readonly IMatcher Matcher;
    public readonly object Token;

    public TokenDefinition(string regex, object token)
    {
        this.Matcher = new RegexMatcher(regex);
        this.Token = token;
    }
}

public sealed class Lexer : IDisposable
{
    private readonly TextReader reader;
    private readonly TokenDefinition[] tokenDefinitions;

    private string lineRemaining;

    public Lexer(TextReader reader, TokenDefinition[] tokenDefinitions)
    {
        this.reader = reader;
        this.tokenDefinitions = tokenDefinitions;
        nextLine();
    }

    private void nextLine()
    {
        do
        {
            lineRemaining = reader.ReadLine();
            ++LineNumber;
            Position = 0;
        } while (lineRemaining != null && lineRemaining.Length == 0);
    }

    public bool Next()
    {
        if (lineRemaining == null)
            return false;
        foreach (var def in tokenDefinitions)
        {
            var matched = def.Matcher.Match(lineRemaining);
            if (matched > 0)
            {
                Position += matched;
                Token = def.Token;
                TokenContents = lineRemaining.Substring(0, matched);
                lineRemaining = lineRemaining.Substring(matched);
                if (lineRemaining.Length == 0)
                    nextLine();

                return true;
            }
        }
        throw new Exception(string.Format("Unable to match against any tokens at line {0} position {1} \"{2}\"",
                                          LineNumber, Position, lineRemaining));
    }

    public string TokenContents { get; private set; }

    public object Token { get; private set; }

    public int LineNumber { get; private set; }

    public int Position { get; private set; }

    public void Dispose()
    {
        reader.Dispose();
    }
}

Exemple de programme:

string sample = @"( one (two 456 -43.2 "" \"" quoted"" ))";

var defs = new TokenDefinition[]
{
    // Thanks to [steven levithan][2] for this great quoted string
            // regex
    new TokenDefinition(@"([""'])(?:\\\1|.)*?\1", "QUOTED-STRING"),
    // Thanks to http://www.regular-expressions.info/floatingpoint.html
    new TokenDefinition(@"[-+]?\d*\.\d+([eE][-+]?\d+)?", "FLOAT"),
    new TokenDefinition(@"[-+]?\d+", "INT"),
    new TokenDefinition(@"#t", "TRUE"),
    new TokenDefinition(@"#f", "FALSE"),
    new TokenDefinition(@"[*<>\?\-+/A-Za-z->!]+", "SYMBOL"),
    new TokenDefinition(@"\.", "DOT"),
    new TokenDefinition(@"\(", "LEFT"),
    new TokenDefinition(@"\)", "RIGHT"),
    new TokenDefinition(@"\s", "SPACE")
};

TextReader r = new StringReader(sample);
Lexer l = new Lexer(r, defs);
while (l.Next())
{
    Console.WriteLine("Token: {0} Contents: {1}", l.Token, l.TokenContents);
}

Sortie:

Token: LEFT Contents: (
Token: SPACE Contents:
Token: SYMBOL Contents: one
Token: SPACE Contents:
Token: LEFT Contents: (
Token: SYMBOL Contents: two
Token: SPACE Contents:
Token: INT Contents: 456
Token: SPACE Contents:
Token: FLOAT Contents: -43.2
Token: SPACE Contents:
Token: QUOTED-STRING Contents: " \" quoted"
Token: SPACE Contents:
Token: RIGHT Contents: )
Token: RIGHT Contents: )

6voto

Andy Dent Points 9852

Il est peut-être exagéré, mais jetez un oeil à l'Ironie sur CodePlex.

L'ironie est un kit de développement pour la mise en œuvre de langues .NET plate-forme. Il utilise la flexibilité et la puissance du langage c# et .NET Framework 3.5 pour mettre en œuvre une complètement nouvelle et simplifiée de la technologie de compilateur de la construction. Contrairement à la plupart des yacc/lex-style de solutions Ironie ne pas employer n'importe quel scanner ou de l'analyseur de génération de code à partir de spécifications de grammaire écrite dans un service spécialisé méta-langage. Dans l'Ironie de la langue cible de la grammaire est codé en c# à l'aide de la surcharge d'opérateur pour exprimer la grammaire des constructions. Ironie du scanner et de l'analyseur de modules utilisation de la grammaire codé en c# de la classe de contrôle de l'analyse de processus. Voir l'expression de la grammaire de l'échantillon pour un exemple de définition de la grammaire en classe de c#, et de l'utiliser dans un travail de l'analyseur.

5voto

Juliet Points 40758

Sauf si vous avez très peu conventionnelle de la grammaire, je serais fortement recommandé de ne pas rouler vos propres lexer/parser.

J'ai l'habitude de trouver lexer/analyseurs syntaxiques de C# sont vraiment en manque. Cependant, F# vient avec fslex et fsyacc, que vous pouvez apprendre comment l'utiliser dans ce tutoriel. J'ai écrit plusieurs lexer/analyseurs en F#, et de les utiliser en C#, et il est très facile à faire.

Je suppose que c'est pas vraiment un pauvre homme lexer/parser, voyant que vous avez à apprendre un tout nouveau langage pour commencer, mais c'est un début.

2voto

Chris S Points 32376

Changer ma réponse d'origine.

Jetez un œil à SharpTemplate qui a des analyseurs pour différents types de syntaxe, par exemple

 #foreach ($product in $Products)
   <tr><td>$product.Name</td>
   #if ($product.Stock > 0)
      <td>In stock</td>
   #else
     <td>Backordered</td>
   #end
  </tr>
#end
 

Il utilise des expressions régulières pour chaque type de jeton:

 public class Velocity : SharpTemplateConfig
{
    public Velocity()
    {
    	AddToken(TemplateTokenType.ForEach, @"#(foreach|{foreach})\s+\(\s*(?<iterator>[a-z_][a-z0-9_]*)\s+in\s+(?<expr>.*?)\s*\)", true);
    	AddToken(TemplateTokenType.EndBlock, @"#(end|{end})", true);
    	AddToken(TemplateTokenType.If, @"#(if|{if})\s+\((?<expr>.*?)\s*\)", true);
    	AddToken(TemplateTokenType.ElseIf, @"#(elseif|{elseif})\s+\((?<expr>.*?)\s*\)", true);
    	AddToken(TemplateTokenType.Else, @"#(else|{else})", true);
    	AddToken(TemplateTokenType.Expression, @"\${(?<expr>.*?)}", false);
    	AddToken(TemplateTokenType.Expression, @"\$(?<expr>[a-zA-Z_][a-zA-Z0-9_\.@]*?)(?![a-zA-Z0-9_\.@])", false);
    }
}
 

Qui est utilisé comme ça

 foreach (Match match in regex.Matches(inputString))
{
    ...

    switch (tokenMatch.TokenType)
    {
    	case TemplateTokenType.Expression:
    		{
    			currentNode.Add(new ExpressionNode(tokenMatch));
    		}
    		break;

    	case TemplateTokenType.ForEach:
    		{
    			nodeStack.Push(currentNode);

    			currentNode = currentNode.Add(new ForEachNode(tokenMatch));
    		}
    		break;
    	....
    }

    ....
}
 

Il pousse et sort d'une pile pour garder l'état.

2voto

Kieron Points 10261

Malcolm Crowe a une excellente implémentation LEX / YACC pour C # ici . Fonctionne en créant des expressions régulières pour le LEX ...

Téléchargement direct

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X