TreeDL: язык описания структуры деревьев


Содержание

1. Введение
2. Общие возможности
Декларативная информация
Документационные комментарии
Типы
3. Модули
Виды модулей
Полное имя модуля
Использование модулей
Пользовательский код
4. Типы вершин
Имя типа вершин
Модификаторы
Наследование типов вершин
Пользовательский код
Члены типа вершин
Атрибуты
Дети
Пользовательский код
5. Перечислимые типы
Имя перечислимого типа
Множество значений перечислимого типа
6. Операции над деревом
Сигнатура операции
Ветвь операции
Наследование операций
1. Лексическая грамматика
Символы перевода строки
Пробельные символы
Комментарии
Идентификаторы
Ключевые слова
Пользовательские типы
Литералы
2. Синтаксическая грамматика
3. Семантические ограничения
4. История изменений

Глава 1. Введение

TreeDL (Tree Description Language) - это язык описания древовидных структур данных, использующихся внутри программы, и операций над ними.

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

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

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

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

Язык TreeDL позволяет определять набор типов вершин, которые могут быть использованы в дереве. Тип задаёт именованные связи родительской вершины с дочерними и дополнительную информацию – атрибуты, связанные с вершиной.

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

Глава 2. Общие возможности

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

Декларативная информация

Для каждой сущности в описании структуры дерева возможно указать дополнительную декларативную информацию. Это расширяет возможности описания дерева без изменения языка описания. Декларативная информация доступна на этапе обработки структуры дерева и может быть использована инструментом обработки.

Хранение дополнительной информации непосредственно в описании дерева позволяет обойтись без дополнительных конфигурационных файлов. Однако не следует помещать в описание дерева информацию, которая не является неизменной относительно описания дерева.

Декларативная информация задается в виде набора именованных свойств:

[29] properties ::= ( "[" ( property )* "]" )+  
[30] property ::= qid "="
( <BOOL_LITERAL> | <INT_LITERAL> | <STRING_LITERAL> ) ";"
 
[31] qid ::= <ID> ( "." <ID> )*  

Имя свойства qid - квалифицированный идентификатор. Значение - булевская величина <BOOL_LITERAL>, целое число <INT_LITERAL> или строка <STRING_LITERAL>.

Пример:

[
  treedl.language = "java";
  has.key = true;
  max.length = 8;
]

Заданы три свойства, имеющие, соответственно, строковое, булевское и целое значения.

Документационные комментарии

Для каждой сущности в описании структуры дерева возможно задать документационный комментарий. Документационные комментарии доступны инструменту обработки описания дерева. Формат документационного комментария зависит от целевого языка трансляции и используемого инструмента обработки. Например, при трансляции в язык программирования Java документационные комментарии в формате javadoc могут быть просто перенесены в исходный текст сгенерированных классов для последующей обработки инструментом javadoc.

Документационные комментарии <DOCOMMENT> могут быть расположены только в определенных синтаксической грамматикой языка TreeDL местах. Лексическая структура документационных комментариев описана в Приложение 1, Лексическая грамматика.

Пример:

/**
 * Documentation comment
 */

Типы

Язык TreeDL позволяет определять типы вершин и перечислимые типы. Тип имеет имя <type:ID>, которое необходимо указывать при использовании. Если тип используется не в том модуле, в котором определен, имя следует уточнять синонимом имени модуля <module:ID>, в котором определен тип.

Расширенным именем типа будем называть имя типа <type:ID> при необходимости уточнённое синонимом имени модуля <module:ID>.

Используемый тип может определяться следующим образом:

[26] type ::= ( predefined_type | type_ref | <TYPE> ) ( "?" | "*" | "+" )?  
[27] predefined_type ::= "object" | "bool" | "string" | "char" | "short" | "int" | "long" | "float" | "double"  
[28] type_ref ::= ( <module:ID> "." )? <type:ID>  

Тип может быть определен как:

  1. один из предопределенных типов predefined_type;

  2. тип вершины или перечислимый тип с помощью расширенного имени type_ref;

  3. пользовательский тип <TYPE>

с модификатором, определяющим количество элементов типа:

  • без модификатора - ровно один элемент;

  • "?" - 0 или 1 элемент (необязательный атрибут);

  • "*" - 0 или более элементов (значение атрибута - список объектов указанного типа);

  • "+" - 1 или более элементов (значение атрибута - список объектов указанного типа).

Пример:

  • int - предопределенный целочисленный тип;

  • T - тип вершин или перечислимый тип, определенный на языке TreeDL;

  • T? - тип, значениями которого является значения типа T или null;

  • <java.util.Date>* - список из 0 или более элементов типа java.util.Date, определенного в целевом языке.

Глава 3. Модули

Единицей описания на языке TreeDL является модуль

[7] module ::= ( <DOCOMMENT> )? ( properties )?
( "tree" | "module" ) qid
( ":" ( base_module ( "," base_module )*
( "," <TYPE> )* | <TYPE> ( "," <TYPE> )* )
)? ";"
( header )? ( body )?
( const_type_decl | node_type_decl | operation_decl )*
 

который состоит из

Виды модулей

Модули бывают двух типов - модули описания структуры и модули операций.

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

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

Полное имя модуля

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

Пример:

tree com.unitesk.atp.treedl.TDL;

...

Модуль описания структуры дерева с именем com.unitesk.atp.treedl.TDL.

module com.unitesk.atp.treedl.Checker;

...

Модуль операций com.unitesk.atp.treedl.Checker.

Использование модулей

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

[8] base_module ::= ( <synonym:ID> "=" )? qid  

При использовании типа, определённого в используемом модуле, имя типа необходимо уточнить синонимом имени модуля. По умолчанию синонимом имени модуля является самый правый идентификатор его полного имени. При наличии конфликтов или по другим причинам синоним имени модуля может быть введён явно с помощью <synonym:ID>. Полное имя определяемого модуля также вводит синоним. Областью видимости синонимов является модуль, в заголовке которого они определены.

Пример:

module com.unitesk.atp.treedl.Checker : TreeDL = com.unitesk.atp.treedl.TDL;

...

Модуль операций com.unitesk.atp.treedl.Checker использует модуль описания структуры com.unitesk.atp.treedl.TDL определяя для него синоним TreeDL.

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

[9] header ::= ( <DOCOMMENT> )? ( properties )?
"header" <CODE>
 
[10] body ::= ( <DOCOMMENT> )? ( properties )?
"body" <CODE>
 

Результат трансляции модуля в язык программирования может быть модифицирован с помощью пользовательского кода. Существует возможность указать базовые типы <TYPE>, вставить код <CODE> из конструкции header в начало и код из конструкции body внутрь результата трансляции. Подробно обработка этих конструкций описана в схеме трансляции для конкретного языка программирования.

Пример:

[ treedl.language = "java"; ]
module com.unitesk.atp.treedl.TDL;

header
{
    import java.util.Map;
}

body
{
    public static Map getTypes() { ... }
}

Код конструкций header и body вставлен в результат трансляции модуля в язык программирования:

package com.unitesk.atp.treedl;

// header code
import java.util.Map;

public class TDL
{
    // body code
    public static Map getTypes() { ... }

    ...
}

Глава 4. Типы вершин

Определение типа вершин дерева

[12] node_type_decl ::= ( <DOCOMMENT> )? ( properties )?
( "abstract" | "root" )# "node" <name:ID>
( ":" ( type_ref ( "," <TYPE> )* | <TYPE> ( "," <TYPE> )* ) )?
"{" ( member )* "}"
 
[13] member ::= body | constructor | field  

состоит из:

Имя типа вершин

Имя типа вершины - это идентификатор <name:ID>, по которому можно ссылаться на тип вершины из любого определения типа вершины или операции в том же модуле.

При использовании типа из других модулей необходимо использовать расширенное имя типа.

Модификаторы

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

Если тип вершины помечен модификатором root, то он и его наследники допустимы в качестве типа корневой вершины дерева.

Пример:

abstract node Statement { ... }

Абстрактный базовый тип вершин Statement.

root node CompilationUnit { ... }

Корневая вершина дерева.

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

Тип вершины может быть определён на основе другого - базового типа вершины. Если указано расширенное имя type_ref базового типа вершины, то все члены этого типа наследуются и могут быть использованы в определяемом типе вершины. По умолчанию тип вершины наследует абстрактный базовый тип всех вершин Node:

