Явный конечный автомат
Для этого Без использования какой-либо выделенной библиотеки я предлагаю создать конечный автомат.
Вы будете перебирать символы строки, и в зависимости от того, с каким символом вы столкнулись, вы обновляете состояние машины. Оптимизация возможна, однако давайте начнем с обычной ясности.
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 и не способен восстанавливаться после синтаксической ошибки. Он может быть изменен для таких целей. Также он не будет интегрироваться с редакторами кода на любом уровне.
Если вы хотите полноценное решение. У меня есть два предложения:
Такого рода вещи вы бы использовали, если бы хотели создать язык программирования поверх него.
И, конечно же, я должен связать вас с "Компиляторы: принципы, методы и инструменты" , обычно известной как "Книга Дракона".