DDL

The Data Definition Language

 

Language Specification

 


Table of Contents

 

Table of Contents. 2

DDL: The Data Definition Language. 3

Definitions and Instances. 4

The Basics: Struct 6

Structure Members. 7

Primitive Data Members. 7

Arrays. 7

Structure Data Members. 8

Expressions. 9

Summary so far 9

Configuration Section. 11

Config: word_length specification. 11

Structure Level Configuration. 12

Struct config: init 12

Struct config: word_length. 12

Struct config: size. 13

Advanced Features. 14

Array Specifications. 14

Constant 14

Expression. 14

EOF specification. 15

Terminator Specification. 15

Address Specification. 16

Uses of address specification. 17

A Few Points about Address specification. 18

Conditional Members: when-then-else construct 19

Import and Export 20

Specifying Comments. 21

Notes and Limitations. 21

Notes. 22

Limitations. 22

Appendix: Math Operators and Functions. 23

Precedence of Mathematical Operators. 23

List of Operators and Functions Supported. 23

Predefined Constants. 24

Appendix: BMP File DDL script 25


DDL: The Data Definition Language

The central theme of the language is to be able to define data formats. The way the language can express various data format should be flexible, able to contain a large subset of the data formats that are practically used and should be intuitive.

 

The DDL has been designed with these goals in mind and we have made progress, to varying degrees, in each of these directions.

 

The DDL is based on a simple concept – the programmer of the DDL script describes an data format in the DDL syntax. This format is the DDL script or program. A user of the DDL script would ask the DDL interpreter to load the script and provide the interpreter with a block of data (typically a file) from which he wishes to retrieve data. Once the DDL interpreter / engine has access to the data definition and the data source it is ready to answer questions posed by the user. The user can ask the engine for the value of ‘x’ and the DDL engine refers to the script follows the rules there to determine the location and types of the variable ‘x’ and extracts its value from the data source.

 

A DDL script doesn’t really ‘run’ in the conventional sense of an interpreted language. A DDL script is only a definition of the data and does not define any operations. However it is not a static definition but a dynamic definition in the sense that it can define data formats with conditional statements. Thus the complete information about what members exist in a given data block cannot be always obtained by looking at the DDL script but will also require information from the particular block of data that the script is being ‘run’ on.

 

The DDL project homepage is http://ddl.sscli.net .

You can mail the authors at spark@sscli.net and dolly@sscli.net

 


Definitions and Instances

One of the central concepts to understand for a DDL beginner is the difference between a DDL script as it is written and the properties that it takes up during runtime.

 

What you write In a DDL script forms the definition of the data that you wish to define. This definition is like a map or a format-specification of the data. So here you completely define the types, locations and quantities of all the pieces of information that you are interested in defining. The script is then loaded by the DDL interpreter or the DDL engine and additional attributes are assigned to it at runtime.

 

Data definition takes the form of structures. A structure is a single unit of data that the DDL can understand. Structures can be composed of simple primitive types as well as other structure definitions. One can compare this to the kind of structures that are defined in C or a similar language.

 

One important difference in the DDL is that unlike a C style structure where you define all the members that it can have as a fixed set during design time, a DDL structure can have or not have certain members during runtime. These members are defined in conditional blocks. The DDL gives you a when-then-else conditional block for this purpose.

 

Since structures can have members that are only determined at runtime, unlike a C like language the size, in bits or bytes, of a structure cannot be completely determined at design time.

 

A DDL script typically consists on multiple structures. One of these structures is given a special structure and is called the ‘init’ structure. An ‘init‘ structure is one that has an instance that is automatically created by the DDL interpreter when the script is ‘run’. You can compare this to be similar to be similar to the main() in a C like programming language.

 

The ‘init’ structure can contain declaration of primitive types and other structure instances. This is like declaring members in structures that are instances of other structures. Similarly any of the other structures that you define in the DDL can contain instances of other structures. So the DDL script is in a sense letting you define an arbitrary kind of hierarchical data structure that your data can be mapped to.

 

This is a sample DDL script that emulates a binary tree:

struct TreeStart : init

{

      i8          SignatureByte

      TreeNode    RootNode

}

 

