Простой (в основном) парсер переменных - PullRequest
4 голосов
/ 04 апреля 2011

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

Анализатор должен иметь возможность хранить набор переменных (map<string, string> на данный момент) и иметь возможность заменять токены на соответствующие значения в строках. Значения переменных могут содержать другие переменные, которые будут разрешаться при использовании переменной ( не при ее добавлении, поскольку переменные могут добавляться со временем).

Текущая грамматика переменной выглядит примерно так:

$basepath$/resources/file.txt
/$drive$/$folder$/path/file

Мой текущий анализатор использует пару stringstream s («output» и «varname»), записывает в поток «output», пока не найдет первый $, поток «varname» до второго $, затем ищет до переменной (используя содержимое varname.str()). Это очень просто и хорошо работает, даже когда рекурсивно переходит в значения переменных.

String Parse(String input)
{
    stringstream output, varname;
    bool dest = false;
    size_t total = input.length();
    size_t pos = 0;
    while ( pos < total )
    {
        char inchar = input[pos];
        if ( inchar != '$' )
        {
            if ( dest ) output << inchar;
            else varname << inchar;
        } else {
            // Is a varname start/end
            if ( !dest )
            {
                varname.clear();
                dest = true;
            } else {
                // Is an end
                Variable = mVariables.find(varname.str());
                output << Parse(Variable.value());
                dest = false;
            }
        }

        ++pos;
    }

    return output.str();
}

(проверка ошибок и тому подобное удалены)

Однако этот метод не дает мне результата, когда я пытаюсь применить его к желаемой грамматике. Я хотел бы что-то похожее на то, что Visual Studio использует для переменных проекта:

$(basepath)/resources/file.txt
/$(drive)/$(folder)/path/file

Я бы тоже хотел иметь возможность:

$(base$(path))/subdir/file

Повторение имени переменной привело меня к стене, и я не уверен, что лучший способ продолжить.

На данный момент у меня есть два возможных понятия:

Итерируйте по входной строке, пока я не найду $, ищи a (как следующий символ, затем найду совпадение) (считая уровни внутри и снаружи, пока не будет достигнута правильная близкая пара). Отправьте этот бит для анализа, затем используйте возвращенное значение в качестве имени переменной. Однако, похоже, что это будет грязно и приведет к большому скопированию.

Вторая концепция - использовать char *, или, возможно, char * &, и двигаться вперед, пока я не достигну завершающего нуля. Функция синтаксического анализатора может использовать указатель в рекурсивных вызовах к себе при анализе имен переменных. Я не уверен, как лучше всего реализовать эту технику, кроме того, чтобы каждый вызов отслеживал имя, которое он анализировал, и добавлял возвращаемое значение всех вызовов, которые он делает.

Проект должен компилироваться только в VS2010, так что потоки и строки STL, поддерживаемые биты C ++ 0x и специфичные для Microsoft функции - все это честная игра (универсальное решение предпочтительнее в случае изменения этих требований, но это не так необходимо на данный момент). Однако использование других библиотек бесполезно, особенно не Boost.

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

1 Ответ

3 голосов
/ 04 апреля 2011

Простое решение состоит в том, чтобы найти первый ')' в строке, а затем переместиться назад, чтобы увидеть, есть ли идентификатор, перед которым стоит "$ (". Если это так, замените его и перезапустите сканирование. Если вы не нашли"$ (" идентификатор , затем найдите следующий ')' - когда нет ни одного, вы закончили.

Объяснить: при поиске ) вы можете бытьубедитесь, что вы нашли полный идентификатор для вашей замены, который затем может внести вклад в какой-то другой идентификатор, используемый в последующей замене.

ПРИМЕР

Had a great time on $($(day)$(month)), did you?

Dictionary: "day" -> "1", "month" -> "April", "1April" -> "April Fools Day"

Had a great time on $($(day)$(month)), did you?
                           ^ find this
Had a great time on $($(day)$(month)), did you?
                      ^^^^^^ back up to match this complete substitution
Had a great time on $(1$(month)), did you?
                      ^ substitution made, restart entire process...
Had a great time on $(1$(month)), did you?
                              ^ find this
etc.
...