DDL is the Data Definition Language. This language is to record a general-kind data in a textual form similar to the C language. But there are important differences between how DDL and C interprets data and expressions. DDL is a "bye-bye XML" language.
Here is a brief introduction to the language. You can read this document for further details.
DDL is to define named constants. Each constant has an associated type. Types can be basic or derived. The most important derived types are structures.
Basic types are integral types, text and ip. Integral types are:
uint8 sint8 uint16 sint16 uint32 sint32 uint64 sint64 (sint,uint) = (sint32,uint32) or (sint64,uint64) int = sint ulen = uint32 or uint64 >= uint
text type represents a character string.
ip type represents an IPv4 address.
int, sint, uint are target-dependent "machine-word-length" types.
ulen is a target-dependent "machine-address-length" type.
Derived types are pointers, arrays and structures:
int a = 10 ; int * pa = &a ; text [a] B = { "b1" , "b2" } ; text [] C = { "c1" , "c2" } ; struct S { text name = "unnamed" ; int id = 0 ; }; S record = { "" , 10 } ;
If the array length is not given explicitly, then it is inferred from the initializer. Structure members may have default initializers.
Type aliases and scopes (aka "namespaces") are supported:
scope Data { type List = int[] ; } // scope Data Data#List list = {1,2,3} ;
Dots can be used to designate the current scope or parent scopes:
scope S1 { int i1 = 1 ; scope S2 { int i2 = 2 ; scope S3 { int i3 = 3 ; int i = .#i3 + ..#i2 + ...#i1 ; // absolute names int j = i3 + i2 + i1 ; // relative names int k = i1 + S2#i2 + S2#S3#i3 ; // more relative names } } }
The language is commutative, i.e. the order of declarations is not important:
int a = c + 1 ; int b = a + 1 ; int c = 100 ;
Structures define scopes and may declare types and constants:
struct S { type Val = int ; const ulen Len = 100 ; type List = Val[Len] ; }; scope S { List list = {1,2,3} ; }
DDL supports file inclusion:
scope Inc { include <some_file.ddl> }
But the content of an included file must be a proper DDL text, except names may not be defined inside the included file. Binding a file name with a particular file (real or virtual) is implementation-defined.
DDL elements are: scopes, types, type aliases, constants, literals and expressions. DDL text is a sequence of definitions. A definition defines a scope, a structure, a type alias or a constant. Literals and expressions are used to define a constants value.
DDL accepts long and short comments:
/* This is a long comment */ // short
Scope is a "definition directory". Scopes can be nested or split. Each named language element belongs to some scope. The full element name is # Scope1Name # Scope2Name # ... # ElementName.
scope A { // open scope #A .... } // close scope #A scope B { // open scope #B scope C { // open scope #B#C .... } // close scope #B#C .... } // close scope #B scope A { // open again scope #A .... } // close scope #A
When a name is used to refer to a language element, it may be either relative or absolute. A relative name starts from a name, an absolute name starts from the # or one or more dots. One dot means the current scope, 2 dots means the parent scope and so on. A relative name is looked in all scopes down from the current.
scope S1 { int i = 1 ; scope S2 { int i = 2 ; scope S3 { int i = 3 ; int j = -3 ; scope S4 { int i = 4 ; int i1 = i ; // 4 int i2 = j ; // -3 int i3 = S1#i ; // 1 int i4 = #S1#i ; // 1 int i5 = .#i ; // 4 int i6 = ..#i ; // 3 int i7 = ...#i ; // 2 } } } }
There are basic types and derived types. Basic types are designated by the following keywords:
sint8 uint8 sint16 uint16 sint32 uint32 sint64 uint64 int sint uint ulen text ip
Derived types are pointers, arrays and structures.
Pointer type is designated by the type and the following asterisk:
Type *
Array type is designated by the type and the following pair of square brackets with the optional expression inside:
Type [] Type [Expression]
The expression defines the array length. The resulting type of this expression is ulen.
Non-pointer and non-array type can be a basic type name, a type alias name, a structure name or a structure definition.
// basic type name int a = 0 ; ulen b = 0 ; // type alias name type Int = int ; Int c = 0 ; // struct name struct S {}; S s = {} ; // struct definition struct S1 {} s1 = {} ;
Type alias definition looks like:
type Name = Type ;
Structure definition looks like:
struct Name { Type1 field_name1 [ = Expression1 ] ; .... Typen field_namen [ = Expressionn ] ; };
Each structure has a name and a list of fields. Each field has a name, a type and an optional default value, determined by an expression. A structure also defines a scope. This scope can be extended. A structure definition can also defines a scope type aliases, structures and constants:
struct S { struct T {}; const ulen Len = 10 ; type List = T[Len] ; const List list = {} ; };
To define a constant inside a structure (not a field) the keyword const must be used.
Constant definition looks like:
Type Name = Expression ;
This definition defines the constant with the given name in the current scope with the given type and value, determined by the expression.
Constant can be defined inside a structure definition with the keyword const:
struct S { const Type name = Expression ; };
Without const it would be a field definition.
Universal null literal:
null
Decimal literals:
1234567890
Hexadecimal literals:
1234567890h 0abcdefH 0ABCDEFh
Binary literals:
1001B 1010b
Simple string literals:
'simple text'
Advanced string literals accept usual back-slash special character representations:
"advanced text\n" "\b\t\n\v\f\r"
IP literals:
192.168.1.10
Expressions are used to assign a value to a constant (including implicitly defined constants, like array lengths or default structure member values). Expression can be scalar or compound. Scalar expressions can be used to assign a value of a basic type, compound — for structures and arrays. A special class of scalar expressions defines pointer values.
The most important thing about expressions: they are calculated in the context of the resulting type. Resulting type of expression is the type of constant this expression is used to assign a value to.
Compound expression is a list of expressions, or a list of named expressions:
struct S { int a; int b; int c = 10 ; }; S s1 = {} ; // empty list, a = 0, b = 0, c = 10 S s2 = { 1 , 2 } ; // partial list, a = 1, b = 2, c = 10 S s3 = { 1 , 2 , 3 } ; // full list, a = 1, b = 2, c = 3 S s4 = { .a = 1 } ; // named list, a = 1, b = 0, c = 10
Named lists can be used as value modifiers:
struct S { int a; int b; int c = 10 ; }; S s = { 1 , 2 , 3 } ; S s1 = s { .a = -1 } ; // a = -1, b = 2, c = 3 S s2 = { 1 , 2 } { .a = -1 } ; // a = -1, b = 2, c = 10
Usual arithmetic operations +a, -a, a+b, a-b, a*b, a/b, a%b can be used with integral values. Operands are evaluated to the resulting integral type and operation is performed with this value types. Additive integral operations and multiplication are operations in the correspondent residual ring. Division operations follows C convention:
a == (a/b)*b+(a%b) sign( a%b ) == sign(a) abs( a%b ) < abs(b)
Signed integral types use 2'd complementary representation. Integral literal conversions are performed by the module reduction.
There is a special integral cast operation:
int x = uint8( 12345 ) ;
The expression inside a cast expression is evaluated to the resulting cast type.
ip constant can be received only from an IP literal.
text constant can be received from a text literal, an IP literal, an integral literal and from a binary plus operator.
text a = "string" ; // string text b = 1.2.3.4 ; // 1.2.3.4 text c = 100000000000000000000 ; // 100000000000000000000 text d = "string"+2222222222222222222222 ; // string2222222222222222222222
Integral literals are converted as is. IP literals are converted to IP address first.
null is a "universal null".
int a = null ; struct S { int a; int b; int c = 10 ; }; S s = null ; // a = 0, b = 0, c = 0
Pointers can be used the same way as in C.
int a = 10 ; int * pa = &a ; int b = *pa ; // 10 int[10] c = {0,1,2,3,4,5,6,7,8,9} ; int * pc = c+5 ; int d = *pc ; // 5 int e = pc[2] ; // 7 int l = pc - c ; // 5
struct S { int a; int b; }; S s = { 1 , 2 } ; S * ps = &s ; int a = ps->a ; int b = (*ps).b ;
Polymorphic pointer may point to objects of different types. It looks like:
type Ptr = {int,uint} * ; int a = 1 ; uint b = 2 ; Ptr ptr_a = &a ; Ptr ptr_b = &b ;
When you define a default value for a structure field, you may use the name with question mark to refer a name, which is bounded with the definition at the point of usage.
struct S { int a = ?A + 10 ; }; int A = 100 ; S s = {} ; // s.a == 110 scope Inner { int A = 200 ; S s = {} ; // s.a == 210 }