ASCII art diagram converter: Difference between revisions
Content added Content deleted
m (Added 'draft task' marker) |
(+ D entry. Code by Adam D. Ruppe from the D newsgroup, modified and hopefully improved.) |
||
Line 44: | Line 44: | ||
Bonus: perform a thoroughly validation of the input string. |
Bonus: perform a thoroughly validation of the input string. |
||
=={{header|D}}== |
|||
This solution generates anonymous struct code at compile-time, that can be mixed-in inside a struct or class. |
|||
<lang d>string makeStructFromDiagram(in string rawDiagram) pure @safe { |
|||
import std.conv: text, format; |
|||
import std.string: strip, splitLines, indexOf; |
|||
import std.array: empty, popFront; |
|||
static void commitCurrent(ref uint anonCount, |
|||
ref uint totalBits, |
|||
ref uint currentBits, |
|||
ref string code, |
|||
ref string currentName) pure @safe { |
|||
if (currentBits) { |
|||
code ~= "\t"; |
|||
currentName = currentName.strip; |
|||
if (currentName.empty) { |
|||
anonCount++; |
|||
currentName = "anonymous_field_" ~ anonCount.text; |
|||
} |
|||
string type; |
|||
if (currentBits == 1) |
|||
type = "bool"; |
|||
else if (currentBits <= ubyte.sizeof * 8) |
|||
type = "ubyte"; |
|||
else if (currentBits <= ushort.sizeof * 8) |
|||
type = "ushort"; |
|||
else if (currentBits <= uint.sizeof * 8) |
|||
type = "uint"; |
|||
else if (currentBits <= ulong.sizeof * 8) |
|||
type = "ulong"; |
|||
//else if (currentBits <= ucent.sizeof * 8) |
|||
// type = "ucent"; |
|||
else assert(0, "Too many bits for the item "~ currentName); |
|||
immutable byteOffset = totalBits / 8; |
|||
immutable bitOffset = totalBits % 8; |
|||
// Getter: |
|||
code ~= "@property " ~ type ~ " " ~ currentName ~ |
|||
"() const pure nothrow @safe {\n"; |
|||
code ~= "\t\t"; |
|||
if (currentBits == 1) { |
|||
code ~= format("return (_payload[%d] & (1 << (7-%d)))"~ |
|||
" ? true : false;", |
|||
byteOffset, bitOffset); |
|||
} else if (currentBits < 8) { |
|||
auto mask = (1 << currentBits) - 1; |
|||
mask <<= 7 - bitOffset - currentBits + 1; |
|||
code ~= format("return (_payload[%d] & 0b%08b) >> %d;", |
|||
byteOffset, mask, |
|||
7 - bitOffset - currentBits + 1); |
|||
} else { |
|||
assert(currentBits % 8 == 0); |
|||
assert(bitOffset == 0); |
|||
code ~= type ~ " v = 0;\n\t\t"; |
|||
code ~= "version(LittleEndian) {\n\t\t"; |
|||
foreach (immutable i; 0 .. currentBits / 8) |
|||
code ~= "\tv |= (cast(" ~ type ~ ") _payload[" ~ |
|||
text(byteOffset + i) ~ "]) << (" ~ |
|||
text((currentBits / 8) - i - 1) ~ |
|||
" * 8);\n\t\t"; |
|||
code ~= "} else static assert(0);\n\t\t"; |
|||
code ~= "return v;"; |
|||
} |
|||
code ~= "\n"; |
|||
code ~= "\t}\n\t"; |
|||
// Setter: |
|||
code ~= "@property void " ~ currentName ~ "(in " ~ type ~ |
|||
" value) pure nothrow @safe {\n"; |
|||
code ~= "\t\t"; |
|||
if (currentBits < 8) { |
|||
auto mask = (1 << currentBits) - 1; |
|||
mask <<= 7 - bitOffset - currentBits + 1; |
|||
code ~= format("_payload[%d] &= ~0b%08b;\n\t\t", |
|||
byteOffset, mask); |
|||
code ~= "assert(value < " ~ text(1 << currentBits) ~ |
|||
");\n\t\t"; |
|||
code~=format("_payload[%d] |= cast(ubyte) value << %d;", |
|||
byteOffset, |
|||
7 - bitOffset - currentBits + 1); |
|||
} else { |
|||
assert(currentBits % 8 == 0); |
|||
assert(bitOffset == 0); |
|||
code ~= "version(LittleEndian) {\n\t\t"; |
|||
foreach (immutable i; 0 .. currentBits / 8) |
|||
code ~= "\t_payload[" ~ text(byteOffset + i) ~ |
|||
"] = (value >> (" ~ |
|||
text((currentBits / 8) - i - 1) ~ |
|||
" * 8) & 0xff);\n\t\t"; |
|||
code ~= "} else static assert(0);"; |
|||
} |
|||
code ~= "\n"; |
|||
code ~= "\t}\n"; |
|||
totalBits += currentBits; |
|||
} |
|||
currentBits = 0; |
|||
currentName = null; |
|||
} |
|||
enum C : char { pipe='|', cross='+' } |
|||
enum cWidth = 3; // Width of a bit cell in the table. |
|||
immutable diagram = rawDiagram.strip; |
|||
uint bitCountPerRow = 0, anonCount = 0, totalBits, currentBits; |
|||
string currentName; |
|||
string code = "struct {\n"; // Anonymous. |
|||
foreach (line; diagram.splitLines) { |
|||
line = line.strip; |
|||
if (line[0] == C.cross) { |
|||
commitCurrent(anonCount, totalBits, |
|||
currentBits, code, currentName); |
|||
if (bitCountPerRow == 0) |
|||
bitCountPerRow = (line.length - 1) / cWidth; |
|||
else |
|||
assert(bitCountPerRow == (line.length - 1) / cWidth); |
|||
} else { |
|||
// A field of some sort. |
|||
while (line.length > 2) { |
|||
assert(line[0] != '/', |
|||
"Variable length data not supported"); |
|||
assert(line[0] == C.pipe, "Malformed table"); |
|||
line.popFront; |
|||
const idx = line[0 .. $ - 1].indexOf(C.pipe); |
|||
if (idx != -1) { |
|||
const field = line[0 .. idx]; |
|||
line = line[idx .. $]; |
|||
commitCurrent(anonCount, totalBits, |
|||
currentBits, code, currentName); |
|||
currentName = field; |
|||
currentBits = (field.length + 1) / cWidth; |
|||
commitCurrent(anonCount, totalBits, |
|||
currentBits, code, currentName); |
|||
} else { |
|||
// The full row or a continuation of the last. |
|||
currentName ~= line[0 .. $ - 1]; |
|||
// At this point, line does not include the first |
|||
// C.pipe, but the length will include the last. |
|||
currentBits += line.length / cWidth; |
|||
line = line[$ .. $]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
// Using bytes to avoid endianness issues. |
|||
// hopefully the compiler will optimize it, otherwise |
|||
// maybe we could specialize the properties more. |
|||
code ~= "\n\tprivate ubyte[" ~ text((totalBits + 7) / 8) ~ |
|||
"] _payload;\n"; |
|||
return code ~ "}"; |
|||
} |
|||
void main() { // Testing. |
|||
import std.stdio; |
|||
enum diagram = " |
|||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
|||
| ID | |
|||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
|||
|QR| Opcode |AA|TC|RD|RA| Z | RCODE | |
|||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
|||
| QDCOUNT | |
|||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
|||
| ANCOUNT | |
|||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
|||
| NSCOUNT | |
|||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
|||
| ARCOUNT | |
|||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+"; |
|||
// To debug the code generation: |
|||
//pragma(msg, diagram.makeStructFromDiagram); |
|||
// Usage. |
|||
static struct Header { |
|||
mixin(diagram.makeStructFromDiagram); |
|||
} |
|||
Header h; |
|||
h.ID = 10; |
|||
h.RA = true; |
|||
h.ARCOUNT = 255; |
|||
h.Opcode = 7; |
|||
// See the byte representation to test the setter's details. |
|||
h._payload.writeln; |
|||
// Test the getters: |
|||
assert(h.ID == 10); |
|||
assert(h.RA == true); |
|||
assert(h.ARCOUNT == 255); |
|||
assert(h.Opcode == 7); |
|||
}</lang> |
|||
{{out}} |
|||
<pre>[0, 10, 56, 128, 0, 0, 0, 0, 0, 0, 0, 255]</pre> |
|||
Uncommenting the pragma(msg) in the main function, you can see that it generates code like: |
|||
<lang d>struct { |
|||
@property ushort ID() const pure nothrow @safe { |
|||
ushort v = 0; |
|||
version(LittleEndian) { |
|||
v |= (cast(ushort) _payload[0]) << (1 * 8); |
|||
v |= (cast(ushort) _payload[1]) << (0 * 8); |
|||
} else static assert(0); |
|||
return v; |
|||
} |
|||
@property void ID(in ushort value) pure nothrow @safe { |
|||
version(LittleEndian) { |
|||
_payload[0] = (value >> (1 * 8) & 0xff); |
|||
_payload[1] = (value >> (0 * 8) & 0xff); |
|||
} else static assert(0); |
|||
} |
|||
... |
|||
private ubyte[12] _payload; |
|||
}</lang> |
|||
It also supports larger values like this, that is 32 bits long: |
|||
<pre>+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
|||
| ThirtyTwo | |
|||
| | |
|||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+</pre> |