La versión original que publicado aquí como una respuesta tenía el problema de que sólo funcionaba mientras que hubo más de un "expresiones regulares" que coincide con la actual expresión. Es decir, tan pronto como solo un Regex coincida, devolverá un token, mientras que la mayoría de la gente quiere que el Regex sea "codicioso". Este fue especialmente el caso de cosas como "cadenas citadas".
La única solución que se encuentra en la parte superior de Regex es leer la entrada línea por línea (lo que significa que no puede tener tokens que abarcan varias líneas). Puedo vivir con esto, es, después de todo, ¡un lexer de un pobre! Además, en general es útil obtener información del número de línea del Lexer en cualquier caso.
Por lo tanto, aquí hay una nueva versión que resuelve estos problemas. Crédito también va al programa Ejemplo 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();
}
}
:
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);
}
Salida:
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:)
En mi opinión, hay muchas herramientas buenas para generar código lexer/analizador (como ANTLR o Irony), pero si nunca escribes un código de análisis básico desde cero, podría ser difícil aprovechar estos generadores. Recientemente escribí un analizador de expresiones matemáticas de código abierto simple (https://github.com/gsscoder/exprengine). Evalúa el recorrido del árbol de sintaxis abstracta (AST) mediante el patrón de visitante. ¡El lexer/analizador está escrito desde cero, sin generadores! Espero que pueda ser útil. – gsscoder