Урок 1. Типы данных, работа с переменными
Как и в большинстве языков программирования, встроенный язык SilkTest-а использует некоторые объекты для хранения данных. Это могут быть константы, это могут быть переменные. Все данные в SilkTest-е типизированы, то есть все данные имеют некоторый тип, который позволяет определять, что хранится в данной переменной или константе и как с ней работать. Типы данных в SilkTest-е условно можно разделить на элементарные (простые) и составные (сложные). Данные элементарных типов воспринимаются, обрабатываются как единое целое и доступ к отдельным их частям невозможен как правило ввиду отсутствия этих самых отдельных частей. С составными типами можно работать как с единым целым, а также напрямую воздействовать на определенную часть данных данного типа.
Объявление переменных и контант имеет следующий вид:
 
        [const] <тип> <имя_переменной> [ = <значение> ]
        
Здесь:
  • тип - тип данных, в котором хранится и обрабатывается переменная или константа
  • имя_переменной - идентификатор переменной или константы, представляющий собой последовательность буквенных, числовых символов и знаков подчеркивания. Имя переменной или константы не может начинаться с числового символа.
  • значение - некоторая величина, которая изначально присваивается переменной или константе. Для переменных эта часть необязательна, а константы без начальной инициализации объявлены быть не могут
Несколько переменных одногои того же типа могут быть объявлены в одной строке. Для этого их достаточно перечислить через запятую, при этом дублировать имя типа для второй и последующих переменных не надо.
Простые типы данных
Числовые и логические типы данных
Числовые типы данных, исходя из их названия, позволяют хранить числовые данные. Числовые типы позволяют хранить как целочисленные значения, так и дробные.
Стандартным целочисленным типом данных в SilkTest-e является INTEGER. Данный тип позволяет хранить целочисленные значения. Также, для совместимости, имеют место такие типы данных, как INT, LONG , SHORT и их unsigned-модификации. Эти типы используются для объявления функций, хранящихся во внешних dll-файлах, поэтому в обычных условиях дляхранения целочисленных значений желательно ограничиться типом INTEGER.
Для хранения действительных чисел в SilkTest-e используется тип REAL. Для совместимости, также имеют место такие типы данных для хранения действительных чисел как FLOAT, DOUBLE.
Таже имеется встроенный тип NUMBER, который позволяет хранить как целые, так и действитльные числа в зависимости от того, какой тип больше подходит. Данный тип лучше использовать только в тех случаях, когда четко неизвестно какие числабудут использованы, целые илидействительные. В остальных случаях лучше все-таки использовать INTEGER и REAL для целых и действительных чисел соответственно.
Из логических типов имеется только один - BOOLEAN. Он принимает значения TRUE или FALSE.
Запишем в файле Lesson01.t примеры объявления переменных. Файл имеет вид:
Code
 
[ ] // Lesson 1: Datatypes usage: declaration, initialization, modification, printing
[+] main()
 [ ] INTEGER i, iCount = 5 // Several variables of the same type may be declared in the same line
 [ ] REAL rValue
 [ ] REAL rCoef = 2.5
 [ ] NUMBER nCount = 3
 [ ] NUMBER nValue = 4.8
 [ ] BOOLEAN bValue = TRUE
Как видно из примера, переменные могут быть объявлены как с инициализацией, так и без нее, несколько переменных одного типа могут быть объявлены в одной строке.
Строки
Строки являются, наверное, самым ходовым типом в SilkTest-е, поскольку весь ввод-вывод данных в тестируемых приложениях в общем случае имеет текстовое представление. Более того, в конечном счете именно строки выводятся в файл отчета при выполнении скрипта. Также, все имеющиеся или поддерживаемые в SilkTest-е типы данных конвертируются в строки с наименьшими трудностями. Поэтому работе со строками следует уделить наибольшее внимание.
Строкой в SilkTest-е считается любая последовательность символов, заключенных в одинарные или двойные кавычки (при этом, естественно, первая и последняя кавычка-ограничитель должны быть одного вида, то есть будет неправильным заключить строку междуодинарной и двойнойкавычкой). Типом данных, который предназначен для хранения строк является STRING. Учитывая все вышесказанное, опишем декларации переменных-строк с инициализацией. В том же файле дописываем:
Code
 
 [ ] STRING sValue = "Test"
 [ ] STRING sSingleQuoteText = 'Test'