struct TreeNode

{

      i8    LeftValue

      i8    RightValue

 

      //if LeftValue is ‘A’ then I assume there exists a left

      // subtree

      when (LeftValue == 65)

      {

            TreeNode    LeftChild

};

      //if RightValue is ‘A’ then I assume there exists a right

      // subtree

      when (RightValue == 65)

      {

            TreeNode    RightChild

};

 

}

 

If you look at the above script you will notice that there are two structures defined. While the structure called TreeStart has got two fixed members its size is not known until the script is run against the user provided data. This is because it contains an element of TreeNode type. The TreeNode structure has two elements defined called LeftValue and RightValue but also has members called RightChild and LeftChild that are defined within when blocks. These conditionally defined elements can exist in some instances of the TreeNode structure while it need not exist in other instances.

 

Thus there is difference in the data elements that structure can have and these are instance specific. The runtime decisions are always made on the basis of the data that is provided to the script interpreter. Like in the above script the values of LeftValue and RightValue need to be known to determine is the LeftChild and RightChild are members of a particular instance of the structure.

 

This means that you can ask the DDL engine for values of ‘LeftValue’ and ‘RightValue’ to every instance of TreeNode. But you can ask the DDL engine to retrieve values of LeftChild or RightChild only to instances that contain these members.

 

If you didn’t grasp everything, it is ok. Some of the gray areas will become clearer as you proceed. It is, after all, rather simple in concept.


The Basics: Struct

In the DDL the primary building block is a structure element denoted by the keyword ‘struct’. A data definition file can contain one or more structures defined within it. These structures can be thought of as C style structures for ease of understanding. The differences shall show themselves in the course of this document.

 

This is how you would specify a structure in DDL

 

struct SomeStructure

{

      //structure members here

}

 

This defines a structure called ‘SomeStructure’, the name can be of your choosing.

 

A structure is a building block. A structure can hold various data elements which you define according to the format of the data that you want the DDL to interpret. As an example the structure below contains two members, both of which are bytes.

 

struct SomeStructure

{

i8 a

i8 b
}

 

A typical DDL language file will contain some optional header information and a collection of structures that constitute the format that the file wishes to describe.

 

The order in which the structure definitions are present in a DDL file is irrelevant. A structure need not be forward declared if it being used within another structure as would be required by C like languages.


Structure Members

A structure can contain the following types of members:

·         Primitive Data Members

·         Arrays

·         Struct Data Members

·         Expressions

 

Structures also provide for conditional membership using the when-then-else construct.

Primitive Data Members

Primitive data members are those that are types that are predefined by the DDL.

These are always unsigned integer values and can range in data size from 1 bit to 32 bits. The primitive data types are always denoted with the prefix ‘i’ followed by the number of bits that you would like the types to have.

 

Examples:

·         i8 denotes a byte

·         i16 denotes a 16 bit unsigned integer

·         i32 denotes a 32 bit unsigned integer

·         i3 denotes a 3bit unsigned integer value

 

In short all kinds of declarations between i1 to i32 are possible.

 

You can define a structure member with a primitive type as follows

i<size> <name of variable>

 

Example:

i8 FirstByte

            Declares a byte called FirstByte.

 

You can define structures to contain any number of primitive type declarations. For example you would define a structure that contains 2 byte values called FistByte and SecondByte like this:

 

struct SomeStructure
{

      i8 FirstByte

      i8 SecondByte

}

 

The size of any structure is the sum of sizes of its members. This rule is violated in certain cases such as

·         when a size is explicitly specified

·         when no size is specified and the address specified for a member causes it to exceed the sum of sizes rule.

 

These shall be explained when we describe those features.

Let us move onto arrays.

Arrays

I assume you can write your own structures that contain primitive types before you read this.

In DDL you would declare an array of members like this:

<data type>[ <size of the array> ] <name of variable>

 

Arrays are conceptually similar to the arrays in other higher programming languages. The size of the array can be a constant, i.e. a number, or can be an expression. What this means is that the size of the array can be calculated and substituted during runtime of the DDL script.

 

Here is an example:

struct ArraySample

{

      i8[10]      ByteArray

}

 

