Die ursprüngliche Version, die ich hier gepostet als Antwort ein Problem in hatte, dass es funktioniert nur, wenn es mehr als einen „Regex“ war, der die aktuelle abgestimmt Ausdruck. Das heißt, sobald nur ein Regex übereinstimmte, würde er ein Token zurückgeben - während die meisten Leute wollen, dass der Regex "gierig" ist. Dies war insbesondere bei Dingen wie "Anführungszeichen" der Fall.
Die einzige Lösung, die auf Regex sitzt, ist das Lesen der Eingabe Zeile für Zeile (was bedeutet, dass Sie keine Token haben können, die sich über mehrere Zeilen erstrecken). Ich kann damit leben - es ist schließlich ein lexer eines armen Mannes! Außerdem ist es normalerweise nützlich, in jedem Fall Informationen zur Zeilennummer aus dem Lexer zu erhalten.
Also, hier ist eine neue Version, die diese Probleme anspricht. Kredit geht auch this
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();
}
}
Programm Beispiel:
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);
}
Ausgang:
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:)
Meiner Meinung nach gibt es eine Menge guter Werkzeuge zum Generieren von Lexer/Parser-Code (wie ANTLR oder Irony), aber wenn Sie nie einen grundlegenden Parsing-Code von Grund auf schreiben, könnte es schwierig sein, diese Generatoren zu nutzen. Ich habe vor kurzem einen einfachen Open-Source-Mathe-Ausdruck-Parser geschrieben (https://github.com/gsscoder/exprengine). Es evaluiert das Durchlaufen des abstrakten Syntaxbaums (AST) unter Verwendung des Besuchermusters. Der Lexer/Parser wird von Grund auf neu geschrieben, keine Generatoren! Ich hoffe es kann hilfreich sein. – gsscoder