Поскольку ограничителем строки является одинарная или двойная кавычка, то естественно, нужно как-то маскировать символ кавычки внутри строки. Например, нам нужно проинициализировать строку значением Test "Quote. Поскольку допускаются либоодинарные либо двойные кавычки в качестве ограничителей строки, то в данном случае мыможем воспользоваться одинарными кавычками для корректной инициализации строки, содержащей двойную кавычку. То есть вполне допустима запись вида:
Code
 
 [ ] STRING sQuoteExp1 = 'Test " Quote'
В данном случае двойная кавычка не воспринимается как ограничитель строки, а воспринимается как обычный символ.
Но могут возникнуть случаи, когда одна строка содержит и одинарные и двойные кавычки, например Test 'both" quotes. Проблему с одной из кавычек можно обойти, введя в качестве ограничителя строки кавычку другого типа. Введем в качестве такого ограничителя двойную кавычку (в дальнейшем будет использован именно этот ограничитель). Итак, при данных условиях одинарная кавычка уже воспринимается как обычный символ. А вот двойную кавычку внутри строки надо как-то замаскировать. В отличие от строк в языке С, в SilkTest-е обратный слэш не является маскирующим символом. Скорее всего это связано с тем, что строки в SilkTest-е имеют значительно меньше специальных символов, чем строки в С. Для маскировки двойной кавычки в данном случае используется символдвойной кавычки, то есть двойная кавычка маскирует двойную кавычку, идущую следом. Исходя из этого, строку с разными типами кавычек можно использовать в виде:
Code
 
 [ ] STRING sQuoteMixed = "Test 'both"" quotes"
Это наиболее популярный способ устранения проблем со специальными символами. Но могут быть альтернативы. Например:
Code
 
 [ ] STRING sQuoteMixedNoMask = "Test 'both{chr(asc('"'))} quotes"
В последнем примере символ двойной кавычки внутри строки получается как результат выражения chr(asc('"')), где asc('"') - стандартная функция, принимающая в качестве параметра строку с одним символом и возвращающая ASCII-код данного символа, а chr - функция, принимающая ASCII-код символа и возвращающая его строковое представление. Для полной ясности действий, выполняемых в последнем примере осталось только разобраться, что означают фигурные скобки внутри строки.
Как уже было сказано выше, строки использются достаточно интенсивно, в том числе и для вывода результатов в отчет. Причем зачастую нужно выводить значения конкретных переменных, более того, их нужно выводить вместе с некоторым текстом, который как-то описывает выводимое значение. Поэтому есть необходимость преобразования величин различных типов к строкам. Но поскольку в SilkTest-е имеет место типизация данных, то у подобных преобразований есть определенные трудности. Более того, некоторые значения нужно прямо вставить в строки. Например, нам нужно вывести в отчет строку вида Coeficient value is <rCoef value>, где <rCoef value> - значение переменной rCoef, которую мы описали и проинициализировали выше, в предыдущем подпункте. Вот для подобного формирования строки и предусмотрена возможность вставки. Для данного примера допишем в файл Lesson01.t код вида:
Code
 
 [ ] Print( "Coeficient value is {rCoef}" )
Print - это стандартная функция вывода текста в стандартный файл отчета. Теперь обратим внимание на то, что здесь в строке опять появились фигурные скобки. Итак, что же это такое. Фигурные скобки в строках предназначены для преобразования значения выражения внутри фигурных скобок к строковому представлению с последующей вставкой в главную строку. Применительно к нашему примеру, вначале будет извлечено значение переменной nCoef (в данном случае это 2.5 ) , затем это значение будет преобразовано в строковое представление ( опять же для данного случая это будет строка "2.500000"), а затем полученная строка будет вставлена вместо блока фигурных скобок и в результате мы получим вывод строки вида:
 
        Coeficient value is 2.500000
        
