DDL Console
Tutorial
and
Example of Hosting the DDL
The DDL Console is a sample test application distributed with the DDL library. The DDL console can be used for getting an understanding of the way the DDL works and how it can be hosted. You can also test your scripts with the console application. You can also use the program’s source code to understand how to interface with the API exposed by the DDL. At relevant places I will list the C# code snippet that executes, corresponding to the user interaction, to make things more clear.
The DDL console application is written in C# and needs the Managed C++ assembly System.DDL.dll to function.
The DDL project homepage is http://ddl.sscli.net .
You can mail the authors at spark@sscli.net and dolly@sscli.net
To use the DDL console you need a DDL script file and a data-file corresponding to the script file. Some script and data files have been provided as examples. This tutorial uses the script binary_tree.ddl and the data file binary_tree_data.bin as an example.
This document contains a description of the commands as well as a walkthrough.
You start the console by typing ddlconsole at the command prompt.
c:\ddl>ddlconsole
DDL Console v0.8
>
The console displays its standard prompt after a startup message. At this prompt you can enter the console commands.
The first thing you will want to do will be to open a DDL script and a data file. The script file contains the actual DDL code that you write and the data file contains the data that you can be read by the DDL interpreter using your script file.
c:\ddl>ddlconsole
DDL Console v0.8
>open
DDL Source File:
On typing the open command, the console prompts you for the source file. You can enter the relative or absolute path to the source file.
c:\ddl>ddlconsole
DDL Console v0.8
>open
DDL Source File: c:\ddl\samples\binary_tree.ddl
Data File:
The console prompts you for the data files name and path.
c:\ddl>ddlconsole
DDL Console v0.8
>open
DDL Source File: c:\ddl\samples\binary_tree.ddl
Data File: c:\ddl\samples\binary_tree_data.bin
>
After both file names have been given the DDL console silently displays the prompt ‘>’. At this point the source file has been loaded and parsed and the data file has been opened.
Now you can start querying the DDL for information. The default location the DDL rests is the instance of the init structure of you DDL script. Internally the DDL creates an instance of the init structure. The path to the init structure is represented as a single dot ‘.’. This path is analogous with paths in directory structures in a file system. The contents of any path is the member variables of the structure that is represented by that path.
This means that the contents of your ‘.’ Path is the contents of the init structure of your script.
As long as there are no errors the console is silent. If there are errors the console would display a message like this:
c:\ddl>ddlconsole
DDL Console v0.8
>open
DDL Source File: c:\ddl\samples\binary_tree.ddl
Data File: c:\ddl\samples\binary_tree_data.bin
DDL Exception:
DDLError:Failed to open Source file
>
You can refer to the commands errors and clear to understand what to do in the case of an error.
This is C# snippet that executed corresponding to the open command. If you are hosting the DDL in your application you will have a similar snippet. If you are currently trying to understand the DDL only, you can ignore the snippet.
void Open()
{
if(m!=null)
m.Dispose();
Console.Write("DDL Source File: ");
string src = Console.ReadLine();
Console.Write("Data File: ");
string data = Console.ReadLine();
try
{
//this is how you intiialise the DDL
m = new ManagedDDLEngine();
m.LoadSourceFile(src);
m.OpenDataFile(data);
m.InterpretData();
}
catch(DDLException e)
{
Console.WriteLine("DDL Exception:\n{0}:{1}",e.DDLErrorType,e.DDLMessage);
}
catch(Exception e)
{
Console.WriteLine("Generic Exception:");
Console.WriteLine(e.Message);
}
}
‘m’ has been declared prior to this as
ManagedDDLEngine m;
The DDL internally maintains a log of all errors that occur in sequence. Whenever you ask the DDL to carry out an operation and there are errors, this list is filled. The DDL throws an exception which details the message of the first internal error. The exception also indicates that the host program should examine the internal error list and respond to the error situation.
The error command displays this internal error list. Each error will be displayed indicated by an error type and the error message. The error type indicates if the error originates in the lexical analyzer, parser or in some other part of the DDL.
This an example errors output
c:\ddl>ddlconsole
DDL Console v0.8
>open
DDL Source File: c:\ddl\samples\binary_tree.ddl
Data File: c:\ddl\samples\binary_tree_data.bin
DDL Exception:
DDLError:Failed to open Source file
>errors
DDLError: Failed to open Source file
DDLError: Unable to load the source file into a buffer
2 Error(s)
>
The DDL will not execute any other command as long as there are pending errors in the internal error list. The host of the DDL has to explicitly empty the error list. Corresponding to this the user will have to type the clear command to clear any current errors in the list.
The open command is an exception to this rule, as you can see from the above snippet the open command creates a new instance of the DDL interpreter thus automatically cleaning out any previous errors in the list.
This is the C# snippet that displays the errors
void GetErrors()
{
try
{
int c=0;
foreach(ErrorInfo ei in m.GetErrors())
{
Console.WriteLine("{0}: {1}",ei.Type,ei.Message);
c++;
}
Console.WriteLine("{0} Error(s)",c);
}
catch(DDLException e)
{
Console.WriteLine("DDL Exception:\n{0}:{1}",e.DDLErrorType,e.DDLMessage);
}
catch(Exception e)
{
Console.WriteLine("Generic Exception:");
Console.WriteLine(e.Message);
}
}
The clear command clears the internal error list (refer the errors command for details). The clear command produces no output and simply displays the prompt.
This is the clear commands internal implementation:
void ClearErrors()
{
try
{
m.ClearErrors();
}
catch(DDLException e)
{
Console.WriteLine("DDL Exception:\n{0}:{1}",e.DDLErrorType,e.DDLMessage);
}
catch(Exception e)
{
Console.WriteLine("Generic Exception:");
Console.WriteLine(e.Message);
}
}
The dir command is used to list all the members in the current location. It is assumed that you have successfully opened the source and data files using open by now.
If you have just opened the source and data files, typing the dir command will give you an output like this
c:\ddl>ddlconsole
DDL Console v0.8
>open
DDL Source File: c:\ddl\samples\binary_tree.ddl
Data File: c:\ddl\samples\binary_tree_data.bin
>dir
value i16 16bits #0
Lflag i8 8bits #16
Rflag i8 8bits #24
v10 EXP 0bits #0
left STR 96bits #32
right STR 32bits #128
>
There are 5 columns of information here.
· The first column has the name of the member variable (Ex value, Lflag)
· The second column has its data type as declared in the DDL script file. Expression types are indicated as EXP and structure types are indicated as STR.
· The third member indicates the size of the member in bits.
· The fourth entry appears only is the member is an array. It indicates the length (number of elements) of the array in square brackets. (In this example none of the members are array types and so the array length indication is not given)
· The fifth column, prefixed with a #, indicates the offset address of the member from the base address of the structure instance that it is a part of.
To fully understand this example output you must examine the DDL script that was loaded. This is the file binary_tree.ddl
//binary_tree.ddl
struct BinaryTreeNode : init
{
i16 value
v10 = value * 10
i8 Lflag
i8 Rflag
when(Lflag == 65)
{
BinaryTreeNode left
};
when(Rflag == 65)
{
BinaryTreeNode right
};
}
If you correlate the member types you can see that each member name has been reported to have the appropriate type.
Notice that the members ‘left’ and ‘right’ have been displayed in the dir output while in the script, they occur within ‘when’ clauses. This means that the DDL has evaluated their when-blocks conditions and these conditions evaluated to true.
There maybe other instances of the same structure where left and right need not exist.
After you initialize the DDL engine you are at the path ‘.’. For the DDLConsole you would do this by executing the open command successfully.
A path represents a structure instance. A root or the default path represents the first structure instance created by your DDL which is your init structure.
The contents of a path represent the members that exist in the structure instance that is represented by the path. In the default path the members indicate the members of the init structure.
Now any structure can contain members that are instances of other structures. To be able to access these we must change our path to indicate the other structure.
For example in the init structure contained 2 members one of which was a 16 bit type named ‘a’ and the other an instance of a structure type called ‘Test’ which is named ‘x’. Let all structures of type ‘Test’ contain two members ‘a’ and ‘b’ that are 16 bit types.
So the content of path ‘.’ will be ‘a’ and ‘x’. If we change path to ‘.x’ we will have access to the member of instance ‘x’, i.e. this path will have members ‘a’ and ‘b’
The fully qualified names of all the types in this example are
‘.a’
‘.x.a’
‘.x.b’
You can think of this hierarchical structure to be similar to that is object oriented programming or to traversing a directory tree.
When you are changing path through an array instance, you can indicate the instance you ant to change to by appending a ‘:’ (colon) followed by the instance number. For example to represent the 3rd member into an array of structures named ‘arr’ you can say ‘arr:3’
The cd command is used to change the current path. The cd command takes one argument which can either be a absolute path or a relative path. An absolute path starts from the root, i.e. it start with a ‘.’ (dot).
In our example:
c:\ddl>ddlconsole
DDL Console v0.8
>open
DDL Source File: c:\ddl\samples\binary_tree.ddl
Data File: c:\ddl\samples\binary_tree_data.bin
>dir
value i16 16bits #0
Lflag i8 8bits #16
Rflag i8 8bits #24
v10 EXP 0bits #0
left STR 96bits #32
right STR 32bits #128
>
We know that left and right are member structures. So we can cd to either of them.
c:\ddl>ddlconsole
DDL Console v0.8
>open
DDL Source File: c:\ddl\samples\binary_tree.ddl
Data File: c:\ddl\samples\binary_tree_data.bin
>dir
value i16 16bits #0
Lflag i8 8bits #16
Rflag i8 8bits #24
v10 EXP 0bits #0
left STR 96bits #32
right STR 32bits #128
>cd left
.left>
After the cd your prompt will indicate you current path. Thus we are in the structure member called left that is part of your init structure.
If you execute a dir here you will see the results
c:\ddl>ddlconsole
DDL Console v0.8
>open
DDL Source File: c:\ddl\samples\binary_tree.ddl
Data File: c:\ddl\samples\binary_tree_data.bin
>dir
value i16 16bits #0
Lflag i8 8bits #16
Rflag i8 8bits #24
v10 EXP 0bits #0
left STR 96bits #32
right STR 32bits #128
>cd left
.left>dir
value i16 16bits #0
Lflag i8 8bits #16
Rflag i8 8bits #24
v10 EXP 0bits #0
left STR 64bits #32
.left>
Notice that here there is only the left member, the right member does not exist. That means that the condition for right was not satisfied in this case. You can revert back to listing of the DDL script at this point.
To return to the root at any point, type ‘cd .’ .
There is currently no support for moving one level up the tree.
This the code snippet for the cd command
string Seek(string[] subcmds)
{
try
{
m.Seek(subcmds[1]);
return m.Tell();
}
catch(DDLException e)
{
Console.WriteLine("DDL Exception:\n{0}:{1}",e.DDLErrorType,e.DDLMessage);
}
catch(Exception e)
{
Console.WriteLine("Generic Exception:");
Console.WriteLine(e.Message);
}
return "";
}
Our example uses the following DDL script file:
//binary_tree.ddl
struct BinaryTreeNode : init
{
i16 value
v10 = value * 10
i8 Lflag
i8 Rflag
when(Lflag == 65)
{
BinaryTreeNode left
};
when(Rflag == 65)
{
BinaryTreeNode right
};
}
This is a hex dump of the contents of the data file using (good old) debug:
0000:0000 01 00 41 41 02 00 41 42-03 00 42 41 04 00 42 42 ..AA..AB..BA..BB
0000:0010 05 00 43 43 ..CC
So now if you try mapping the DDL script to this data you will notice that the value of ‘value’ of the init structure will map the locations that contain 0x01 and 0x00 making i16 value = 0x0001 (for little-endian machines)
Similarly i8 Rflag will be 0x41 = 65 and i8 Lflag will be 0x41 = 65. Sicnce these are both 65, the when conditions are satisfied.
Which means that immediately following Rflag an instance of BinaryTreeNode called left must exist.
Therefore i16 left.value will be 0x02 and 0x00 = 0x0002. And similarly
i8 left.Lflag = 0x41 = 65 and
i8 left.Lflag = 0x42 = 66
Therefore ‘.left’ will contain the member ‘left’ and will not contain the member ‘right’ This is what our dir output had shown us.
If you form a mental image of the binary tree defined by the script and the data file it will look like this

This command will read all the data and expression values in the current location. If any member is a structure it will read the value of only its 0th member.
For our present example this is the output
.>ra
value = 1 (i16)
Lflag = 65 (i8)
Rflag = 65 (i8)
v10 = 10 (ExpressionType)
left = 0 (StructType)
right = 0 (StructType)
.>cd left
.left>ra
value = 2 (i16)
Lflag = 65 (i8)
Rflag = 66 (i8)
v10 = 20 (ExpressionType)
left = 0 (StructType)
.left>
This is the code snippet for the readall command:
string ReadAll()
{
try
{
ArrayList al = m.List();
m.GetValues(al);
foreach(MemberInfo mi in al)
{
Console.WriteLine("{0,20} = {1,8} ({2})",
mi.Name,mi.Value,mi.Type);
}
}
catch(DDLException e)
{
Console.WriteLine("DDL Exception:\n{0}:{1}",e.DDLErrorType,e.DDLMessage);
}
catch(Exception e)
{
Console.WriteLine("Generic Exception:");
Console.WriteLine(e.Message);
}
return "";
}
The recursive-read-all command can be used to read the values of all values of the all the variables exposed by the DDL.
If there is an array of values then it will read the values of all the members or till the internal MaxArrayDisplay number of elements, which ever is lesser.
Example show rra executed at the root and one of the structures under the root.
.>rra
[i16] .value = 1
[ i8] .Lflag = 65
[ i8] .Rflag = 65
[EXP] .v10 = 10
[i16] .left.value = 2
[ i8] .left.Lflag = 65
[ i8] .left.Rflag = 66
[EXP] .left.v10 = 20
[i16] .left.left.value = 3
[ i8] .left.left.Lflag = 66
[ i8] .left.left.Rflag = 65
[EXP] .left.left.v10 = 30
[i16] .left.left.right.value = 4
[ i8] .left.left.right.Lflag = 66
[ i8] .left.left.right.Rflag = 66
[EXP] .left.left.right.v10 = 40
[i16] .right.value = 5
[ i8] .right.Lflag = 67
[ i8] .right.Rflag = 67
[EXP] .right.v10 = 50
.>cd left
.left>rra
[i16] .left.value = 2
[ i8] .left.Lflag = 65
[ i8] .left.Rflag = 66
[EXP] .left.v10 = 20
[i16] .left.left.value = 3
[ i8] .left.left.Lflag = 66
[ i8] .left.left.Rflag = 65
[EXP] .left.left.v10 = 30
[i16] .left.left.right.value = 4
[ i8] .left.left.right.Lflag = 66
[ i8] .left.left.right.Rflag = 66
[EXP] .left.left.right.v10 = 40
.left>
Note that rra displays the fully qualified names of all the elements and also indicates their types.
Internally the C# snippet is implemented as a recursive function that creates all the path values and recursively moves down each path possible.
string RecursiveReadAll(string path)
{
try
{
string realpath = path;
m.Seek(path);
if(path==".")
path="";
ArrayList ls = m.List();
foreach(MemberInfo mi in ls)
{
if(mi.Type == MemberTypes.StructType)
{ //recurse
if(mi.Length == 1)
{
RecursiveReadAll(path+"."+mi.Name);
m.Seek(realpath);
}
else
{
for(int i=0;i<mi.Length && i<MaxArrayDisplay;i++)
RecursiveReadAll(path+"."+mi.Name+":"+i.ToString());
m.Seek(realpath);
if(mi.Length > MaxArrayDisplay)
Console.WriteLine("Remaining {0} array members not displayed",
mi.Length - MaxArrayDisplay);
}
}
else if(mi.Length > 1)
{ //loop
string type;
if(mi.Type == MemberTypes.ExpressionType)
type="EXP";
else
type=mi.Type.ToString();
for(int i=0;i<mi.Length && i<MaxArrayDisplay;i++)
{
Console.WriteLine("[{2,3}] {0,1} = {1,1}",
path+"."+mi.Name+":"+i.ToString(),
m.GetValue(mi.Name,i),
type);
}
if(mi.Length > MaxArrayDisplay)
Console.WriteLine("Remaining {0} array members not displayed",
mi.Length - MaxArrayDisplay);
}
else
{
string type;
if(mi.Type == MemberTypes.ExpressionType)
type="EXP";
else
type=mi.Type.ToString();
Console.WriteLine("[{2,3}] {0} = {1,1}",
path+"."+mi.Name,
m.GetValue(mi.Name),
type);
}
}
}
catch(DDLException e)
{
Console.WriteLine("DDL Exception:\n{0}:{1}",e.DDLErrorType,e.DDLMessage);
}
catch(Exception e)
{
Console.WriteLine("Generic Exception:");
Console.WriteLine(e.Message+e.StackTrace);
}
return "";
}
If you understand this snippet, you would have understood all the essentials required to host and interact with the DDL engine.
This displays the current path (the same as is displayed in the prompt). So I don’t think this would be used very much. However you will notice that pwd immediately after the open will display a ‘.’ Even though the prompt does not reflect it.
(The cd command when used with no parameters display the path)
This displays the current running directory of the console application.
Will exit from the console.