abstract node Node
{
    attribute late Node? parent;
}

Этот тип вершин определяет атрибут parent, который содержит ссылку на родительскую вершину или null, если вершина корневая.

Пример:

node IfStatement : Statement { ... }

Тип вершин IfStatement, унаследованный от Statement.

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

Результат трансляции типа вершины в язык программирования может быть модифицирован с помощью пользовательского кода. Существует возможность указать базовые типы <TYPE> типа вершины. Подробно обработка этой конструкции описана в схеме трансляции для конкретного языка программирования.

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

Члены типа вершин

Существуют следующие виды членов типа вершин:

  • Дети, определяющие типы дочерних вершин для вершины данного типа;

  • Атрибуты, определяющие дополнительную информацию, связанную с вершинами данного типа. Дети являются частным случаем атрибутов;

  • Пользовательский код, который будет помещен в результат трансляции типа вершины.

[13] member ::= body | constructor | field  

Атрибуты

Атрибут связывает именованное значение произвольного типа с вершиной дерева. Атрибут предоставляет две операции доступа к значению:

  • get - получить текущее значение;

  • set - установить новое значение.

Частным случаем атрибутов являются дети, которые задают собственно структуру дерева. Значением ребенка является дочерняя вершина или список дочерних вершин. Любая вершина дерева, кроме корневой, является дочерней ровно для одной вершины, которая называется родительской вершиной. Все вершины дерева имеют атрибут parent, который содержит ссылку на родительскую вершину или null, если вершина корневая.

Определение атрибута типа вершин

[15] field ::= ( <DOCOMMENT> )? ( properties )?
modifiers ("attribute" | "child" ) modifiers
type <name:ID> ( "=" <init:CODE> )?
( get | set )#
 
[16] modifiers ::= ( "abstract" | "custom" | "late" | "override" | "noset" | "setonce" )#  

состоит из:

Имя атрибута

Имя атрибута <name:ID> - это идентификатор, по которому осуществляется доступ к значению атрибута.

Способ хранения атрибута и пользовательский код

Существует два способа хранения значения атрибута: стандартный и пользовательский.

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

Если задан модификатор custom, то используется пользовательский способ хранения значения атрибута. Операции доступа к значению атрибута также должны быть заданы пользователем с помощью конструкций get и set, код из которых будет вставлен в соответствующие операции.

[17] get ::= "get" <get:CODE>  
[18] set ::= "set" <set:CODE>  

Пользовательский код конструкций get и set может использоваться и с атрибутами, использующими стандартный способ хранения значения. Код конструкции get может использовать и изменять значение переменной <name:ID>, которое будет возвращено в качестве значения атрибута.

  • При стандартном способе хранения эта переменная автоматически будет инициализирована текущим значением атрибута.

  • При пользовательском способе хранения переменная должна быть инициализирована пользовательским кодом.

Не следует вставлять код, явно возвращающий значение - он будет сгенерирован автоматически.

Код конструкции set может использовать и изменять значение неявно заданного параметра <name:ID>.

  • При стандартном способе хранения будет автоматически сгенерирован финальный код, устанавливающий значение атрибута в значение <name:ID>.

  • При пользовательском способе хранения пользовательский код должен сохранить значение атрибута самостоятельно.

Пример:

attribute string fullName;

Для хранения значения атрибута fullName используется поле типа string.

attribute custom string name
get
{
    name = fullName.substring( fullName.lastIndexOf( '.' ) + 1 );
}
set { ... };

Значение атрибута name не хранится, а вычисляется на основе атрибута fullName, способ получения и установки значения определяется конструкциями get и set.

Установка значений атрибутов

  • Если для атрибута не указан модификатор late, значение атрибута должно быть указано при создании вершины дерева.

  • Если модификатор late указан и указан инициализатор атрибута, то при создании вершины для атрибута вызывается set-операция, которой передается указанное в инициализаторе значение <init:CODE>.

  • Если модификатор late указан, а инициализатор атрибута не указан, значение атрибута до первого вызова set-операции определяется схемой трансляции описания структуры дерева в язык программирования. При этом могут быть нарушены ограничения на значение атрибута, которые указаны в его типе (например, null для обязательных атрибутов) или проверяются в пользовательском коде set-операции.

