From 7eb02038c73a6801b647bb4a855f9101ee4f41dc Mon Sep 17 00:00:00 2001 From: Ketmar Dark Date: Tue, 21 Jun 2016 05:30:12 +0300 Subject: [PATCH] added mapio generator (written in D) --- src/tools/mapiogen/lexer.d | 607 ++++++++++++++++++++++++++++++ src/tools/mapiogen/mapiogen.d | 679 ++++++++++++++++++++++++++++++++++ 2 files changed, 1286 insertions(+) create mode 100644 src/tools/mapiogen/lexer.d create mode 100644 src/tools/mapiogen/mapiogen.d diff --git a/src/tools/mapiogen/lexer.d b/src/tools/mapiogen/lexer.d new file mode 100644 index 0000000..8b3ff2e --- /dev/null +++ b/src/tools/mapiogen/lexer.d @@ -0,0 +1,607 @@ +/* coded by Ketmar // Invisible Vector + * Understanding is not required. Only obedience. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +module lexer; +static assert(__VERSION__ >= 2071, "you need as least DMD 2.071 to compile this code"); + + +// ////////////////////////////////////////////////////////////////////////// // +static if (!is(typeof(usize))) private alias usize = size_t; + + +// ////////////////////////////////////////////////////////////////////////// // +public struct Loc { + string file; + int line, col; + uint tpos; + + string toString () const { import std.string : format; return "%s (%s,%s)".format(file, line, col); } + string toStringNoFile () const { import std.string : format; return "(%s,%s)".format(line, col); } + + @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (line > 0 && col > 0); } +} + + +// ////////////////////////////////////////////////////////////////////////// // +public class ErrorAt : Exception { + Loc loc; + + this (string msg, Throwable next=null, string file=__FILE__, usize line=__LINE__) pure nothrow @safe @nogc { super(msg, file, line, next); } + this (in Loc aloc, string msg, Throwable next=null, string file=__FILE__, usize line=__LINE__) pure nothrow @safe @nogc { loc = aloc; super(msg, file, line, next); } +} + + +// ////////////////////////////////////////////////////////////////////////// // +public struct Token { +public: + enum Type { + EOF = -1, + Id, + Str, + Num, + Spec, + } + +private: + const(char)[] tkstr; + +public: + Loc loc, eloc; // token start, token end (after last char) + Type type = Type.EOF; // token type + long num; // should be enough for everyone + +@safe: + void mustbeType (Token.Type tp, string msg="identifier expected", string file=__FILE__, usize line=__LINE__) { + pragma(inline, true); + if (type != tp) throw new ErrorAt(loc, msg, null, file, line); + } + void mustbeId (string msg="identifier expected", string file=__FILE__, usize line=__LINE__) { pragma(inline, true); mustbeType(Type.Id, msg, file, line); } + void mustbeStr (string msg="string expected", string file=__FILE__, usize line=__LINE__) { pragma(inline, true); mustbeType(Type.Str, msg, file, line); } + void mustbeNum (string msg="number expected", string file=__FILE__, usize line=__LINE__) { pragma(inline, true); mustbeType(Type.Num, msg, file, line); } + void mustbeSpec (string msg="punctuation expected", string file=__FILE__, usize line=__LINE__) { pragma(inline, true); mustbeType(Type.Spec, msg, file, line); } + + string toString () const @trusted { + import std.string : format; + final switch (type) with (Type) { + case EOF: return "(%s,%d): ".format(loc.line, loc.col); + case Id: return "(%s,%d): Id:%s".format(loc.line, loc.col, tkstr); + case Str: return "(%s,%d): Str:%s".format(loc.line, loc.col, Lexer.quote(tkstr)); + case Num: return "(%s,%d): Num:%s".format(loc.line, loc.col, num); + case Spec: return "(%s,%d): Spec:<%s>".format(loc.line, loc.col, tkstr); + } + assert(0); + } + +nothrow: + // get immutable string + // this converts id to `string` via `.idup`, use with caution! + // `.idup` is used to not anchor the whole source string + @property string istr () const { pragma(inline, true); return (tkstr.length ? tkstr.idup : null); } + +const pure nothrow @nogc @property: + const(char)[] str () { pragma(inline, true); return tkstr; } + bool isId () { pragma(inline, true); return (type == Type.Id); } + bool isStr () { pragma(inline, true); return (type == Type.Str); } + bool isNum () { pragma(inline, true); return (type == Type.Num); } + bool isSpec () { pragma(inline, true); return (type == Type.Spec); } + bool isEOF () { pragma(inline, true); return (type == Type.EOF); } +} + + +// ////////////////////////////////////////////////////////////////////////// // +public final class Lexer { +private: + const(char)[] text; + uint tpos; + Loc cpos; // position for last `getChar()` + Loc pend; // end of previous token, for better error messages + bool eof; + bool lastWasEOL = true; + Token[] lookup; + Token tokeof; // will be fixed by `nextToken()` + +public: + this(T) (const(char)[] atext, T afname=null) if (is(T : const(char)[])) { + text = atext; + if (afname.length > 0) { static if (is(T == string)) cpos.file = afname; else cpos.file = afname.idup; } + tokeof.loc.file = cpos.file; + nextToken(); + pend.line = 1; + pend.col = 1; + pend.tpos = 0; + } + + void error (string msg, string file=__FILE__, usize line=__LINE__) { + pragma(inline, true); + throw new ErrorAt((lookup.length == 0 ? loc : lookup[0].loc), msg, null, file, line); + } + + static private void error (in ref Token tk, string msg, string file=__FILE__, usize line=__LINE__) { + pragma(inline, true); + throw new ErrorAt(tk.loc, msg, null, file, line); + } + + static private void error() (in auto ref Loc loc, string msg, string file=__FILE__, usize line=__LINE__) { + pragma(inline, true); + throw new ErrorAt(loc, msg, null, file, line); + } + + const(char)[] line (uint idx) { + if (idx == 0) ++idx; + uint pos = 0; + while (--idx > 0) { + while (pos < text.length && text.ptr[pos] != '\n') ++pos; + ++pos; + } + if (pos >= text.length) return null; + uint epos = pos; + while (epos < text.length && text.ptr[epos] != '\n') ++epos; + while (epos > pos && text.ptr[epos-1] <= ' ') --epos; + return text[pos..epos]; + } + + void popFront () { + if (lookup.length > 0) { + pend = lookup.ptr[0].eloc; + ++pend.col; // for better error messages + ++pend.tpos; // to be consistent + foreach (immutable idx; 1..lookup.length) lookup.ptr[idx-1] = lookup.ptr[idx]; + lookup.length -= 1; + lookup.assumeSafeAppend; + } + nextToken(); + } + + @property pure nothrow @safe @nogc { + bool empty () const { pragma(inline, true); return (lookup.length == 0); } + ref inout(Token) front () inout { pragma(inline, true); return (lookup.length ? lookup.ptr[0] : tokeof); } + // current token's loc + auto loc () inout { pragma(inline, true); return front.loc; } + auto eloc () inout { pragma(inline, true); return front.eloc; } + auto peloc () inout { pragma(inline, true); return pend; } + + bool isId () const { pragma(inline, true); return front.isId; } + bool isStr () const { pragma(inline, true); return front.isStr; } + bool isNum () const { pragma(inline, true); return front.isNum; } + bool isSpec () const { pragma(inline, true); return front.isSpec; } + } + + // this eats identifier + void expect (const(char)[] id, string file=__FILE__, usize line=__LINE__) { + if (!front.isId || front.str != id) error(loc, "`"~id.idup~"` expected", file, line); + popFront(); + } + + // this eats identifier + void expectCI (const(char)[] id, string file=__FILE__, usize line=__LINE__) { + if (front.isId && id.length == front.str.length) { + bool ok = true; + foreach (immutable idx, char ch; front.str) { + if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's `tolower()` + char c1 = id[idx]; + if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's `tolower()` + if (ch != c1) { ok = false; break; } + } + if (ok) { popFront(); return; } + } + error(loc, "`"~id.idup~"` expected", file, line); + } + + auto expectSpec (string msg="punctuation expected", string file=__FILE__, usize line=__LINE__) { + mustbeSpec(msg, file, line); + auto res = lookup[0].str; + popFront(); + return res; + } + + // this converts id to `string` via `.idup`, use with caution! + // `.idup` is used to not anchor the whole source string + string expectId (string msg="identifier expected", string file=__FILE__, usize line=__LINE__) { + mustbeId(msg, file, line); + auto res = lookup[0].istr; + popFront(); + return res; + } + + // this converts id to `string` via `.idup`, use with caution! + // `.idup` is used to not anchor the whole source string + string expectStr (string msg="string expected", string file=__FILE__, usize line=__LINE__) { + //pragma(inline, true); + mustbeStr(msg, file, line); + auto res = lookup[0].istr; + popFront(); + return res; + } + + // `mustbe` doesn't eat token + void mustbeType (Token.Type tp, string msg="identifier expected", string file=__FILE__, usize line=__LINE__) { pragma(inline, true); return front.mustbeType(tp, msg, file, line); } + void mustbeId (string msg="identifier expected", string file=__FILE__, usize line=__LINE__) { pragma(inline, true); return front.mustbeId(msg, file, line); } + void mustbeStr (string msg="string expected", string file=__FILE__, usize line=__LINE__) { pragma(inline, true); return front.mustbeStr(msg, file, line); } + void mustbeNum (string msg="number expected", string file=__FILE__, usize line=__LINE__) { pragma(inline, true); return front.mustbeNum(msg, file, line); } + void mustbeSpec (string msg="punctuation expected", string file=__FILE__, usize line=__LINE__) { pragma(inline, true); return front.mustbeSpec(msg, file, line); } + + bool eat (const(char)[] id) { + if (front.isId && front.str == id) { popFront(); return true; } + return false; + } + + const(char)[] eatCI (const(char)[] id) { + if (front.isId && id.length == front.str.length) { + bool ok = true; + foreach (immutable idx, char ch; front.str) { + if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's `tolower()` + char c1 = id[idx]; + if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's `tolower()` + if (ch != c1) { ok = false; break; } + } + if (ok) { auto res = front.str; popFront(); return res; } + } + return null; + } + + ref Token peek (uint dist) { + while (!eof && lookup.length <= dist) nextToken(); + return (dist < lookup.length ? lookup.ptr[dist] : tokeof); + } + + ref Token opIndex (usize dist) { pragma(inline, true); return peek(dist); } + + // return loc for next `getChar()` + Loc nextLoc () nothrow @safe @nogc { + Loc res = cpos; + if (lastWasEOL) { ++res.line; res.col = 1; } else ++res.col; + return res; + } + + char peekChar (uint dist=0) nothrow @trusted @nogc { + pragma(inline, true); + return (tpos+dist >= text.length ? '\0' : (text.ptr[tpos+dist] ? text.ptr[tpos+dist] : ' ')); + } + + // return char or 0 + char getChar () nothrow @trusted @nogc { + if (tpos >= text.length) { tpos = text.length; eof = true; } + if (eof) return '\0'; + cpos.tpos = tpos; + char ch = text.ptr[tpos++]; + if (ch == '\0') ch = ' '; + if (lastWasEOL) { ++cpos.line; cpos.col = 1; } else ++cpos.col; + lastWasEOL = (ch == '\n'); + return ch; + } + + // skip blanks and comments + //TODO: make special "comment" token(s)? + void skipBlanks () @safe { + for (;;) { + char ch = peekChar; + if (ch == '/' && peekChar(1) == '/') { + // single-line comment + do { ch = getChar(); } while (ch != 0 && ch != '\n'); + continue; + } else if (ch == '(' && peekChar(1) == '*') { + getChar(); // skip starting char + auto lc = cpos; + getChar(); // skip star + char pch = ' '; + ch = ' '; // we need this + for (;;) { + pch = ch; + ch = getChar(); + if (ch == 0) error(lc, "unterminated comment"); + if (ch == ')' && pch == '*') break; + } + continue; + } else if (ch == '{') { + getChar(); // skip starting char + auto lc = cpos; + do { + ch = getChar(); + if (ch == 0) error(lc, "unterminated comment"); + } while (ch != '}'); + continue; + } + if (ch == 0 || ch > 32) return; + getChar(); + } + } + + private void nextToken () { + if (eof) return; + + skipBlanks(); + if (peekChar == '\0') { + eof = true; + tokeof.loc = cpos; + tokeof.eloc = cpos; + return; + } + + Token tk; + auto tkspos = tpos; + char ch = getChar(); + tk.loc = cpos; + + // quoted string + if (ch == '"' || ch == '\'') { + char ech = ch; + tk.type = Token.Type.Str; + ++tkspos; // skip quote + for (;;) { + ch = getChar(); + if (ch == 0) error(tk, "unterminated string"); + if (ch == ech) break; + } + tk.tkstr = text[tkspos..tpos-1]; // -1 due to eaten quote + tk.eloc = cpos; + lookup ~= tk; + return; + } + + // hex number + if (ch == '$') { + long n = 0; + tk.type = Token.Type.Num; + getChar(); // skip dollar + int dv = digitValue(peekChar); + if (dv < 0 || dv > 15) error(tk, "hex number expected"); + for (;;) { + dv = digitValue(peekChar); + if (dv < 0 || dv > 15) break; + n = n*16+dv; + getChar(); + } + ch = peekChar; + if (isIdChar(ch) || ch == '.') error(tk, "hex number expected"); + tk.num = n; + tk.tkstr = text[tkspos..tpos]; + tk.eloc = cpos; + lookup ~= tk; + return; + } + + // number + if (isDigit(ch)) { + long n = ch-'0'; + tk.type = Token.Type.Num; + for (;;) { + if (!isDigit(peekChar)) break; + ch = getChar(); + n = n*10+ch-'0'; + } + tk.num = n; + tk.tkstr = text[tkspos..tpos]; + tk.eloc = cpos; + ch = peekChar; + if (isIdChar(ch)) error(tk, "invalid number"); + lookup ~= tk; + return; + } + + // identifier + if (isIdStart(ch)) { + tk.type = Token.Type.Id; + while (isIdChar(peekChar)) getChar(); + tk.tkstr = text[tkspos..tpos]; + tk.eloc = cpos; + lookup ~= tk; + return; + } + + static immutable string[9] longSpecs = [ + "<=", + ">=", + ":=", + "<>", + "+=", + "-=", + "*=", + "/=", + "..", + ]; + enum MaxSpecLength = { + int ml = 0; + foreach (string s; longSpecs) if (s.length > ml) ml = cast(int)s.length; + return ml; + }(); + + // delimiter + char[MaxSpecLength] dbuf; + dbuf[0] = ch; + uint len = 0; + for (;;) { + ch = dbuf[len]; + bool found = false; + foreach (string s; longSpecs) if (len < s.length && s[len] == ch) { found = true; break; } + if (!found) break; + if (len > 0) getChar(); // this char should be eaten + if (++len >= MaxSpecLength) break; + dbuf[len] = peekChar(0); + } + tk.type = Token.Type.Spec; + tk.tkstr = text[tkspos..tpos]; + tk.eloc = cpos; + lookup ~= tk; + } + + auto select(RetType, string mode="peek", A...) (scope A args) { pragma(inline, true); return selectN!(RetType, mode)(0, args); } + + auto selectN(RetType, string mode="peek", A...) (usize n, scope A args) { + import std.traits : ReturnType; + + static assert(mode == "peek" || mode == "pop" || mode == "pop-nondefault", "selectN: invalid mode: '"~mode~"'"); + + template isGoodDg(usize idx, T) { + private import std.traits; + static if (idx < A.length && isCallable!(A[idx]) && arity!(args[idx]) == 1) { + enum isGoodDg = is(Parameters!(A[idx])[0] == T); + } else { + enum isGoodDg = false; + } + } + + template isGoodArglessDg(usize idx) { + private import std.traits; + static if (idx < A.length && isCallable!(A[idx]) && arity!(args[idx]) == 0) { + enum isGoodArglessDg = true; + } else { + enum isGoodArglessDg = false; + } + } + + // sorry, but this has to be string mixin, due to possible empty `arg` + enum DoCallDg(string arg) = + "static if (!is(ReturnType!(A[xidx]) == void)) return cast(RetType)(args[xidx]("~arg~")); else { args[xidx]("~arg~"); return RetType.init; }"; + + // we can't have inner mixin templates, so... sorry, it's string again + enum CallDg = q{ + static if (isGoodDg!(xidx, Token)) { mixin(DoCallDg!"tk"); } + else static if (isGoodDg!(xidx, Loc)) { mixin(DoCallDg!"tk.loc"); } + else static if (isGoodDg!(xidx, Token.Type)) { mixin(DoCallDg!"tk.type"); } + else static if (isGoodDg!(xidx, Keyword)) { mixin(DoCallDg!"tk.Kw"); } + else static if (isGoodArglessDg!(xidx)) { mixin(DoCallDg!""); } + else static assert(0, "selectN: invalid delegate #"~xidx.stringof); + }; + + auto tk = peek(n); + bool found = false; + foreach (immutable aidx, immutable arg; args) { + static if (aidx%2 == 0) { + static if (is(typeof(arg) == Keyword) || is(typeof(arg) == Token.Type)) { + static if (is(typeof(arg) == Keyword)) found = (tk == arg); + else static if (is(typeof(arg) == Token.Type)) found = (tk.type == arg); + else static assert(0, "wtf?!"); + if (found) { + // process `mode` + static if (mode != "peek") popFront(); + // call delegate + enum xidx = aidx+1; + mixin(CallDg); + } + } else { + // default + // process `mode` + static if (mode == "pop") popFront(); + // call delegate + enum xidx = aidx; + mixin(CallDg); + } + } + } + error(tk, "selectN is out of nodes"); + assert(0); + } + +static: + private immutable byte[256] digitValues = { + byte[256] res = -1; + foreach (ubyte idx; '0'..'9'+1) res[idx] = cast(byte)(idx-'0'); + foreach (ubyte idx; 'A'..'Z'+1) res[idx] = cast(byte)(idx-'A'+10); + foreach (ubyte idx; 'a'..'z'+1) res[idx] = cast(byte)(idx-'a'+10); + return res; + }(); + + private immutable bool[256] idStartChars = { + bool[256] res = false; + foreach (ubyte idx; 'A'..'Z'+1) res[idx] = true; + foreach (ubyte idx; 'a'..'z'+1) res[idx] = true; + res['_'] = true; + return res; + }(); + + private immutable bool[256] idChars = { + bool[256] res = false; + foreach (ubyte idx; '0'..'9'+1) res[idx] = true; + foreach (ubyte idx; 'A'..'Z'+1) res[idx] = true; + foreach (ubyte idx; 'a'..'z'+1) res[idx] = true; + res['_'] = true; + return res; + }(); + + bool isDigit() (char ch) { pragma(inline, true); return (ch >= '0' && ch <= '9'); } + int digitValue() (char ch) { pragma(inline, true); return digitValues.ptr[cast(ubyte)ch]; } + bool isIdStart() (char ch) { pragma(inline, true); return idStartChars.ptr[cast(ubyte)ch]; } + bool isIdChar() (char ch) { pragma(inline, true); return idChars.ptr[cast(ubyte)ch]; } + + string gmlQuote (const(char)[] s) { + import std.array : appender; + auto res = appender!string(); + enum Prev { Nothing, Char, Spec } + Prev prev = Prev.Nothing; + foreach (char ch; s) { + if (ch < ' ' || ch == 127 || ch == '"') { + import std.conv : to; + final switch (prev) with (Prev) { + case Nothing: break; + case Char: res.put(`"+`); break; + case Spec: res.put(`+`); break; + } + prev = Prev.Spec; + res.put("chr("); + res.put(to!string(cast(uint)ch)); + res.put(")"); + } else { + final switch (prev) with (Prev) { + case Nothing: res.put('"'); break; + case Char: break; + case Spec: res.put(`+"`); break; + } + prev = Prev.Char; + res.put(ch); + } + } + if (prev == Prev.Nothing) return `""`; + if (prev == Prev.Char) res.put('"'); + return res.data; + } + + /// quote string: append double quotes, screen all special chars; + /// so quoted string forms valid D string literal. + /// allocates. + string quote (const(char)[] s) { + import std.array : appender; + import std.format : formatElement, FormatSpec; + auto res = appender!string(); + FormatSpec!char fspc; // defaults to 's' + formatElement(res, s, fspc); + return res.data; + } +} + + +version(lexer_test) unittest { + import std.file; + import std.stdio; + //enum FName = "z00.txt"; + enum FName = "shared/MAPDEF.pas"; + string s; + { + auto fl = File(FName); + auto buf = new char[](cast(uint)fl.size); + fl.rawRead(buf[]); + s = cast(string)buf; + } + auto lex = new Lexer(s, FName); + try { + while (!lex.empty) { + writeln(lex.front); + lex.popFront(); + } + } catch (ErrorAt e) { + writeln("PARSE ERROR: ", e.line); + writeln(e.loc); + } +} diff --git a/src/tools/mapiogen/mapiogen.d b/src/tools/mapiogen/mapiogen.d new file mode 100644 index 0000000..25b0444 --- /dev/null +++ b/src/tools/mapiogen/mapiogen.d @@ -0,0 +1,679 @@ +/* coded by Ketmar // Invisible Vector + * Understanding is not required. Only obedience. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +module mapiogen; +static assert(__VERSION__ >= 2071, "you need as least DMD 2.071 to compile this code"); + +import std.stdio; + +import lexer; + + +// ////////////////////////////////////////////////////////////////////////// // +bool useDelphiAlignment = false; +ubyte[string] triggers; +string[ubyte] trignums; + + +// ////////////////////////////////////////////////////////////////////////// // +struct Field { + enum Type { Bytes, Chars, Integral, TPoint, Boolean } + + string name; + string typename; + uint size; + uint ofs; + Type type; +} + +struct Record { + string[] ids; // null: default + Field[] fields; + uint size; // max size + bool normal; + + string getRWName () const { + import std.string : capitalize; + if (ids.length == 0) return "Default"; + if (normal) return ids[0].capitalize; + return ids[0][8..$].capitalize; + } + + // calc field offsets and record size + void finalize (bool packed=false) { + // calculate offsets + uint ofs = 0; + foreach (immutable idx, ref fld; fields) { + fld.ofs = ofs; + // delphi does this (roughly) + if (!packed && fld.size != 1) { + if (useDelphiAlignment && fld.type == Field.Type.TPoint) { + } else { + //ubyte pd = (fld.size > 4 ? 2 : fld.size > 2 ? 4 : 2); + ubyte pd = (fld.size > 2 ? 4 : 2); + if (fld.type == Field.Type.Chars && fld.size > 1) pd = 2; + fld.ofs += ofs.padding(pd); + } + } + ofs = fld.ofs+fld.size; + } + size = ofs; + //if (fields.length > 0 && fields[$-1].size != 1 && fields[$-1].type == Field.Type.Integral) size += size.padding(2); // just in case + } +} + + +Record[] tgrecords; +Record[] records; + + +// ////////////////////////////////////////////////////////////////////////// // +// 0 128 Default (Byte128) +void dumpRecord (in ref Record rec) { + foreach (const ref fld; rec.fields) { + writefln("%3s %3s %s (%s)", fld.ofs, fld.size, fld.name, fld.typename); + } +} + + +void dumpRecords () { foreach (const ref rec; tgrecords) rec.dumpRecord(); } + + +// ////////////////////////////////////////////////////////////////////////// // +void genMisc (File fo) { + fo.write( +q{procedure getBytesAt (var dest; const buf; ofs, len: Integer); +begin + Move((PChar(@buf)+ofs)^, dest, len); +end; + +procedure getWordAt (var dest; const buf; ofs: Integer); +type PWord = ^Word; PByte = ^Byte; +var + p: PByte; + d: PWord; +begin + p := PByte(@buf); Inc(p, ofs); + d := PWord(@dest); + d^ := p^; + Inc(p); + d^ := (d^) or ((p^) shl 8); +end; + +procedure getIntAt (var dest; const buf; ofs: Integer); +type PInt = ^LongWord; PByte = ^Byte; +var + p: PByte; + d: PInt; +begin + p := PByte(@buf); Inc(p, ofs); + d := PInt(@dest); + d^ := p^; + Inc(p); + d^ := (d^) or ((p^) shl 8); + Inc(p); + d^ := (d^) or ((p^) shl 16); + Inc(p); + d^ := (d^) or ((p^) shl 24); +end; + +procedure putBytesAt (var buf; ofs: Integer; const src; len: Integer); +begin + Move(src, (PChar(@buf)+ofs)^, len); +end; + +procedure putWordAt (var buf; ofs: Integer; const src); +type PWord = ^Word; PByte = ^Byte; +var + p: PByte; + d: PWord; +begin + p := PByte(PChar(@buf)+ofs); + d := PWord(@src); + p^ := (d^) and $ff; + Inc(p); + p^ := ((d^) shr 8) and $ff; +end; + +procedure putIntAt (var buf; ofs: Integer; const src); +type PInt = ^LongWord; PByte = ^Byte; +var + p: PByte; + d: PInt; +begin + p := PByte(PChar(@buf)+ofs); + d := PInt(@src); + p^ := (d^) and $ff; + Inc(p); + p^ := ((d^) shr 8) and $ff; + Inc(p); + p^ := ((d^) shr 16) and $ff; + Inc(p); + p^ := ((d^) shr 24) and $ff; +end; + +}); +} + + +void genReader (File fo, in ref Record rec) { + fo.write( + " procedure xread", rec.getRWName, " ();\n"~ + " begin\n" + ); + foreach (const ref fld; rec.fields) { + final switch (fld.type) { + case Field.Type.Bytes: + case Field.Type.Chars: + case Field.Type.Boolean: + fo.writeln(" getBytesAt(tr.", fld.name, ", buf, ", fld.ofs, ", ", fld.size, ");"); + break; + case Field.Type.Integral: + switch (fld.size) { + case 1: fo.writeln(" getBytesAt(tr.", fld.name, ", buf, ", fld.ofs, ", ", fld.size, ");"); break; + case 2: fo.writeln(" getWordAt(tr.", fld.name, ", buf, ", fld.ofs, ");"); break; + case 4: fo.writeln(" getIntAt(tr.", fld.name, ", buf, ", fld.ofs, ");"); break; + default: assert(0); + } + break; + case Field.Type.TPoint: + fo.writeln(" getIntAt(tr.", fld.name, ".x, buf, ", fld.ofs, ");"); + fo.writeln(" getIntAt(tr.", fld.name, ".y, buf, ", fld.ofs+4, ");"); + break; + } + } + fo.writeln(" end;\n"); +} + + +void genReaders (File fo) { + fo.write( + "procedure mb_Read_TriggerData (var tr: TTriggerData; ttype: Integer; const buf; bufsize: Integer);\n" + ); + uint maxsize = 0; + foreach (const ref rec; tgrecords) { + if (rec.ids.length == 0) continue; + if (rec.size > maxsize) maxsize = rec.size; + fo.genReader(rec); + } + fo.write( + "begin\n"~ + " if (bufsize < ", maxsize, ") then raise Exception.Create('invalid buffer size in mb_Read_TriggerData');\n" + ); + foreach (const ref rec; tgrecords) { + foreach (string id; rec.ids) { + fo.writeln(" if (ttype = ", id, ") then begin xread", rec.getRWName, "(); exit; end;"); + } + } + fo.writeln(" raise Exception.Create('invalid trigger type in mb_Read_TriggerData');"); + fo.writeln("end;\n\n"); + foreach (ref rec; records) { + assert(rec.normal); + fo.writeln("procedure mb_Read_", rec.ids[0], " (var tr: ", rec.ids[0], "; const buf; bufsize: Integer);"); + fo.genReader(rec); + fo.write( + "begin\n"~ + " if (bufsize < ", rec.size, ") then raise Exception.Create('invalid buffer size in read", rec.ids[0], "');\n" + " xread", rec.getRWName, "();\n"~ + "end;\n\n" + ); + } +} + + +void genWriter (File fo, in ref Record rec) { + fo.write( + " procedure xwrite", rec.getRWName, " ();\n"~ + " begin\n" + ); + foreach (const ref fld; rec.fields) { + final switch (fld.type) { + case Field.Type.Bytes: + case Field.Type.Chars: + case Field.Type.Boolean: + fo.writeln(" putBytesAt(buf, ", fld.ofs, ", tr.", fld.name, ", ", fld.size, ");"); + break; + case Field.Type.Integral: + switch (fld.size) { + case 1: fo.writeln(" putBytesAt(buf, ", fld.ofs, ", tr.", fld.name, ", ", fld.size, ");"); break; + case 2: fo.writeln(" putWordAt(buf, ", fld.ofs, ", tr.", fld.name, ");"); break; + case 4: fo.writeln(" putIntAt(buf, ", fld.ofs, ", tr.", fld.name, ");"); break; + default: assert(0); + } + break; + case Field.Type.TPoint: + fo.writeln(" putIntAt(buf, ", fld.ofs , ", tr.", fld.name, ".x);"); + fo.writeln(" putIntAt(buf, ", fld.ofs+4, ", tr.", fld.name, ".y);"); + break; + } + } + fo.writeln(" end;\n"); +} + + +void genWriters (File fo) { + fo.write( + "procedure mb_Write_TriggerData (var buf; bufsize: Integer; ttype: Integer; var tr: TTriggerData);\n" + ); + uint maxsize = 0; + foreach (const ref rec; tgrecords) { + assert(!rec.normal); + if (rec.ids.length == 0) continue; + if (rec.size > maxsize) maxsize = rec.size; + fo.genWriter(rec); + } + fo.write( + "begin\n"~ + " if (bufsize < ", maxsize, ") then raise Exception.Create('invalid buffer size in mb_Write_TriggerData');\n" + ); + foreach (const ref rec; tgrecords) { + foreach (string id; rec.ids) { + fo.writeln(" if (ttype = ", id, ") then begin xwrite", rec.getRWName, "(); exit; end;"); + } + } + fo.writeln(" raise Exception.Create('invalid trigger type in mb_Write_TriggerData');"); + fo.writeln("end;\n\n"); + foreach (ref rec; records) { + assert(rec.normal); + fo.writeln("procedure mb_Write_", rec.ids[0], " (var buf; bufsize: Integer; var tr: ", rec.ids[0], ");"); + fo.genWriter(rec); + fo.write( + "begin\n"~ + " if (bufsize < ", rec.size, ") then raise Exception.Create('invalid buffer size in write", rec.ids[0], "');\n" + " xwrite", rec.getRWName, "();\n"~ + "end;\n\n" + ); + } +} + + +// ////////////////////////////////////////////////////////////////////////// // +void printCaret (Lexer lex, Loc loc, File ofile=stdout) { + auto line = lex.line(loc.line); + if (line.length == 0) return; + ofile.writeln(line); + foreach (immutable _; 1..loc.col) ofile.write(' '); + ofile.writeln('^'); +} + + +// ////////////////////////////////////////////////////////////////////////// // +ubyte padding (uint size, ubyte alg) { + uint nsz = (size+alg-1)/alg*alg; + return cast(ubyte)(nsz-size); +} + + +// ////////////////////////////////////////////////////////////////////////// // +void parseType (ref Field fld, const(char)[] typestr, Lexer lex) { + import std.algorithm : startsWith; + import std.string : toLower; + auto type = typestr.toLower; + if (type.startsWith("byte") || type.startsWith("char")) { + import std.conv : to; + fld.type = (type[0] == 'b' ? Field.Type.Bytes : Field.Type.Chars); + if (type.length == 4) { + fld.size = 1; + return; + } + try { + auto sz = to!uint(type[4..$]); + if (sz < 1 || sz > 32767) throw new Exception("invalid size"); + fld.size = sz; + return; + } catch (Exception) {} + } else if (type == "tpoint") { + fld.type = Field.Type.TPoint; + fld.size = 4*2; + return; + } else if (type == "boolean") { + fld.type = Field.Type.Boolean; + fld.size = 1; + return; + } else if (type == "integer") { + fld.type = Field.Type.Integral; + fld.size = 4; + return; + } else if (type == "word") { + fld.type = Field.Type.Integral; + fld.size = 2; + return; + } else if (type == "shortint") { + fld.type = Field.Type.Integral; + fld.size = 1; + return; + } + lex.error("invalid type: '"~typestr.idup~"'"); +} + + +/* +(TargetPoint: TPoint; + d2d_teleport: Boolean; + silent_teleport: Boolean; + TlpDir: Byte); +*/ +Field[] parseFields (Lexer lex) { + Field[] res; + if (!lex.isSpec || lex.front.str != "(") lex.error("'(' expected"); + lex.popFront(); + for (;;) { + if (lex.isSpec && lex.front.str == ")") { lex.popFront(); break; } + string[] names; + for (;;) { + names ~= lex.expectId(); + if (lex.isSpec && lex.front.str == ":") break; + if (lex.isSpec && lex.front.str == ",") { lex.popFront(); continue; } + lex.error("':' expected"); + } + if (!lex.isSpec || lex.front.str != ":") lex.error("':' expected"); + lex.popFront(); + auto type = lex.expectId(); + //writeln(" ", names[], ": <", type, ">"); + foreach (string name; names) { + Field fld; + fld.name = name; + fld.typename = type; + fld.parseType(type, lex); + res ~= fld; + } + if (!lex.isSpec) lex.error("';' or ')' expected"); + if (lex.front.str == ";") { lex.popFront(); continue; } + if (lex.front.str != ")") lex.error("';' or ')' expected"); + } + if (lex.isSpec && lex.front.str == ";") lex.popFront(); + return res; +} + + +/* +TargetPoint: TPoint; + d2d_teleport: Boolean; + silent_teleport: Boolean; + TlpDir: Byte; + end; +*/ +Field[] parseRecFields (Lexer lex) { + Field[] res; + for (;;) { + if (lex.eatCI("end") !is null) break; + string[] names; + for (;;) { + names ~= lex.expectId(); + if (lex.isSpec && lex.front.str == ":") break; + if (lex.isSpec && lex.front.str == ",") { lex.popFront(); continue; } + lex.error("':' expected"); + } + if (!lex.isSpec || lex.front.str != ":") lex.error("':' expected"); + lex.popFront(); + auto type = lex.expectId(); + foreach (string name; names) { + Field fld; + fld.name = name; + fld.typename = type; + fld.parseType(type, lex); + res ~= fld; + } + if (lex.eatCI("end") !is null) break; + if (!lex.isSpec || lex.front.str != ";") lex.error("';' expected"); + lex.popFront(); + } + return res; +} + + +// ////////////////////////////////////////////////////////////////////////// // +bool isGoodTriggerName (const(char)[] id) { + import std.algorithm : startsWith; + import std.string : indexOf; + if (!id.startsWith("TRIGGER_")) return false; + if (id == "TRIGGER_MAX") return false; + if (id[8..$].indexOf('_') >= 0) return false; + return true; +} + + +// ////////////////////////////////////////////////////////////////////////// // +void parseMapDef (string fname) { + import std.string : format, toLower, toUpper; + Lexer lex; + { + auto fl = File(fname); + auto buf = new char[](cast(uint)fl.size); + fl.rawRead(buf[]); + lex = new Lexer(cast(string)buf, fname); + } + // find "interface" + while (!lex.empty) { + if (!lex.front.isId) { lex.popFront(); continue; } + if (lex.front.str.toLower == "interface") break; + lex.popFront(); + } + if (lex.empty) throw new Exception("where is my interface?!"); + enum Section { Unknown, Const, Type } + Section section; + while (!lex.empty) { + if (lex.front.isId) { + auto kw = lex.front.str.toLower; + if (kw == "implementation") break; + if (kw == "const") { + //writeln("CONST!"); + section = Section.Const; + lex.popFront(); + continue; + } + if (kw == "type") { + //writeln("TYPE!"); + section = Section.Type; + lex.popFront(); + continue; + } + } + if (section == Section.Const) { + if (!lex.isId) lex.error("identifier expected"); + auto id = lex.front.istr.toUpper; + lex.popFront(); + auto lc = lex.loc; + if (lex.expectSpec() != "=") lex.error(lc, "'=' expected"); + if (isGoodTriggerName(id)) { + lex.mustbeNum(); + auto lcn = lex.loc; + auto n = lex.front.num; + lex.popFront(); + if (n < 0 || n > 255) lex.error(lcn, "invalid value (%s) for '%s'".format(n, id)); + auto b = cast(ubyte)n; + if (id in triggers) lex.error(lc, "duplicate constant '%s'".format(id)); + if (auto tg = b in trignums) lex.error(lcn, "same value (%s) for triggers '%s' and '%s'".format(n, id, *tg)); + triggers[id] = b; + trignums[b] = id; + //writeln("trigger: ", id, " (", b, ")"); + } else { + while (!lex.empty) { + if (lex.front.isSpec && lex.front.str == ";") break; + lex.popFront(); + } + } + lc = lex.loc; + if (lex.expectSpec() != ";") lex.error(lc, "';' expected"); + continue; + } + if (section == Section.Type) { + if (!lex.isId) lex.error("identifier expected"); + auto id = lex.front.istr.toUpper; + lex.popFront(); + auto lc = lex.loc; + if (lex.expectSpec() != "=") lex.error(lc, "'=' expected"); + //writeln("id: ", id); + if (id != "TTRIGGERDATA") { + // skip definition + while (!lex.empty) { + if (lex.eatCI("end") !is null) break; + lex.popFront(); + } + lc = lex.loc; + if (lex.expectSpec() != ";") lex.error(lc, "';' expected"); + continue; + } else { + lex.expectCI("record"); + lex.expectCI("case"); + lex.expectCI("byte"); + lex.expectCI("of"); + // now parse defs + for (;;) { + if (lex.eatCI("end") !is null) break; + string[] ids; + Field[] fields; + if (lex.isNum) { + if (lex.front.num != 0) lex.error(lc, "'0' expected"); + lex.popFront(); + if (!lex.isSpec || lex.front.str != ":") lex.error("':' expected"); + lex.popFront(); + //writeln("=== DEFAULT ==="); + ids = null; + fields = lex.parseFields(); + } else { + for (;;) { + ids ~= lex.expectId(); + if (lex.isSpec && lex.front.str == ":") { lex.popFront(); break; } + if (lex.isSpec && lex.front.str == ",") { lex.popFront(); continue; } + lex.error("',' or ':' expected"); + } + //writeln("=== ", ids[], " ==="); + fields = lex.parseFields(); + } + tgrecords ~= Record(ids, fields); + tgrecords[$-1].finalize; + //writeln("=== ", ids[], " === : ", rcsize); + } + lc = lex.loc; + if (lex.expectSpec() != ";") lex.error(lc, "';' expected"); + break; // we are done + } + } + lex.popFront(); + continue; + } +} + + +// ////////////////////////////////////////////////////////////////////////// // +void parseMapStruct (string fname) { + import std.string : format, toLower, toUpper; + + static bool isGoodRecName (const(char)[] id) { + import std.algorithm : startsWith, endsWith; + import std.string : indexOf, toUpper; + id = id.toUpper; + if (!id.startsWith("T") || !id.endsWith("_1")) return false; + return true; + } + + static bool isGoodDef (Lexer lex) { + import std.string : toLower; + auto tk = lex.peek(1); + if (!tk.isSpec || tk.str != "=") return false; + tk = lex.peek(2); + if (!tk.isId || tk.str.toLower != "packed") return false; + tk = lex.peek(3); + if (!tk.isId || tk.str.toLower != "record") return false; + return true; + } + + Lexer lex; + { + auto fl = File(fname); + auto buf = new char[](cast(uint)fl.size); + fl.rawRead(buf[]); + lex = new Lexer(cast(string)buf, fname); + } + // find "interface" + while (!lex.empty) { + if (!lex.front.isId) { lex.popFront(); continue; } + if (lex.front.str.toLower == "interface") break; + lex.popFront(); + } + if (lex.empty) throw new Exception("where is my interface?!"); + enum Section { Unknown, Type } + Section section; + while (!lex.empty) { + if (lex.front.isId) { + auto kw = lex.front.str.toLower; + if (kw == "implementation") break; + if (kw == "type") { + section = Section.Type; + lex.popFront(); + continue; + } + } + if (section == Section.Type) { + if (lex.isId && isGoodRecName(lex.front.str) && isGoodDef(lex)) { + string origId = lex.front.istr; + lex.popFront(); + lex.popFront(); // skip "=" + lex.expectCI("packed"); + lex.expectCI("record"); + // now parse fields + Record rec; + rec.ids ~= origId; + rec.fields = lex.parseRecFields(); + rec.normal = true; + rec.finalize(true); + records ~= rec; + { + auto lc = lex.loc; + if (lex.expectSpec() != ";") lex.error(lc, "';' expected"); + } + continue; + } + } + lex.popFront(); + continue; + } +} + + +// ////////////////////////////////////////////////////////////////////////// // +void main () { + try { + parseMapDef("../../shared/MAPDEF.pas"); + parseMapStruct("../../shared/MAPSTRUCT.pas"); + debug { + dumpRecords(); + } else { + { + auto fo = File("mapstructio.inc", "w"); + fo.genMisc(); + fo.genReaders(); + fo.genWriters(); + } + { + auto fo = File("mapstructsizes.inc", "w"); + fo.writeln("const"); + foreach (ref rec; records) fo.writeln(" SizeOf_", rec.ids[0], " = ", rec.size, ";"); + fo.writeln(); + fo.writeln("procedure mb_Read_TriggerData (var tr: TTriggerData; ttype: Integer; const buf; bufsize: Integer);"); + fo.writeln("procedure mb_Write_TriggerData (var buf; bufsize: Integer; ttype: Integer; var tr: TTriggerData);"); + foreach (ref rec; records) { + fo.writeln("procedure mb_Read_", rec.ids[0], " (var tr: ", rec.ids[0], "; const buf; bufsize: Integer);"); + fo.writeln("procedure mb_Write_", rec.ids[0], " (var buf; bufsize: Integer; var tr: ", rec.ids[0], ");"); + } + } + } + } catch (ErrorAt e) { + writeln("PARSE ERROR: ", e.loc, ": ", e.msg); + //lex.printCaret(e.loc); + } +} -- 2.29.2