![]() |
![]() ![]() ![]() ![]() ![]() |
![]() |
TreesBoost , ,
Why use parse treesПарсовые деревья представляют собой в памяти представление входа со структурой, которая соответствует грамматике. Преимущества использования парсовых деревьев вместо семантических действий:
Пример Теперь, когда вы думаете, что хотите использовать деревья, я приведу пример того, как их использовать. Итак, следуя традиции (и остальной документации), мы сделаем калькулятор. Вот грамматика:
Теперь вы заметите, что единственное, что отличается в этой грамматике, это директива token_node_d. Это приводит к тому, что соответствие целого правила оказывается в одном узле. Без token_node_d каждый персонаж получит свой собственный узел. Далее отметим, что token_node_d является неявным lexeme (что означает, что для перехода на анализ уровня символов не требуется lexeme_d). Как вы скоро увидите, легче преобразовать вход в инт, когда все символы находятся в одном узле. Вот как делается анализ для создания дерева:
pt_parse() аналогично parse(). Существует четыре перегрузки: две для пар первого и последнего итераторов и две для струн персонажей. Две функции выполняют парсер шкипера, а две другие — нет. Структура tree_parse_info содержит ту же информацию, что и parse_info Структура, а также один дополнительный элемент данных, называемый деревьями. После того, как деревья закончатся, они будут содержать дерево. Вот как вы можете использовать дерево для оценки входных данных:
Теперь вы спрашиваете, откуда взялась оценка ? Это часть духа? К сожалению, evaluate() является лишь частью выборки. Вот оно:
Таким образом, здесь вы видите, что оценка () просто вызывает eval_expression(), передавая итератор информации. деревья. Вот остальные примеры:
Итак, вам нравится то, что вы видите, но, возможно, вы думаете, что дерево анализа слишком сложно обработать? С помощью еще нескольких директив можно сгенерировать абстрактное дерево синтаксиса (ast) и сократить объем кода оценки как минимум на 50%. Итак, без промедления, вот грамматика калькулятора:
Отличия от грамматики дерева разбора hi-lighted в bold-red. Директива inner_node_d приводит к тому, что первый и последний узлы, генерируемые прилагаемым парсером, отбрасываются, так как при оценке выражения скобки нас действительно не волнуют. Директива root_node_d является ключом к генерации ast. Узел, генерируемый парсером внутри root_node_d, помечается как корневой узел. Когда создается корневой узел, он становится корневым или родительским узлом других узлов, генерируемых тем же правилом. Для начала анализа и генерации аста необходимо использовать функции ast_parse, которые аналогичны функциям pt_parse.
Вот функция eval_expression (обратите внимание, что для обработки аста нам нужна только одна функция вместо четырех):
Usagept_parseДля создания дерева разбора можно назвать одну из пяти свободных функций: template <typename FactoryT, typename IteratorT, typename ParserT, typename SkipT> ast_parseДля создания абстрактного синтаксисного дерева (коротко говоря) вы называете одну из пяти свободных функций: template <typename FactoryT, typename IteratorT, typename ParserT, typename SkipT> tree_parse_infoСтруктура tree_parse_info, возвращенная из pt_parse и ast_parse, содержит информацию о парсе:
tree_matchКогда Дух генерирует дерево, функция члена парсера parse() возвращает объект, соответствующий дереву, вместо объекта соответствия. Tree_match имеет три параметра шаблона. Первый тип итератора по умолчанию char const*. Второй — это фабрика узлов, которая по умолчанию выполняет функцию node_val_data_factory. Третий — тип атрибута, хранящийся в матче. Дерево_матч имеет переменную члена, которая представляет собой контейнер (a std::vector) из tree_node объектов, называемых деревьями. По соображениям эффективности, когда дерево_матч копируется, деревья не копируются, они перемещаются на новый объект, а исходный объект остается с пустым контейнером дерева. tree_match поддерживает тот же интерфейс, что и класс матчей: он имеет оператор bool(), поэтому вы можете протестировать его для успешного матча: if(matched), и вы можете запросить длину матча через функцию long(). Класс имеет такой интерфейс:
Когда анализ успешно завершен, элемент данных деревьев будет содержать корневой узел дерева.
Наличие духа, создающего дерево, похоже на то, как делается нормальный анализ:
tree_nodeКак только вы создали дерево, позвонив pt_parse или ast_parse, у вас есть tree_parse_info, который содержит корневой узел дерева, и теперь вам нужно что-то сделать с деревом. Деревья данных tree_parse_info представляют собой std::vector
Этот класс просто используется для отделения структуры дерева от данных, хранящихся в дереве. Это общий узел, и любой тип может храниться внутри него и передаваться через значение члена данных. Тип по умолчанию для T - node_val_data. node_val_dataКласс node_val_data содержит фактическую информацию о каждом узле. Это включает в себя последовательность текста или токена, которая была проанализирована, id, которая указывает, какое правило создало узел, булев флаг, который указывает, был ли узел помечен как корневой узел, и необязательное значение, указанное пользователем. Это и есть интерфейс:
parser_id, checking and settingЕсли узел генерируется правилом, он будет иметь набор id. Каждое правило имеет идентификатор, который он устанавливает все узлы, генерируемые этим правилом. id имеет тип parser_id. Ид по умолчанию каждого правила устанавливается на адрес этого правила (преобразуется в целое число). Это не всегда удобно, так как код, обрабатывающий дерево, может не иметь доступа к правилам, а может и не иметь возможности сравнивать адреса. Таким образом, вы можете переопределить идентификатор по умолчанию, используемый каждым правилом, , предоставив ему конкретный идентификатор . Затем при обработке дерева можно вызвать node_val_data::id(), чтобы увидеть, какое правило создал этот узел. structure/layout of a parse treeparse tree layoutДерево организовано по правилам. Каждое правило создает новый уровень в дереве. Все парсеры, прикрепленные к правилу, создают узел, когда производится успешный матч. Эти узлы становятся детьми узла, созданного правилом. Итак, следующий код:
При выполнении код возвращает дерево_match, m. m.trees[0] будет содержать такое дерево:
Корневой узел не будет содержать никакого текста, и его идентификатор будет установлен на адрес myrule. У него будет четверо детей. Идентификатор каждого ребенка будет установлен по адресу myrule, будет содержать текст, как показано на диаграмме, и не будет иметь детей. ast layoutПри вызове ast_parse дерево генерируется по-разному. В основном он работает так же, как при создании дерева. Разница возникает, когда правило генерирует только один подузел. Вместо создания нового уровня ast_parse не будет создавать новый уровень, он просто покинет существующий узел. Итак, этот код:
он будет генерировать один узел, содержащий «a». Если бы вместо tree_match_policy использовали ast_match_policy, дерево выглядело бы так:
ast_match_policy имеет эффект устранения промежуточных уровней правил, которые являются просто сквозными правилами. Этого недостаточно для генерации абстрактных синтаксических деревьев, также необходим root_node_d. root_node_d будет объяснено позже. switching: gen_pt_node_d[] & gen_ast_node_d[]Если вы хотите смешать и сопоставить поведение дерева разбора и аста в вашем приложении, вы можете использовать директивы gen_pt_node_d[] и gen_ast_node_d[]. При прохождении разбора по директиве gen_pt_node_d включается поведение создания дерева разбора. Когда используется директива gen_ast_node_d, замкнутый парсер генерирует дерево, используя поведение аста. Обратите внимание на то, как объявляются ваши правила, если вы используете правило в этих директивах. Политика соответствия сканера должна соответствовать желаемому поведению. Если вы избегаете правил и используете примитивные парсеры или грамматики, у вас не будет проблем. DirectivesЕсть еще несколько директив, которые можно использовать для контроля за генерацией деревьев. Эти директивы влияют только на генерацию деревьев. В противном случае они не будут иметь никакого эффекта. no_node_dЭта директива аналогична gen_pt_node_d и gen_ast_node_d, что изменяет политику соответствия сканера, используемую прилагаемым парсером. Как следует из названия, он не генерирует деревья, он полностью выключает их. Это полезно, если есть части вашей грамматики, которые не нужны в дереве. Например: ключевые слова, операторы (*, -, && и т.д.) Устраняя их из дерева, можно уменьшить использование памяти и время разбора. Эта директива имеет те же требования в отношении правил, что и gen_pt_node_d и gen_ast_node_d. См. пример файла xml_grammar.hpp (в libs/spirit/example/application/xml каталоге), например использование no_node_d[]. discard_node_dЭта директива имеет аналогичную цель с no_node_d, но работает иначе. Он не переключает политику соответствия сканера, поэтому закрытый парсер по-прежнему генерирует узлы. Созданные узлы отбрасываются и не появляются в дереве. Использование discard_node_d медленнее, чем no_node_d, но оно не страдает от недостатка необходимости указывать другой тип правила для любого правила внутри него. leaf_node_d/token_node_dОба leaf_node_t и token_node_d работают одинаково. Они создают единый узел для матча, генерируемого закрытым парсером. В отличие от предыдущих версий Spirit, эта директива является неявной лексемой и изменяет сканер (см. Scanner Business). reduced_node_dЭта директива объединяет все узлы, генерируемые закрытым парсером. Для более ранних версий Spirit leaf_node_d и token_node_d были реализованы таким образом. Новая реализация этих директив намного быстрее, поэтому reduced_node_d в первую очередь предусмотрена для переносимости и может быть полезна при использовании фабрики пользовательских узлов. infix_node_dЭто полезно для удаления разделителей из списков. Он отбрасывает все узлы в четных положениях. Таким образом, это правило:
отбросит все узлы запятой и сохранит все целые узлы. discard_first_node_dЭто отбрасывает первый сгенерированный узел. discard_last_node_dЭто отбрасывает последний созданный узел. inner_node_dЭто отбрасывает первый и последний созданный узел. root_node_d and ast generationДиректива root_node_d используется для поддержки генерации аста. Это не имеет никакого эффекта при создании дерева. Когда парсер заключен в root_node_d, генерируемый им узел помечается как корень. Это влияет на то, как он обрабатывается, когда он добавляется в список генерируемых узлов. Вот пример:
При разборе 5+6 образуется следующее дерево:
При разборе 1+2+3 будет генерироваться следующее:
При создании нового узла для определения того, как будет генерироваться дерево, используются следующие правила:
После разбора покидает текущее правило, флаг корневого узла на верхнем узле выключается. Это означает, что директива root_node_d влияет только на текущее правило.
parse_tree_iteratorКласс parse_tree_iterator позволяет сортировать дерево по духу. Класс повторяется над токенами в листовых узлах в том же порядке, в котором они были созданы. parse_tree_iterator шаблонизирован на ParseTreeMatchT. Он построен с контейнером из деревьев и возможностью запуска. Вот пример использования:
advanced tree generationnode valuenode_val_data может содержать значение. По умолчанию он содержит void_t, который является пустым классом. Вы можете указать тип, используя параметр шаблона, который затем будет храниться в каждом узле. Тип должен быть по умолчанию конструируемым и присваиваемым. Вы можете получить и установить значение, используя
и
Чтобы указать тип значения, вы должны использовать другую фабрику node_val_data , чем по умолчанию. Следующий пример показывает, как модифицировать фабрику для хранения и извлечения двойника внутри каждого node_val_data. typedef node_val_data_factory<> factory_t;skip;18> // получить доступ к двойнику в корневом узле skip>деревьев>.Начать.<7 access_node_dТеперь вы можете задаться вопросом: «Что хорошего в том, чтобы иметь значение, которое я могу хранить в каждом узле, но не иметь никакого способа его настройки?» Для этого и существует access_node_d. access_node_d является директивой. Это позволяет прикрепить к нему действие, например:
Прилагаемое действие пройдет по 3 параметрам: Ссылка на корневой узел дерева, генерируемый парсером, и текущие первый и последний итераторы. Действие может устанавливать значение, хранящееся в узле. Tree node factoriesУстановив завод, вы можете контролировать, какие типы узлов создаются и как они создаются. Существует 3 предопределенных фабрики: node_val_data_factory, node_all_val_data_factory и node_iter_data_factory. Вы также можете создать свою собственную фабрику для поддержки своих типов узлов. Использование фабрик с грамматикой довольно просто, вам просто нужно указать тип фабрики как явный параметр шаблона для бесплатной функции ast_parse: typedef node_iter_data_factory<int> factory_t; Вместо этого использовать фабрику напрямую с правилами немного сложнее, потому что фабрика является шаблонным параметром политики соответствия сканера, поэтому вы должны использовать пользовательский сканер:
node_val_data_factoryЭто завод по умолчанию. Он создает узлы node_val_data. Узлы листьев содержат копию согласованного текста, а промежуточные узлы — нет. node_val_data_factory имеет один параметр шаблона: ValueT. ValueT определяет тип значения, которое будет храниться в node_val_data. node_all_val_data_factoryЭта фабрика также создает node_val_data. Разница между ним и node_val_data_factory в том, что каждый узел содержит весь текст, который его охватывает. Это означает, что корневой узел будет содержать копию всей парсируемой входной последовательности. node_all_val_data_factory имеет один параметр шаблона: ValueT. ValueT определяет тип значения, которое будет храниться в node_val_data. node_iter_data_factoryЭта фабрика создает parse_tree_iter_node. Этот узел хранит итераторы во входной последовательности вместо создания копии. Он может использовать гораздо меньше памяти. Однако входная последовательность должна оставаться действительной для срока службы дерева, и не рекомендуется использовать итератор multi_pass с этим типом узла. Все уровни дерева будут содержать итератор начала и конца. node_iter_data_factory имеет один параметр шаблона: ValueT. ValueT определяет тип значения, которое будет храниться в узле_val_data. customВы можете создать собственную фабрику. Это должно выглядеть так:
Copyright © 2001-2002 Daniel C. Nuffer
Статья Trees раздела может быть полезна для разработчиков на c++ и boost. Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта. :: Главная :: ::
|
||||||||||||||||||||||||||||||||||||||||
©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007 |