По аналогии, в предпоследнем примере, когда надо было как-то вставить двойную кавычку в строку, выражение chr(asc('"')) в результате возвращает строку, содержащую всего один символ - двойную кавычку, которая потом вставляется вместо блока фигурных скобок. То же самое выражение можно записать и в виде:
Code
 
 [ ] STRING sQuoteMixedNoMask2 = "Test 'both{'"'} quotes"
Следует также отметить, что значения выражений во вставках преобразуются в стандартное строковое представление для данного типа. Это было продемонстрировано для переменной nCoef, которая была инициализирована значением 2.5 , а врезультате преобразований это значение вывелось как 2.500000.
Очевидно, что фигурные скобки тоже являются специальными символами для строк. Соответственно, если нужно присвоить строковое значение, содержащее фигурные скобки, то эти скобки надо как-то маскировать. А маскируются они одинарной или двойной кавычкой в зависимости от того, какие кавычки являются ограничителями строк. Причем нужно маскировать только открывающуюся фигурную скобку. Закрывающаяся фигурная скобка без соответствующей открывающейся скобки воспринимается как обычный символ. Допишемв файл Lesson01.t строку кода:
Code
 
 [ ] STRING sBracketMask = "Test "{both} brackets"
Учитывая, что строковые вставки могут содержать любое выражение, результатом которого является величина любого типа, можно отметить, что данные вставки могут быть успешноиспользованы для преобразования произвольных величин к строкам. При этом не стоит забывать, что данные преобразования осуществляются в стандартном формате. В качестве примера, последовательно преобразуем к строке sValue значения переменных nCount, nValue, bValue, которые уже объявлены и проинициализированы в файле Lesson01.t. Дополним данный файл следующим фрагментом:
Code
 
 [ ] sValue = "{nCount}"
 [ ] sValue = "{nValue}"
 [ ] sValue = "{bValue}"
Соответственно, когда мы знаем о такой возможности преобразования величин различных типов к строкам, у нас не должно уже возникать вопросов относительно того, как преобразовать величину того или иного типа к строке. Такого вопроса вообще не должно существовать, так как к строкам путемвставок преобразуется ВСЁ. Вопрос только в том, в каком формате эти значения преобразуются. Но этот вопрос уже решается исходя из конкретных задач и общего подхода тут нет и быть не должно.
И еще одна характеристика строк. Тип STRING весьма условно отнесен к простым типам данных. Это было сделано лишь потому, что данный тип удобно рассматривать в контексте простых типов данных. На самом же деле тип STRING предоставляет доступ не только ко всему значению в целом, но и к отдельным элементам. По аналогии с языком С, строки в SilkTest-е можно представить в виде некоторого массива. Ключевым различием строк в С и строк в SilkTest-e является то, что отдельный элемент строки в С является величиной типа char ( собственно неважно как он называется, важно то, что тип элемента не соответствует типу строки), в то время как каждый элемент типа STRING тоже является величиной типа STRING. Таким образом мы можем получить подстроку длиной в один символ, используя индекс. Выглядит это примерно так:
Code
 
 [ ] STRING sValue1 = "Test String"
 [ ] sValue = sValue1[3] // sValue now contains "s"
В предыдущем примере мы получили 3-й символ строки "Test String". Но использование индексов позволяет не только извлекать подстроки, но и присваивать им некоторые значения. Например, в продолжение предыдущего примера, запишем такой код:
Code
 
 [ ] sValue1[5] = "" // Now sValue1 contains "TestString"
 [ ] sValue1[5] = " Insert S" // Now sValue1 contains "Test Insert String"
