“Лексический анализатор” бедного человека для C#

Все классы расширяют Object неявно, так что это просто избыточное кодирование, не имеющее никакого влияния.

32
задан Svante 20 June 2009 в 01:23
поделиться

7 ответов

Исходная версия, которую я отправил здесь как ответ, имела проблему, в которой она только работала, в то время как был больше чем один "Regex", который соответствовал текущему выражению. Таким образом, как только только один Regex соответствовал, он возвратит маркер - тогда как большинство людей хочет, чтобы Regex был "жадным". Это особенно имело место для вещей, таких как "заключенные в кавычки строки".

Единственное решение, которое находится сверху Regex, состоит в том, чтобы считать вход линию за линией (что означает, что у Вас не может быть маркеров, которые охватывают несколько строк). Я могу жить с этим - это - в конце концов, лексический анализатор бедного человека! Кроме того, обычно полезно вытащить информацию о номере строки из Лексического анализатора в любом случае.

Так, вот новая версия, которая решает эти проблемы. Кредит также переходит к этому

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() => 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);

Вывод:

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: )
25
ответ дан 27 November 2019 в 21:05
поделиться

У Malcolm Crowe есть большая реализация LEX/YACC для C# здесь . Работы путем создания регулярных выражений для LEX...

Прямая загрузка

2
ответ дан 27 November 2019 в 21:05
поделиться

Это может быть излишеством, но взглянуть на Иронию на CodePlex.

Ирония является комплектом разработчика для реализации языков на платформе.NET. Это использует гибкость и питание c# языка и Платформы.NET 3.5 для реализации абсолютно новой и оптимизированной технологии конструкции компилятора. В отличие от большинства существующих yacc/lex-style решений Ирония не использует сканера или генерации кода синтаксического анализатора от спецификаций грамматики, записанных в специализированном метаязыке. Насмешливо грамматика выходного языка кодируется непосредственно в операторе использования c#, перегружающемся для выражения конструкций грамматики. Сканер иронии и модули синтаксического анализатора используют грамматику, закодированную как c# класс для управления процессом парсинга. Посмотрите образец грамматики выражения для примера определения грамматики в c# классе и использование его в рабочем синтаксическом анализаторе.

6
ответ дан 27 November 2019 в 21:05
поделиться

Возможно использовать Flex и Бизона для C#.

исследователь А в Университете Ирландии разработал частичную реализацию, которая может быть найдена в следующей ссылке: Flex/бизон для C#

можно было определенно считать, что 'бедные укомплектовывают лексический анализатор', поскольку у него, кажется еще, все еще есть некоторые проблемы с его реализацией, такие как никакой препроцессор, проблемы с 'свисанием' случай, и т.д.

0
ответ дан 27 November 2019 в 21:05
поделиться

Если у Вас нет очень нетрадиционной грамматики, я был бы сильно , рекомендуют не лексическому анализатору/синтаксическому анализатору самокрутки.

я обычно нахожу, что лексическому анализатору/синтаксическим анализаторам для C# действительно недостает. Однако F# идет с fslex и fsyacc, который можно изучить, как использовать в этом учебном руководстве . Я записал несколько лексических анализаторов/синтаксических анализаторов в F# и использовал их в C# и его очень легком, чтобы сделать.

я предполагаю не действительно лексический анализатор/синтаксический анализатор бедного человека, видя, что необходимо выучить совершенно новый язык для начала работы, но это - запуск.

5
ответ дан 27 November 2019 в 21:05
поделиться

Изменение моего исходного ответа.

Смотрят на SharpTemplate, который имеет синтаксические анализаторы для различных типов синтаксиса, например,

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

Он использует regexes для каждого типа маркера:

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

, Который используется как это

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

    ....
}

, Это продвигает и появляется от Стека для хранения состояния.

2
ответ дан 27 November 2019 в 21:05
поделиться

Если Вы смотрите на ExpressionConverter в моем библиотека WPF Converters , это имеет основной lexing и парсинг выражений C#. Никакой включенный regex, из памяти.

0
ответ дан 27 November 2019 в 21:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: