DDL language (v.2)

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.

Brief introduction

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

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.

Comments

DDL accepts long and short comments:


/* This is a long comment */

// short

Scopes

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

   }

  }

 }

}

Types and type aliases

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 ;

Structures

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.

Constants

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.

Literals

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

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 pointers

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 ;

Point of usage name binding

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
 }