Вначале мы присвоили 5-му символу строки sValue1 пустую строку, что аналогично удалению символа. А затем вместо 5-го символа вставили больше, чем 1 символ. Результирующая строка автоматически расширилась.
Строки обладают наибольшими возможностями по сравнению с другими типами данных, но эти возможности надо не забывать использовать.
ANYTYPE
Часто могут возникнуть случаи, когда тип величины, с которой надо будет работать, в общем виде неизвестен. Это могут быть как строки, так и числа или вообще что-то непонятное. В тех случаях, когда некоторая величина может применять значения произвольных типов, используется тип ANYTYPE. К величине данного типа можно присвоить любую величину. Например:
Code
 
 [ ] ANYTYPE aValue
 [ ] sValue = "Test"
 [ ] iCount = 5
 [ ] rCoef = 2.5
 [ ] 
 [ ] aValue = sValue // aValue now contains "Test"
 [ ] aValue = iCount // aValue now contains 5
 [ ] aValue = rCoef // aValue now contains 2.5 
С извлечением значения немного сложнее. При извлечении значения из ANYTYPE величина, хранящаяся в переменной данного типа преобразуется к типу, к которому нужно присвоить. То есть происходит преобразование типов. Есть явное и неявное преобразование типов. Об этом будет рассказано позднее. Сейчас важно знать только то, что проблемы с присвоением величины ANYTYPE переменной какого-то определенного типа связаны с несовместимость или частичной совместимостью величин, хранящихся в переменной ANYTYPE и переменной, к которой присваивается данное значение. В продолжение предыдущего примера допишем следующее:
Code
 
 [ ] iCount = aValue // iCount now contains 2
 [ ] rCoef = sValue // rCoef now contains 2.5
Этот фрагмент кода выполнится без ошибок выполнения. Единственное, что следует отметить, это то, что iCount примет значение 2 вместо 2.5 так как это переменная типа INTEGER и действительные числа не хранит. В ходе преобразования отбрасывается дробная часть.
Но если в данном примере мы попытаемся присвоить значение в aValue строковой переменной, то мы получим ошибку выполнения, связанную с несовместимостью типов, так как в данный момент в aValue хранится величина, неприводимая явно к строке. Поэтому, оперируя с величинами данного типа, нужно выработать механизм в максимальной степени исключающий вероятность присвоения величин несовместимых между собой типов.
DATATYPE
Поскольку 4Test обеспечивает возможность работы со значениями произвольных типов, то иногда возникает необходимость определения непосредственно используемого типа данных. То есть иногда нужно где-то хранить значение используемого типа данных. Для этого существует специальный тип DATATYPE, который хранит некоторый тип данных. То есть величина "тип данных" тоже является некоторым типом. Использование типа DATATYPE удобно при извлечении значений из переменных типа ANYTYPE.
Это были основные простые типы данных, о которых нужно знать. Существует еще ряд встроенных типов, но они будут рассматриваться уже в контексте их применения и на данный момент их рассматривать не имеет смысла
Составные типы данных
Списки
Список представляет из себя линейную структуру, группирующую однородные элементы. Ключевым словом, определяющим, что переменная является списком, является LIST. Поскольку список группирует в себе однородные элементы, то это в первую очередь подразумевает, что эти элементы имеют одинаковый тип данных. Для того, чтобы конкретизировать, списком каких элементов является объявляемая переменная, используется конструкция вида:
LIST OF <имя типа> <имя переменной [ = значение]>
Таким образом, например, список строк объявляется в виде:
Code
 
 [ ] LIST OF STRING lsValue
Имеется несколько способов проинициализировать такие списки. Во-первых, это можно сделать явно, перечислив в фигурных скобках через запятую все элементы:
Code
 
  [ ] lsValue = { "Text1" , "Text2" , "etc" }
Вышеприведенным примером мы проинициализировали список, состоящий из 3-х элементов. Список сам расширится до нужных размеров. Можно будет по-новой проинициализировать его новыми значениями ( с другим количеством этих значений ). В любом случае задачи очистки старых значений и заполнения новыми данными переносятся полностью на SilkTest. Следствием этого является возможность очистить список обычной операцией присвоения пустого списка:
Code
 
  [ ] lsValue = {}
