Синтаксис hrc-скриптов библиотеки colorer

вместо введения

HRC - это формат хранения описаний синтаксиса языков программирования, скриптов и другой структурированной текстовой информации. Он используется в библиотеке колорер для синтаксического разбора и расцветки целевого текста. В общем-то, это довольно сложный скриптовый язык с мощными возможностями описания синтаксиса. hrc основан на метаязыке xml, и все его описания хранятся именно в этом формате. В поставке колорера вы найдете специально для этого описания синтаксиса hrc в DTD (hrc.dtd) и стилевые таблицы преобразования в html (hrc.xsl).

Для того чтобы полностью понять hrc, вам надо было его создать, но в общем-то можно «врубиться» и на халяву. Перво-наперво вы должны понять для чего собственно нужен hrc, и как в базовом виде он работает. Поэтому предварительно почитайте еще документацию по регулярным выражениям колорера - cregexp.html. Сразу буду предполагать, что слова xml (или html) вы не пугаетесь - ну а если пугаетесь, то откройте любой hrc файл и окиньте взглядом. Если вы программист или около того, то, в общем-то, идею поймете быстро. Ну а если не поняли - «Плюньте на это, Киса».

Перед тем, как все описывать, давайте прикинем общую систему скриптов. Пускай в упрощенном варианте у нас каждый язык описывается некоторым блоком - назовем его схемой (scheme). То есть, к примеру, есть схема языка C/C++, Pascal, Perl... Далее, каждой такой языковой схеме сопоставим блок типа языка (type) - в нем будет храниться дополнительная информация о нашем языке. К примеру, расширения файлов этого типа (если у файла нет фиксированного расширения, там же будет храниться возможность определения этого типа по первой строчке файла). Здесь же будет содержаться описание языка - для вывода пользователю, ну и так далее. Описания всех типов пусть хранятся в одном файле (они занимают мало места), а вот схемы языков пусть лежат в своих файлах. Как теперь мы будет загружать схемы? Можно загрузить все схемы сразу (положим перечислив их все в этом же главном файле) - но если схем много, то это будет не эффективно. Давайте в каждом типе еще указывать, файл с какими схемами этому типу требуется. Таким образом, когда пользователь откроет конкретный файл, колорер выберет нужный тип файла по расширению и только тогда загрузит необходимые этому типу файла схемы. Вот так вот в общем виде.

фортам (именно фортам) hrc

В базе колорера hrc файлов очень много, но все они имеют один формат. Содержимое любого файла выглядит более-менее стандартно:

<?xml version="1.0" encoding="Windows-1251"?>
<!DOCTYPE hrc SYSTEM "hrc.dtd">
<?xml-stylesheet type="text/xsl" href="hrc.xsl"?>
<!--
     комментарии создателя
-->
<hrc version="4ever">

<!-- основное содержимое -->

</hrc>

первые три строчки - это стандартные включения xml, а вот в блоке <hrc> и лежат все основные определения. Заметьте, что параметр version не обязателен и есть только в главном файле, к которому мы сейчас и перейдем.

colorer.hrc

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

  <include name="regions.hrc"/>
  <include name="defines.hrc"/>

  <type descr="c++" name="cpp" exts="/\.(cpp)|(cxx)|(cc)|(hpp)|h$/i">
    <load name="base/cpp.hrc"/>
    <scheme name="cpp"/>
  </type>

Здесь используется тэг <include name="filename.hrc"/> Это часто используемый в определениях тэг указывает колореру о необходимости подключить файл с именем filename.hrc. Заметьте, что файл подключается безусловно, то есть если вы укажете <include name="base/c.hrc">, то файл со схемой Си загрузится безо всяких отлагательств.

Далее, тэг <type ..> создает отдельный тип файла. В нашем случае это язык C++. Заметьте, что поле descr задает описание языка для отображения пользователю, а параметр name задает уникальное системное имя типа. Далее, параметр exts указывает регулярное выражение в формате колорера (cregexp), которое распознает имя файла C++.

Внутри тэга type мы имеем тэг <load name="base/cpp.hrc"/> ,который указывает колореру, в каком файле искать саму схему языка C++. таких тэгов scheme может быть несколько - тогда колорер загрузит все указанные файлы. Ну и наконец, в тэге <scheme name="cpp"/> указывается имя схемы, которую надо использовать для расцветки языка C++. Заметьте, что здесь оно совпадает с именем типа. Это не обязательно - но рекомендуется правилами написания hrc.