This has a fixed array of 10 bytes

 

Another example:

struct ArraySample2

{

      i16   ArraySizeby2

      i32[ArraySizeby2 * 2]   DWordArray

}

This structure contains a variable whose value determines the number of elements in the array called DWordArray.

 

Structure Data Members

A structure is an entity that defines a unit of data. This data can not only consist of primitive data types but can also consist of other structures. When a structure variable is declared, the parent structure contains an instance of the declared structure.

 

The syntax for declaring a structure instance is as follows:

<data type> <name of variable>
Here ‘data type’ indicates a structure name instead of a primitive type.

 

Similarly one can declare arrays of structure instances. This is syntactically the same as declaring an array of primitive types.

<data type>[ <size of the array> ] <name of variable>

 

An example of a structure containing another one:

struct ParentStructure

{

      i8          a

      ChildStructure    cs

}

struct ChildStructure

{

      i8    b

      i8    c

}

 

The size of ChildStructure is sizeof(b) + sizeof(c) = 2 bytes.
The size of ParentStructure is sizeof(a) + sizeof(ChildStructure) = 3 bytes

 

This shows parent structure containing an array of ChildStructure instances that depends on the value of ‘a’.

struct ParentStructure

{

      i8          a

      ChildStructure[a]       cs

}

struct ChildStructure

{

      i8    b

      i8    c

}

 

The term data member of a structure is generally used to mean both primitive members as well as structure instances.

Expressions

Until now, all the kind of members that we saw in a structure, were ones that used space. That is to say, all these types defined a certain kind of data which would get a value according to the data file / block provided to the DDL interpreter.

 

Expressions don’t use up any data space. Expressions are simply values that are calculated out of other members of the structure. An expressions value can be calculated out of the values of other primitive types or out of the value of other expressions.

 

Expression declarations take the form:
<name of variable> = <mathematic expression>

 

Example:

struct Sample
{

      i16   x

      tenx = x * 10

}

This is a structure that has two members ‘x’ and ‘tenx’. These two members are exposed to the person who uses the script. The user is free to ask the interpreter to retrieve the value of ‘x’ or of ‘tenx’. The user sees both of these as types that he can query for value.

 

Since the expression type uses no space the size of Sample = sizeof(x) + sizeof(tenx) = i16 + 0 = 2 bytes

 

The appendix to this document has a list of all the supported operators that can be used in expressions as well as a list of all the defined math functions.

 

Some rules/notes about expressions:

·         An expression can consist of only members that are defined in the structure that it is a part of.

·         An expression does not use any memory/data space in the data file.

·         An expressions value is always assigned to the member variable as a c++ double type value.

 

Summary so far

If you are still reading this, you would have picked up enough information about the DDL to start writing a few DDL scripts of your own. Do try it before you go very much further.

 

The following sections go onto describe various facilities provided by the language. The very basics have been covered already. To truly consider yourself DDL-fit you must understand the API exposed by the DDL interpreter/engine and the programming model that it requires. You can refer to these in the appropriate [RJ1] document.

 

Having come so far, you can also take a look at the sample BMP file script given in the appendix of this document. While not all the usages have been covered, you should be able to, pretty much, understand the BMP script.


Configuration Section

Every DDL script file can have an optional configuration section. The configuration section, if it exists, must be defined before any of the structures are defined.

 

This is the syntax of the configuration section

 

config

      // <statements that contain configuration information

end

 

These are the possible pieces of information that can be configured here:

·         The word length value that applies to the entire script

 

Config: word_length specification

The DDL script has a concept of word_length. The word length is used in tandem with specification of offset addresses of data members in a structure. The concept of offset address specification is covered later in the document.

 

The default value of word length is 16 bits. You can override that by specifying the value of word length in the configuration section. The value of word length defined in the configuration section will be applicable for all the structures within the file unless a structure overrides this value by a structure level redefinition of word length.

 

Syntax of the word length definition:

word_length = <value in bits>

 

Example on a configuration section that defines word length to 4 bytes:

config 

      word_length = 32

end

 


Structure Level Configuration

Certain configurations are allowed or possible at the structure level and not applicable to the entire DDL script. These are defined along with the definition of the structure.

 

The syntax for using structure level configurations is:

struct <structure name> : <comma separated list of structure level configuration>

{

      <structure members>

}

 

The following information can set at the structure level:

·         Indication of the init structure of the script

·         Word length value applicable to this structure

·         Size specification for the structure

 

This is an example of a structure having three pieces of information defined at the structure level:

struct Sample : init, size = 80, word_length = 4

{

      @ 0,0 i2 a

      @ 1,0 i2 b

}

 

The order in which structure level configuration is provided is not important.

 

Struct config: init

One and only one structure in a DDL file must be marked as init. This is crucial to the working of the DDL and it is imperative that you understand this.

 

The init structure is the first structure that is instantiated by the DDL engine, by default. What this means is that when the engine is initialized it will create an instance of this structure first and map the instance to the zero-th offset of the data source. If the data source is a file, then the init structure would be mapped to the beginning of the file.

 

 When you consider your DDL script as a data hierarchical structure, the init structure will form the root node of the tree. All other data in the data source will have to be accessed either as a member of the init structure or as member of structure instance contained by the init structure.

 

In the appendix the BMP files DDL script is given. There the structure called BMPHeader is marked as the init structure.

Syntax:

struct <name> : init

{

}

 

 

Struct config: word_length

You can specify the word length value for a specific structure. If used this will override an default value or any word_length value specified at the script level configuration section.

 

Syntax:

struct <name> : word_length = <value in bits>

{

}

 

Struct config: size

The size attribute is used to specify the size of the structure in bits. The size of the structure is used for all sorts of calculations by the DDL engine. For example consider the following script:

struct Sample

{

      i8 a

      AnotherStruct s

      i8 b 

}

struct AnotherStruct

{

      i8 c

}

 

If the user asks the DDL for the value of ‘b’ the DDL engine will need to know where ‘b’ lies in the data file. The location of ‘b’ will be calculated as follows :

Location of b = Location of ‘a’ + sizeof(a) + sizeof(s)

            Now assuming we know the location of ‘a’, sizeof(a) is a known value (8 bits) the only unknown is the size of ‘s’. the size of s has to be calculated as the sum of AnotherStruct’s members in this case. This is small over head to the running of the system, but in a simple case like this it is only one time over head, once the size of the the structure is calculated it will be retained for all further calculations.

 

This overhead however is far greater when the structure has a variable size. It is disable to specify, whenever possible, the size of the structure.

 

If the size if specified by the user the DDL engine will use the user specified size even if it is not in agreement with the calculated size of the structure.

 

This allows for some interesting usages such as specifying a structure to be of a certain size while you define members that only define a part of that size, leaving some regions unspecified. Similarly you could define members that fall outside of the size of the structure, if that is required for some special case.

Syntax:

struct <name> : size = <value in bits>

{

}

 

Example:

struct Sample : size = 32

{

      i8    red

      i8    green

      i8    blue

}

The size is greater than the sum of the members here, leaving the last byte of information unspecified.

 

 


Advanced Features

After you are familiar with most of the basic features, for writing real-world scripts you may need to know of one or more of these advanced features.

For each of these usages I will also briefly cover implications of their usage on performance and the internal working of the DDL.

 

These include

·         Array specifications

·         Address specification

·         Conditional Members: when-then-else

·         Imports and Exports

 

Array Specifications

We have looked at array usage in the DDL briefly in the past. This is a slightly more rigorous approach.

The basic syntax for specifying an array is

<data type> [ <size specification> ] <variable name>

 

The DDL does not have support for multidimensional arrays – this is not really required as multidimensional arrays are simply a conceptual representation of single dimensional arrays. Similarly it would help to keep in mind that internally all data members are treated as arrays. Single members, i.e. member that are not specified as arrays, are treated as trivial arrays of size one.

 

The size can be specified in the following ways:

Constant

            The array size can be a constant value: any positive integer. Any expression that yields a constant value also falls in this category.

Syntax:

<data type>[ <constant> ] <variable name>

Expression

            The array size can be an expression. The expression can consist of variables that are valid for expression type data members. Using an expression as a array index is useful when the number of elements in the array will be determined according to values in the data itself.

