Как узнать разницу между + в и из строк? - PullRequest
1 голос
/ 08 июня 2019

Я делаю простой компилятор и работаю над разбором строк.На данный момент мой код:

    while (stringToParse.Contains(" + ") || stringToParse.Contains("+ ") || stringToParse.Contains(" +")) {
        stringToParse = stringToParse.Replace(" +", "+").Replace("+ ", "+").Replace(" + ", "+");
    }
    string[] splitString = stringToParse.Split("+");

Но что-то вроде:

"\"hello \" + \"world \" + \" + \" + \"hello\""

Вернется:

["\"hello "\", "\"world \"", "\"", "\"", ]

(безобратная косая черта)

Но что-то вроде:

""hello " + "world " + " + " + "hello""

Возвращает:

[""hello "", ""world "", """, """, ]

Так как я могу указать, если " + " находится в строкеили как разделитель?Есть ли способ обнаружить что-то вроде следующего?

...(any number of non " or + characters)...+...(any number of " or + characters)

Мой ожидаемый результат будет:

[""hello "", ""world "", ""+""]

1 Ответ

4 голосов
/ 08 июня 2019

Явный конечный автомат

Для этого Без использования какой-либо выделенной библиотеки я предлагаю создать конечный автомат.

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

var characters = input.ToCharArray();
var results = new List<string>();
var current = string.Empty;

// 0 = not inside quotes, we expect +
// 1 = not inside quotes, we expect "
// 2 = inside quotes
var state = 1;

foreach (var character in characters)
{
    switch (state)
    {
        case 0:
            // We are not inside quotes, we expect +
            if (character == '+')
            {
                state = 1;
                continue;
            }
            if (char.IsWhiteSpace(character))
            {
                continue;
            }
            // error?
            break;
        case 1:
            // We are not inside quotes, we expect "
            if (character == '\"')
            {
                state = 2;
                continue;
            }
            if (char.IsWhiteSpace(character))
            {
                continue;
            }
            // error?
            break;
        case 2:
            // We are inside quotes, we expect "
            if (character == '\"')
            {
                state = 0;
                results.Add(current);
                current = string.Empty;
                continue;
            }
            current += character;
            break;
        default:
            // error?
            break;
    }
}

if (state != 0)
{
    // error
}

// You can use results.ToArray();

Возможные оптимизации:

  • Мы можем использовать StringBuilder вместо конкатенации.
  • Также мы можем использовать IndexOf , чтобы найти следующий соответствующий символ.
  • Мы можем проверить, является ли строка (кусок символов) пустой или пробел (возможно, используя IsNullOrWhiteSpace ).
  • Мы можем использовать AsSpan , поэтому вместо него мы можем работать с ReadOnlySpan .

Вы также можете увидеть, как вы можете добавить поддержку ваших собственных escape-последовательностей или любого другого материала.


Неявный конечный автомат (с классом помощника)

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

  • Метод, который возвращает следующий символ ... или еще лучше, который возвращает истину, если следующий символ соответствует параметру (и продвигается), или ложь (и не продвигается).
  • Метод, который возвращает все символы до следующего экземпляра определенного символа (и переходит к нему).

Основным преимуществом такого подхода является то, что мне больше не нужно переходить от символа к символу, поэтому мне не нужно иметь переменную state. Вместо этого я мог позволить структуре кода напоминать форму моей грамматики.

Подождите, я написал такой класс: StringProcessor . Он является частью Theraot.Core nuget , он используется для анализа строк в BigInteger.

var processor = new Theraot.Core.StringProcessor(input);
var results = new List<string>();

while (!processor.EndOfString)
{
    // SkipWhile skips all the characters that match
    processor.SkipWhile(char.IsWhiteSpace);
    // Read returns true (and advances after) if what is next matches the paramter
    if (processor.Read('"'))
    {
        // ReadUntil advances after and returns everything found before the parameter 
        // Note: it does not advance after the parameter.
        results.Add(processor.ReadUntil('"'));
        processor.Read('"'); 
    }
    processor.SkipWhile(char.IsWhiteSpace);
    if (!processor.Read('+'))
    {
        // error?
    }
}

Обратите внимание, что класс, такой как StringProcessor, использованный выше, значительно упрощает задачу, что делает его жизнеспособным для простых языков.


Пользовательский токенизатор

Конечно, для чего-то более сложного вы можете поискать токенизатор.

В качестве примера рассмотрим, что у нас есть "грамматика":

Document: Many
{
    Whitespace
    String:
    {
        QuoteSymbol
        NonQuoteSymbol
        QuoteSymbol
    }
    Whitespace
    PlusSymbol
}

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

Не было бы неплохо написать следующее?

var QuoteSymbol = Pattern.Literal("QuoteSymbol", '"');
var NonQuoteSymbol = Pattern.Custom("NonQuoteSymbol", s => s.ReadUntil('"'));
var String = Pattern.Conjunction("String", QuoteSymbol, NonQuoteSymbol, QuoteSymbol);

var WhiteSpace = Pattern.Custom("WhiteSpace", s => s.ReadWhile(char.IsWhiteSpace));
var PlusSymbol = Pattern.Literal("PlusSymbol", '+');
var Document = Pattern.Repetition(
    Pattern.Conjunction(WhiteSpace, String, WhiteSpace, PlusSymbol)
);

var results = from TerminalSymbol symbol
              in Document.Parse(input)
              where symbol.Pattern == String
              select symbol.ToString();

Написание подобного кода облегчит изменение языка. Что ж, мы все еще пишем код, однако вы могли бы представить, что анализируете файл с грамматикой языка, который вы хотите анализировать ... Необычно!

Как и следовало ожидать, для создания необходимого кода для его работы требуется дополнительная работа. Или, вы знаете, получите некоторый код, который уже работает (связанный код построен на StringProcessor).


Language Toolkits

Код, представленный ранее, не подходит для prettyprinter и не способен восстанавливаться после синтаксической ошибки. Он может быть изменен для таких целей. Также он не будет интегрироваться с редакторами кода на любом уровне.

Если вы хотите полноценное решение. У меня есть два предложения:

Такого рода вещи вы бы использовали, если бы хотели создать язык программирования поверх него.


И, конечно же, я должен связать вас с "Компиляторы: принципы, методы и инструменты" , обычно известной как "Книга Дракона".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...