Generating data structures from text diagrams

Data structures are often defined with text diagrams. Like domain-specific languages, D can parse these strings and generate data structure definitions at compile time. We'll briefly demonstrate the technique by writing a parser for a simple diagram.

How to do it…

Let's execute the following steps to generate data structures from text diagrams:

  1. Start by writing a regular program to parse the diagram into structured fields. For our diagram, we'll want to split it into lines and then split the data lines into individual fields. The name will be the text within and the length will be one byte per four characters.
  2. Take the structured data and generate the D code from it with string concatenation. Here, we'll build an anonymous structure with each field from the diagram, using simple integral types to match the size. Write out your code with pragma(msg) or at runtime while debugging until the code looks right.
  3. Use the mixin expression to compile your code in a struct block. If you use this pattern often, you may encapsulate it in a struct block with a compile-time string parameter and then alias that parameter to a final name.

This is shown in the following code:

enum diagramString = '
+------------------+
|LEN | ID | MSG    |
+------------------+
';

struct DiagramField {
  string name;
  int length;
}
DiagramField[] readDiagram(string diagram) {
  DiagramField[] fields;
  import std.string;
  auto lines = diagram.splitLines();
  foreach(line; lines) {
    if(line.length == 0) continue; // blank line
    if(line[0] == '+') continue; // separator line

    auto parts = line.split("|");
    foreach(part; parts) {
      if(part.length == 0) continue;
      DiagramField field;
      field.name = part.strip;
      field.length = part.length / 4;
      fields ~= field;
    }
  }

  return fields;
}

string toStructDefinition(DiagramField[] fields) {
  string code = "struct {
";

  foreach(field; fields) {
    string type;
    switch(field.length) {
      case 1: type = "ubyte"; break;
      case 2: type = "ushort"; break;
      case 4: type = "uint"; break;
      case 8: type = "ulong"; break;
      default: assert(0);
    }
    code ~= "	" ~ type ~ " " ~ field.name ~ ";
";
  }

  code ~= "
}";
  return code;
}

struct StructFromDiagram(string diagram) {
  mixin(toStructDefinition(readDiagram(diagram)));
}
alias Message = StructFromDiagram!diagramString;

/*
// an alternative way to form to the struct
struct Message {
  mixin(toStructDefinition(readDiagram(diagramString)));
}
*/

void main() {
  import std.stdio;
  debug writeln(toStructDefinition(readDiagram(diagramString)));
  Message m;
  m.ID = 5;
  writeln(m);
}

The output will be as follows:

StructFromDiagram!("x0a+------------------+x0a|LEN | ID | MSG    |x0a+------------------+x0a")(0, 5, 0)

Since we used the alias method, the internal type name (used in the automatic toString implementation) is made from compile-time arguments, including the diagram. The alternate way results in a different name, but the same data.

How it works…

Whereas we made data and code in the previous two recipes, here we finished our demonstration of the technique discussed in the previous recipes by creating a data structure from a string at compile time. The pattern is similar: parse the string and then output what it represents in D with the help of templates and mixins. We simply generate a struct definition with appropriate data members. This definition can be mixed into an existing struct type or aliased to a more convenient name and used directly. While parsing a diagram looks different, the code is basically the same as any other domain-specific language.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.230.81