DEADSOFTWARE

added mapio generator (written in D)
[d2df-sdl.git] / src / tools / mapiogen / mapiogen.d
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 module mapiogen;
18 static assert(__VERSION__ >= 2071, "you need as least DMD 2.071 to compile this code");
20 import std.stdio;
22 import lexer;
25 // ////////////////////////////////////////////////////////////////////////// //
26 bool useDelphiAlignment = false;
27 ubyte[string] triggers;
28 string[ubyte] trignums;
31 // ////////////////////////////////////////////////////////////////////////// //
32 struct Field {
33 enum Type { Bytes, Chars, Integral, TPoint, Boolean }
35 string name;
36 string typename;
37 uint size;
38 uint ofs;
39 Type type;
40 }
42 struct Record {
43 string[] ids; // null: default
44 Field[] fields;
45 uint size; // max size
46 bool normal;
48 string getRWName () const {
49 import std.string : capitalize;
50 if (ids.length == 0) return "Default";
51 if (normal) return ids[0].capitalize;
52 return ids[0][8..$].capitalize;
53 }
55 // calc field offsets and record size
56 void finalize (bool packed=false) {
57 // calculate offsets
58 uint ofs = 0;
59 foreach (immutable idx, ref fld; fields) {
60 fld.ofs = ofs;
61 // delphi does this (roughly)
62 if (!packed && fld.size != 1) {
63 if (useDelphiAlignment && fld.type == Field.Type.TPoint) {
64 } else {
65 //ubyte pd = (fld.size > 4 ? 2 : fld.size > 2 ? 4 : 2);
66 ubyte pd = (fld.size > 2 ? 4 : 2);
67 if (fld.type == Field.Type.Chars && fld.size > 1) pd = 2;
68 fld.ofs += ofs.padding(pd);
69 }
70 }
71 ofs = fld.ofs+fld.size;
72 }
73 size = ofs;
74 //if (fields.length > 0 && fields[$-1].size != 1 && fields[$-1].type == Field.Type.Integral) size += size.padding(2); // just in case
75 }
76 }
79 Record[] tgrecords;
80 Record[] records;
83 // ////////////////////////////////////////////////////////////////////////// //
84 // 0 128 Default (Byte128)
85 void dumpRecord (in ref Record rec) {
86 foreach (const ref fld; rec.fields) {
87 writefln("%3s %3s %s (%s)", fld.ofs, fld.size, fld.name, fld.typename);
88 }
89 }
92 void dumpRecords () { foreach (const ref rec; tgrecords) rec.dumpRecord(); }
95 // ////////////////////////////////////////////////////////////////////////// //
96 void genMisc (File fo) {
97 fo.write(
98 q{procedure getBytesAt (var dest; const buf; ofs, len: Integer);
99 begin
100 Move((PChar(@buf)+ofs)^, dest, len);
101 end;
103 procedure getWordAt (var dest; const buf; ofs: Integer);
104 type PWord = ^Word; PByte = ^Byte;
105 var
106 p: PByte;
107 d: PWord;
108 begin
109 p := PByte(@buf); Inc(p, ofs);
110 d := PWord(@dest);
111 d^ := p^;
112 Inc(p);
113 d^ := (d^) or ((p^) shl 8);
114 end;
116 procedure getIntAt (var dest; const buf; ofs: Integer);
117 type PInt = ^LongWord; PByte = ^Byte;
118 var
119 p: PByte;
120 d: PInt;
121 begin
122 p := PByte(@buf); Inc(p, ofs);
123 d := PInt(@dest);
124 d^ := p^;
125 Inc(p);
126 d^ := (d^) or ((p^) shl 8);
127 Inc(p);
128 d^ := (d^) or ((p^) shl 16);
129 Inc(p);
130 d^ := (d^) or ((p^) shl 24);
131 end;
133 procedure putBytesAt (var buf; ofs: Integer; const src; len: Integer);
134 begin
135 Move(src, (PChar(@buf)+ofs)^, len);
136 end;
138 procedure putWordAt (var buf; ofs: Integer; const src);
139 type PWord = ^Word; PByte = ^Byte;
140 var
141 p: PByte;
142 d: PWord;
143 begin
144 p := PByte(PChar(@buf)+ofs);
145 d := PWord(@src);
146 p^ := (d^) and $ff;
147 Inc(p);
148 p^ := ((d^) shr 8) and $ff;
149 end;
151 procedure putIntAt (var buf; ofs: Integer; const src);
152 type PInt = ^LongWord; PByte = ^Byte;
153 var
154 p: PByte;
155 d: PInt;
156 begin
157 p := PByte(PChar(@buf)+ofs);
158 d := PInt(@src);
159 p^ := (d^) and $ff;
160 Inc(p);
161 p^ := ((d^) shr 8) and $ff;
162 Inc(p);
163 p^ := ((d^) shr 16) and $ff;
164 Inc(p);
165 p^ := ((d^) shr 24) and $ff;
166 end;
168 });
172 void genReader (File fo, in ref Record rec) {
173 fo.write(
174 " procedure xread", rec.getRWName, " ();\n"~
175 " begin\n"
176 );
177 foreach (const ref fld; rec.fields) {
178 final switch (fld.type) {
179 case Field.Type.Bytes:
180 case Field.Type.Chars:
181 case Field.Type.Boolean:
182 fo.writeln(" getBytesAt(tr.", fld.name, ", buf, ", fld.ofs, ", ", fld.size, ");");
183 break;
184 case Field.Type.Integral:
185 switch (fld.size) {
186 case 1: fo.writeln(" getBytesAt(tr.", fld.name, ", buf, ", fld.ofs, ", ", fld.size, ");"); break;
187 case 2: fo.writeln(" getWordAt(tr.", fld.name, ", buf, ", fld.ofs, ");"); break;
188 case 4: fo.writeln(" getIntAt(tr.", fld.name, ", buf, ", fld.ofs, ");"); break;
189 default: assert(0);
191 break;
192 case Field.Type.TPoint:
193 fo.writeln(" getIntAt(tr.", fld.name, ".x, buf, ", fld.ofs, ");");
194 fo.writeln(" getIntAt(tr.", fld.name, ".y, buf, ", fld.ofs+4, ");");
195 break;
198 fo.writeln(" end;\n");
202 void genReaders (File fo) {
203 fo.write(
204 "procedure mb_Read_TriggerData (var tr: TTriggerData; ttype: Integer; const buf; bufsize: Integer);\n"
205 );
206 uint maxsize = 0;
207 foreach (const ref rec; tgrecords) {
208 if (rec.ids.length == 0) continue;
209 if (rec.size > maxsize) maxsize = rec.size;
210 fo.genReader(rec);
212 fo.write(
213 "begin\n"~
214 " if (bufsize < ", maxsize, ") then raise Exception.Create('invalid buffer size in mb_Read_TriggerData');\n"
215 );
216 foreach (const ref rec; tgrecords) {
217 foreach (string id; rec.ids) {
218 fo.writeln(" if (ttype = ", id, ") then begin xread", rec.getRWName, "(); exit; end;");
221 fo.writeln(" raise Exception.Create('invalid trigger type in mb_Read_TriggerData');");
222 fo.writeln("end;\n\n");
223 foreach (ref rec; records) {
224 assert(rec.normal);
225 fo.writeln("procedure mb_Read_", rec.ids[0], " (var tr: ", rec.ids[0], "; const buf; bufsize: Integer);");
226 fo.genReader(rec);
227 fo.write(
228 "begin\n"~
229 " if (bufsize < ", rec.size, ") then raise Exception.Create('invalid buffer size in read", rec.ids[0], "');\n"
230 " xread", rec.getRWName, "();\n"~
231 "end;\n\n"
232 );
237 void genWriter (File fo, in ref Record rec) {
238 fo.write(
239 " procedure xwrite", rec.getRWName, " ();\n"~
240 " begin\n"
241 );
242 foreach (const ref fld; rec.fields) {
243 final switch (fld.type) {
244 case Field.Type.Bytes:
245 case Field.Type.Chars:
246 case Field.Type.Boolean:
247 fo.writeln(" putBytesAt(buf, ", fld.ofs, ", tr.", fld.name, ", ", fld.size, ");");
248 break;
249 case Field.Type.Integral:
250 switch (fld.size) {
251 case 1: fo.writeln(" putBytesAt(buf, ", fld.ofs, ", tr.", fld.name, ", ", fld.size, ");"); break;
252 case 2: fo.writeln(" putWordAt(buf, ", fld.ofs, ", tr.", fld.name, ");"); break;
253 case 4: fo.writeln(" putIntAt(buf, ", fld.ofs, ", tr.", fld.name, ");"); break;
254 default: assert(0);
256 break;
257 case Field.Type.TPoint:
258 fo.writeln(" putIntAt(buf, ", fld.ofs , ", tr.", fld.name, ".x);");
259 fo.writeln(" putIntAt(buf, ", fld.ofs+4, ", tr.", fld.name, ".y);");
260 break;
263 fo.writeln(" end;\n");
267 void genWriters (File fo) {
268 fo.write(
269 "procedure mb_Write_TriggerData (var buf; bufsize: Integer; ttype: Integer; var tr: TTriggerData);\n"
270 );
271 uint maxsize = 0;
272 foreach (const ref rec; tgrecords) {
273 assert(!rec.normal);
274 if (rec.ids.length == 0) continue;
275 if (rec.size > maxsize) maxsize = rec.size;
276 fo.genWriter(rec);
278 fo.write(
279 "begin\n"~
280 " if (bufsize < ", maxsize, ") then raise Exception.Create('invalid buffer size in mb_Write_TriggerData');\n"
281 );
282 foreach (const ref rec; tgrecords) {
283 foreach (string id; rec.ids) {
284 fo.writeln(" if (ttype = ", id, ") then begin xwrite", rec.getRWName, "(); exit; end;");
287 fo.writeln(" raise Exception.Create('invalid trigger type in mb_Write_TriggerData');");
288 fo.writeln("end;\n\n");
289 foreach (ref rec; records) {
290 assert(rec.normal);
291 fo.writeln("procedure mb_Write_", rec.ids[0], " (var buf; bufsize: Integer; var tr: ", rec.ids[0], ");");
292 fo.genWriter(rec);
293 fo.write(
294 "begin\n"~
295 " if (bufsize < ", rec.size, ") then raise Exception.Create('invalid buffer size in write", rec.ids[0], "');\n"
296 " xwrite", rec.getRWName, "();\n"~
297 "end;\n\n"
298 );
303 // ////////////////////////////////////////////////////////////////////////// //
304 void printCaret (Lexer lex, Loc loc, File ofile=stdout) {
305 auto line = lex.line(loc.line);
306 if (line.length == 0) return;
307 ofile.writeln(line);
308 foreach (immutable _; 1..loc.col) ofile.write(' ');
309 ofile.writeln('^');
313 // ////////////////////////////////////////////////////////////////////////// //
314 ubyte padding (uint size, ubyte alg) {
315 uint nsz = (size+alg-1)/alg*alg;
316 return cast(ubyte)(nsz-size);
320 // ////////////////////////////////////////////////////////////////////////// //
321 void parseType (ref Field fld, const(char)[] typestr, Lexer lex) {
322 import std.algorithm : startsWith;
323 import std.string : toLower;
324 auto type = typestr.toLower;
325 if (type.startsWith("byte") || type.startsWith("char")) {
326 import std.conv : to;
327 fld.type = (type[0] == 'b' ? Field.Type.Bytes : Field.Type.Chars);
328 if (type.length == 4) {
329 fld.size = 1;
330 return;
332 try {
333 auto sz = to!uint(type[4..$]);
334 if (sz < 1 || sz > 32767) throw new Exception("invalid size");
335 fld.size = sz;
336 return;
337 } catch (Exception) {}
338 } else if (type == "tpoint") {
339 fld.type = Field.Type.TPoint;
340 fld.size = 4*2;
341 return;
342 } else if (type == "boolean") {
343 fld.type = Field.Type.Boolean;
344 fld.size = 1;
345 return;
346 } else if (type == "integer") {
347 fld.type = Field.Type.Integral;
348 fld.size = 4;
349 return;
350 } else if (type == "word") {
351 fld.type = Field.Type.Integral;
352 fld.size = 2;
353 return;
354 } else if (type == "shortint") {
355 fld.type = Field.Type.Integral;
356 fld.size = 1;
357 return;
359 lex.error("invalid type: '"~typestr.idup~"'");
363 /*
364 (TargetPoint: TPoint;
365 d2d_teleport: Boolean;
366 silent_teleport: Boolean;
367 TlpDir: Byte);
368 */
369 Field[] parseFields (Lexer lex) {
370 Field[] res;
371 if (!lex.isSpec || lex.front.str != "(") lex.error("'(' expected");
372 lex.popFront();
373 for (;;) {
374 if (lex.isSpec && lex.front.str == ")") { lex.popFront(); break; }
375 string[] names;
376 for (;;) {
377 names ~= lex.expectId();
378 if (lex.isSpec && lex.front.str == ":") break;
379 if (lex.isSpec && lex.front.str == ",") { lex.popFront(); continue; }
380 lex.error("':' expected");
382 if (!lex.isSpec || lex.front.str != ":") lex.error("':' expected");
383 lex.popFront();
384 auto type = lex.expectId();
385 //writeln(" ", names[], ": <", type, ">");
386 foreach (string name; names) {
387 Field fld;
388 fld.name = name;
389 fld.typename = type;
390 fld.parseType(type, lex);
391 res ~= fld;
393 if (!lex.isSpec) lex.error("';' or ')' expected");
394 if (lex.front.str == ";") { lex.popFront(); continue; }
395 if (lex.front.str != ")") lex.error("';' or ')' expected");
397 if (lex.isSpec && lex.front.str == ";") lex.popFront();
398 return res;
402 /*
403 TargetPoint: TPoint;
404 d2d_teleport: Boolean;
405 silent_teleport: Boolean;
406 TlpDir: Byte;
407 end;
408 */
409 Field[] parseRecFields (Lexer lex) {
410 Field[] res;
411 for (;;) {
412 if (lex.eatCI("end") !is null) break;
413 string[] names;
414 for (;;) {
415 names ~= lex.expectId();
416 if (lex.isSpec && lex.front.str == ":") break;
417 if (lex.isSpec && lex.front.str == ",") { lex.popFront(); continue; }
418 lex.error("':' expected");
420 if (!lex.isSpec || lex.front.str != ":") lex.error("':' expected");
421 lex.popFront();
422 auto type = lex.expectId();
423 foreach (string name; names) {
424 Field fld;
425 fld.name = name;
426 fld.typename = type;
427 fld.parseType(type, lex);
428 res ~= fld;
430 if (lex.eatCI("end") !is null) break;
431 if (!lex.isSpec || lex.front.str != ";") lex.error("';' expected");
432 lex.popFront();
434 return res;
438 // ////////////////////////////////////////////////////////////////////////// //
439 bool isGoodTriggerName (const(char)[] id) {
440 import std.algorithm : startsWith;
441 import std.string : indexOf;
442 if (!id.startsWith("TRIGGER_")) return false;
443 if (id == "TRIGGER_MAX") return false;
444 if (id[8..$].indexOf('_') >= 0) return false;
445 return true;
449 // ////////////////////////////////////////////////////////////////////////// //
450 void parseMapDef (string fname) {
451 import std.string : format, toLower, toUpper;
452 Lexer lex;
454 auto fl = File(fname);
455 auto buf = new char[](cast(uint)fl.size);
456 fl.rawRead(buf[]);
457 lex = new Lexer(cast(string)buf, fname);
459 // find "interface"
460 while (!lex.empty) {
461 if (!lex.front.isId) { lex.popFront(); continue; }
462 if (lex.front.str.toLower == "interface") break;
463 lex.popFront();
465 if (lex.empty) throw new Exception("where is my interface?!");
466 enum Section { Unknown, Const, Type }
467 Section section;
468 while (!lex.empty) {
469 if (lex.front.isId) {
470 auto kw = lex.front.str.toLower;
471 if (kw == "implementation") break;
472 if (kw == "const") {
473 //writeln("CONST!");
474 section = Section.Const;
475 lex.popFront();
476 continue;
478 if (kw == "type") {
479 //writeln("TYPE!");
480 section = Section.Type;
481 lex.popFront();
482 continue;
485 if (section == Section.Const) {
486 if (!lex.isId) lex.error("identifier expected");
487 auto id = lex.front.istr.toUpper;
488 lex.popFront();
489 auto lc = lex.loc;
490 if (lex.expectSpec() != "=") lex.error(lc, "'=' expected");
491 if (isGoodTriggerName(id)) {
492 lex.mustbeNum();
493 auto lcn = lex.loc;
494 auto n = lex.front.num;
495 lex.popFront();
496 if (n < 0 || n > 255) lex.error(lcn, "invalid value (%s) for '%s'".format(n, id));
497 auto b = cast(ubyte)n;
498 if (id in triggers) lex.error(lc, "duplicate constant '%s'".format(id));
499 if (auto tg = b in trignums) lex.error(lcn, "same value (%s) for triggers '%s' and '%s'".format(n, id, *tg));
500 triggers[id] = b;
501 trignums[b] = id;
502 //writeln("trigger: ", id, " (", b, ")");
503 } else {
504 while (!lex.empty) {
505 if (lex.front.isSpec && lex.front.str == ";") break;
506 lex.popFront();
509 lc = lex.loc;
510 if (lex.expectSpec() != ";") lex.error(lc, "';' expected");
511 continue;
513 if (section == Section.Type) {
514 if (!lex.isId) lex.error("identifier expected");
515 auto id = lex.front.istr.toUpper;
516 lex.popFront();
517 auto lc = lex.loc;
518 if (lex.expectSpec() != "=") lex.error(lc, "'=' expected");
519 //writeln("id: ", id);
520 if (id != "TTRIGGERDATA") {
521 // skip definition
522 while (!lex.empty) {
523 if (lex.eatCI("end") !is null) break;
524 lex.popFront();
526 lc = lex.loc;
527 if (lex.expectSpec() != ";") lex.error(lc, "';' expected");
528 continue;
529 } else {
530 lex.expectCI("record");
531 lex.expectCI("case");
532 lex.expectCI("byte");
533 lex.expectCI("of");
534 // now parse defs
535 for (;;) {
536 if (lex.eatCI("end") !is null) break;
537 string[] ids;
538 Field[] fields;
539 if (lex.isNum) {
540 if (lex.front.num != 0) lex.error(lc, "'0' expected");
541 lex.popFront();
542 if (!lex.isSpec || lex.front.str != ":") lex.error("':' expected");
543 lex.popFront();
544 //writeln("=== DEFAULT ===");
545 ids = null;
546 fields = lex.parseFields();
547 } else {
548 for (;;) {
549 ids ~= lex.expectId();
550 if (lex.isSpec && lex.front.str == ":") { lex.popFront(); break; }
551 if (lex.isSpec && lex.front.str == ",") { lex.popFront(); continue; }
552 lex.error("',' or ':' expected");
554 //writeln("=== ", ids[], " ===");
555 fields = lex.parseFields();
557 tgrecords ~= Record(ids, fields);
558 tgrecords[$-1].finalize;
559 //writeln("=== ", ids[], " === : ", rcsize);
561 lc = lex.loc;
562 if (lex.expectSpec() != ";") lex.error(lc, "';' expected");
563 break; // we are done
566 lex.popFront();
567 continue;
572 // ////////////////////////////////////////////////////////////////////////// //
573 void parseMapStruct (string fname) {
574 import std.string : format, toLower, toUpper;
576 static bool isGoodRecName (const(char)[] id) {
577 import std.algorithm : startsWith, endsWith;
578 import std.string : indexOf, toUpper;
579 id = id.toUpper;
580 if (!id.startsWith("T") || !id.endsWith("_1")) return false;
581 return true;
584 static bool isGoodDef (Lexer lex) {
585 import std.string : toLower;
586 auto tk = lex.peek(1);
587 if (!tk.isSpec || tk.str != "=") return false;
588 tk = lex.peek(2);
589 if (!tk.isId || tk.str.toLower != "packed") return false;
590 tk = lex.peek(3);
591 if (!tk.isId || tk.str.toLower != "record") return false;
592 return true;
595 Lexer lex;
597 auto fl = File(fname);
598 auto buf = new char[](cast(uint)fl.size);
599 fl.rawRead(buf[]);
600 lex = new Lexer(cast(string)buf, fname);
602 // find "interface"
603 while (!lex.empty) {
604 if (!lex.front.isId) { lex.popFront(); continue; }
605 if (lex.front.str.toLower == "interface") break;
606 lex.popFront();
608 if (lex.empty) throw new Exception("where is my interface?!");
609 enum Section { Unknown, Type }
610 Section section;
611 while (!lex.empty) {
612 if (lex.front.isId) {
613 auto kw = lex.front.str.toLower;
614 if (kw == "implementation") break;
615 if (kw == "type") {
616 section = Section.Type;
617 lex.popFront();
618 continue;
621 if (section == Section.Type) {
622 if (lex.isId && isGoodRecName(lex.front.str) && isGoodDef(lex)) {
623 string origId = lex.front.istr;
624 lex.popFront();
625 lex.popFront(); // skip "="
626 lex.expectCI("packed");
627 lex.expectCI("record");
628 // now parse fields
629 Record rec;
630 rec.ids ~= origId;
631 rec.fields = lex.parseRecFields();
632 rec.normal = true;
633 rec.finalize(true);
634 records ~= rec;
636 auto lc = lex.loc;
637 if (lex.expectSpec() != ";") lex.error(lc, "';' expected");
639 continue;
642 lex.popFront();
643 continue;
648 // ////////////////////////////////////////////////////////////////////////// //
649 void main () {
650 try {
651 parseMapDef("../../shared/MAPDEF.pas");
652 parseMapStruct("../../shared/MAPSTRUCT.pas");
653 debug {
654 dumpRecords();
655 } else {
657 auto fo = File("mapstructio.inc", "w");
658 fo.genMisc();
659 fo.genReaders();
660 fo.genWriters();
663 auto fo = File("mapstructsizes.inc", "w");
664 fo.writeln("const");
665 foreach (ref rec; records) fo.writeln(" SizeOf_", rec.ids[0], " = ", rec.size, ";");
666 fo.writeln();
667 fo.writeln("procedure mb_Read_TriggerData (var tr: TTriggerData; ttype: Integer; const buf; bufsize: Integer);");
668 fo.writeln("procedure mb_Write_TriggerData (var buf; bufsize: Integer; ttype: Integer; var tr: TTriggerData);");
669 foreach (ref rec; records) {
670 fo.writeln("procedure mb_Read_", rec.ids[0], " (var tr: ", rec.ids[0], "; const buf; bufsize: Integer);");
671 fo.writeln("procedure mb_Write_", rec.ids[0], " (var buf; bufsize: Integer; var tr: ", rec.ids[0], ");");
675 } catch (ErrorAt e) {
676 writeln("PARSE ERROR: ", e.loc, ": ", e.msg);
677 //lex.printCaret(e.loc);