Syntax:

<data type>[ <math expression> ] <variable name>

 

            The penalty for using a expression is that for every structure instance that contains this array declaration the size of the array will have to be determined. This is will involve reading the values of the variables that form a part of the array size expression.

            Now if the reading values of these variables depend on yet other variables then the DDL engine may have to do many levels of reads before it can determine the size of the array.

            Determining the size of the array will be required for cases where you don’t actually need to access any of the array members. Such cases arise when there are probably data members that lie after the array declaration. To be able to access these members the DDL must determine their addresses. To determine their addresses the DDL must determine where the array ends. Which means that the DDL must know the total size used by the array – this is will involve knowing the size (i.e. number of members in the array) as well as the size of each member in the array.

 

            While the DDL is designed to internally handle these issues and cache values for performance, it is good to be aware of these situations and to possibly try avoiding them. In cases, where the situation demands, variable size arrays are a must – go ahead and use them.

 

EOF specification

            In some practical cases, the size of an array cannot be determined as a constant; neither can it be determined from the values of any other variables in the data. One such cases is that an array continues till the end of the data source. A typical example is many kinds of table formats where the number of rows are not specified but will go on till the end of the file.

 

Arrays till end-of-file are specified using ##eof in the place of the array size specification.

Syntax:

<data type>[ ## eof ] <variable name>

 

The example shows a table of user_name, id and phone_number that runs till the end of file.

struct  UserTable : init

{

      UserData[##eof]   rows

}

struct UserData

{

      i8[25]      user_name

      i32         user_id

      i8[10]      phone_number

}

 

Terminator Specification

The ‘eof’ case was one way an array could terminate according to some external condition. Another ways an array could terminate is till it sees some kind of a terminator.

 

For example in a C style string (asciiz) a sequence of bytes is terminated by a byte with zero value. This is one kind of terminator. There could be other kinds of terminators as well. For example an array of 16 bit integers might end when you read a 16 bit integer with the value 0xffff. Or say an array of bytes would terminate when you come upon a byte that has its msb bit set.

 

Terminator specification lets you specify various kinds of array terminations. The general syntax for an array terminator specification is:

<data type>[ # <terminate value>, <value bit size>,<value offset>] <variable name>

 

Before I venture to explain this, here is an example:

i8[# 0,8,0] string

This represents a zero terminated asciiz string.

 

There are three constant values that together make up the terminator specification. If they look a little unobvious its because they try to apply to a general class of terminator specifications.

 

These can interpreted as follows: The array terminates when you encounter the value <terminate value> which is of size <value bit size> bits at an offset of <value offset> bits from the end of the last element that was accepted to be part of the array.

 

Consider the case of the asciiz string. The asciiz string terminates when you encounter a 0 byte. This zero byte shoukd immediately follow the last character in the string. So since it is a zero valued byte that is the terminator, terminate value is 0 and the value bit-size is 8 (for byte). Now since the 0 byte will occur immediately after the last byte that forms part of the array there no offset difference between the last element and the terminator symbol. So the value offset is 0.

 

Does that make things easier?

Here is the encoding of a Unicode zero terminated string:

i16 [# 0, 16, 0 ] string

 

            Terminator based arrays, both ##eof as well #terminator are arrays of variable length. However, it is easy to calculate the size of a ##eof based array if you know the size of each element in the array. If the array elements have a fixed size and the file size is known before hand, then a simple subtraction division can determine the size of the array.

           

Arrays that terminate with the general terminator triplet require that the DDL engine read every array element and see if that is the terminating element of the array by searching for the terminator value.

 

All kinds of array specifications incur a serious performance penalty if the size of each element is variable. To know the total space used by the array the DDL engine will have to determine the size of the array and the space required for each member element in the array to determine the size of the array.

Please keep this is mind while writing DDL scripts. Again, if the data format mandates it, then there is not much choice.

 

An example of an array with a variable size for each member and a terminator is a zero byte terminated DBCS string. This is an example of such a strings encoding. (In a DBCS string, if the msb bit of a byte is set then the character at that location spans across 2 bytes, else the character is one byte is size. The string is terminated with a 0 value byte)

struct DBCSCharacter

{

      i7    ignore

      i8    msb_bit

      when(msb_bit == 0 )

      {

            @ 0 i8 value

}

when(msb_bit == 1)

{

      @ 0 i16 value

};

}

struct DBCSString

{

      DBCSString[# 0, 8, 0] string

}

 

Address Specification

The concept of address specification is simple. Consider the following script:

script Sample

{

      i16   a

      i4    b

      i20   c

}

 

Suppose an instance of the struct Sample was mapped in the data buffer provided to say offset ‘x’. Then it follows that the value of ‘a’ will be the value contained in the bits x to x+16.

The value of ‘b’ is the value of bits x+16 to x+20 and the value of ‘c’ is the value of the bits x+20 to x+40.

 

So speaking in terms of offset from the base address of Sample’s instance, ‘a’ is at offset 0, ‘b’ is at offset 16 and ‘c’ is at offset 20. Relative to the starting of the structure the address of ‘a’ is 0, ‘b’ is 16 and ‘c’ is 20.

 

These addresses are automatically calculated by the DDL depending on the size of the elements ‘a’, ‘b’ and ‘c’. The address of each element depended on only the sum of sizes of the elements defined before it.

 

The concept of address specification is used to specify the address of a member and is used when you want the member to be mapped at a specific address relative to the start of the struct, irrespective of the members defined before it.

 

Addresses can be specified in two formats. This is the first:

@ <constant offset> <data type> <variable>

and for an array

@ <constant offset> <data type> [ <array specification> ] variable

 

The address is calculated using the value of the word_length specification. The offset of the member declared in the above manner will be at the bit address relative to the beginning of the structure calculated as

Address = constant offset * word_length

 

For example is word_length is 16 then the declaration

@     2     i32   a

states that the 32 bit value of ‘a’ is to be read at an offset of 4 bytes from the base address of the structure.

 

The address can also be specified as

@ <word_length multiple> , <bit offset> <data type> <variable>

and

@ <word_length multiple> , <bit offset> <data type> [ <array specification> ] <variable>

 

When the address is specified as two comma separated values, it is calculated as

Address = word_length multiple * word_length + bit offset

 

Coming to our example, where word_length is 16

@     2,2   i32   a

states that the 32 bit value of ‘a’ is to be read from an offset of 36 bits from the base address of the structure it is a part of.

 

Uses of address specification

Address specification can be advantageous

·         When you need variables to overlap in the data source region

·         When you need to define variables to read values only from certain parts of the struct and you don’t need to define variables for the remaining empty regions

Example:

struct Sample : word_length = 16

{

      @ 2   i8    a

      @ 2,2 i8    b    

     

      @ 4   i16   c

}

Here ‘a’ and ‘b’ have their data partially overlapping. ‘c’ is defined to be at bit offset 16 * 4. There are no variables defined for the data that lies between ‘b’ and ‘c’.

·         When you need to define members that fall outside of the size of the structure. This is a kind of a cheat to read into data that would form part of some other structure.

struct Sample : word_length = 16, size = 32

{

      @     i8    a

      @ 10  i8    b    

}

Here the element ‘b’ will fall outside the size of sample.

 

A Few Points about Address specification

·         The size of a structure is calculated as the address of used bit that is farthest from the base address of the structure instance.

Example:

struct Sample : word_length = 16

{

      @ 3   i1    a

      @ 1   i16   b

}

The size of this structure is 3 * 16 + 1 bits.
The structure has two members. ‘a’ is at location 16 * 3 and has a size of 1 bit. ‘b’ is at location 16 * 1 and has an a size of 16 bits. Therefore the size of the structure is fixed as the size dictated by ‘a’, which is the larger of the two values.

 

This default behavior is overridden if the size of the structure is explicitly given.

 

·         After a member is declared with an address specification, any following members defined without an address specification will address values cumulatively.

struct Sample : word_length = 16

{

      @ 3   i1    a

            i8    aa

      @ 1   i16   b

            i32   bb

}    

 

‘a’ is read from location x+3*16 to x+3*16+1

‘aa’ is read from location x+3*16+1 to x+3*16+9

‘b’ is read from location x+1*16 to x+1*16+16

‘bb’ is read from location x+2*16 to x+2*16+32

            Since ‘bb’ occupies the farthest used bit in the structure, the size of the structure is x+2*16+32 bits.

 

 

Conditional Members: when-then-else construct

The only conditional construct given to you a in the DDL is the when-then-else. This is a lot like the if-elseif-else construct of C on steroids.

 

Often you will need to declare certain members in a structure depending on some conditions. If the conditions are not satisfied, the declarations needn’t exist, or probably you need to declare some other members. This si the purpose of the when-then-else.

 

This is the syntax of a when-then-else block showing multiple ‘when’ blocks and the ‘then’ and ‘else’ blocks:

when ( <condition> )

{

      <statements>

}

when ( <condition> )

{

      <statements>

}

then

{

      <statements>

}

else

{

      <statements>

};

 

The logic behind the construct is like this. Only one of the when blocks in cascade like this will be executed. The condition of each ‘when’ block is evaluated, in the order of appearance of the ‘when’ blocks.

The when a condition is evaluated to ‘true’, the contents of the when block are ‘executed’. If none of the ‘when’ block conditions succeed, then the contents of the ‘else’ block is evaluated (if an else is provided).

So far the description is similar to that of an if-elseif-else construct. The difference comes in with the usage of the ‘then’ block. If any of the ‘when’ blocks evaluate to ‘true’ then in addition to the contents of that ‘when’ block the contents of the ‘then’ block are also executed.

 

A when-then-else construct can comprise of only a single ‘when’ block, like a standalone if condition. Additional ‘when’ blocks, the ‘then’ and ‘else’ blocks are optional and depend on the logical need of the structure to be defined.

 

The usage of a ‘then’ block when there is only one ‘when’ block is trivial.

 

It is important to have the terminating semi colon after a when-then-else construct. This is used to differentiate the meaning between

when ( a1 == 1)

{

      i8    b

};

when ( a2 == 2)

{

      i16   c

};

and

when ( a1 == 1)

{

      i8    b

}

when ( a2 == 2)

{

      i16   c

};

In the second case both the ‘when’s are part of the same construct, so both ‘b’ and ‘c’ cannot exist. Whereas in the first case both the ‘when’s are different and both can individually get satisfied, which means that both ‘b’ and ‘c’ could exist in the first case.

 

A condition can comprise of a combination of logical expressions operated upon by logical operators. A logical expression is typically the combination of mathematical expressions related by relational operators. The appendix lists the supported operators.

 

A practical example of the usage of the when-then-else can be seen in the BMP file format DDL script that is given in the appendix. The palette usage, image buffer etc are defined according to color specification of the BMP file.

 

Import and Export

This feature is more of a special case feature and would be used rather infrequently.

The DDL allows expressions to contain variables that are defined only within the current structure. Imports and exports provide a mechanism to override that behavior.

 

Certain structures can have conditions, array sizes etc to depend on elements not only of that structure but of other structures. Import and export of structure members is one way of accomplishing this.

 

struct test

{

      structA sa

      structB sb 

}

 

struct structA

{

      i8 b

}

 

struct structB : size = 32

{

      i8 a

      when(b<10)

      {

            i8 c

      }

}

Notice structB. The when will never be resolved because it does not contain an element called ‘b’. The element 'b' can be imported into the structure. So when the instance of structB is declared, it has to be marked with an import attribute.

 

struct test

{

      structA sa

      import structB sb

}

 

Now to provide the variable required for structB we have to export it from somewhere. We mark sa to be an instance that exports, with the export attribute and then give the export attribute to the elements of structA that we wish to export.

 

struct test

{

      export structA sa

      import structB sb

}

 

struct structA

{

      export i8 b

}

 

struct structB : size = 32

{

      i8 a

      when(b<10)

      {

            i8 c

      }

}

 

This will make structB resolvable with b taking the value of the element 'b' as exported in structA.

 

Notes:

·         You can import only into structures that have a fixed size.

·         A structure instance can import elements belonging only to member instances of the same structure of which it is a part.

·         An instance can import elements from any number of other structures and export can be made available in any number of other structures.

·         Ideally the names of the exported members must be unique among the exporting and importing structures.

·         Only basic bit-type single element members can be exported.

 

Specifying Comments

All script file comments are to be enclosed in C style /* */. Only multi-line comments are allowed.

 

The lexical analyzer works in the commented portion also, though the parser doesn’t work on it. Therefore certain characters may not be allowed even in the commented regions.

Ex
 /* The single quote character  ‘ will throw an error*/

 

Notes and Limitations

 

Notes

·         Structure sizes set by the user are Not overridden by the size count of the members

·         Structures that have only expression types in conditional blocks are not variable size structures

·         All values are read from the data file as unsigned long types (32b). They are type cast to double for all internal calculations. The return value of GetValue() also does this type cast.

·         Any path starting with a '.' is considered a path from the root, paths not starting with a '.' are considered paths under the current path (i.e. like a sub dir).

 

Limitations

·         All references within a structure can to only to elements within itself

·         Arrays of variable sized structures are not allowed for eof and till_char type specifications

·         The do work for an expression based array size. This has been disabled due to the computational overhead required for making those calculations.

·         Equations cannot contain array elements.

 

 


Appendix: Math Operators and Functions

 

Precedence of Mathematical Operators
Operators are evaluated in according to the precedence table give. Operators are listed in increasing order of precedence.

 

Precedence table

Unary/Binary

&& ||

B

< > <= >= == !=

B

+ -

B

* /

B

**

B

^ | & << >>

B

! + -  ()

U

 

List of Operators and Functions Supported

 

Operator/Function

Usage

Unary/Binary

Explanation

+

a + b

B

Addition

-

a – b

B

Subtraction

*

a * b

B

Multiplication

/

a / b

B

Division

**

a**b

B

Power (a to the power b)

%

a % b

B

Modulus

<

a<b

B

Less than

>

a>b

B

Greater than

<=

a<=b

B

Less than or equal

>=

a>=b

B

Greater than or equal

==

a==b

B

Compare for equal

<>

a<>b

B

Compare for not equal

||

a || b

B

Logical OR

&&

a&& b

B

Logical AND

+

+a

U

Unary plus (absolute positive value)

-

-a

U

Unary minus (negation)

!

!a

U

Not

|

a | b

B

Bitwise OR

&

a & b

B

Bitwise AND

^

a ^ b

B

Bitwise XOR

<<

a<<b

B

Bitwise left shift

>>

a>>b

B

Bitwise right shift

**

a**b

B

a to the power b

sin

sin(a)

U

Sine in radians

cos

cos(a)

U

Cosine in radians

tan

tan(a)

U

Tangent in radians

sec

sec(a)

U

Secant in radians

cosec

cosec(a)

U

Cosec in radians

cotan

cot(a)

U

Cotan in radians

log

log(a)

U

Logarithm to base e

ln

ln(a)

U

Natural log

sqrt

sqrt(a)

U

Square root

cbrt

cbrt(a)

U

Cube root

exp

exp(a)

U

e to the power a

atan

atan(a)

U

Tan inverse

asin

asin(a)

U

Sin inverse

acos

acos(a)

U

Cos inverse

log2

log2(a)

U

Log to base 2

logx

logx(a)

U

Log to base x

factorial

fact(a,b)

B

Product  1 to ‘a’ with ‘b’ size increments

summation

sigma(a,b)

B

Sum of 0 to ‘a’ with ‘b’ size increments

ceil

ceil(a, b)

B

Smallest multiple of ‘b’ larger than ‘a’

floor

floor(a,b)

B

Largest multiple of ‘b’ smaller than ‘a’

integer

int(a)

U

Convert to signed integer

 

Predefined Constants

These constants are pre-defined within the DDL.

 

Predefined Constants

Meaning

M_PI

Value of PI

M_E

Value of natural number e

M_SQ_PI

Square of pi

M_SQ_E

Square of e

M_SQRT_PI

Square root of pi

M_SQRT_E

Square root of e

M_CBRT_PI

Cube root of pi

M_CBRT_E

Cube root of e

M_SQRT_2

Square root of 2