Пример для SemaTesK.

Maria Arkhipova, Sophia Zelenova
© 2007 Институт Системного Программирования РАН
UniTesK Lab

 

Начальные сведения. 1

Представление синтаксиса целевого языка. 1

Построение TreeDL. 2

Неформальное описание семантики калькулятора. 6

Написание SRL. 6

1.     Спецификация правила: “Имена объявляемых переменных уникальны”. 6

2.     Спецификация правила: “Переменная должна быть объявлена до использования”. 8

3.     Спецификация правил, описывающих ограничения на типы.. 11

Написание sscl 16

Текстовое представление TreeDL-узлов. 16

Запуск генератора. 19

Приложение A.    Пример файла srl.properties для STG-проекта. 21

Приложение B.    Пример файла stg.properties для STG-проекта. 22

Приложение C. 23

Приложение D.    Пример файла project.xml для STG-проекта. 27

Приложение E.    Пример файла project.properties для STG-проекта. 28

 

Начальные сведения

Инструмент SemaTesK предназначен для автоматической генерации семантических тестов на целевом языке. Для создания пакета тестов требуется

  1. задать синтаксис целевого языка в виде TreeDL спецификации
  2. задать семантику целевого языка в виде спецификаций, написанных на языках SRL
  3. задать отображение TreeDL-узлов в текстовое представление
  4. запустить SemaTesK

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

Представление синтаксиса целевого языка

Обычно для синтаксиса целевого языка имеется описание грамматики в виде какой-либо разновидности BNF.

Язык нашего калькулятора имеет следующий синтаксис:

 

program ::= ( stmt )+;
stmt ::= var_decl | assign | output;
var_decl ::= type <ID> "=" expr ";" ;
assign ::= <ID> "=" expr ";" ;
output ::= "print" expr ";" ;
type ::= "int" | "double";
expr ::= arg ( "+" arg )* ;
arg ::= var | const;
var ::= <ID>;
const ::= <INT> | <DOUBLE> ;

 

Здесь | разделяет альтернативы правила, ? означает, что последовательность внутри скобок может отсутствовать, + указывает на список из 1 или более элементов, а * - на список из 0 или более элементов. <ID>, <INT> и <DOUBLE> являются лексемами, т.е. обозначают элементы, принадлежащие некоторым множествам (строк-идентификаторов, целых чисел и действительных чисел двойной точности соответственно). Мы будем строить TreeDL-представление для синтаксиса, опираясь на это описание.

Построение TreeDL

Полное описание языка TreeDL можно найти на сайте проекта TreeDL в разделе Documentation.

TreeDL-описание включает в себя заголовок и описание TreeDL-узлов. Вот общий вид заголовка, для TreeDL-файла, подаваемого на вход SemaTesK:

 

[ translate.language = "java";
  visitor.name = "<имя интерфейса визитера>";
  startnode.name = "<имя узла стартового правила>";
  target.language = "<общий префикс имен>";
  target.extension = "<расширение файлов, содержащих тесты>"; 
  all_tests_file_name[1] = "<имя файла, в который будут записаны текстовые представления всех сгенерированных тестов>";
]
tree ru.ispras.redverst.stg.langdep.modl.<общий префикс имен>Tree
   : <com.unitesk.atp.tree.TreeClass>;
header{...}

 

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

 

ru.ispras.redverst.stg.langdep.modl.<общий префикс>Tree

 

и базовое описание

 

<com.unitesk.atp.tree.TreeClass>

 

Блок header{} должен содержать импорты всех классов, используемых в данном TreeDL-описании.

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

В нашем случае в заголовок имеет вид:

 

[ translate.language = "java";
  visitor.name = "CalcVisitor";
  startnode.name = "Program";
  target.language = "Calc";
  target.extension = "clc";
]
tree ru.ispras.redverst.stg.langdep.modl.CalcTree
: <com.unitesk.atp.tree.TreeClass>;
header{}

 

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

 

ru.ispras.redverst.stg.base.BaseNode

 

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

Теперь мы опишем правила составления TreeDL-описания по BNF-описанию.

program ::= ( stmt )+;

 

ему мы сопоставим такой узел TreeDL-описания:

 

node Program : <BaseNode>
{
    child Stmt+ stmts;
}

 

Здесь, как и в BNF знак + означает список из одного или более элементов.

stmt ::= var_decl | assign | output;

 

мы опишем четыре узла:

 

abstract node Stmt : <BaseNode> {}
node VarDecl : Stmt { ... }
node Assign : Stmt { ... }
node Output : Stmt { ... }

 

 

type ::= "int" | "double";

 

будет задаваться такими узлами:

 

abstract node Type : <BaseNode> {}
node IntType : Type {}
node DoubleType : Type {}

 

 

node Id : <BaseNode>
{
    attribute <String> name;
}

 

Правило, описывающее определение переменной,

 

var_decl ::= type <ID> "=" expr ";" ;

 

будет задано узлом

 

node VarDecl : Stmt
{
    child Type type;
    child Id id;
    child Expr expr;
}

 

ВАЖНО! Для текущей версии SemaTesK имя поля (ребенка или атрибута) в TreeDL-описании считается идентификатором этого поля. Поэтому во всем TreeDL-описании не должно быть полей с одинаковыми именами. В случае несоблюдении этого условия при генерации могут возникнуть ошибки из-за того, что инструмент принял одно поле за другое.

 

Задание[2]: напишите узлы для остальных правил грамматики.

 

Вот такое TreeDL-описание должно получиться в итоге:

 

[ translate.language = "java";
  visitor.name = "CalcVisitor";
  startnode.name = "Program";
  target.language = "Calc";
  target.extension = "clc";
]
tree ru.ispras.redverst.stg.langdep.modl.CalcTree
   : <com.unitesk.atp.tree.TreeClass>;
header{}
 
node Program : <BaseNode>
{
    child Stmt+ stmts;
}
 
abstract node Stmt : <BaseNode> {}
 
node VarDecl : Stmt
{
    child Type type;
    child Id id;
    child Expr expr;
}
 
node Assign : Stmt
{
    child Id assId;
    child Expr assExpr;
}
 
node Output : Stmt
{
    child Expr expr;
}
 
abstract node Type : <BaseNode> {}
 
node IntType : Type {}
 
node DoubleType : Type {}
 
node Expr : <BaseNode>
{
   child Arg+ args;
}
 
abstract node Arg : <BaseNode> {}
 
abstract node Const : Arg {}
 
node IntConst : Const
{
    attribute <int> intValue;
}
 
node DoubleConst : Const
{
    attribute <double> doubleValue;
}
 
node Var : Arg
{
    child Id varId;
}
 
node Id : <BaseNode>
{
    attribute <String> name;
}

 

ВАЖНО! Желательно, чтобы в TreeDL-файле наследники одного узла указывались бы в порядке от простого к сложному (в смысле семантики узлов), т.к. указанный в TreeDL-файле порядок используется инструментом при итерации. В случае несоблюдения этого правила при генерации могут возникать нежелательные ситуации. Именно поэтому в нашей записи сначала идут константы IntConst и DoubleConst, а затем переменная Var, т.к. переменная имеет более сложную семантику, чем константы (например, для нее требуется существование декларации).

 

Неформальное описание семантики калькулятора

Семантика нашего калькулятора включает следующие правила:

Написание SRL

Описание семантики состоит из списка описаний семантических связей.

1.                 Спецификация правила: “Имена объявляемых переменных уникальны”

Рассмотрим первое семантическое правило для калькулятора Имена объявляемых переменных уникальны. Для него мы в SRL-файле определим семантическую связь:

 

many-to-many relation UniqVarName
{
    comment "1_UniqVarName"
    ...
}

 

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

 

Далее после служебным словом relation задается идентификатор правила.

 

ВАЖНО! Идентификаторы всех правил должны быть уникальны в рамках одного SRL-документа. Идентификатор правила представляет собой последовательность латинских букв и цифр и может начинаться с любой латинской буквы в верхнем или нижнем регистре, с символа '_' или '@'.

 

После служебного слова comment можно задать текстовое описание данного правила. Это не обязательно, но настоятельно рекомендуется.

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

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

 

target VarDecl {…}
source VarDecl {…}

 

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

 

target VarDecl { id }
source VarDecl { id }

 

ВАЖНО! При написании семантических связей надо иметь в виду, что семантическая связь определяется именем узла-цели, списком в фигурных скобках для узла-цели, именем узла-источника и списком в фигурных скобках для узла-источника. Нельзя написать две связи с одинаковыми указанными элементами, надо всю семантику для таких связей описывать в одном месте. Иначе при работе инструмента могут возникнуть ошибки. Кроме того в фигурных скобках не должно быть ссылки на attribute, только на child.

Порядок, в котором будут идти декларации, для данной семантической связи нам не важен. Это мы укажем с помощью модификатора arbitrary, сразу после конструкции comment:

 

comment "1_UniqVarName"
arbitrary

 

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

 

context c1: same Program 

 

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

 

ВАЖНО! В пределах одного правила имена все контекстов должны быть различны.

 

Модификатор same указывает на то, что поддерево с корнем Program является общим для источника и цели.

 

ВАЖНО! Контекст описывает минимальное относительно источника и цели поддерево, имеющее корень указанного типа.

Итак, описанная нами семантическая связь имеет следующий вид:

 

many_to_many relation UniqVarName

{

     comment "UniqVarName"

     arbitrary

     unequal

     target VarDecl { id }

     source VarDecl { id }

     context c1: same Program

}

2.                 Спецификация правила: “Переменная должна быть объявлена до использования”

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

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

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

Спецификация подправила: “Переменная в левой части присваивания должна быть объявлена до использования”

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

Сначала определим тип семантической связи. Поскольку переменная из левой части инструкции присваивания должна иметь ровно одну декларацию, а одна декларация может относиться к разным таким переменным, то связь будет иметь тип one_to_many:

 

one_to_many relation ExistenceOfVarDeclaration_2_1
{
    comment "Existence of var declaration for left part of assignment"
    
}
 

 

Как и раньше, нам нужно задать тип узла-источника и тип узла-цели. В данном случае целью будет "целевой" узел Assign, описывающий инструкцию присваивания, а источником – узел VarDecl, описывающий декларацию переменной. В фигурных скобках после описания узла-цели следует указать имя дочернего узла цели соответствующего имени переменной в левой части присваивания, а именно: assId.

 

target Assign { assId }
source VarDecl {…}

После описания узла-источника в фигурных скобках следует указать имя дочернего узла узла VarDecl, а именно: id. Потому что именно этот дочерний узел соответствует имени переменной.

 

target Assign { assId }
source VarDecl { id }

 

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

 

semantic

 

Так как нет никаких ограничений на то, где должно выполняться правило и должны встречаться инструкции присваивания и соответствующая ей декларация, то в качестве окружения мы снова укажем стартовый узел Program:

 

context c1: same Program

 

Модификатор same указывает на то, что поддерево с корнем Program является общим для источника и цели. Как и в предыдущем случае, имя контекста - c1.

Вот полное описание получившейся семантической связи:

 

one_to_many relation ExistenceOfVarDeclaration_2_1
{

    comment "Existence of var declaration for left part of assignment"

    semantic

    equal

    target Assign { assId }

    source VarDecl { id }

    context c1: same Program

}

 

Задание: напишите семантическую связь для второго подправила: Для переменной, используемой в выражении (такую переменную описывает узел Var), должна существовать декларация переменной с таким же именем. (Указание: рассуждайте так же, как и в случае первого подправила).

Спецификация подправила: “Переменная не может быть проинициализирована сама собой при объявлении”

Следующее правило, для которого мы будем описывать семантические связи, звучит так: Идентификатор переменной v, используемой в правой части декларации d, должен быть отличным от идентификатора переменной, определяемой декларацией d.  

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

Сначала определим тип семантической связи. Поскольку имя переменной из левой части инструкции присваивания не должно совпадать с именем никакой переменной из правой части этого присваивания, то связь будет иметь тип many_to_many:

 

many_to_many relation SelfInitInVarDeclaration
{
    comment "In var declaration var must not be initialized by itself"
    
}
 

 

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

 

context c1: same VarDecl

 

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

 

target this.target_context {…}

 

Служебное слово this указывает на то, что далее следует ссылка на объект данного правила, а target_context позволяет сослаться на контекст цели. Когда контексты цели и источника совпадают, принято ссылаться на контекст цели[3].

В качестве источника укажем узел типа Var, соответствующий переменной справа от знака присваивания:

 

source Var {…}

 

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

 

target this.target_context { id }
source Var { varId }

 

Для данной связи неважен порядок вывода:

 

arbitrary

Вот полное описание получившейся семантической связи:

 

many_to_many relation SelfInitInVarDeclaration
{

    comment "In var declaration var must not be initialized by itself"

    arbitrary

    unequal

    target this.target_context {id }

    source Var { varId }

    context c1: same VarDecl

}

 

3.                 Спецификация правил, описывающих ограничения на типы

Рассмотрим следующие правила из описания семантики нашего калькулятора:

1.      Если один из аргументов выражения имеет тип double, то все выражение имеет тип double, если же все аргументы выражения имеют тип int, то выражение имеет тип int.

2.      Тип int является подтипом типа double. Т.е. любой элемент, имеющий тип int, автоматически является элементом типа double.

3.      В декларации тип выражения-правой части должен быть подтипом типа описываемой переменной.

4.      В инструкции присваивания тип правой части должен быть подтипом типа левой части.

Первое правило описывает способ вычисления некоторой вспомогательной величины называемой "тип". Второе правило описывает соотношения между возможными значениями этой величины (таких значений два – int и double). Третье и четвертое правила описывают ограничения на значения величины "тип" для некоторых элементов.

 

Замечание: вообще говоря, первое правило необходимо дополнить еще правилами вычисления типов для констант и переменных, например, такими: Если в декларации переменной стоит модификатор int, то она имеет тип int. Если в декларации переменной стоит модификатор double, то она имеет тип double. Если в записи константы точка отсутствует, то константа имеет тип int. Если в записи константы точка присутствует, то константа имеет тип double. Часто в неформальном описании семантики подобные подробности могут опускаться, как сами собой разумеющиеся. Мы же составляем формальное описание семантики и поэтому не можем пренебрегать такими "мелочами".

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

Вершины в TreeDL-описании, моделирующие типы целевого языка, должны наследовать базовый тип вершин ru.ispras.redverst.stg.semrel.TypeDesc из библиотеки stg_lib-draft-2.0.jar. Следовательно, изменим TreeDL-спецификацию нашего языка

 

abstract node Type : <TypeDesc>

{

}

 

node IntType : Type

{

}

 

node DoubleType : Type

{

}

 

Кроме того, для вершин, моделирующих конструкции целевого языка, для которых определено понятие типа, должна быть определена дочерняя вершина с именем type и типом, унаследованным от TypeDesc. В нашем примере такими вершинами являются Expr и Arg. Описание этих узлов в TreeDL-спецификации примет вид:

 

node Expr : <BaseNode>

{

   child Arg+ args;

   child Type type;

}

 

abstract node Arg : <BaseNode>

{

   child Type type;
   attribute <Integer> value;

}

 

 

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

 

node Const : Arg

{

    body

    {

          public Object newValue()

          {

            if (this.type instanceof IntType)

            {

                  return new Integer((value.intValue()>=Byte.MAX_VALUE || value.intValue()<=Byte.MIN_VALUE ? value.intValue() : Byte.MAX_VALUE - 10));

            }

            if (this.type instanceof DoubleType)

            {

                  return new Double("1.00000001" + value.intValue());

            }

            throw new IllegalArgumentException("type");         

          } 

      public String toString()

      {   

            return newValue().toString();

      }

    }

}

 

Замечание: Обратите внимание, что при внесении последних изменений в TreeDL-описание узел Const перестал быть абстрактным, а узлы IntConst и DoubleConst вообще больше не нужны.

 

Метод Const#toString() надо использовать при разработке конвертера тестов из внутреннего представления в текстовое.

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

 

type_set PrimitiveTypes { ref IntType, ref DoubleType }

 

Теперь мы можем перейти непосредственно к формализации семантических правил, перечисленных в начале раздела.

Спецификация правила вычисления типа выражения в зависимости от типов его параметров

Рассмотрим первое правило: Если один из аргументов выражения имеет тип double, то все выражение имеет тип double, если же все аргументы выражения имеют тип int, то выражение имеет тип int.

Данное правило означает, что если один из аргументов выражения имеет тип double, тогда и все выражение имеет тип double, а в том случае, когда такого аргумента нет, тип выражения может считаться как int, так и double, поскольку тип int приводим к типу double.

Таким образом, контекст данного правила может быть описан так:

 

context c1: same Expr

 

В качестве цели рассмотрим корень контекста, а качестве источника аргумент выражения:

 

target this.target_context {}    

source Arg  {}  

 

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

 

target this.target_context { type[is DoubleType] }    

source Arg  { type[is DoubleType] }  

 

Поскольку данное семантическое правило фактически является требованием на существования дочерней вершины с именем type типа DoubleType в поддереве, начинающемся в вершине-цели, при условии существования такой вершины в поддереве, начинающемся в вершине-источнике, то в данном правиле вместо ключевых слов equal или unequal, задающих способ вычисления атрибутов, используем ключевое слово present.

Тип семантического правила будет many-to-many, так как требуется рассмотреть все пары Expr-Arg.

В итоге мы получим семантическое правило следующего вида:

many_to_many relation ExprType_Double

{

     comment "Expression type is defined by the type of its arguments: if expr is of type Double it means that there is at least one double argument in this expression"

     arbitrary

     present

     target this.target_context { type[is DoubleType] }    

     source Arg  { type[is DoubleType] }       

     context c1: same Expr

}

 

 Поскольку семантическое требования о совместимости типов int и double мы уже описали посредством задания семейства типов, то перейдем сразу к следующему правилу.

Спецификация правила: “В декларации тип выражения правой части должен быть подтипом типа описываемой переменной”

Сначала определим контекст семантического правила. Укажем в качестве контекста декларацию переменной:

 

context c1: same VarDecl

 

Теперь в качестве цели укажем узел, соответствующий инициализирующему выражению в правой части:

 

target Expr { type }

 

так как мы собираемся потребовать, чтобы тип Expr был приводим к типу объявленной переменной. Операция приведения типов некоммутативная, а язык SRL позволяет требовать приводимости типов в поддереве цели к типам в поддереве источника. Для этого используем ключевое слово compatible.

 

compatible

target Expr { type }

source this.target_context  { type }

context c1: same VarDecl

 

Compatible означает, что данное правило накладывает следующие ограничения на типы аргументов источника и цели:

1.      типы аргументов источника и цели должны быть унаследованного от TypeDesc;

2.      типы аргументы источника и цели должны принадлежать одному семейству типов;

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

Данное семантическое правило может иметь тип one-to-many или one-to-one. В данном случае все равно, какой тип выбрать, так как в указанном контексте может быть только одна пара источник-цель. Для того чтобы явно подчеркнуть это свойство данного правило выберем тип one-to-one.

В итоге семантическое правило будет иметь вид:

one_to_one relation AssignTypeCompatibility

{

     comment "The type of assignment right part must be compatible to the type of the assignment left part"

     arbitrary

     compatible

     target Expr { type }

     source this.target_context  { type }

     context c1: same VarDecl

}

 

Спецификация правила: “В инструкции присваивания тип правой части должен быть подтипом типа левой части”

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

В качестве контекста рассмотрим инструкцию ¾ присваивание:

 

context c1: same Assign

 

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

 

this.target_context.ExistenceOfVarDeclaration_2_1[target=prev].source

 

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

Поскольку связи типа ExistenceOfVarDeclaration_2_1 должны быть установлены в дереве прежде, чем будут устанавливаться связи описываемого типа, требуется явно указать зависимость между соответствующими семантическими правилами:

 

… relation AssignTypeCompatibility_2 : ExistenceOfVarDeclaration_2_1

{

     comment "The type of assignment right part must be compatible to the type of the assignment left part"

     
source this.target_context.ExistenceOfVarDeclaration_2_1[target=prev].source {type}
     
}

Задание: допишите семантическое правило AssignTypeCompatibility_2. (Указание: рассуждайте так же, как и в предыдущем случае).

 

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

 

Задание: попробуйте самостоятельно написать семантическое правило AssignTypeCompatibility_3. (Указание: рассуждайте так же, как и в предыдущем случае).

 

Написание sscl

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

Поскольку в нашем примере синтаксические ограничения полностью описаны в грамматике, то файл calc.sscl будет пустым.

Текстовое представление TreeDL-узлов

Для того чтобы сгенерированные тесты выводились, нужно написать отображение из TreeDL-представления в текстовое.

За вывод текста отвечает компонент-принтер с именем

 

ru.ispras.redverst.stg.langdep.visitors.<общий префикс>VisitorPrinter

 

В нашем случае общий префикс равен Calc (мы задавали его в TreeDL-файле), и полное имя принтера такое:

 

ru.ispras.redverst.stg.langdep.visitors.CalcVisitorPrinter

 

Класс CalcVisitorPrinter должен реализовать интерфейс CalcVisitor и наследоваться от библиотечного класса ru.ispras.redverst.stg.base.visitors.VisitorPrinter. Таким образом, следует включить в описание класса следующий код

 

import ru.ispras.redverst.stg.base.visitors.VisitorPrinter;
import ru.ispras.redverst.stg.langdep.modl.CalcTree.*;
import ru.ispras.redverst.stg.semrel.SemanticRelation;
import ru.ispras.redverst.stg.base.BaseNode;
import ru.ispras.redverst.stg.exceptions.StgException;

 

О классе VisitorPrinter можно подробнее узнать на сайте сайте проекта TreeDL.

 

Декларация класса  CalcVisitorPrinter должна выглядеть так:

 

public class CalcVisitorPrinter extends  VisitorPrinter implements CalcVisitor
{
  
}

 

 

Метод java.util.List start( BaseNode node ) интерфейса CalcVisitor нам не нужен, поэтому мы просто вернем из него пустой список:

 

public java.util.List start( BaseNode node ) throws StgException 
{
    return new java.util.ArrayList();
}

 

Для всякого неабстрактного узла мы должны вывести его текстовое представление. Делается это в соответствующем visit-методе. Строковое представление передается во внутренний буфер с помощью специальных функций (txt(String), nl(), list(...) и др.).

Напишем вывод текстового представления идентификатора. Нам нужно просто напечатать его значение. Для этого мы вызовем функцию txt(...), передав ей форматированную строку[4]:

public void visitId( Id node ) throws StgException 
{ 
   txt( "${name}" ); 
}

 

Здесь выражение ${name} означает, что при формировании текста сюда будет вставлено текстовое значение атрибута name текущего узла node. Если в скобках после $ написано имя ребенка, а не атрибута, то будет вызван соответствующий visit-метод для этого ребенка.

Задание: напишите visit-методы для узлов Const и Var.

Теперь напишем вывод текстового представления декларации VarDecl. Для этого мы снова воспользуемся функцией txt. Напомним правило грамматики для декларации переменной:

 

var_decl ::= type <ID< "=" expr ";" ;

 

Выражение-параметр функции txt почти в точности воспроизводит это правило:

 

public void visitVarDecl( VarDecl node ) throws StgException 
{
    txt( "${type} ${id} = ${expr};" ); nl();
}

 

Здесь появилась еще одна функция – nl(). Она осуществляет перевод строки (сокращение nl расшифровывается как new line).

Задание: напишите visit-методы для узлов Assign и Output. Указание: воспользуйтесь соответствующими правилами грамматики.

 