Но данный способ инициализации неудобен при большом количестве элементов или в случае, если эти элементы содержат много символов. Для этих целей есть другой способ инициализации списков. Проинициализируем список из примеров выше теми же значениями в виде:
Code
 
 [+] lsValue = {...}
         [ ] "Text1"
         [ ] "Text2"
         [ ] "etc"
Данная конструкция применима для любых списков и удобна тем, что однородные данные перечисляются не в строку, а в столбец друг над другом, что во-первых избавляет от необходимости прокручивать окно редактора по горизонтали ( что зачастую неудобно ), а во-вторых позволяет скрыть сами элементы внутри блока ( обратите внимание на знак "+" в первой строке последнего фрагмента кода). Одним из преимуществ также является то, что если мы так инициализируем список строк, то мы можем контролировать форматирование текста, равномерно расставлять отступы и т.п. Да и просто такая запись отражает саму структуру данного списка.
Из всех списков наиболее часто используется список строк, что в общем-то неудивительно, так как ввод-вывод так или иначе сводится к строковым величинам. Соответственно с иниациализацией списков данного вида чаще приходится сталкиваться. При инициализации списка строк вышеприведенным способом возникает необходимость маскировать специальные символы, ставить кавычки вначале и конце каждой строки. Если список строк содержит константные величины, то такие дополнительные корректировки только забирают время, поэтому для цпрощения инициализации списков строк предусмотрена еще одна конструкция. Список из предыдущего примера может быть проинициализирован так:
Code
 
 [+] lsValue = <text>
         [ ] Text1
         [ ] Text2
         [ ] etc
 [ ] 
Данная конструкция заполняет список значениями в таком виде, в каком они записаны. Единственным ограничением такого способа является отсутствие возможности вставки в строку. То есть такой код:
Code
 
 [+] lsValue = <text>
         [ ] Text1
         [ ] Text2
         [ ] etc
 [ ] 
инициализирует список строк в том виде, в котором этот список задан, при этом полностью игнорируются все специальные символы, что делает невозможным параметризацию какого-либо фрагмента строки. Каждая строка соответствует элементу списка. Данная конструкция применима только к списку строк.
У списков нет ограничений на возможные типы данных элементов, поэтому вполне возможно создать список списков. Вполне возможна конструкция вида:
Code
 
 [ ] LIST OF LIST OF STRING llsValue
Инициализируется такой список по тем же правилам, что и все списки с той поправкой, что каждый элемент данного списка тоже является списком. То есть данный список списков строк мы можем проинициализировать так:
Code
 
 [ ] llsValue = {{"LIST","OF","LIST"},{"OF","STRING","WITH"},{"LINEAR","INITIALIZATION","TYPE"}}
или так
Code
 
 [+] llsValue = {...}
         [+] {...}
                 [ ] "LIST"
                 [ ] "OF"
                 [ ] "LIST"
         [+] {...}
                 [ ] "OF"
                 [ ] "STRING"
                 [ ] "WITH"
         [+] {...}
                 [ ] "MODULE"
                 [ ] "INITIALIZATION"
                 [ ] "TYPE"
а если список списков строк состоит из элементов, имеющих постоянный вид, то вполне возможна и такая конструкция:
Code
 
 [+] llsValue = {...}
         [+] <text>
                 [ ] LIST
                 [ ] OF
                 [ ] LIST
         [+] <text>
                 [ ] OF
                 [ ] STRING
                 [ ] WITH
         [+] <text>
                 [ ] MODULE
                 [ ] INITIALIZATION
                 [ ] TYPE
Поскольку списки представляют собой составные объекты, то нужно как-то обращаться не только к списку в целом, но и к отдельным элементам данного списка. Для этого используется обращение по индексам:
Code
 
 [+] lsValue = {...}
         [ ] "Test"
         [ ] "List"
 [ ] lsValue[1] = "Modified" // Now list contains items "Modified" and "List"