В тэге type может использоваться и параметр switch:

  <type descr="default" name="default" exts="//">
    <switch type="messages" match="/^from/i"/>
    <switch type="diff"     match="/^(diff)|(---)/i"/>
  </type>

Этот параметр указывает колореру, что если файл этого типа имеет первую строчку, которая совпадает с регулярным выражением match, то необходимо переключиться на тип, указанный в параметре type. Заметьте, что в этом параметре указывается не описание типа - а его системное имя (name).

В описанном выше случае, если выбирается тип default (то есть файл не попадает ни под один ранее определенный тип), и первая строчка содержит символы diff или ---, то произойдет выбор не типа default, а типа diff.

основное содержимое hrc

Забыл я еще сказать об одном маленьком тэге -

<define name="dNumber"    value="10"/>
Этот тэг определяет цветовой регион с именем dNumber, присваивая ему значение 10. Цветовой (или точнее пока синтаксический) регион определяет одну изолированную лексему в файле. К примеру здесь это число. Этот можно использовать и по-другому:
<define name="dNumHex"    value="dNumber"/>
Здесь новый регион dNumHex определится через dNumber. Пока можно представлять, что регионы это обычные цвета. А почему это не совсем правильно я объясню чуть позже.

Ну, теперь надо говорить об основном блоке любого hrc файла - блоке <scheme> Этот блок и определяет конкретную схему языка, то есть тот элемент, который производит разбор. Внутри этого блока определяются конкретные элементы, отвечающие за разбор текста и его расцветку. Начнем от простого к сложному.

блок keywords

Этот блок определяет набор ключевых слов, которые будут выделяться как определенные лексемы:

  <keywords region="dKeyWord" ignorecase="ignorecase" worddiv="/[^\w_]/">
     <word name="asm"/>
     <word name="auto"/>
     <word name="break"/>
     <word name="case"/>
     <word name="cdecl"/>
     <word name="char"/>
     <word name="const"/>
     <symb name="->"/>
  </keywords>

вложенные в него тэги word задают выделение слов (то есть строк, которые имеют границами переходы с символа слова на неслово), а тэги symb задают выделение символов - то есть игнорируют границы выделяемой лексемы. параметр region в блоке keywords используется для указания региона, сопоставляемого указанным ключевым словам. Такой же параметр можно указывать и непосредственно у каждого тэга word или symb. Еще один параметр - worddiv, задает класс символов, которые считаются символами-разделителями слов. В большинстве случаев этот параметр можно не указывать - по умолчанию разделителями считаются все символы кроме символов слова(букв, цифр), и символа подчеркивания.

Последний параметр этого блока - ignorecase="ignorecase" указывает колореру, что все слова в этом блоке надо искать без учета регистра символов.

regexp

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

   <regexp match='/".*?"/' region0="dString"/>
   <regexp match="/'.*$/"  region0="dComment"/>
   <regexp match="/\b[0-9.]+(e|E[\-+]?\d+)?\B/" region0="dNumber"/>

Первый его параметр (и единственный обязательный) это регулярное выражение match. Это регулярное выражение сопоставляется с каждой позицией строки. В случае совпадение, происходит синтаксическое выделение в соответствии с параметрами regionX, где X - числа от 0 до F. Каждый i-й регион указывает каким регионом (ну или цветом) выделить соответствующуу i-ю скобку в регулярном выражении. region0 всегда соответствует всему регэкспу.

block

Это основной тэг, который позволяет языку hrc создавать очень сложные языковые конструкции, хотя для понимания он довольно прост.

   <block start="/\/\*/" end="/\*\//" scheme="Comment"
          region="cComment" region00="dpOpenStruct" region10="dpCloseStruct"/>

У этого тэга первые два параметра start и end являются так же регулярными выражениями, и определяют начало и окончание выделяемого блока. Третий обязательный параметр scheme задает имя схемы, на которую нужно переключиться между этими началом и концом. В указанном случае наш блок определяет обычный многострочный комментарий языка Си/Си++. Иными словами этот блок говорит, «Вот я встретил сочетание /* - так что начну дальше все выделять новой схемой "Comment" - пока не встречу сочетание */»

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

Для цветового выделения у тэга block существует рабор параметров regionXX. Отдельно стоящий параметр region задает цвет выделения всего этого блока - от начала до конца. А вот параметры region0i и region1j - задают выделение i-х скобок в тэге start и j-х скобок в тэге end. Как и в тэге regexp region00 и region10 используются для выделения всего регэкспа start или end, а все, начиная с единицы, выделяют соответствующую по номеру скобку Вообще, умелое использование этого тэга может дать поразительные результаты - об этом я расскажу чуть ниже.