Далее напишем вывод текстового представления выражения Expr. Нам нужно вывести список аргументов разделяя их плюсами. Для этого мы воспользуемся библиотечной функцией list:

 

public void visitExpr( Expr node ) throws StgException 
{
    list( "i", 0, node.sizeArgs(), "${args[i]}", " + " ); 
}

 

Первый аргумент функции list – имя итерационной переменной, второй аргумент – начальное значение итерационной переменной, третий аргумент – длина выводимого списка, четвертый аргумент – форматированная строка, содержащая обращение к очередному элементу списка ${args[i]}, наконец, пятый аргумент – разделитель элементов списка.

Нам осталось написать вывод текстового представления узлов: Type, IntType, DoubleType, Arg, Stmt  и Program.

Для узлов Type, Stmt и Arg методы будут пустые, так как это абстрактные узлы:

 

public void visitType( Type node ) throws StgException {}
public void visitArg( Arg node ) throws StgException {}
public void visitStmt( Stmt node ) throws StgException {}

 

Задание: Напишите методы для узлов IntType и DoubleType, которые должны отвечать за вывод в текстовый файл названий типов int и double соответственно.

 

Рассмотрим теперь узел Program. В нем нам нужно просто вывести список инструкций. Далее, для вывода полученного списка нам хотелось бы воспользоваться библиотечной функцией list(...):

 

list( "i", 0, node.getStmts().size(), "${stmts[i]}", "" );

 

Кроме того здесь можно вывести список идентификаторов всех семантических правил, которые применялись при построении очередного дерева. Для этого надо воспользоваться полем semTree унаследованным от VisitorPrinter. Данное поле хранит дерево внутреннего представления и много другой дополнительной информации в частности список примененных семантических правил. Мы написали код для того чтобы оформить список идентификаторов семантических правил в виде комментариев в заголовке теста. Идентификатор первичного семантического правила, для проверки которого строился тест, будет помечен словом MAIN.

Вот так выглядит вывод текстового представления для узла Program:

 

public void visitProgram( Program node ) throws StgException 
{
  //объявим переменную для формирования комментариев
  String commentStr = "";
        
  //определим количество примененных сем. правил        
  int fullListSize = semTree.getFullSemRelList().size();
        
  //рассмотрим поочередно идентификаторы всех семантических правил
  for (int i = 0; i < fullListSize;i++)
  {
    //рассмотрим i-ую семантическую связь
    SemanticRelation currRel = semTree.getFullSemRelList().getSemList(i);
    
    //сформируем комментарий, содержащий идентификатор семантического правила        
    String currComment = "\n /*"
         + (currRel.getSrd().equals(semTree.getMainRelation())? " MAIN ":"")
         + currRel.getSrd().getId()
         + "*/ \n";
    //отбросим повторяющиеся идентификаторы
    if(commentStr.indexOf(currComment)==-1)
    {
       commentStr = commentStr + currComment;                               
    }
  }        
  //выведем получившуюся строку комментариев           
  txt(commentStr);   
  list( "i", 0, node.getStmts().size(), "${stmts[i]}", "" );
}

 

Запуск генератора

В первую очередь  следует создать STG-проект. STG проект представляет собой набор следующих файлов:

§         TreeDL-описание грамматики целевого языка;

§         SRL-описание правил статической семантики;

§         SSCL-описание семантических ограничений на синтаксис[5];

§         java-класс, реализующий текстовое представление TreeDL-узлов;

§         файл srl.properties, содержащий список целей генерации (пример приводится в Приложении A);

§         файл stg.properties, содержащий параметры генерации (пример приводится в Приложении B);

§         файлы treedl.plugins и treedl.properties, содержащие настройки для трансляции TreeDL-описания[6] (пример содержимого treedl.plugins и treedl.properties для STG-проекта приводится в Приложении С);

§         файл project.xml, который необходим для формирования проекта для Maven[7] (пример приводится в Приложении D);

§         файл project.properties, содержащий свойства Maven-проекта (пример приводится в Приложении E).

 

На платформах под управлением ОС семейства UNIX в командном интерпретаторе в директории, содержащей STG-проект, нужно выполнить команду

> stg calc.tdl calc.srl calc.sscl Calc TestsDir

 

Для генерации тестов на платформах под управлением ОС семейства Windows необходимо запустить командный интерпретатор, перейти в нем в директорию, содержащую STG-проект, и выполнить команду

> stg.bat calc.tdl calc.srl calc.sscl Calc TestsDir

 

В результате в директории, содержащей STG-проект, сгенерируется директория TestsDir, с тестами. По умолчанию тесты создаются в текущей директории.

Тесты имеют следующую структуру. Если в TreeDL-описании был указан параметр all_tests_file_name, тогда все тесты будут сгенерированы в файл с указанным именем, который будет располагаться по следующему адресу:

 

<указанная директория для тестов>\1\<all_tests_file_name>

 

Если в TreeDL-описании не был указан параметр all_tests_file_name, тогда все тесты будут хранится следующим образом:

 

<указанная директория для тестов> \ <PrimarySemRelID>(<TargetType>) <nid> \ <tid>\Test<ttid>\

 

Здесь используются следующие обозначения:

§         <PrimarySemRelID> - идентификатор семантического правила, заданный в *.srl и присутствующий в srl.properties в строке <PrimarySemRelID>=true;

§         <TargetType> - тип цели семантического правила  <PrimarySemRelID>, указанный в *.srl  или неабстрактный тип, потомок указанного типа цели;

§         <nid> - целочисленный идентификатор, начинающийся с единицы, используется для того, чтобы различать созданные во время разных сессий генерации тесты, направленные на тестирование одной и той же ситуации;

§         <tid> - целочисленный идентификатор, начинающийся с единицы, используется для того, чтобы в одной директории не хранить больше 1000 тестов;

§         Test<ttid> - целочисленный идентификатор, начинающийся с единицы, используется для формирования уникального имени для директории, содержащий один тест.

 

Таким образом, в результате генерации тестов для языка Calc появится тест по следующему адресу:

 

TestsDir\UniqVarName(VarDecl) 1\1\Test1

 

Это тест предназначен для тестирования семантического правила, имеющего спецификацию в calc.srl с идентификатором UniqVarName, и примененного для синтаксической конструкции, которой соответствует декларация VarDecl в calc.tdl.

Приложение A.       Пример файла srl.properties для STG-проекта

 

# UniqVarName = true

ExistenceOfVarDeclaration_2_1 = true

ExistenceOfVarDeclaration_2_2 = true

SelfInitInVarDeclaration = true

ExprType_Double = false

AssignTypeCompatibility = true

AssignTypeCompatibility_2 = true

AssignTypeCompatibility_3 = true

 

 

В srl.properties следует указывать идентификаторы тех семантических правил, для которых требуется сгенерировать тесты. Строка <идентификатор сем. правила> = true означает, что для указанного правила тесты нужны, <идентификатор сем. правила> = false, говорит о том, что для данного правила тесты не надо генерировать. Также тесты не будут генерироваться для правил, которые вообще не указаны в srl.properties.

 

Комментарии вводятся символом #.

Приложение B.           Пример файла stg.properties для STG-проекта

 

count_tests = false

iter_depth = 5

recur_depth = 3

tests_number = 15

list_size = 2

 

 

Здесь параметры означают следующее:

 

Имя параметра

Значение

Смысл

Значение по умолчанию

count_tests

true

 

Генератор только производит подсчет кол-ва тестов без перевода их в текст

false

false

Генератор работает в обычном режиме

iter_depth

>=0

Задает глубину, ниже которой узлы дерева итерироваться не будут, вместо этого итератор будет возвращать всегда один и тот же объект. Глубина 0 означает, что итерировать требуется только узел-корень дерева, т.е. будут меняться значения только атрибутов этого узла.

0

recur_depth

>0

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

1

tests_number

>0

Задает ограничение на количество тестов, которые будут сгенерированы для каждого правила, указанного в srl.properties.

без ограничений

list_size

>0

Задает ограничение на длину списков детей при итерировании. Не является запретом на существование в дереве списков большей длины.

1

 

Комментарии вводятся символом #.

 

Приложение C.            

Пример файла treedl.plugins для STG-проекта:

 

# actions

dump         com.unitesk.atp.tree.tool.DumpAction

check        com.unitesk.atp.treedl.CheckAction

xref         com.unitesk.atp.treedl.PrintHTMLAction

translate    com.unitesk.atp.treedl.TranslateAction

visitor      com.unitesk.atp.treedl.VisitorAction

creator      com.unitesk.atp.treedl.CreatorAction

 

# stg files generators

stgnodegen   ru.ispras.redverst.stg.tdl_plugins.StgNodesGenerateAction

visgen       ru.ispras.redverst.stg.generator.StgVisitorGenerateAction

nitergen     ru.ispras.redverst.stg.generator.StgNodeIteratorGenerateAction

 

# language descriptions

java         com.unitesk.atp.treedl.JavaLanguageDescription

 

# file generators

java_translate              com.unitesk.atp.treedl.JavaNodeGenerator

java_visitor_empty     com.unitesk.atp.treedl.JavaEmptyVisitorGenerator

java_visitor_copy      com.unitesk.atp.treedl.JavaCopyVisitorGenerator

java_creator_interface com.unitesk.atp.treedl.JavaInterfaceCreatorGenerator

java_creator_null      com.unitesk.atp.treedl.JavaNullCreatorGenerator

java_creator_node      com.unitesk.atp.treedl.JavaNodeCreatorGenerator

 

 

Данный файл содержит список плагинов TreeDL[8]. В разделе «stg files generators» приводится список плагинов, которые поставляются вместе с дистрибутивом STG и необходимы для правильной работы генератора.

 

Название плагина

Назначение

stgnodegen  

Отвечает за трансляцию TreeDL-описания в java-классы, предназначенные для STG-генерации

visgen          

Отвечает за генерацию визитеров, которые перечислены в файле treedl.properties

nitergen       

Отвечает за генерацию итераторов TreeDL-узлов

 

Комментарии вводятся символом #.

Пример файла treedl.propeties для STG-проекта:

 

 

srl.output.dir                                                         src/java

sscl.output.dir                                                       src/java

treedl.visgen.output.dir                                        src/java

treedl.stgnodegen.output.dir                                src/java

treedl.nitergen.output.dir                                     src/java

 

treedl.stgnodegen.update                                     false

treedl.visgen.update                                             false

treedl.nitergen.update                                          false

 

treedl.visgen.childReceiverVisitor                      true

treedl.visgen.childAdderVisitor                          true

treedl.visgen.copierNodeWithAttributeVisitor   true

treedl.visgen.copierTreeKeepSemanticsVisitor  false

treedl.visgen.expTypeCounterVisitor                  true

treedl.visgen.nodeSeekerVisitor                          true

treedl.visgen.nodeCreatorVisitor                         true

treedl.visgen.oneBranchLocationVisitor             true

treedl.visgen.printerVisitor                                  false

treedl.visgen.semanticPrinterVisitor                    true

treedl.visgen.syntaxCompleteVisitor                   true

treedl.visgen.treeCreatorVisitor                           true

treedl.visgen.treeStructPrinterVisitor                  true

treedl.visgen.constValueGetterVisitor                false

treedl.visgen.expresTypeChangerVisitor            true

treedl.visgen.descenListReceiverVisitor             true

treedl.visgen.nodeIteratorReceiverVisitor           true

treedl.visgen.exchangeChildVisitor                     true

treedl.visgen.multiWayChildAdderVisitor          true

treedl.visgen.attributeValueSetVisitor                 true

treedl.visgen.deleteNodeVisitor                              true

 

 

 

Данный файл в основном содержит список параметров TreeDL-плагинов, перечисленных в файле treedl.plugins. Также в этом файле можно указать некоторые параметры для SRL-трансляции.

 

Здесь параметры означают следующее:

 

Имя параметра

Значение

Смысл

Значение по умолчанию

srl.output.dir

src/java

Путь, по которому должны лежать java-классы ¾ результат трансляции srl-описания. Должен совпадать с путем указанным в теге    <sourceDirectory> в соответствующем project.xml.

Параметр обязательный

 

sscl.output.dir

src/java

treedl.visgen.output.dir

src/java

treedl.stgnodegen.output.dir

src/java

treedl.nitergen.output.dir

src/java

treedl.stgnodegen.update

false

В случае значения true, действия за которые отвечает плагин stgnodegen (visgen, nitergen соответственно) будут производиться только в том случае, если был изменен файл *.tdl

 

treedl.visgen.update                                           

false

treedl.nitergen.update                                        

false

treedl.visgen.childReceiverVisitor

true

Будет сгенерирован визитер, отвечающий за получение списка детей

Параметр обязательный

 

treedl.visgen.childAdderVisitor

true

Будет сгенерирован визитер, отвечающий за добавление  узлов в поддерево 

treedl.visgen.copierNodeWithAttributeVisitor

true

Будет сгенерирован визитер, позволяющий копировать узлы

treedl.visgen.nodeSeekerVisitor

true

Будет сгенерирован визитер, позволяющий найти узел в дереве

treedl.visgen.printerVisitor

false

Будет сгенерирован визитер, заготовка для формирования визитера, реализующего перевод тестов в текстовое представление

treedl.visgen.syntaxCompleteVisitor

true

Будет сгенерирован визитер, отвечающий за достраивание дерева до синтаксически полного

treedl.visgen.treeCreatorVisitor

true

Будет сгенерирован визитер, позволяющий создать узел в дереве

treedl.visgen.expresTypeChangerVisitor

true

Будет сгенерирован визитер, отвечающий за выполнение действия над атрибутами, соответствующего compatible

treedl.visgen.descenListReceiverVisitor

true

Будет сгенерирован визитер, позволяющий сформировать список всех узлов в некотором поддереве

treedl.visgen.multiWayChildAdderVisitor

true

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

treedl.visgen.attributeValueSetVisitor

true

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

treedl.visgen.deleteNodeVisitor

true

Будет сгенерирован визитер, который отвечает за удаление узлов в дереве

 

Комментарии вводятся символом #.

 

Приложение D.           Пример файла project.xml для STG-проекта

<?xml version="1.0" encoding="ISO-8859-1"?>

<project>

  <pomVersion>3</pomVersion>

  <id>stg_langdep</id>

  <name>stg_langdep</name>

  <groupId>stg</groupId>

  <currentVersion>2.0.3</currentVersion>

  <dependencies>

    <dependency>

      <groupId>antlr</groupId>

      <artifactId>antlr</artifactId>

      <version>2.7.4</version>

    </dependency>

    <dependency>   

      <groupId>atplib</groupId>

      <artifactId>atplib</artifactId>

      <version>3.0</version>

    </dependency>

    <dependency>

      <groupId>treedl</groupId>

      <artifactId>treedl</artifactId>

      <version>1.0</version>

    </dependency>

    <dependency>

      <groupId>iterator</groupId>

      <artifactId>iterator</artifactId>

      <version>1.1</version>

    </dependency>                          

    <dependency>

      <groupId>stg</groupId>

      <artifactId>stg_lib</artifactId>

      <version>draft-2.0</version>

    </dependency>  

  </dependencies>

    <build>

    <sourceDirectory>src/java</sourceDirectory>

    <resources>

      <resource>

        <directory>src/conf</directory>

        <includes>

          <include>*.properties</include>

        </includes>

      </resource>

    </resources>

  </build>

</project>

 

 

ВАЖНО! Текст, выделенный полужирным шрифтом, изменять нельзя.

Приложение E.           Пример файла project.properties для STG-проекта

 

maven.compile.source=1.4

maven.compile.target=1.4

 

 

Это файл содержит установки необходимые для нормальной работы STG, если на платформе установлен язык java 5.0.

 

Приложение F.            Полный текст TreeDL и SRL описаний для Calc

Здесь приводится полный текст TreeDL-описания для рассматриваемого примера

 

 

[ translate.language = "java";

  visitor.name = "CalcVisitor";

  startnode.name = "Program";

  target.language = "Calc";

  target.extension = "clc";

  all_tests_file_name = "calc.tests";

]

tree ru.ispras.redverst.stg.langdep.modl.CalcTree : <com.unitesk.atp.tree.TreeClass>;

 

node Program : <BaseNode>

{

    child Stmt+ stmts;

}

 

abstract node Stmt : <BaseNode> {}

 

node VarDecl : Stmt

{

    child Type type;

    child Id id;

    child Expr expr;

}

 

node Assign : Stmt

{

    child Id assId;

    child Expr assExpr;

}

 

node Output : Stmt

{

    child Expr expr;

}

 

abstract node Type : <TypeDesc> {}

 

node IntType : Type {}

 

node DoubleType : Type {}

 

node Expr : <BaseNode>

{

   child Arg+ args;

   child Type type;

}

 

abstract node Arg : <BaseNode>

{

   child Type type;

   attribute <Integer> value;

}

 

node Const : Arg

{

    body

    {

           public Object newValue()

           {

                      if (this.type instanceof IntType)

                      {

                                  return new Integer((value.intValue()>=Byte.MAX_VALUE

                                                                   || value.intValue()<=Byte.MIN_VALUE

                                                                   ? value.intValue() : Byte.MAX_VALUE - 10));

                      }

                      if (this.type instanceof DoubleType)

                      {

                                  return new Double("1.00000001" + value.intValue());

                      }

                      throw new IllegalArgumentException("type");              

           } 

           public String toString()

           {         

                      return newValue().toString();

           }

    }

}

 

node Var : Arg

{

    child Id varId;

}

 

node Id : <BaseNode>

{

    attribute <String> name;

}

 

 

Далее приводится полный текст SRL-описания для языка Calc.

 

 

many_to_many relation UniqVarName

{

     comment "UniqVarName"

     arbitrary

     unequal

     target VarDecl { id }

     source VarDecl { id }

     context c1: same Program

}