Индекс элемента в списке не должен превышать размерность списка. Если некоторому элементу списка присвоить пустое значение, то это не будет равносильно удалению элемента. Это только сделает соответствующий элемент пустым.
Объединение
Часто может возникнуть необходимость оперировать с величинами некоторого фиксированного множества типов. При этом нужна величина одного из этих типов. Например, у нас есть выпадающий список и из него мы должны выбрать некоторый элемент. Нам удобно было бы либо указать явно имя необходимого элемента либо его номер в списке. То есть в этом случае нам нужно что-то, что может хранить в себе либо число либо строку. Безусловно, тип ANYTYPE всегда может помочь нам в этом. Но данный тип позволяет хранить величины вообще произвольных типов, а не только тех, которые нам нужны. То есть данная задача выполнима, но для ее выполнения есть более рациональные способы, чем использование ANYTYPE. Для таких задач целесообразнее использовать объединения.
Объединением является специфический тип данных, включающий в себя ограниченное количество типов данных, который позволяет хранить величину одного из допустимых для данного объединения типов данных. Объявление объединения имеет следующую форму:
type <Union_name> is <comma-separated list of accepted types>
А теперь то же самое, но на конкретном примере. Например, мы хотим создать такой тип, который бы мог позволить хранить либо строки либо числа, при этом любой другой тип либо недопускается либо преобразуется к допустимым типам. Данный тип объявляется так:
Code
 
[ ] type ITEM is INTEGER, STRING
Поскольку данная запись представляет собой объявление некоторого типа, а не переменной, то данный код должен находиться вне структурных блоков програмного кода. В данном случае, его нельзя помещать внутрь main, а нужно расположить где-то за пределами. Фактически мы объявили свой тип данных, который потом можно использовать как обычные типы, например:
Code
 
 [ ] ITEM sItem
 [ ] sItem = "Test" // sItem now contains string "Test"
 [ ] sItem = 0 // sItem now contains integer 0
То есть мы объявили переменную данного типа, а затем присвоили вначале строку, потом число. При этом предыдущее значение затирается. То есть в каждый момент времени переменная типа-объединения хранит ОДНО значение одного из допустимых типов.
Такая возможность, как создание типов-объединений, позволяет давать типам упрощенные имена, то есть переименовать некоторые существующие типы и придать им более удобное имя. Например:
Code
 
[ ] type TEXT is LIST OF STRING
Здесь мы определили тип TEXT, который может хранить значения LIST OF STRING. То есть мы фактически переименовали тип списка строк и теперь мы можем написать нечто вроде:
Code
 
 [+] TEXT lsText = <text>
         [ ] This is usual list of string
         [ ] except the type is now named as TEXT
Как видно из последнего примера, производимая инициализация созданного типа аналогична инициализации списка строк. Фактически это и есть список строк, который просто был назван по-другому.
Таким образом, объединения целесообразно использовать для описания типов, позволяющих хранить величину одного из допустимых типов или для придания существующему типу более компактного и понятного имени.
Enum
Часто могут возникать случаи, когда некоторая величина может принимать некоторый фиксированный набор значений. Наиболее ярким примером являются коды ошибки. То есть есть некоторый фиксированный набор величин и за его пределы значение некоторой переменной просто не выйдет. Для таких целей существует такая разновидность сложных типов как "перечисление" или enum. Объявляется такой тип в виде:
 
                       enum <Enum_Name> is enum
                               <Enum Elements>
Здесь Enum_Name - это имя создаваемого типа данных, Enum Elements - последовательность элементов вида:
<Item Name> [= <value>]
, где <Item Name> - имя элемента (фактически это имя константы, с той разницей, что она задана внутри типа-перечисления), а <value> - его значение ( принимаются числовые значения, но это необязательная составляющая ). Посмотрим как это выглядит на конкретном примере. Создадим тип, который содержит значения дней недели, которым соответствуют номера от 1 до 7 ( порядковый номер дня недели ).Код имеет вид:
Code
 