inherit

Это самый интеллектуальный тэг - с его помощью можно на порядок облегчить себе работу (хотя можно обойтись и без него). Тэг inherit задает схему, от которой текущая схема наследует все свойства - то есть ключевые слова, регулярные выражения, блоки и другие наследования. Чтобы проще понять,

<scheme name="Comment">
   <inherit scheme="mNetEMail"/>
   <inherit scheme="mNetURL"/>
</scheme>

вот определяется схема комментарий (Comment). Раньше я говорил что она пустая - но она не совсем пустая. В комментариях удобно выделять почтовые адреса - чего здесь и делается. Но для этого они не описываются заново через тэги regexp, а просто наследуются из уже существующих схем. Фактически, эти две строчки эквивалетны тому, что вы скопируете содержимое наследуемых схем в эту схему. Теперь понимаете, как это облегчает труд, если надо наследовать свойства большой схемы? К примеру язык Си++ - это Си с некоторыми дополнительными ключевыми словами. Но вместо полного переписывания схемы копированием мы просто наследуем в языке Си++ язык Си. Заметьте, что при этом даже в памяти (внутри колорера) копирования не происходит - за счет чего уменьшается и объем hrc кодов и объем используемой памяти.

Усредненное представление

Так. Ну и что же мы имеет на данный момент? Как вы могли догадаться, схема, ссылаясь на другие схемы, определяет тем самым сложную структуру взаимодействия внутри hrc-кодов. О ней милой и поговорим. Во-первых, сразу предостерегу: явно поймите различия между включением схемы и наследованием ее. И то и другое на уровне кодов - рекурсия, но первое - это рекурсия вынужденная, а второе - рекурсия явная.

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

Другое дело с наследованием. Если я наследую схему, которая еще не определена, а она наследует меня, то чего же будет? Никакой логики. И правильно - потому что будет мгновенное зацикливание парсера. Поэтому и нельзя наследовать еще неопределенную схему (есть интересные исключения - можно «заставить» другую схему наследовать любую схему - но это позже). Такие наглые попытки просто пресекаются колорером.

Может кому-то показаться на примитивном уровне, что схемы не так то уж нужны. Бросьте в него камень. Нет такой схемы в hrc кодах, где бы ни встречался тэг блочной структуры. Начиная от элементарных блоков комментариев, и кончая сложными системами взаимодействия схем в xml-кодах - везде используется наследование как механизм обобщения и изменения свойств, и вызовы блоков как механизм реализации разбора сложных структур языков.

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

Причем это очень элегантное Объектно-Ориентированное программирование. Ведь в сущности своей ООП предназначено для создания красивой внутренней структуры программы - оно помогает не пользователю, а программисту. Согласитесь, что любую объектно-ориентированную программу на том же Си++ можно переписать и в другом виде. Вот только насколько это будет выгоднее... Более того, ООП важно не в виде конечной реализации, а внутри программы, в самой идее, логике работы. Если программа требует постоянного изменения, переделывания, устранения несовместимостей самой с собой, то какой бы язык не использовался при ее написании - она не будет объектно-ориентированной. И с другой стороны, программа, написанная даже на ассемблере, может выражать красивые и элегантные архитектурные идеи - лишь из своей структуры работы. Язык часто ограничивает поток мысли - у него не хватает классов, лексем и операторов.

Само по себе ООП - это не только ключевые слова class, public, protected и virtual. Что такое инкапсуляция? Фредерик Брукс сказал, а Эрик Реймонд перефразировал: "Покажите мне код, не показывая своих структур данных и я по-прежнему буду пребывать в заблуждении. Покажите мне свои структуры данных и, как правило, ваш код мне не понадобиться; и так все будет понятно".

Инкапсуляция, как выражение дуализма кода и данных. Что такое в колорере весь hrc? Да всего лишь набор списков списков списков структур - но он не зависит от парсера в явном виде. Объем hrc-кодов в 20000 строк обрабатывается парсером размером в 500 строк. hrc инкапсулирует, объединяя всю логику работы и представляя коды по синтаксическому анализу. Очень важно в сложных языках до мелочей понимать логику взаимодействия различных схем. Особенно это необходимо будет при применении механизма виртуализации схем.

Предварительное заключение

Ну, если вы чего-нибудь из этого поняли, то это хорошо. Дальше я начну объяснять некоторые более глубокие свойства hrc, поэтому если показанную выше структуру hrc вы расплывчато представляете, то попробуйте посмотреть примеры. Первым делом сам colorer.hrc, defines.hrc - там определяется набор простеньких схемок - макросов, regions.hrc - в этом файле определяются все встроенные лексемы - только они используются в цветовых определениях.

Попробуйте посмотреть простенькие скрипты - навроде config/litestep.hrc - на них можно понять основные свойства.

Расширенные свойства межсхемных взаимодействий

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

Границы

Итак, первое - границы. Если внимательно читали cregexp.html (а его вы должны были прочитать прежде чем лезть сюда), то наверное заметили странные параметры (метасимволы \m и \M), задающие смену начала и окончания (скажем мягко) регулярного выражения. Эти параметры могут использоваться как в блоках, так и в одиночных регулярных выражениях. Какую роль они играют?

Первая - довольно простая. Помните, я говорил про region00 (region0 для regexp)? Этот регион сопоставляется не со скобкой, а со всем регулярным выражением. Так вот по умолчанию все регулярное выражение - это и есть все регулярное выражение - то есть от начала своего до конца. А вот параметрами \m (начало) и \M (конец) вы можете явно указать эти границы. Причем ни на какие другие параметры этого регэкспа это изменение влиять не будет.

Ну это было просто. А теперь о влиянии, которое оказывают эти параметры на разбор. В обычном однострочном regexp, если вы сместите влево конец регулярного выражения, то все будет по старому (то есть расцветится оно по-старому), но вот дальнейший анализ начнется именно с точки нового окончания. Иными словами с этим параметром регэксп как бы может отдавать часть себя (а именно свой конец - как это плохо не звучит) на разбор парсеру - то есть другим элементам схемы. Будет происходить наложение - или же это можно охарактеризовать как «прозрачный регэксп». К примеру, в языке Паскаль ищется определение функции. Это сложный регэксп, он может включать в себя разные ключевые слова, символы там... ну кучу всего. Если вы найдете функцию и успокоитесь, то например ключевое слово возвращаемого значения этой функции больше не выделится. Точно так же не выделяются скобки и другие символы. А это непорядок. Что делать? вы смещаете конец регулярного выражения в самое его начало и тем самым отдаете парсеру все эти недовыделенные ключевые слова:

   <regexp match="/^ \s* \M (procedure)|(function) \s+ ([_\w]+)/ix" region3="dFunction"/>

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

Итак, как же происходит работа в сложных схемных структурах с точки зрения учета границ? В блоке start аналогично обычному регэкспу парсером учитывается только его окончание - начиная с этого места ведется анализ вложенной схемы этого блока. Как происходит разбор дальше и, что самое интересное, как происходит возврат в родительскую схему? Здесь все очень завязано с понятием приоритета регулярного выражения и приоритета блока. По умолчанию, если вы сами ничего не указываете, приоритет regexp и block - высокий. Вот и рассмотрим работу «по умолчанию»

Итак, у нас работает вложенная схема, и каким-то образом нам надо поймать окончание этой схемы. Предположим, что в конце строки находится тот самый наш долгожданный конец блока - но до него еще есть довольно много регулярных выражений и даже не исключено что других блоков. Что происходит? Анализ в этом случае ведется вплоть до окончания нашего родительского блока. Но! Если вдруг найдется регэксп или блок, который на самом краю либо совпадет с ним, или же, недоходя до него, заглотит его:

  К примеру если есть окончание блока "}", а во вложенном блоке определен
  комментарий в Си стиле, то иллюстрация будет такая:
  // any text....     } - ou - our end bracket!

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

Чтобы это предотвратить, нужно воспользоваться параметром понижения приоритета. И в regexp, и в block этот параметр выглядит как lowpriority="lowpriority". Теперь, если в указанном примере в регулярном выражении, определяющем комментарий, мы вставим этот параметр, то комментарий выделится только до конца нашей фигурной скобки, и управление вернется родительскому блоку. Для регэкспов с пониженным приоритетом наша закрывающая скобка будет эдаким псевдо-концом строки.

А причем же здесь границы регэкспов? А вот именно границы и определяют то, когда откат будет происходить а когда не будет. Если границы регэкспа и блока end перекрываются, то будет откат. Иначе нет. Из-за этого, прошу заметить, в параметре end важны обе позиции: как начальная (\m) так и конечная (\M).

Как пример всей этой болтовни можно привести такой код:

<scheme name="xslTags">
   <keywords region="xmlOpenTag">
     <word name="apply-imports"/>
     <word name="apply-templates"/>
     <word name="attribute"/>
     <word name="attribute-set"/>
     <word name="call-template"/>
   </keywords>
</scheme>

<scheme name="xslTagList">
   <block start="/\b(xsl)(\:) \M [\w\-]+/ix" end="/~[\w\-]+\m/" scheme="xslTags"
          region01="xmlNameSpace" region02="dSymb"/>
</scheme>

Положим у нас есть очень много ключевых слов, начинающихся с символов "xsl:". xsl:apply-template, xsl:attribute, и так далее. Конечно их можно перечислить и так, но нас в данном случае интересует исследовательская сторона проблемы. Что делает указанный выше пример? Смотрите. В схеме xmlTagList в начальном регэкспе блока записывается обобщенный вариант таких ключевых слов, но у него смещен конец - как раз на начало самого ключевого слова без префикса "xsl:". Теперь, в блоке end используется очень интересный оператор - "~". Этот оператор «привязывает» регэксп к окончанию блока start - то есть как раз к позиции оператора \M в нем. Далее в end матчится такая же последовательность символов слова, и только после этого ставится параметр \m, который указывает, что начало конца блока сместится вправо - тем самым позволяя ключевым словам в схеме xlsTags без препятствий выделиться.

Ну, это один из примеров - на самом деле с этими возможностями можно придумывать намного более сложные вещи. Смотрите к примеру схему языка Perl - base/perl.hrc - всю вам ее понять все равно не удастся, но некоторые части можно проанализировать. К примеру поддержку таких «строенных» конструкций:


 s(          #
   $foo
   (sf)

   )          # great perversion - but...
          #  note here could be only comments
   (

   12        # foo quux
   ($bar)

  )ix;

Виртуализация и наследование

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

Во многих языках существуют констукции, которые задают вложенность языка самого в себя. Это такие естественные конструкции, как фигурные скобки в Си, или блоки begin/end в паскале. Вообще, они как бы не нуждаются в дополнительной обработке, но в последних версиях колорера появившиеся возможности обработки парных констукций и поиска ошибок вынуждают писать схемы, которые уже больше похожи на описания лексического анализатора - иными словами схемы, которые разбирают именно структуру языка (следуя его логике). Такая работа позволяет использовать обработку парных конструкций, поиска ошибок в этих вложенностях, поиск функций (и вообще любых списковых структур).

Все было бы хорошо, но такая работа несколько не согласуется с уже показанным мною механизмом наследования. Предположим, схема Си++ наследует схему Си, доопределяя ее необходимыми свойствами. Но в схеме Си содержатся структуры, генерирующие рекурсивный же ее вызов из себя. Что тогда получится при разборе Си++ кода? На верхнем уровне это будет Си++ схема, но как только встретится любая структура, переводящая контекст в схему Си (те же парные скобки), вложенная схема будет уже чистым Си!

Еще один пример нелогичной работы механизма наследования. Представьте, что у вас есть схема, которая наследует схему некоего сложного языка (состоящего из многих схем), доопределяя некоторые ее свойства. Но очень часто при наследовании необходимо оставить неизменными свойства самого непосредственного предка, изменив при этом свойства некоторых схем, используемых им. Такое сделать вообще никак не удастся.

Можно представить, что традиционная схема наследования является одноуровневой. То есть я могу использовать свойства других схем, но сами эти схемы будут для меня черными ящиками - нельзя изменить их поведение «изнутри». И вот теперь, после этих примеров, я покажу как введенный в колорере механизм виртуализации позволяет разрешить поставленные проблемы.

Посмотрите на этот пример:

   <inherit scheme="c">
     <virtual scheme="c" subst="cpp"/>
   </inherit>

Это то самое место, где в схеме Си++ наследуется схема Си. Но наследуется она необычно - а с использованием тэга virtual. Этот тэг и реализует механизм виртуализации. Что он значит? Он говорит колореру, что при включении в схему Си++ схемы Си, любые дальнейшие попытки сослаться на схему Си из нее же будут приводить к тому, что эти ссылки заменятся на ссылки на схему Си++. Произойдет подмена, виртуализация схемы Си на схему Си++. Как теперь понимаете, проблема с вложенными в себя же языковыми структурами полностью снимается. Замечу, что под понятием ссылки на схему я имею ввиду либо переключение на нее в каком либо блоке, либо наследование ее, либо ее же виртуализация - причем в любой схеме, вложенной по любой рекурсии в схему Си. Естественно, блоков virtual может быть хоть сколько.

На элементарном уровне, надеюсь, разобрались. Виртуализация - это всего лишь замена одной схемы на другую везде, где она встречается. Теперь более сложные мыслишки. Виртуализация может быть вложенной. Ну не надо истерического смеха. На самом деле, некоторые даже смогут это понять. Для того чтобы в это врубиться, надо представить, что каждая сложная схема представляет (для виртуального механизма) набор своих подсхем, которые можно виртуализовать. Если описания требуют виртуализации одной или нескольких из них, то этот набор изменится: схемы подменятся. А на дальнейших уровнях виртуализации вплоть до самого верхнего все будет точно так же.

Еще важно сказать одно предложение. Если есть набор уровней виртуализаций, и на каком-то из них необходима подмена, то для подмененной схемы (на которую подменили) отключатся все системы виртуализации низших уровней, а виртуализации более высоких порядков останутся.

Как пример покажем такую систему взаимодействия:

<scheme name="mPairedBrackets">
   <!-- paired block -->
   <block start="/(\{)/" end="/(\})/" scheme="mPairedBrackets"
          region00="dSymbol2" region01="dpOpenStruct"
          region10="dSymbol2" region11="dpCloseStruct"/>
   <block start="/(\()/" end="/(\))/" scheme="mPairedBrackets"
          region00="dSymbol" region01="dpOpenStruct"
          region10="dSymbol" region11="dpCloseStruct"/>
   <block start="/(\[)/" end="/(\])/" scheme="mPairedBrackets"
          region00="dSymbol" region01="dpOpenStruct"
          region10="dSymbol" region11="dpCloseStruct"/>
</scheme>

<scheme name="C">
   <inherit scheme="mPairedBrackets">
     <virtual scheme="mPairedBrackets" subst="c"/>
   </inherit>
...

Схема mPairedBrackets определяет набор для расцветки и обнаружения парных скобок всех типов. Причем в качестве вложенной схемы она использует себя - тем самым достигается любая вложенность любых скобок. Но посмотрите теперь на схему "C". Она наследует эту схему, но виртуализирует ее на себя. Что получится? схема mPairedBrackets будет играть роль скелета для схемы Си - эта схема обретет возможность матчинга парных конструкций.

Такое элементарное применение виртуального механизма можно встретить во многих схемах. Если вам надо что-то сложнее, то попробуйте понять работу схем ASP, PHP. Ну на крайняк xml/xsl (там проще).

Правила написания hrc

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

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

При определении синтаксического выделения пользуйтесь встроенными лексемами (d.*). Вообще говоря, помимо встроенных вы ничего больше и не сможете использовать. Но еще более важное правило - определяйте выделение элементов текста в соответствии со смыслом этих лексем. Иными словами не надо определять комментарии цветом чисел только потому что вам цвет их нравится. Колорер - синтаксический парсер. Он ничего не знает на начальном этапе о реальных цветах того или иного региона. Если вам хочется видеть цвета на свой вкус, то создавайте свои HRD файлы, но не уродуйте оригинальные hrc.

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

Общая структура

Ну, на последок окиньте взглядом всю структуру hrc:

<?xml version="1.0" encoding="Windows-1251"?>
<!DOCTYPE hrc SYSTEM "hrc.dtd">
<?xml-stylesheet type="text/xsl" href="hrc.xsl"?>
<hrc ver="4ever">
  <define name="rname" value="regid"/>
  <include name="fname"/>
  <scheme name="sname">
    <inherit scheme="sname">
      <virtual scheme="sname" subst="sname"/>
    </inherit>
    <regexp match="//" regioni="rname" lowpriority="lowpriority"/>
    <block  start="//" end="//" scheme="sname"
            region="rname" regionij="rname" lowpriority="lowpriority"/>
    <keywords ignorecase="ignorecase" worddiv="/[]/" region="rname">
      <word name="wname" region="rname"/>
      <symb name="wname"/>
    </keywords>
  </scheme>
  <type descr="descr" name="tname" exts="//">
    <load name="fname">
    <scheme name="sname">
    <switch type="tname" match="//">
  </type>
</hrc>
Когда ты был мал, ты знал все что знал,
И собаки не брали твой след...
Теперь ты открыт, ты отбросил свой щит,
Ты не помнишь кто прав а кто слеп...