one_to_many relation ExistenceOfVarDeclaration_2_1

{

    comment "Existence of var declaration for left part of assignment"

    semantic

    equal

    target Assign { assId }

    source VarDecl { id }

    context c1: same Program

}

one_to_many relation ExistenceOfVarDeclaration_2_2

{

    comment "Existence of var declaration for var use in expression"

    semantic

    equal

    target Var { varId }

    source VarDecl { id }

    context c1: same Program

}

many_to_many relation SelfInitInVarDeclaration

{

     comment "In var declaration var must not be initialized by itself"

     arbitrary

     unequal

     target this.target_context { id }

     source Var { varId }

     context c1: same VarDecl

}

many_to_many relation ExprType_Double

{

     comment "Expression type is defined by the type of its arguments: if expr is of type Double it means that there is at least one double argument in this expression"

     arbitrary

     present

     target this.target_context { type[is DoubleType] }    

     source Arg  { type[is DoubleType] }       

     context c1: same Expr

}

one_to_one relation AssignTypeCompatibility

{

     comment "The type of assignment right part must be compatible to the type of the assignment left part"

     arbitrary

     compatible

     target Expr { type }

     source this.target_context  { type }

     context c1: same VarDecl

}

one_to_one relation AssignTypeCompatibility_2 : ExistenceOfVarDeclaration_2_1

{

     comment "The type of assignment right part must be compatible to the type of the assignment left part"

     arbitrary

     compatible

     target Expr { type }

     source this.target_context.ExistenceOfVarDeclaration_2_1[target = prev].source  {type }

     context c1: same Assign

}

one_to_one relation AssignTypeCompatibility_3 : ExistenceOfVarDeclaration_2_2

{

     comment "The type of var is determined by variable declaration"

     arbitrary

     equal

     target Var { type }

     source this.target.ExistenceOfVarDeclaration_2_2[target = prev].source  {type }

     context c1: same Program

}

type_set PrimitiveTypes { ref IntType, ref DoubleType }

 

 

Далее приводится полный текст java-класса, реализующего перевод тестов для языка Calc в текстовое представление.

 

package ru.ispras.redverst.stg.langdep.visitors;

 

import ru.ispras.redverst.stg.base.BaseNode;

import ru.ispras.redverst.stg.base.visitors.VisitorPrinter;

import ru.ispras.redverst.stg.exceptions.StgException;

import ru.ispras.redverst.stg.langdep.modl.CalcTree.*;

import ru.ispras.redverst.stg.semrel.SemanticRelation;

 

 

public class CalcVisitorPrinter extends VisitorPrinter implements CalcVisitor

{

   public java.util.List start( BaseNode node ) throws StgException

   {

       return new java.util.Vector();

   }

   public void visitId( Id node ) throws StgException

   {

       txt( "${name}" );

   }

   public void visitVarDecl( VarDecl node ) throws StgException

  {

       txt( "${type} ${id} = ${expr};" ); nl();

   }

   public void visitConst( Const node ) throws StgException

  {

       txt(node.toString());

   }

   public void visitVar( Var node ) throws StgException

  {

       txt( "${varId}" );

   }

   public void visitAssign( Assign node ) throws StgException

   {

       txt( "${assId} = ${assExpr};" ); nl();

   }

    public void visitOutput( Output node ) throws StgException

   {

       txt( "print ${expr};" ); nl();

   }

   public void visitExpr( Expr node ) throws StgException

   {

       list( "i", 0, node.sizeArgs(), "${args[i]}", " + " );

   }

   public void visitType( Type node ) throws StgException {}

   public void visitArg( Arg node ) throws StgException {}

   public void visitStmt( Stmt node ) throws StgException {}

 

   public void visitDoubleType(DoubleType node) throws StgException

   {

       txt("double ");

   }

   public void visitIntType(IntType node) throws StgException

   {

       txt("int ");

   }

   public void visitProgram( Program node ) throws StgException

   {

       //объявим переменную для формирования комментариев

       String commentStr = "";

       

       //определим количество примененных сем. правил       

       int fullListSize = semTree.getFullSemRelList().size();

       

       //рассмотрим поочередно идентификаторы всех семантических правил

       for (int i = 0; i < fullListSize;i++)

       {

              //рассмотрим i-ую семантическую связь

              SemanticRelation currRel = semTree.getFullSemRelList().getSemList(i);

   

              //сформируем комментарий, содержащий идентификатор семантического правила       

              String currComment = "\n /*"

                       + (currRel.getSrd().equals(semTree.getMainRelation())? " MAIN ":"")

                       + currRel.getSrd().getId()

                       + "*/ \n";

              //отбросим повторяющиеся идентификаторы

              if(commentStr.indexOf(currComment)==-1)

              {

                     commentStr = commentStr + currComment;                               

              }

       }       

       //выведем получившуюся строку комментариев          

       txt(commentStr);  

       list( "i", 0, node.getStmts().size(), "${stmts[i]}", "" );

    }

}

 



[1] Если этот параметр не указан, тогда все тесты будут записаны в отдельные файлы.

[2] Ответ для этого задания и для всех дальнейших приводятся в Приложении F.

[3] Подробнее о ссылках на объекты правил можно прочитать в STG User Guide.

[4] Правила форматирования строк приводятся в TreeDL документации. Здесь лишь отметим следующее: для того чтобы вызвать метод визитера для дочернего узла нужно поместить имя этого узла в фигурные скобки и поставить перед ними символ $, например txt(“${name}”). Вызов метода, например toString(), который возвращает String, нужно просто вставлять в txt в качестве параметра, например txt(node.toString()).

[5] Если нет семантических ограничений на синтаксис, тогда в проекте должен присутствовать пустой файл с расширением *.sscl.

[6] Настройки TreeDL-трансляции описаны в документации к TreeDL.

[7] Правила оформления конфигурационного файла project.xml для Maven-проекта приводятся в документации к Maven.

[8] Подробное описание реализации, подключения и запуска TreeDL-плагинов приводится в TreeDL-документации.