[+] type DaysOfWeek is enum
 [ ] DOW_MONDAY    = 1
 [ ] DOW_TUESDAY   = 2
 [ ] DOW_WEDNESDAY = 3
 [ ] DOW_THURSDAY = 4
 [ ] DOW_FRIDAY    = 5
 [ ] DOW_SATURDAY = 6
 [ ] DOW_SUNDAY    = 7
В данном примере мы объявили тип данных, который может содержать значения перечисленных внутри констант, в конкретно нашем случае это числа в диапозоне от 1 до 7. Элементы внутри enum - это числовые константы. То же самое объявление можно описать в виде:
Code
 
[+] type DaysOfWeek is enum
 [ ] DOW_MONDAY
 [ ] DOW_TUESDAY
 [ ] DOW_WEDNESDAY
 [ ] DOW_THURSDAY
 [ ] DOW_FRIDAY
 [ ] DOW_SATURDAY
 [ ] DOW_SUNDAY
В данном случае ничего не изменится. Если константе внутри enum не присвоено значение, то значение данной константы принимается на единицу больше, чем значение предыдущей константы. Если первая из перечисленных констант не содержит явного присвоения значения, то ей по умолчанию присваивается значение 1. То есть в предыдущем примере DOW_MONDAY равняется 1, DOW_TUESDAY равняется 2 и так далее. Если мы объявим этот enum в виде:
Code
 
[+] type DaysOfWeek is enum
 [ ] DOW_MONDAY = 2
 [ ] DOW_TUESDAY
 [ ] DOW_WEDNESDAY
 [ ] DOW_THURSDAY
 [ ] DOW_FRIDAY
 [ ] DOW_SATURDAY
 [ ] DOW_SUNDAY
То все константы данного enum примут значения в диапозоне от 2 до 8, что равносильно:
Code
 
[+] type DaysOfWeek is enum
 [ ] DOW_MONDAY    = 2
 [ ] DOW_TUESDAY   = 3
 [ ] DOW_WEDNESDAY = 4
 [ ] DOW_THURSDAY = 5
 [ ] DOW_FRIDAY    = 6
 [ ] DOW_SATURDAY = 7
 [ ] DOW_SUNDAY    = 8
Как уже было указано, элементы enum являются числовыми константами. Более того, они могут использоваться самостоятельно. То есть объявив данный enum, мы можем потом записать нечто в вида:
Code
 
 [ ] INTEGER iValue = DOW_TUESDAY
И, естественно, можно объявить переменную данного типа.
Code
 
 [ ] DaysOfWeek eDOW
Таким образом enum формирует тип, группирующий в себе числовые константы, и который позволяет содержать значения, соответствующие одной из перечисленных внутри enum констант.
Set
Иногда возникает необходимость в одной переменной содержать определенный набор атрибутов или флагов, причем в разных сочетаниях. В качестве примера можно привести стили окон стандартного GUI. Окна могут быть с заголовком, с границей, с возможностью изменения размеров. То есть эти атрибуты могут встречаться как все вместе, так и в определенных комбинациях. Например, окна могут быть без заголовка, но с границей и возможностью менять размеры; может быть с заголовком и границей, но при этом не менять размер. В стандартном Windows API такие стили задаются битовой маской, например WS_OVERLAPPED|WS_BORDER|WS_RESIZABLE. Фактически это выражение, в котором операцией логического "или" формируется число, у которого каждый бит отвечает за определенный стиль. Реально эти константы имеют значения, являющиеся степенью числа 2.
Для операций с подобными величинами в 4Test-е предусмотрена такая разновидность составных типов, как множество или set. В общем виде set объявляется так:
 
                               type <Set name> is set
                                      <Set item list>
Здесь Set name - это имя множества ( фактически имя типа ). Set item list - список элементов множества. Как и в случае enum - это числовые константы с той разницей, что в set их нельзя явно задать, они принимают нужные значения по умолчанию. В качестве примера опишем некоторый тип-множество. Код имеет вид:
Code
 