Режим доступа к атрибутам

  • По умолчанию set-операция атрибута может вызываться произвольное число раз

  • Если указан модификатор setonce, то set-операция может быть выполнена не более одного раза. Для атрибутов без модификатора late или с модификатором late и инициализатором это соответствует вызову set-операции при создании вершины.

  • Если указан модификатор noset, то set-операция для атрибута не определена и его значение должно вычисляться get-операцией.

Абстрактные атрибуты

Атрибут, помеченный модификатором abstract, определяет только наличие значения с заданным именем и типом, но не способ хранения. Способ хранения значения атрибута уточняется наследниками типа вершины.

Абстрактный атрибут может быть определен только в абстрактном типе вершин.

Пример:

abstract node NamedNode
{
    abstract attribute string name;
}

node DefaultNamedNode : NamedNode
{
    attribute string name;
}

node CustomNamedNode : NamedNode
{
    attribute custom string name
    get { ... }
    set { ... };
}

Абстрактный тип вершин NamedNode определяет абстрактный атрибут name. Унаследованный тип вершин DefaultNamedNode определяет стандартный способ хранения унаследованного атрибута name. Унаследованный тип вершин CustomNamedNode определяет пользовательский способ хранения унаследованного атрибута name и операции над ним.

Переопределение атрибутов

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

Если переопределяемый атрибут не абстрактный, то переопределяющий атрибут должен иметь модификатор override. Поскольку способ хранения атрибута в этом случае уже задан, модификатор custom должен быть указан или не указан одновременно для переопределяемого и переопределяющего атрибутов.

Унаследованный тип вершины может при создании потребовать установки значений атрибутов, которые в базовом типе вершины устанавливались после создания (были помечены модификатором lat`e). Если атрибут в базовом типе вершины устанавливался при создании, то и в унаследованном типе вершины он также должен устанавливаться при создании вершины.

Допустимое количество вызовов set-операции не может быть изменено. То есть, модификатор setonce должен быть указан или не указан одновременно для переопределяемого и переопределяющего атрибутов.

Переопределяющий атрибут не может запретить set-операцию, определенную переопределяемым атрибутом, поэтому модификатор noset для переопределяемого атрибута возникнуть не может.

Пример:

node BaseNode
{
    attribute late int+ intList;
}

node MyNode : BaseNode
{
    attribute override int+ intList
    set { ... };
}

В типе вершин MyNode переопределяется атрибут intList: теперь его значение должно быть предоставлено при создании вершины (изчез модификатор late), для операции set определён дополнительный пользовательский код.

Дети

Дети являются частным случаем атрибутов. Атрибут задает ребенка, если вид атрибута - child. Дети имеют ряд дополнительных ограничений:

  • Ребенок не может иметь модификаторов abstract, custom или noset.

  • Значением ребенка является не произвольный объект, а дочерняя вершина или список дочерних вершин.

  • Значением атрибута parent каждой из дочерних вершин является родительская вершина. Вершина дерева не может быть дочерней более чем для одной вершины.

Пример:

node IfStatement : Statement
{
    child Expression condition;
    child Statement thenStatement;
    child Statement elseStatement;
}

Тип вершин IsStatement определяет детей condition, thenStatement и elseStatement, значениями которых являются вершины соответствующих поддеревьев.

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

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

[10] body ::= ( <DOCOMMENT> )? ( properties )?
"body" <CODE>
 

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

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

[14] constructor ::= ( <DOCOMMENT> )? ( properties )?
"constructor" <CODE>
 

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

Пример:

node TryStatement
{
    child Block block;
    child CatchClause* optCatchClauseList;
    child Block? optFinallyBlock;

    constructor
    {
        if( sizeOptCatchClauseList() == 0 && getOptFinallyBlock() == null )
        {
            throw new IllegalArgumentException("There is neither catch clause nor finally block");
        }
    }
}

При создании вершины типа TryStatement создается исключение, если вершина не соответствует синтаксически корректному оператору try языка Java - catch и finally блоки не могут отсутствовать одновременно.

Глава 5. Перечислимые типы

Определение перечислимого типа

[11] const_type_decl ::= ( <DOCOMMENT> )? ( properties )?
( "enum" | "flags" ) <name:ID>
( ":" type_ref )?
"{" ( <const:ID> ( "," <const:ID> )* )? "}"
 

состоит из:

Имя перечислимого типа

Имя перечислимого типа - это идентификатор <name:ID>, по которому можно ссылаться на тип из любого определения типа в том же модуле.

При использовании типа из других модулей необходимо использовать расширенное имя типа.

Множество значений перечислимого типа

Множество значений перечислимого типа определяется явно заданными константами. Множество этих констант является объединением множества констант для базового перечислимого типа type_ref, если он задан, и множества констант <const:ID>.

Существуют два вида перечислимых типов: перечисление и набор флагов. Вид перечислимого типа указывается ключевым словом enum и flags соответственно. Множество значений перечисления совпадает с набором констант. Множество значений набора флагов - это множество всех подмножеств набора констант.

Пример:

enum Color { RED, GREEN, BLUE }

Множество значений перечисления Color состоит из трех значений: RED, GREEN, BLUE.

enum ExtendedColor : Color { WHITE, BLACK }

Множество значений перечисления ExtendedColor состоит из пяти значений: трех значений базового перечислимого типа Color и двух дополнительно заданных WHITE и BLACK.

flags Modifiers { ABSTRACT, CUSTOM, LATE, OVERRIDE, NOSET, SETONCE }

Множество значений набора флагов Modifiers состоит из 64 комбинаций 6 флагов.

Глава 6. Операции над деревом

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

Пример:

enum Sign { PLUS, MINUS, MULT, DIV }

operation string toString( virtual Sign sign )
{
  case( PLUS  ): { return "+"; }
  case( MINUS ): { return "-"; }
  case( MULT  ): { return "*"; }
  case( DIV   ): { return "/"; }
}

Операция toString задает правило преобразования перечислимого типа в строку.

Пример:

abstract node Expression 
{
  child Expression left;
  child Expression right;
}

node AdditionalExpression : Expression { /*... */ }

node MultiplicativeExpression : Expression { /* ... */ }

node RelationalExpression : Expression { /* .. */ }

node EqualityExpression : Expression { /* .. */ }

enum Type { INT, BOOL, STRING }

operation Type getType( virtual Expression expr )
{
  case( AdditionalExpression expr ):
    { 
      if(    getType( expr.getLeft() ) == Type.STRING 
          || getType( expr.getRight() ) == Type.STRING
        )
      {
        return Type.STRING;
      } else {
        return Type.INT;
      }
    }
  case( MultiplicativeExpression expr ): 
    { return Type.INT; }
  case( RelationalExpression expr ):
  case( EqualityExpression expr ):
    { return Type.BOOL; }
}

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

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

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

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

Определение операции

[19] operation_decl ::= ( <DOCOMMENT> )? ( properties )?
( "virtual" )? "operation" signature
( ":" op_ref ( "," op_ref )* )?
"{" ( case )+ "}"
 

состоит из:

Сигнатура операции

[21] signature ::= ( type | "void" ) <name:ID>
"(" ( parameter_decl ( "," parameter_decl )* )? ")"
 
[22] parameter_decl ::= ( "virtual" type_ref | type ) <name:ID>  

Сигнатура операции определяет:

  • тип возвращаемого значения

  • имя операции

  • список формальных параметров операции

Формальный параметр операции имеет имя и тип. Параметр перечислимого типа или типа вершина может быть помечен модификатором virtual. Виртуальные параметры операции определяют набор ветвей операции.

Ветвь операции

Ветвь операции case_signature

[23] case ::= ( case_signature )+ <CODE>  
[24] case_signature ::= ( <DOCOMMENT> )? ( properties )?
"case" "(" ( parameter ( "," parameter )* )? ")" ":"
 
[25] parameter ::= ( type_ref )? <name:ID>  

состоит из:

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

Для каждой комбинации вариантов виртуальных параметров должна быть определена ровно одна ветвь операции. При выполнении операции будет исполнен код <CODE>, относящийся к ветви операции, которой соответствует набор фактических параметров. Ветвь операции может быть определена либо в теле операции, либо в одной из унаследованных операций.

Наследование операций

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

Пример:

tree P;

node A {}
tree Q : P;

abstract node X {}

node B {}

operation string F( virtual Node n )
{
    case( P.A n ): { return "A"; }
    case( B   n ): { return "B"; }
}

Операция F определена на всех типах вершин, определенных в модуле Q и используемым им модуле P. Для каждого неабстрактного типа вершин определена ветвь операции.

tree R : Q;

node C {}

operation string G( virtual Node n )
  : Q.F
{
    case ( C node ): { return "C"; }
}

Операция G определена на всех типах вершин, определенных в модуле R и используемых им модулях P и Q. Для типов вершин из модулей P и Q используется операция Q.F. Поэтому ветви операции определены только для типов вершин, определенных в модуле R.

Если существует ещё один модуль S, также использующий модуль P, и операция H, определенная на этих модулях:

tree S : P;

node D {}

operation string H( virtual Node n )
{
    case( P.A n ): { return "P.A"; }
    case( D   n ): { return "S.D"; }
}

То при попытке расширить определение операции R.G на модуль S с использованием операции H:

tree R : Q, S;

operation string G( virtual Node n )
 : Q.F, S.H
{
    case ( C node ): { return "C"; }
}

возникнет конфликт, поскольку операции Q.F и S.H используют разные ветви операции для типа P.A: результатом операции Q.F является "A", а результатом операции S.H - "P.A". Для его разрешения необходимо либо отказаться от использования одной из операции и определить её ветви заново, либо вынести реализацию операции для модуля P в отдельную операцию и использовать её обеими операциями:

tree P;

node A {}

operation string E( virtual Node n )
{
    case( A n ): { return "P.A"; }
}
tree Q : P;

abstract node X {}

node B {}

operation string F( virtual Node n ) : P.E
{
    case( B n ): { return "B"; }
}
tree S : P;

node D {}

operation string H( virtual Node n ) : P.E
{
    case( D n ): { return "S.D"; }
}
tree R : Q, S;

operation string G( virtual Node n )
 : Q.F, S.H
{
    case ( C node ): { return "C"; }
}

Такое определение операции R.G корректно, потому что обе используемые операции на модуле P используют реализацию операции P.E.

Приложение 1. Лексическая грамматика

Символы перевода строки

Строки разделяются ASCII символами:

  • LF - line feed;

  • CR - carriadge return;

  • CR LF.

Пробельные символы

Токены принудительно разделяются пробельными символами и комментариями. Пробельными символами являются следующие ASCII символы:

  • SP - space;

  • HT - horizontal tab;

  • FF - form feed;

  • символы перевода строки.

Комментарии

  • Однострочный комментарий начинается парой символов '//' и продолжается до конца строки.

  • Многострочный комментарий начинается парой символов '/*', за которыми идет не '*' и продолжается до пары символов '*/'

  • Документационный комментарий <DOCOMMENT> начинается последовательностью символов '/**' и продолжается до пары символов '*/'

Комментарии распознаются на этапе лексического анализа описания дерева и исключаются из последовательности токенов, которая подвергается синтаксическому анализу. Исключение составляют документационные комментарии <DOCOMMENT>.

Идентификаторы

[1] <ID> ::= ('@')? LETTER ( LETTER | DIGIT )*  
[2] LETTER ::= 'A'..'Z' | 'a'..'z' | '_'  
[3] DIGIT ::= '0' .. '9'  

Идентификатор <ID> начинается с буквы LETTER, за которой следует последовательность из 0 или более букв или цифр DIGIT. Символ '@', с которого начинается идентификатор, не является значащим, а служит в качестве префикса при использовании идентификаторов, совпадающих с ключевыми словами. То есть, @id и id - это один и тот же идентификатор.

Ключевые слова

Следующие идентификаторы зарезервированы для использования в качестве ключевых слов и не могут быть использованы в качестве идентификаторов без префикса:

abstract attribute body bool case char child constructor custom double enum false flags float get header int late long module node noset object operation override root set setonce short string tree true virtual void

Пользовательские типы

Типы языка программирования <TYPE> при использовании в описании дерева заключаются в угловые скобки '<' ... '>'. Внутри скобок символы '\', '<' и '>' экранируются с помощью '\'. Парные символы '<' ... '>' не требуют экранирования.

Литералы

В качестве значений свойств используются булевские, целые и строковые литералы:

[4] <BOOL_LITERAL> ::= "true" | "false"  
[5] <INT_LITERAL> ::= ( "-" )? ( DIGIT )+  
[3] DIGIT ::= '0' .. '9'  
[6] <STRING_LITERAL> ::= "\"" ( ~("\"") )* "\""  

Приложение 2. Синтаксическая грамматика

Используемая BNF нотация:

  • "token" - терминальный символ синтаксической грамматики;

  • <name:LEXEME> - терминальный символ типа LEXEME определенного в лексической грамматике. Необязательное имя name используется для пометки терминалов типа LEXEME внутри правила.

  • rule - нетерминальный символ, имя правила.

  • ( ... )? - необязательная часть правила.

  • ( ... )* - повторение 0 или более раз.

  • ( ... )+ - повторение 1 или более раз.

  • ( ... | ... | ... )# - повторение 0 или более раз, но каждая альтернатива не может быть использована более одного раза.

[7] module ::= ( <DOCOMMENT> )? ( properties )?
( "tree" | "module" ) qid
( ":" ( base_module ( "," base_module )*
( "," <TYPE> )* | <TYPE> ( "," <TYPE> )* )
)? ";"
( header )? ( body )?
( const_type_decl | node_type_decl | operation_decl )*
 
[8] base_module ::= ( <synonym:ID> "=" )? qid  
[9] header ::= ( <DOCOMMENT> )? ( properties )?
"header" <CODE>
 
[10] body ::= ( <DOCOMMENT> )? ( properties )?
"body" <CODE>
 
[11] const_type_decl ::= ( <DOCOMMENT> )? ( properties )?
( "enum" | "flags" ) <name:ID>
( ":" type_ref )?
"{" ( <const:ID> ( "," <const:ID> )* )? "}"
 
[12] node_type_decl ::= ( <DOCOMMENT> )? ( properties )?
( "abstract" | "root" )# "node" <name:ID>
( ":" ( type_ref ( "," <TYPE> )* | <TYPE> ( "," <TYPE> )* ) )?
"{" ( member )* "}"
 
[13] member ::= body | constructor | field  
[14] constructor ::= ( <DOCOMMENT> )? ( properties )?
"constructor" <CODE>
 
[15] field ::= ( <DOCOMMENT> )? ( properties )?
modifiers ("attribute" | "child" ) modifiers
type <name:ID> ( "=" <init:CODE> )?
( get | set )#
 
[16] modifiers ::= ( "abstract" | "custom" | "late" | "override" | "noset" | "setonce" )#  
[17] get ::= "get" <get:CODE>  
[18] set ::= "set" <set:CODE>  
[19] operation_decl ::= ( <DOCOMMENT> )? ( properties )?
( "virtual" )? "operation" signature
( ":" op_ref ( "," op_ref )* )?
"{" ( case )+ "}"
 
[20] op_ref ::= ( <module:ID> "." )? <op:ID>  
[21] signature ::= ( type | "void" ) <name:ID>
"(" ( parameter_decl ( "," parameter_decl )* )? ")"
 
[22] parameter_decl ::= ( "virtual" type_ref | type ) <name:ID>  
[23] case ::= ( case_signature )+ <CODE>  
[24] case_signature ::= ( <DOCOMMENT> )? ( properties )?
"case" "(" ( parameter ( "," parameter )* )? ")" ":"
 
[25] parameter ::= ( type_ref )? <name:ID>  
[26] type ::= ( predefined_type | type_ref | <TYPE> ) ( "?" | "*" | "+" )?  
[27] predefined_type ::= "object" | "bool" | "string" | "char" | "short" | "int" | "long" | "float" | "double"  
[28] type_ref ::= ( <module:ID> "." )? <type:ID>  
[29] properties ::= ( "[" ( property )* "]" )+  
[30] property ::= qid "="
( <BOOL_LITERAL> | <INT_LITERAL> | <STRING_LITERAL> ) ";"
 
[31] qid ::= <ID> ( "." <ID> )*  

Приложение 3. Семантические ограничения

Приложение 4. История изменений

2.3

Добавлена возможность использования нескольких подряд идущих секций свойств.

2.2

Добавлено наследование и перегрузка операций.

2.0

Добавлены операции, перепроектированы модификаторы атрибутов и перечислимые типы.