[+] type Colors is set
 [ ] C_RED
 [ ] C_GREEN
 [ ] C_BLUE
Вот подобным образом объявляется множество. В данном примере множество содержит в себе 3 константы C_RED, C_GREEN, C_BLUE, которые равны 1, 2 и 4 соответственно. То есть значения автоматически присваиваются по степени числа 2. Если мы допишем еще одну константу в конец объявления, то она примет значение 8.
Переменные данного типа объявляются как и все другие переменные. Инициализация схожа с инициализацией списка, то есть мы можем для нашего множества сделать такое объявление:
Code
 
 [ ] Colors pColor = {C_RED,C_GREEN}
Также возможна такая инициализация:
Code
 
 [+] pColor = {...}
         [ ] C_RED
         [ ] C_GREEN
При этом, в отличие от списка, set хранит только элементы, определенные внутри данного типа. Еще одним различием является то, что при инициализации/присвоении значения множества повторяющиеся элементы устраняются, то есть в записи вида:
Code
 
 [+] pColor = {...}
         [ ] C_RED
         [ ] C_GREEN
         [ ] C_RED
         [ ] C_RED
         [ ] C_RED
         [ ] C_RED
         [ ] C_GREEN
множество будет иметь то же содержимое, что и в 2-х предыдущих примерах. Повторяющиеся элементы будут просто устранены ( или проигнорированы, как кому удобнее ).
Величины типа-множества не преобразуются в числа напрямую. Для этого надо использовать преобразования типов, о чем будет рассказано ниже.
Record
В некоторых случаях необходимо работать с несколькими переменными разных типов одновременно. Причем они логически неразделимы, что приводит к тому, что раздельно они редко используются. В качестве примера можно привести такую структуру, как "точка". То есть, когда мы оперируем с некоторыми координатами, то как правило координаты обрабатываются совместно. В этом случае удобно с ними работать как с некой целой величиной. В языке С подобные величины описывались в виде структур. В 4Test-е аналогом структуры является такая разновидность составных типов как record.
Record предвтавляет собой тип, группирующий в себя определенный набор переменных, в общем случае, различных типов. Объявление record-а имеет вид:
 
                               type <Record_name> is record
                                      <Record_elements>
Здесь, Record_name - имя создаваемого типа-структуры, Record_elements - последовательность объявления полей вида
<тип> <имя поля>
Фактически объявления полей представляют собой те же объявления переменных за тем исключением, что полям структуры нельзя применить модификаторы вроде private, const и им нельзя присвоить значения. Как и другие пользовательские типы, record должен быть определен вне каких-либо синтаксических блоков. Для примера создадим некоторую тестовую структуру, содержащую число, строку и список строк. Выглядит это так:
Code
 
[+] type TestRecord is record
 [ ] INTEGER iValue
 [ ] STRING sValue
 [ ] LIST OF STRING lsValue
Теперь у нас есть новый тип данных TestRecord и мы можем объявить переменную вида:
Code
 
 [ ] TestRecord pTest
Инициализировать данные структуры можно по аналогии со списками с единственной поправкой на то, что record-ы имеют фиксированное строение и соответственно типы должны соответствовать. то есть вышеобъявленную переменную мы можем проинициализировать так:
Code
 
 [ ] pTest = {5, "Test" , {"Line1", "Line2", "Line3"}}
что удобно применять для небольших записей или вот так:
Code
 
 [+] pTest = {...}
         [ ] 5
         [ ] "Test"
         [+] {...}
                 [ ] "Line1"
                 [ ] "Line2"
                 [ ] "Line3"
Этот способ лучше применим для больших записей. Помимо инициализации структуры целиком, мы можем инициализировать отдельно каждое из полей. Для полей инициализация осуществляется по тем же принципам, что и для обычных переменных того же типа. То есть, вышеприведенная инициализация может быть осуществлена в виде: