1 (* Copyright (C) DooM 2D:Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE a_modes.inc}
17 {.$DEFINE XDYNREC_USE_FIELDHASH} // actually, it is SLOWER with this
24 xparser
, xstreams
, utils
, hashtable
;
27 // ////////////////////////////////////////////////////////////////////////// //
34 TDynFieldList
= specialize TSimpleList
<TDynField
>;
35 TDynRecList
= specialize TSimpleList
<TDynRecord
>;
36 TDynEBSList
= specialize TSimpleList
<TDynEBS
>;
38 // this is base type for all scalars (and arrays)
42 TType
= (TBool
, TChar
, TByte
, TUByte
, TShort
, TUShort
, TInt
, TUInt
, TString
, TPoint
, TSize
, TList
, TTrigData
);
43 // TPoint: pair of Integers
44 // TSize: pair of UShorts
45 // TList: actually, array of records
46 // TTrigData: array of mMaxDim bytes, but internally a record (mRecRef)
47 // arrays of chars are pascal shortstrings (with counter in the first byte)
51 TEBS
= (TNone
, TRec
, TEnum
, TBitSet
);
58 mIVal
: Integer; // for all integer types
59 mIVal2
: Integer; // for point and size
60 mSVal
: AnsiString; // string; for byte and char arrays
61 mRVal
: TDynRecList
; // for list
62 mRHash
: THashStrInt
; // id -> index in mRVal
63 mRecRef
: TDynRecord
; // for TEBS.TRec
64 mMaxDim
: Integer; // for byte and char arrays; <0: not an array; 0: impossible value
65 mBinOfs
: Integer; // offset in binary; <0 - none
66 mRecOfs
: Integer; // offset in record; <0 - none
67 mSepPosSize
: Boolean; // for points and sizes, use separate fields
68 mAsT
: Boolean; // for points and sizes, use separate fields, names starts with `t`
74 mBitSetUnique
: Boolean; // bitset can contain only one value
75 mAsMonsterId
: Boolean; // special hack for triggers: monster record number+1 in binary (so 0 means "none")
77 mDefUnparsed
: AnsiString;
78 mDefSVal
: AnsiString; // default string value
79 mDefIVal
, mDefIVal2
: Integer; // default integer values
80 mDefRecRef
: TDynRecord
;
81 mEBS
: TEBS
; // complex type type
82 mEBSTypeName
: AnsiString; // name of enum, bitset or record
83 mEBSType
: TObject
; // either TDynRecord or TDynEBS; nil means "simple type"; nil for `TTrigData` too
86 mRecRefId
: AnsiString;
91 procedure parseDef (pr
: TTextParser
);
93 procedure parseDefaultValue (); // parse `mDefUnparsed` to `mDefSVal`, `mDefIVal`, `mDefIVal2`, `mDefRecRef`
94 procedure fixDefaultValue (); // this will NOT clone `mDefRecRef`
95 function isDefaultValue (): Boolean;
97 function getListCount (): Integer; inline;
98 function getListItem (idx
: Integer): TDynRecord
; inline; overload
;
99 function getListItem (const aname
: AnsiString): TDynRecord
; inline; overload
;
102 // returns `true` for duplicate record id
103 function addListItem (rec
: TDynRecord
): Boolean; inline;
106 constructor Create (const aname
: AnsiString; atype
: TType
);
107 constructor Create (pr
: TTextParser
);
108 destructor Destroy (); override;
110 class function getTypeName (t
: TType
): AnsiString;
112 function definition (): AnsiString;
113 function pasdef (): AnsiString;
115 function clone (newOwner
: TDynRecord
=nil): TDynField
;
117 procedure parseValue (pr
: TTextParser
);
118 procedure parseBinValue (st
: TStream
);
120 procedure writeTo (wr
: TTextWriter
);
121 procedure writeBinTo (st
: TStream
);
123 // won't work for lists
124 function isSimpleEqu (fld
: TDynField
): Boolean;
126 procedure setValue (const s
: AnsiString);
129 property pasname
: AnsiString read mPasName
;
130 property name
: AnsiString read mName
;
131 property baseType
: TType read mType
;
132 property defined
: Boolean read mDefined write mDefined
;
133 property internal
: Boolean read mInternal write mInternal
;
134 property ival
: Integer read mIVal
;
135 property sval
: AnsiString read mSVal
;
136 property hasDefault
: Boolean read mHasDefault
;
137 property defsval
: AnsiString read mDefSVal
;
138 property ebs
: TEBS read mEBS
;
139 property ebstype
: TObject read mEBSType
;
140 property ebstypename
: AnsiString read mEBSTypeName
; // enum/bitset name
142 property count
: Integer read getListCount
;
143 property item
[idx
: Integer]: TDynRecord read getListItem
;
144 property items
[const aname
: AnsiString]: TDynRecord read getListItem
; default
; // alas, FPC 3+ lost property overloading feature
146 property x
: Integer read mIVal
;
147 property w
: Integer read mIVal
;
148 property y
: Integer read mIVal2
;
149 property h
: Integer read mIVal2
;
153 // "value" header record contains TList fields, with name equal to record type
158 mPasName
: AnsiString;
161 mFields
: TDynFieldList
;
162 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
163 mFieldsHash
: THashStrInt
; // id -> index in mRVal
165 mTrigTypes
: array of AnsiString; // if this is triggerdata, we'll hold list of triggers here
166 mHeader
: Boolean; // true for header record
167 mBinBlock
: Integer; // -1: none
168 mHeaderRec
: TDynRecord
; // for "value" records this is header record with data, for "type" records this is header type record
171 procedure parseDef (pr
: TTextParser
); // parse definition
173 function findByName (const aname
: AnsiString): Integer; inline;
174 function hasByName (const aname
: AnsiString): Boolean; inline;
175 function getFieldByName (const aname
: AnsiString): TDynField
; inline;
177 function getIsTrigData (): Boolean; inline;
178 function getIsForTrig (const aname
: AnsiString): Boolean; inline;
181 function findRecordByTypeId (const atypename
, aid
: AnsiString): TDynRecord
;
182 function findRecordNumByType (const atypename
: AnsiString; rc
: TDynRecord
): Integer;
183 function addRecordByType (const atypename
: AnsiString; rc
: TDynRecord
): Boolean; // `true`: duplicate record id
185 procedure addField (fld
: TDynField
); inline;
186 function addFieldChecked (fld
: TDynField
): Boolean; inline; // `true`: duplicate name
189 constructor Create ();
190 constructor Create (pr
: TTextParser
); // parse definition
191 destructor Destroy (); override;
193 function definition (): AnsiString;
194 function pasdef (): AnsiString;
196 function clone (): TDynRecord
;
198 function isSimpleEqu (rec
: TDynRecord
): Boolean;
200 procedure parseValue (pr
: TTextParser
; beginEaten
: Boolean=false);
201 procedure parseBinValue (st
: TStream
; forceData
: Boolean=false);
203 procedure writeTo (wr
: TTextWriter
; putHeader
: Boolean=true);
204 procedure writeBinTo (st
: TStream
; trigbufsz
: Integer=-1; onlyFields
: Boolean=false);
206 // find field with `TriggerType` type
207 function trigTypeField (): TDynField
;
209 // number of records of the given instance
210 function instanceCount (const typename
: AnsiString): Integer;
213 property id
: AnsiString read mId
; // for map parser
214 property pasname
: AnsiString read mPasName
;
215 property name
: AnsiString read mName
; // record name
216 property size
: Integer read mSize
; // size in bytes
217 //property fields: TDynFieldList read mFields;
218 property has
[const aname
: AnsiString]: Boolean read hasByName
;
219 property field
[const aname
: AnsiString]: TDynField read getFieldByName
;
220 property isTrigData
: Boolean read getIsTrigData
;
221 property isForTrig
[const aname
: AnsiString]: Boolean read getIsForTrig
;
222 property headerType
: TDynRecord read mHeaderRec
;
223 property isHeader
: Boolean read mHeader
;
231 mIds
: array of AnsiString;
232 mVals
: array of Integer;
233 mMaxName
: AnsiString; // MAX field
234 mMaxVal
: Integer; // max value
237 procedure cleanup ();
239 procedure parseDef (pr
: TTextParser
); // parse definition
241 function findByName (const aname
: AnsiString): Integer; inline;
242 function hasByName (const aname
: AnsiString): Boolean; inline;
243 function getFieldByName (const aname
: AnsiString): Integer; inline;
246 constructor Create (pr
: TTextParser
); // parse definition
247 destructor Destroy (); override;
249 function definition (): AnsiString;
250 function pasdef (): AnsiString;
252 // return empty string if not found
253 function nameByValue (v
: Integer): AnsiString;
256 property name
: AnsiString read mName
; // record name
257 property isEnum
: Boolean read mIsEnum
;
258 property has
[const aname
: AnsiString]: Boolean read hasByName
;
259 property field
[const aname
: AnsiString]: Integer read getFieldByName
;
265 recTypes
: TDynRecList
; // [0] is always header
266 trigTypes
: TDynRecList
; // trigdata
267 ebsTypes
: TDynEBSList
; // enums, bitsets
270 procedure parseDef (pr
: TTextParser
);
272 function getHeaderRecType (): TDynRecord
; inline;
275 constructor Create (pr
: TTextParser
); // parses data definition
276 destructor Destroy (); override;
278 function findRecType (const aname
: AnsiString): TDynRecord
;
279 function findTrigFor (const aname
: AnsiString): TDynRecord
;
280 function findEBSType (const aname
: AnsiString): TDynEBS
;
282 function pasdef (): AnsiString;
284 // creates new header record
285 function parseMap (pr
: TTextParser
): TDynRecord
;
287 // creates new header record
288 function parseBinMap (st
: TStream
): TDynRecord
;
291 property headerType
: TDynRecord read getHeaderRecType
;
295 {$IF DEFINED(D2D_DYNREC_PROFILER)}
296 procedure xdynDumpProfiles ();
304 {$IF DEFINED(D2D_DYNREC_PROFILER)},xprofiler
{$ENDIF};
307 // ////////////////////////////////////////////////////////////////////////// //
308 function StrEqu (const a
, b
: AnsiString): Boolean; inline; begin result
:= (a
= b
); end;
311 // ////////////////////////////////////////////////////////////////////////// //
312 constructor TDynField
.Create (const aname
: AnsiString; atype
: TType
);
320 if (mType
= TType
.TList
) then
322 mRVal
:= TDynRecList
.Create();
323 mRHash
:= hashNewStrInt();
328 constructor TDynField
.Create (pr
: TTextParser
);
335 destructor TDynField
.Destroy ();
342 procedure TDynField
.cleanup ();
357 mSepPosSize
:= false;
359 mHasDefault
:= false;
371 mBitSetUnique
:= false;
372 mAsMonsterId
:= false;
378 function TDynField
.clone (newOwner
: TDynRecord
=nil): TDynField
;
382 result
:= TDynField
.Create(mName
, mType
);
383 result
.mOwner
:= mOwner
;
384 if (newOwner
<> nil) then result
.mOwner
:= newOwner
else result
.mOwner
:= mOwner
;
385 result
.mPasName
:= mPasName
;
386 result
.mName
:= mName
;
387 result
.mType
:= mType
;
388 result
.mIVal
:= mIVal
;
389 result
.mIVal2
:= mIVal2
;
390 result
.mSVal
:= mSVal
;
391 if (mRVal
<> nil) then
393 if (result
.mRVal
= nil) then result
.mRVal
:= TDynRecList
.Create(mRVal
.count
);
394 if (result
.mRHash
= nil) then result
.mRHash
:= hashNewStrInt();
395 for rec
in mRVal
do result
.addListItem(rec
.clone());
397 result
.mRecRef
:= mRecRef
;
398 result
.mMaxDim
:= mMaxDim
;
399 result
.mBinOfs
:= mBinOfs
;
400 result
.mRecOfs
:= mRecOfs
;
401 result
.mSepPosSize
:= mSepPosSize
;
403 result
.mDefined
:= mDefined
;
404 result
.mHasDefault
:= mHasDefault
;
405 result
.mOmitDef
:= mOmitDef
;
406 result
.mInternal
:= mInternal
;
407 result
.mNegBool
:= mNegBool
;
408 result
.mBitSetUnique
:= mBitSetUnique
;
409 result
.mAsMonsterId
:= mAsMonsterId
;
410 result
.mDefUnparsed
:= mDefUnparsed
;
411 result
.mDefSVal
:= mDefSVal
;
412 result
.mDefIVal
:= mDefIVal
;
413 result
.mDefIVal2
:= mDefIVal2
;
414 result
.mDefRecRef
:= mDefRecRef
;
416 result
.mEBSTypeName
:= mEBSTypeName
;
417 result
.mEBSType
:= mEBSType
;
418 result
.mRecRefId
:= mRecRefId
;
422 // won't work for lists
423 function TDynField
.isSimpleEqu (fld
: TDynField
): Boolean;
425 if (fld
= nil) or (mType
<> fld
.mType
) then begin result
:= false; exit
; end;
427 TType
.TBool
: result
:= ((mIVal
<> 0) = (fld
.mIVal
<> 0));
428 TType
.TChar
: result
:= (mSVal
= fld
.mSVal
);
435 result
:= (mIVal
= fld
.mIVal
);
436 TType
.TString
: result
:= (mSVal
= fld
.mSVal
);
439 result
:= ((mIVal
= fld
.mIVal
) and (mIVal2
= fld
.mIVal2
));
440 TType
.TList
: result
:= false;
443 if (mRecRef
= nil) then begin result
:= (fld
.mRecRef
= nil); exit
; end;
444 result
:= mRecRef
.isSimpleEqu(fld
.mRecRef
);
446 else raise Exception
.Create('ketmar forgot to handle some field type');
451 procedure TDynField
.setValue (const s
: AnsiString);
455 stp
:= TStrTextParser
.Create(s
+';');
464 procedure TDynField
.parseDefaultValue ();
466 stp
: TTextParser
= nil;
468 oIVal
, oIVal2
: Integer;
472 if not mHasDefault
then
487 stp
:= TStrTextParser
.Create(mDefUnparsed
+';');
492 mDefRecRef
:= mRecRef
;
505 // default value should be parsed
506 procedure TDynField
.fixDefaultValue ();
508 if mDefined
then exit
;
509 if not mHasDefault
then
511 if mInternal
then exit
;
512 raise Exception
.Create(Format('field ''%s'' in record ''%s'' of record type ''%s'' is not set', [mName
, mOwner
.mId
, mOwner
.mName
]));
514 if (mEBS
= TEBS
.TRec
) then mRecRef
:= mDefRecRef
;
522 // default value should be parsed
523 function TDynField
.isDefaultValue (): Boolean;
525 if not mHasDefault
then begin result
:= false; exit
; end;
526 if (mEBS
= TEBS
.TRec
) then begin result
:= (mRecRef
= mDefRecRef
); exit
; end;
528 TType
.TChar
, TType
.TString
: result
:= (mSVal
= mDefSVal
);
529 TType
.TPoint
, TType
.TSize
: result
:= (mIVal
= mDefIVal2
) and (mIVal2
= mDefIVal2
);
530 TType
.TList
, TType
.TTrigData
: result
:= false; // no default values for those types
531 else result
:= (mIVal
= mDefIVal
);
536 function TDynField
.getListCount (): Integer; inline;
538 if (mRVal
<> nil) then result
:= mRVal
.count
else result
:= 0;
542 function TDynField
.getListItem (idx
: Integer): TDynRecord
; inline; overload
;
544 if (mRVal
<> nil) and (idx
>= 0) and (idx
< mRVal
.count
) then result
:= mRVal
[idx
] else result
:= nil;
548 function TDynField
.getListItem (const aname
: AnsiString): TDynRecord
; inline; overload
;
552 if (mRVal
<> nil) and mRHash
.get(aname
, idx
) then result
:= mRVal
[idx
] else result
:= nil;
556 function TDynField
.addListItem (rec
: TDynRecord
): Boolean; inline;
559 if (mRVal
<> nil) then
562 if (Length(rec
.mId
) > 0) then result
:= mRHash
.put(rec
.mId
, mRVal
.count
-1);
567 class function TDynField
.getTypeName (t
: TType
): AnsiString;
570 TType
.TBool
: result
:= 'bool';
571 TType
.TChar
: result
:= 'char';
572 TType
.TByte
: result
:= 'byte';
573 TType
.TUByte
: result
:= 'ubyte';
574 TType
.TShort
: result
:= 'short';
575 TType
.TUShort
: result
:= 'ushort';
576 TType
.TInt
: result
:= 'int';
577 TType
.TUInt
: result
:= 'uint';
578 TType
.TString
: result
:= 'string';
579 TType
.TPoint
: result
:= 'point';
580 TType
.TSize
: result
:= 'size';
581 TType
.TList
: result
:= 'array';
582 TType
.TTrigData
: result
:= 'trigdata';
583 else raise Exception
.Create('ketmar forgot to handle some field type');
588 function TDynField
.definition (): AnsiString;
590 result
:= mPasName
+' is '+quoteStr(mName
)+' type ';
591 result
+= getTypeName(mType
);
592 if (mMaxDim
>= 0) then result
+= Format('[%d]', [mMaxDim
]);
593 if (mRecOfs
>= 0) then result
+= Format(' offset %d', [mRecOfs
]);
595 TEBS
.TNone
: begin end;
596 TEBS
.TRec
: result
+= ' '+mEBSTypeName
;
597 TEBS
.TEnum
: result
+= ' enum '+mEBSTypeName
;
598 TEBS
.TBitSet
: begin result
+= ' bitset '; if mBitSetUnique
then result
+= 'unique '; result
+= mEBSTypeName
; end;
600 if mAsMonsterId
then result
+= ' as monsterid';
601 if mHasDefault
and (Length(mDefUnparsed
) > 0) then result
+= ' default '+mDefUnparsed
;
604 if (mType
= TType
.TPoint
) then begin if (mAsT
) then result
+= ' as txy' else result
+= ' as xy'; end
605 else if (mType
= TType
.TSize
) then begin if (mAsT
) then result
+= ' as twh' else result
+= ' as wh'; end;
607 if mOmitDef
then result
+= ' omitdefault';
608 if mInternal
then result
+= ' internal';
612 function TDynField
.pasdef (): AnsiString;
614 result
:= mPasName
+': ';
616 TType
.TBool
: result
+= 'Boolean;';
617 TType
.TChar
: if (mMaxDim
> 0) then result
+= formatstrf('Char%d;', [mMaxDim
]) else result
+= 'Char;';
618 TType
.TByte
: result
+= 'ShortInt;';
619 TType
.TUByte
: result
+= 'Byte;';
620 TType
.TShort
: result
+= 'SmallInt;';
621 TType
.TUShort
: result
+= 'Word;';
622 TType
.TInt
: result
+= 'LongInt;';
623 TType
.TUInt
: result
+= 'LongWord;';
624 TType
.TString
: result
+= 'AnsiString;';
626 if mAsT
then result
:= 'tX, tY: Integer;'
627 else if mSepPosSize
then result
:= 'X, Y: Integer;'
628 else result
+= 'TDFPoint;';
630 if mAsT
then result
:= 'tWidth, tHeight: Word;'
631 else if mSepPosSize
then result
:= 'Width, Height: Word;'
632 else result
+= 'TSize;';
633 TType
.TList
: assert(false);
634 TType
.TTrigData
: result
+= formatstrf('Byte%d;', [mMaxDim
]);
635 else raise Exception
.Create('ketmar forgot to handle some field type');
640 procedure TDynField
.parseDef (pr
: TTextParser
);
645 fldrecname
: AnsiString;
646 fldpasname
: AnsiString;
647 asxy
, aswh
, ast
: Boolean;
656 lebs
: TDynField
.TEBS
;
678 lebs
:= TDynField.TEBS.TNone
;
680 fldpasname
:= pr.expectId
(); // pascal field name
683 fldname
:= pr.expectStr
();
686 fldtype
:= pr.expectId
();
689 if pr.eatDelim
('[') then
691 lmaxdim
:= pr.expectInt
();
692 if
(lmaxdim
< 1) then raise Exception.Create
(Format
('invalid field ''%s'' array size', [fldname
]));
696 while (pr
.tokType
<> pr
.TTSemi
) do
698 if pr
.eatId('offset') then
700 if (fldofs
>= 0) then raise Exception
.Create(Format('duplicate field ''%s'' offset', [fldname
]));
701 fldofs
:= pr
.expectInt();
702 if (fldofs
< 0) then raise Exception
.Create(Format('invalid field ''%s'' offset', [fldname
]));
706 if pr
.eatId('as') then
708 if pr
.eatId('xy') then asxy
:= true
709 else if pr
.eatId('wh') then aswh
:= true
710 else if pr
.eatId('txy') then begin asxy
:= true; ast
:= true; end
711 else if pr
.eatId('twh') then begin aswh
:= true; ast
:= true; end
712 else if pr
.eatId('monsterid') then begin asmonid
:= true
; end
713 else raise Exception.Create
(Format
('invalid field ''%s'' as what?', [fldname
]));
717 if pr
.eatId('enum') then
719 lebs
:= TDynField
.TEBS
.TEnum
;
720 if (Length(fldrecname
) <> 0) then raise Exception
.Create(Format('field ''%s'' already typed as ''%s''', [fldname
, fldrecname
]));
721 fldrecname
:= pr
.expectId();
725 if pr
.eatId('bitset') then
727 lebs
:= TDynField
.TEBS
.TBitSet
;
728 if (Length(fldrecname
) <> 0) then raise Exception
.Create(Format('field ''%s'' already typed as ''%s''', [fldname
, fldrecname
]));
729 unique
:= pr
.eatId('unique');
730 fldrecname
:= pr
.expectId();
734 if pr
.eatId('default') then
736 if hasdefStr
or hasdefInt
or hasdefId
then raise Exception
.Create(Format('field ''%s'' has duplicate default', [fldname
]));
741 defstr
:= pr
.expectStr(true); // allow empty strings
746 defstr
:= pr
.expectId();
751 defint
:= pr
.expectInt();
754 raise Exception
.Create(Format('field ''%s'' has invalid default', [fldname
]));
759 if pr
.eatId('omitdefault') then
765 if pr
.eatId('internal') then
771 if (pr
.tokType
<> pr
.TTId
) then raise Exception
.Create(Format('field ''%s'' has something unexpected in definition', [fldname
]));
773 if (Length(fldrecname
) <> 0) then raise Exception
.Create(Format('field ''%s'' already typed as ''%s''', [fldname
, fldrecname
]));
774 fldrecname
:= pr
.expectId();
775 lebs
:= TDynField
.TEBS
.TRec
;
778 pr
.expectTT(pr
.TTSemi
);
782 if (fldtype
= 'bool') then mType
:= TType
.TBool
783 else if (fldtype
= 'negbool') then begin mType
:= TType
.TBool
; mNegBool
:= true; end
784 else if (fldtype
= 'char') then mType
:= TType
.TChar
785 else if (fldtype
= 'byte') then mType
:= TType
.TByte
786 else if (fldtype
= 'ubyte') then mType
:= TType
.TUByte
787 else if (fldtype
= 'short') then mType
:= TType
.TShort
788 else if (fldtype
= 'ushort') then mType
:= TType
.TUShort
789 else if (fldtype
= 'int') then mType
:= TType
.TInt
790 else if (fldtype
= 'uint') then mType
:= TType
.TUInt
791 else if (fldtype
= 'string') then mType
:= TType
.TString
792 else if (fldtype
= 'point') then mType
:= TType
.TPoint
793 else if (fldtype
= 'size') then mType
:= TType
.TSize
794 else if (fldtype
= 'trigdata') then mType
:= TType
.TTrigData
795 else raise Exception
.Create(Format('field ''%s'' has invalid type ''%s''', [fldname
, fldtype
]));
797 if (lmaxdim
> 0) and (mType
<> TType
.TChar
) and (mType
<> TType
.TTrigData
) then raise Exception
.Create(Format('field ''%s'' of type ''%s'' cannot be array', [fldname
, fldtype
]));
798 if (mType
= TType
.TTrigData
) then
800 if (lmaxdim
< 1) then raise Exception
.Create(Format('field ''%s'' of type ''%s'' cannot be array', [fldname
, fldtype
]));
801 if (Length(fldrecname
) > 0) then raise Exception
.Create(Format('field ''%s'' of type ''%s'' cannot have another type', [fldname
, fldtype
]));
802 lebs
:= TDynField
.TEBS
.TRec
;
805 if hasdefStr
then self
.mDefUnparsed
:= quoteStr(defstr
)
806 else if hasdefInt
then self
.mDefUnparsed
:= Format('%d', [defint
])
807 else if hasdefId
then self
.mDefUnparsed
:= defstr
;
809 self
.mHasDefault
:= (hasdefStr
or hasdefId
or hasdefInt
);
810 self
.mPasName
:= fldpasname
;
812 self
.mEBSTypeName
:= fldrecname
;
813 self
.mBitSetUnique
:= unique
;
814 self
.mAsMonsterId
:= asmonid
;
815 self.mMaxDim
:= lmaxdim
;
816 self.mBinOfs
:= fldofs
;
817 self.mRecOfs
:= fldofs
;
818 self.mSepPosSize
:= (asxy
or aswh
);
820 self.mOmitDef
:= omitdef
;
821 self.mInternal
:= ainternal
;
825 procedure TDynField
.writeBinTo (st
: TStream
);
834 TEBS
.TNone
: begin end;
837 if (mMaxDim
>= 0) then
839 // this must be triggerdata
840 if (mType
<> TType
.TTrigData
) then
842 raise Exception
.Create(Format('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName
, mName
]));
845 GetMem(buf
, mMaxDim
);
846 if (buf
= nil) then raise Exception
.Create(Format('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName
, mName
]));
848 FillChar(buf
^, mMaxDim
, 0);
849 if (mRecRef
<> nil) then
851 ws
:= TSFSMemoryChunkStream
.Create(buf
, mMaxDim
);
852 mRecRef
.writeBinTo(ws
, mMaxDim
); // as trigdata
854 st
.WriteBuffer(buf
^, mMaxDim
);
857 if (buf
<> nil) then FreeMem(buf
);
863 TType
.TByte
: maxv
:= 127;
864 TType
.TUByte
: maxv
:= 254;
865 TType
.TShort
: maxv
:= 32767;
866 TType
.TUShort
: maxv
:= 65534;
867 TType
.TInt
: maxv
:= $7fffffff;
868 TType
.TUInt
: maxv
:= $7fffffff;
869 else raise Exception
.Create(Format('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName
, mName
]));
871 // find record number
872 if (mRecRef
<> nil) then
874 f
:= mOwner
.findRecordNumByType(mEBSTypeName
, mRecRef
);
875 if (f
< 0) then raise Exception
.Create(Format('record reference type ''%s'' in field ''%s'' not found in record list', [mEBSTypeName
, mName
]));
876 if mAsMonsterId
then Inc(f
);
877 if (f
> maxv
) then raise Exception
.Create(Format('record reference type ''%s'' in field ''%s'' has too big index', [mEBSTypeName
, mName
]));
881 if mAsMonsterId
then f
:= 0 else f
:= -1;
884 TType
.TByte
, TType
.TUByte
: writeInt(st
, Byte(f
));
885 TType
.TShort
, TType
.TUShort
: writeInt(st
, SmallInt(f
));
886 TType
.TInt
, TType
.TUInt
: writeInt(st
, LongWord(f
));
887 else raise Exception
.Create(Format('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName
, mName
]));
891 TEBS
.TEnum
: begin end;
892 TEBS
.TBitSet
: begin end;
893 else raise Exception
.Create('ketmar forgot to handle some EBS type');
901 if (mIVal
<> 0) then writeInt(st
, Byte(1)) else writeInt(st
, Byte(0));
905 if (mIVal
= 0) then writeInt(st
, Byte(1)) else writeInt(st
, Byte(0));
911 if (mMaxDim
= 0) then raise Exception
.Create(Format('invalid string size definition for field ''%s''', [mName
]));
912 if (mMaxDim
< 0) then
914 if (Length(mSVal
) <> 1) then raise Exception
.Create(Format('invalid string size definition for field ''%s''', [mName
]));
915 writeInt(st
, Byte(mSVal
[1]));
919 if (Length(mSVal
) > mMaxDim
) then raise Exception
.Create(Format('invalid string size definition for field ''%s''', [mName
]));
921 if (Length(s
) > 0) then st
.WriteBuffer(PChar(s
)^, Length(s
));
922 for f
:= Length(s
) to mMaxDim
do writeInt(st
, Byte(0));
929 // triggerdata array was processed earlier
930 if (mMaxDim
>= 0) then Exception
.Create(Format('byte array in field ''%s'' cannot be written', [mName
]));
931 writeInt(st
, Byte(mIVal
));
937 if (mMaxDim
>= 0) then raise Exception
.Create(Format('short array in field ''%s'' cannot be written', [mName
]));
938 writeInt(st
, Word(mIVal
));
944 if (mMaxDim
>= 0) then raise Exception
.Create(Format('int array in field ''%s'' cannot be written', [mName
]));
945 writeInt(st
, LongWord(mIVal
));
950 raise Exception
.Create(Format('cannot write string field ''%s''', [mName
]));
954 if (mMaxDim
>= 0) then raise Exception
.Create(Format('pos/size array in field ''%s'' cannot be written', [mName
]));
955 writeInt(st
, LongInt(mIVal
));
956 writeInt(st
, LongInt(mIVal2
));
961 if (mMaxDim
>= 0) then raise Exception
.Create(Format('pos/size array in field ''%s'' cannot be written', [mName
]));
962 writeInt(st
, Word(mIVal
));
963 writeInt(st
, Word(mIVal2
));
976 else raise Exception
.Create('ketmar forgot to handle some field type');
981 procedure TDynField
.writeTo (wr
: TTextWriter
);
985 first
, found
: Boolean;
990 TEBS
.TNone
: begin end;
993 if (mRecRef
= nil) then
995 if (mType
= TType
.TTrigData
) then wr
.put('{}'#10) else wr
.put('null;'#10);
997 else if (Length(mRecRef
.mId
) = 0) then
999 mRecRef
.writeTo(wr
, false); // only data, no header
1003 wr
.put(mRecRef
.mId
);
1010 //def := mOwner.mOwner;
1011 //es := def.findEBSType(mEBSTypeName);
1013 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1014 if (es
= nil) or (not es
.mIsEnum
) then raise Exception
.Create(Format('record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]));
1015 for f
:= 0 to High(es
.mVals
) do
1017 if (es
.mVals
[f
] = mIVal
) then
1024 raise Exception
.Create(Format('value %d in record enum type ''%s'' for field ''%s'' not found', [mIVal
, mEBSTypeName
, mName
]));
1028 //def := mOwner.mOwner;
1029 //es := def.findEBSType(mEBSTypeName);
1031 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1032 if (es
= nil) or es
.mIsEnum
then raise Exception
.Create(Format('record bitset type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]));
1036 for f
:= 0 to High(es
.mVals
) do
1038 if (es
.mVals
[f
] = 0) then
1045 raise Exception
.Create(Format('value %d in record bitset type ''%s'' for field ''%s'' not found', [0, mEBSTypeName
, mName
]));
1050 while (mask
<> 0) do
1052 if ((mIVal
and mask
) <> 0) then
1055 for f
:= 0 to High(es
.mVals
) do
1057 if (es
.mVals
[f
] = mask
) then
1059 if not first
then wr
.put('+') else first
:= false;
1065 if not found
then raise Exception
.Create(Format('value %d in record bitset type ''%s'' for field ''%s'' not found', [mask
, mEBSTypeName
, mName
]));
1072 else raise Exception
.Create('ketmar forgot to handle some EBS type');
1078 if (mIVal
= 0) then wr
.put('false;'#10) else wr
.put('true;'#10);
1083 if (mMaxDim
= 0) then raise Exception
.Create(Format('invalid string size definition for field ''%s''', [mName
]));
1084 wr
.put(quoteStr(mSVal
));
1095 wr
.put('%d;'#10, [mIVal
]);
1100 wr
.put(quoteStr(mSVal
));
1107 wr
.put('(%d %d);'#10, [mIVal
, mIVal2
]);
1120 else raise Exception
.Create('ketmar forgot to handle some field type');
1122 raise Exception
.Create(Format('cannot parse field ''%s'' yet', [mName
]));
1125 procedure TDynField
.parseBinValue (st
: TStream
);
1127 rec
, rc
: TDynRecord
;
1135 TEBS
.TNone
: begin end;
1138 // this must be triggerdata
1139 if (mType
= TType
.TTrigData
) then
1141 assert(mMaxDim
> 0);
1143 // find trigger definition
1144 tfld
:= rec
.trigTypeField();
1145 if (tfld
= nil) then raise Exception
.Create(Format('triggerdata value for field ''%s'' in record ''%s'' without TriggerType field', [mName
, rec
.mName
]));
1146 rc
:= mOwner
.mOwner
.findTrigFor(tfld
.mSVal
); // find in mapdef
1147 if (rc
= nil) then raise Exception
.Create(Format('triggerdata definition for field ''%s'' in record ''%s'' with type ''%s'' not found', [mName
, rec
.mName
, tfld
.mSVal
]));
1149 rc
.mHeaderRec
:= mOwner
.mHeaderRec
;
1151 rc
.parseBinValue(st
, true);
1162 // not a trigger data
1164 TType
.TByte
: f
:= readShortInt(st
);
1165 TType
.TUByte
: f
:= readByte(st
);
1166 TType
.TShort
: f
:= readSmallInt(st
);
1167 TType
.TUShort
: f
:= readWord(st
);
1168 TType
.TInt
: f
:= readLongInt(st
);
1169 TType
.TUInt
: f
:= readLongWord(st
);
1170 else raise Exception
.Create(Format('invalid non-numeric type ''%s'' for field ''%s'' of record ''%s''', [getTypeName(mType
), mName
, mEBSTypeName
]));
1172 if mAsMonsterId
then Dec(f
);
1173 if (f
< 0) then mRecRefId
:= '' else mRecRefId
:= Format('%s%d', [mEBSTypeName
, f
]);
1181 assert(mMaxDim
< 0);
1183 TType
.TByte
: f
:= readShortInt(st
);
1184 TType
.TUByte
: f
:= readByte(st
);
1185 TType
.TShort
: f
:= readSmallInt(st
);
1186 TType
.TUShort
: f
:= readWord(st
);
1187 TType
.TInt
: f
:= readLongInt(st
);
1188 TType
.TUInt
: f
:= readLongWord(st
);
1189 else raise Exception
.Create(Format('invalid non-numeric type ''%s'' for field ''%s'' of record ''%s''', [getTypeName(mType
), mName
, mEBSTypeName
]));
1192 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1193 if (es
= nil) or (es
.mIsEnum
<> (mEBS
= TEBS
.TEnum
)) then raise Exception
.Create(Format('record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]));
1195 // build enum/bitfield values
1196 if (mEBS
= TEBS
.TEnum
) then
1198 mSVal
:= es
.nameByValue(mIVal
);
1199 if (Length(mSVal
) = 0) then raise Exception
.Create(Format('record enum type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName
, mName
, mIVal
]));
1203 // special for 'none'
1206 mSVal
:= es
.nameByValue(mIVal
);
1207 if (Length(mSVal
) = 0) then raise Exception
.Create(Format('record bitset type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName
, mName
, mIVal
]));
1213 while (mask
<> 0) do
1215 if ((mIVal
and mask
) <> 0) then
1217 s
:= es
.nameByValue(mask
);
1218 if (Length(s
) = 0) then raise Exception
.Create(Format('record bitset type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName
, mName
, mask
]));
1219 if (Length(mSVal
) <> 0) then mSVal
+= '+';
1226 //writeln('ebs <', es.mName, '>: ', mSVal);
1230 else raise Exception
.Create('ketmar forgot to handle some EBS type');
1237 if (f
<> 0) then f
:= 1;
1238 if mNegBool
then f
:= 1-f
;
1245 if (mMaxDim
< 0) then
1247 mIVal
:= readByte(st
);
1252 GetMem(tdata
, mMaxDim
);
1254 st
.ReadBuffer(tdata
^, mMaxDim
);
1256 while (f
< mMaxDim
) and (tdata
[f
] <> 0) do Inc(f
);
1259 SetLength(mSVal
, f
);
1260 Move(tdata
^, PChar(mSVal
)^, f
);
1261 mSVal
:= win2utf(mSVal
);
1270 TType
.TByte
: begin mIVal
:= readShortInt(st
); mDefined
:= true; exit
; end;
1271 TType
.TUByte
: begin mIVal
:= readByte(st
); mDefined
:= true; exit
; end;
1272 TType
.TShort
: begin mIVal
:= readSmallInt(st
); mDefined
:= true; exit
; end;
1273 TType
.TUShort
: begin mIVal
:= readWord(st
); mDefined
:= true; exit
; end;
1274 TType
.TInt
: begin mIVal
:= readLongInt(st
); mDefined
:= true; exit
; end;
1275 TType
.TUInt
: begin mIVal
:= readLongWord(st
); mDefined
:= true; exit
; end;
1278 raise Exception
.Create('cannot read strings from binaries yet');
1283 mIVal
:= readLongInt(st
);
1284 mIVal2
:= readLongInt(st
);
1290 mIVal
:= readWord(st
);
1291 mIVal2
:= readWord(st
);
1305 else raise Exception
.Create('ketmar forgot to handle some field type');
1307 raise Exception
.Create(Format('cannot parse field ''%s'' yet', [mName
]));
1311 procedure TDynField
.parseValue (pr
: TTextParser
);
1313 procedure parseInt (min
, max
: Integer);
1315 mIVal
:= pr
.expectInt();
1316 if (mIVal
< min
) or (mIVal
> max
) then raise Exception
.Create(Format('invalid %s value for field ''%s''', [getTypeName(mType
), mName
]));
1321 rec
, rc
: TDynRecord
;
1327 // if this field should contain struct, convert type and parse struct
1329 TEBS
.TNone
: begin end;
1332 // ugly hack. sorry.
1333 if (mType
= TType
.TTrigData
) then
1335 pr
.expectTT(pr
.TTBegin
);
1336 if (pr
.tokType
= pr
.TTEnd
) then
1340 pr
.expectTT(pr
.TTEnd
);
1345 // find trigger definition
1346 tfld
:= rec
.trigTypeField();
1347 if (tfld
= nil) then raise Exception
.Create(Format('triggerdata value for field ''%s'' in record ''%s'' without ''type'' field', [mName
, rec
.mName
]));
1348 rc
:= mOwner
.mOwner
.findTrigFor(tfld
.mSVal
); // find in mapdef
1349 if (rc
= nil) then raise Exception
.Create(Format('triggerdata definition for field ''%s'' in record ''%s'' with type ''%s'' not found', [mName
, rec
.mName
, tfld
.mSVal
]));
1351 rc
.mHeaderRec
:= mOwner
.mHeaderRec
;
1352 //writeln(rc.definition);
1354 rc
.parseValue(pr
, true);
1362 pr
.eatTT(pr
.TTSemi
); // hack: allow (but don't require) semicolon after inline records
1365 // other record types
1366 if (pr
.tokType
= pr
.TTId
) then
1368 if pr
.eatId('null') then
1374 rec
:= mOwner
.findRecordByTypeId(mEBSTypeName
, pr
.tokStr
);
1375 if (rec
= nil) then raise Exception
.Create(Format('record ''%s'' (%s) value for field ''%s'' not found', [pr
.tokStr
, mEBSTypeName
, mName
]));
1380 pr
.expectTT(pr
.TTSemi
);
1383 else if (pr
.tokType
= pr
.TTBegin
) then
1385 //rec := mOwner.mOwner.findRecType(mEBSTypeName); // find in mapdef
1387 if (mEBSType
<> nil) and (mEBSType
is TDynRecord
) then rec
:= (mEBSType
as TDynRecord
);
1388 if (rec
= nil) then raise Exception
.Create(Format('record type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]));
1390 rc
.mHeaderRec
:= mOwner
.mHeaderRec
;
1394 if mOwner
.addRecordByType(mEBSTypeName
, rc
) then
1396 //raise Exception.Create(Format('record type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]));
1397 e_LogWritefln('duplicate record with id ''%s'' for field ''%s'' in record ''%s''', [rc
.mId
, mName
, mOwner
.mName
]);
1399 pr
.eatTT(pr
.TTSemi
); // hack: allow (but don't require) semicolon after inline records
1402 pr
.expectTT(pr
.TTBegin
);
1406 //es := mOwner.mOwner.findEBSType(mEBSTypeName); // find in mapdef
1408 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1409 if (es
= nil) or (not es
.mIsEnum
) then raise Exception
.Create(Format('record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]));
1410 tk
:= pr
.expectId();
1411 if not es
.has
[tk
] then raise Exception
.Create(Format('record enum value ''%s'' of type ''%s'' for field ''%s'' not found', [tk
, mEBSTypeName
, mName
]));
1412 mIVal
:= es
.field
[tk
];
1414 //writeln('ENUM ', mEBSName, '; element <', mSVal, '> with value ', mIVal);
1416 pr
.expectTT(pr
.TTSemi
);
1421 //es := mOwner.mOwner.findEBSType(mEBSTypeName); // find in mapdef
1423 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1424 if (es
= nil) or es
.mIsEnum
then raise Exception
.Create(Format('record bitset type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]));
1428 tk
:= pr
.expectId();
1429 if not es
.has
[tk
] then raise Exception
.Create(Format('record bitset value ''%s'' of type ''%s'' for field ''%s'' not found', [tk
, mEBSTypeName
, mName
]));
1430 mIVal
:= mIVal
or es
.field
[tk
];
1432 if (pr
.tokType
<> pr
.TTDelim
) or ((pr
.tokChar
<> '|') and (pr
.tokChar
<> '+')) then break
;
1433 if mBitSetUnique
then raise Exception
.Create(Format('record bitset of type ''%s'' for field ''%s'' expects only one value', [tk
, mEBSTypeName
, mName
]));
1434 //pr.expectDelim('|');
1435 pr
.skipToken(); // plus or pipe
1438 pr
.expectTT(pr
.TTSemi
);
1441 else raise Exception
.Create('ketmar forgot to handle some EBS type');
1447 if pr
.eatId('true') or pr
.eatId('tan') or pr
.eatId('yes') then mIVal
:= 1
1448 else if pr
.eatId('false') or pr
.eatId('ona') or pr
.eatId('no') then mIVal
:= 0
1449 else raise Exception
.Create(Format('invalid bool value for field ''%s''', [mName
]));
1451 pr
.expectTT(pr
.TTSemi
);
1456 if (mMaxDim
= 0) then raise Exception
.Create(Format('invalid string size definition for field ''%s''', [mName
]));
1457 mSVal
:= pr
.expectStr(true);
1458 if (mMaxDim
< 0) then
1461 if (Length(mSVal
) <> 1) then raise Exception
.Create(Format('invalid string size for field ''%s''', [mName
]));
1462 mIVal
:= Integer(mSVal
[1]);
1468 if (Length(mSVal
) > mMaxDim
) then raise Exception
.Create(Format('invalid string size for field ''%s''', [mName
]));
1471 pr
.expectTT(pr
.TTSemi
);
1476 parseInt(-128, 127);
1477 pr
.expectTT(pr
.TTSemi
);
1483 pr
.expectTT(pr
.TTSemi
);
1488 parseInt(-32768, 32768);
1489 pr
.expectTT(pr
.TTSemi
);
1495 pr
.expectTT(pr
.TTSemi
);
1500 parseInt(Integer($80000000), $7fffffff);
1501 pr
.expectTT(pr
.TTSemi
);
1506 parseInt(0, $7fffffff); //FIXME
1507 pr
.expectTT(pr
.TTSemi
);
1512 mSVal
:= pr
.expectStr(true);
1514 pr
.expectTT(pr
.TTSemi
);
1520 if pr
.eatDelim('[') then edim
:= ']' else begin pr
.expectDelim('('); edim
:= ')'; end;
1521 mIVal
:= pr
.expectInt();
1522 if (mType
= TType
.TSize
) then
1524 if (mIVal
< 0) or (mIVal
> 32767) then raise Exception
.Create(Format('invalid %s value for field ''%s''', [getTypeName(mType
), mName
]));
1526 mIVal2
:= pr
.expectInt();
1527 if (mType
= TType
.TSize
) then
1529 if (mIVal2
< 0) or (mIVal2
> 32767) then raise Exception
.Create(Format('invalid %s value for field ''%s''', [getTypeName(mType
), mName
]));
1532 pr
.expectDelim(edim
);
1533 pr
.expectTT(pr
.TTSemi
);
1546 else raise Exception
.Create('ketmar forgot to handle some field type');
1548 raise Exception
.Create(Format('cannot parse field ''%s'' yet', [mName
]));
1552 // ////////////////////////////////////////////////////////////////////////// //
1553 constructor TDynRecord
.Create (pr
: TTextParser
);
1555 if (pr
= nil) then raise Exception
.Create('cannot create record type without type definition');
1559 mFields
:= TDynFieldList
.Create();
1560 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
1561 mFieldsHash
:= hashNewStrInt();
1571 constructor TDynRecord
.Create ();
1575 mFields
:= TDynFieldList
.Create();
1576 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
1577 mFieldsHash
:= hashNewStrInt();
1585 destructor TDynRecord
.Destroy ();
1590 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
1600 procedure TDynRecord
.addField (fld
: TDynField
); inline;
1602 if (fld
= nil) then raise Exception
.Create('cannot append nil field to record');
1603 mFields
.append(fld
);
1604 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
1605 if (Length(fld
.mName
) > 0) then mFieldsHash
.put(fld
.mName
, mFields
.count
-1);
1610 function TDynRecord
.addFieldChecked (fld
: TDynField
): Boolean; inline; // `true`: duplicate name
1613 if (fld
= nil) then raise Exception
.Create('cannot append nil field to record');
1614 {$IF not DEFINED(XDYNREC_USE_FIELDHASH)}
1615 if (Length(fld
.mName
) > 0) then result
:= hasByName(fld
.mName
);
1617 mFields
.append(fld
);
1618 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
1619 if (Length(fld
.mName
) > 0) then result
:= mFieldsHash
.put(fld
.mName
, mFields
.count
-1);
1624 function TDynRecord
.findByName (const aname
: AnsiString): Integer; inline;
1626 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
1627 if not mFieldsHash
.get(aname
, result
) then result
:= -1;
1630 while (result
< mFields
.count
) do
1632 if StrEqu(aname
, mFields
[result
].mName
) then exit
;
1640 function TDynRecord
.hasByName (const aname
: AnsiString): Boolean; inline;
1642 result
:= (findByName(aname
) >= 0);
1646 function TDynRecord
.getFieldByName (const aname
: AnsiString): TDynField
; inline;
1650 f
:= findByName(aname
);
1651 if (f
>= 0) then result
:= mFields
[f
] else result
:= nil;
1655 function TDynRecord
.getIsTrigData (): Boolean; inline;
1657 result
:= (Length(mTrigTypes
) > 0);
1661 function TDynRecord
.getIsForTrig (const aname
: AnsiString): Boolean; inline;
1666 for f
:= 0 to High(mTrigTypes
) do if StrEqu(mTrigTypes
[f
], aname
) then exit
;
1671 function TDynRecord
.clone (): TDynRecord
;
1676 result
:= TDynRecord
.Create();
1677 result
.mOwner
:= mOwner
;
1679 result
.mPasName
:= mPasName
;
1680 result
.mName
:= mName
;
1681 result
.mSize
:= mSize
;
1682 if (mFields
.count
> 0) then
1684 result
.mFields
.capacity
:= mFields
.count
;
1685 for fld
in mFields
do result
.addField(fld
.clone(result
));
1687 SetLength(result
.mTrigTypes
, Length(mTrigTypes
));
1688 for f
:= 0 to High(mTrigTypes
) do result
.mTrigTypes
[f
] := mTrigTypes
[f
];
1689 result
.mHeader
:= mHeader
;
1690 result
.mBinBlock
:= mBinBlock
;
1691 result
.mHeaderRec
:= mHeaderRec
;
1695 function TDynRecord
.findRecordByTypeId (const atypename
, aid
: AnsiString): TDynRecord
;
1701 if (Length(aid
) = 0) then exit
;
1703 fld
:= mHeaderRec
.field
[atypename
];
1704 if (fld
= nil) then exit
;
1705 if (fld
.mType
<> fld
.TType
.TList
) then raise Exception
.Create(Format('cannot get record of type ''%s'' due to name conflict with ordinary field', [atypename
]));
1707 if (fld
.mRVal
<> nil) then
1709 if fld
.mRHash
.get(aid
, idx
) then begin result
:= fld
.mRVal
[idx
]; exit
; end;
1715 function TDynRecord
.findRecordNumByType (const atypename
: AnsiString; rc
: TDynRecord
): Integer;
1722 fld
:= mHeaderRec
.field
[atypename
];
1723 if (fld
= nil) then exit
;
1724 if (fld
.mType
<> fld
.TType
.TList
) then raise Exception
.Create(Format('cannot get record of type ''%s'' due to name conflict with ordinary field', [atypename
]));
1726 if (fld
.mRVal
<> nil) then
1728 for idx
:= 0 to fld
.mRVal
.count
-1 do
1730 if (fld
.mRVal
[idx
] = rc
) then begin result
:= idx
; exit
; end;
1737 function TDynRecord
.addRecordByType (const atypename
: AnsiString; rc
: TDynRecord
): Boolean;
1742 fld
:= mHeaderRec
.field
[atypename
];
1746 fld
:= TDynField
.Create(atypename
, TDynField
.TType
.TList
);
1747 fld
.mOwner
:= mHeaderRec
;
1748 mHeaderRec
.addField(fld
);
1750 if (fld
.mType
<> fld
.TType
.TList
) then raise Exception
.Create(Format('cannot append record of type ''%s'' due to name conflict with ordinary field', [atypename
]));
1752 if (fld
.mRVal
= nil) then
1754 fld
.mRVal
:= TDynRecList
.Create();
1755 fld
.mRHash
:= hashNewStrInt();
1757 result
:= fld
.addListItem(rc
);
1761 function TDynRecord
.isSimpleEqu (rec
: TDynRecord
): Boolean;
1765 if (rec
= nil) then begin result
:= false; exit
; end; // self.mRecRef can't be `nil` here
1766 if (rec
= self
) then begin result
:= true; exit
; end;
1767 if (mFields
.count
<> rec
.mFields
.count
) then begin result
:= false; exit
; end;
1769 for f
:= 0 to mFields
.count
-1 do
1771 if not mFields
[f
].isSimpleEqu(rec
.mFields
[f
]) then exit
;
1777 function TDynRecord
.trigTypeField (): TDynField
;
1782 for fld
in mFields
do
1784 if (fld
.mEBS
<> TDynField
.TEBS
.TEnum
) then continue
;
1785 if not (fld
.mEBSType
is TDynEBS
) then continue
;
1786 es
:= (fld
.mEBSType
as TDynEBS
);
1788 if StrEqu(es
.mName
, 'TriggerType') then begin result
:= fld
; exit
; end;
1794 // number of records of the given instance
1795 function TDynRecord
.instanceCount (const typename
: AnsiString): Integer;
1800 fld
:= field
[typename
];
1801 if (fld
<> nil) and (fld
.mType
= fld
.TType
.TList
) then result
:= fld
.mRVal
.count
;
1805 procedure TDynRecord
.parseDef (pr
: TTextParser
);
1810 if pr
.eatId('TriggerData') then
1813 if pr
.eatDelim('(') then
1817 while pr
.eatTT(pr
.TTComma
) do begin end;
1818 if pr
.eatDelim(')') then break
;
1819 tdn
:= pr
.expectId();
1820 if isForTrig
[tdn
] then raise Exception
.Create(Format('duplicate trigdata ''%s'' trigtype ''%s''', [mName
, tdn
]));
1821 SetLength(mTrigTypes
, Length(mTrigTypes
)+1);
1822 mTrigTypes
[High(mTrigTypes
)] := tdn
;
1827 tdn
:= pr
.expectId();
1828 SetLength(mTrigTypes
, 1);
1829 mTrigTypes
[0] := tdn
;
1831 mName
:= 'TriggerData';
1835 mPasName
:= pr
.expectId(); // pascal record name
1837 mName
:= pr
.expectStr();
1838 while (pr
.tokType
<> pr
.TTBegin
) do
1840 if pr
.eatId('header') then begin mHeader
:= true; continue
; end;
1841 if pr
.eatId('size') then
1843 if (mSize
> 0) then raise Exception
.Create(Format('duplicate `size` in record ''%s''', [mName
]));
1844 mSize
:= pr
.expectInt();
1845 if (mSize
< 1) then raise Exception
.Create(Format('invalid record ''%s'' size: %d', [mName
, mSize
]));
1846 pr
.expectId('bytes');
1849 if pr
.eatId('binblock') then
1851 if (mBinBlock
>= 0) then raise Exception
.Create(Format('duplicate `binblock` in record ''%s''', [mName
]));
1852 mBinBlock
:= pr
.expectInt();
1853 if (mBinBlock
< 1) then raise Exception
.Create(Format('invalid record ''%s'' binblock: %d', [mName
, mBinBlock
]));
1859 pr
.expectTT(pr
.TTBegin
);
1861 while (pr
.tokType
<> pr
.TTEnd
) do
1863 fld
:= TDynField
.Create(pr
);
1864 //if hasByName(fld.name) then begin fld.Free(); raise Exception.Create(Format('duplicate field ''%s''', [fld.name])); end;
1867 if addFieldChecked(fld
) then
1870 raise Exception
.Create(Format('duplicate field ''%s''', [fld
.name
]));
1874 pr
.expectTT(pr
.TTEnd
);
1878 function TDynRecord
.pasdef (): AnsiString;
1890 result
:= ' '+mPasName
+' = packed record'#10;
1892 for fld
in mFields
do
1894 if fld
.mInternal
then continue
;
1895 if (fld
.mBinOfs
< 0) then continue
;
1896 result
+= ' '+fld
.pasdef
+#10;
1898 result
+= ' end;'#10;
1902 function TDynRecord
.definition (): AnsiString;
1909 result
:= 'TriggerData for ';
1910 if (Length(mTrigTypes
) > 1) then
1913 for f
:= 0 to High(mTrigTypes
) do
1915 if (f
<> 0) then result
+= ', ';
1916 result
+= mTrigTypes
[f
];
1922 result
+= mTrigTypes
[0];
1928 result
:= mPasName
+' is '+quoteStr(mName
);
1929 if (mSize
>= 0) then result
+= Format(' size %d bytes', [mSize
]);
1930 if mHeader
then result
+= ' header';
1933 for f
:= 0 to mFields
.count
-1 do
1936 result
+= mFields
[f
].definition
;
1943 procedure TDynRecord
.parseBinValue (st
: TStream
; forceData
: Boolean=false);
1949 loaded
: array[0..255] of Boolean;
1950 rec
, rect
: TDynRecord
;
1953 mst
: TSFSMemoryChunkStream
= nil;
1955 procedure linkNames (rec
: TDynRecord
);
1960 //writeln('*** rec: ', rec.mName, '.', rec.mId, ' (', rec.mFields.count, ')');
1961 for fld
in rec
.mFields
do
1963 if (fld
.mType
= TDynField
.TType
.TTrigData
) then
1965 if (fld
.mRecRef
<> nil) then linkNames(fld
.mRecRef
);
1968 if (Length(fld
.mRecRefId
) = 0) then continue
;
1969 assert(fld
.mEBSType
<> nil);
1970 rt
:= findRecordByTypeId(fld
.mEBSTypeName
, fld
.mRecRefId
);
1973 e_LogWritefln('record of type ''%s'' with id ''%s'' links to inexistant record of type ''%s'' with id ''%s''', [rec
.mName
, rec
.mId
, fld
.mEBSTypeName
, fld
.mRecRefId
], MSG_WARNING
);
1974 //raise Exception.Create(Format('record of type ''%s'' with id ''%s'' links to inexistant record of type ''%s'' with id ''%s''', [rec.mName, rec.mId, fld.mEBSTypeName, fld.mRecRefId]));
1976 //writeln(' ', rec.mName, '.', rec.mId, ':', fld.mName, ' -> ', rt.mName, '.', rt.mId, ' (', fld.mEBSTypeName, '.', fld.mRecRefId, ')');
1977 fld
.mRecRefId
:= '';
1979 fld
.mDefined
:= true;
1981 for fld
in rec
.mFields
do
1983 //writeln(' ', fld.mName);
1984 fld
.fixDefaultValue(); // just in case
1989 for f
:= 0 to High(loaded
) do loaded
[f
] := false;
1990 mst
:= TSFSMemoryChunkStream
.Create(nil, 0);
1992 if mHeader
and not forceData
then
1994 // parse map file as sequence of blocks
1996 st
.ReadBuffer(sign
[1], 4);
1997 if (sign
<> 'MAP'#1) then raise Exception
.Create('invalid binary map signature');
1999 while (st
.position
< st
.size
) do
2001 btype
:= readByte(st
);
2002 if (btype
= 0) then break
; // no more blocks
2003 readLongWord(st
); // reserved
2004 bsize
:= readLongInt(st
);
2005 {$IF DEFINED(D2D_XDYN_DEBUG)}writeln('btype=', btype
, '; bsize=', bsize
);{$ENDIF}
2006 if (bsize
< 0) or (bsize
> $1fffffff) then raise Exception
.Create(Format('block of type %d has invalid size %d', [btype
, bsize
]));
2007 if loaded
[btype
] then raise Exception
.Create(Format('block of type %d already loaded', [btype
]));
2008 loaded
[btype
] := true;
2009 // find record type for this block
2011 for rec
in mOwner
.recTypes
do if (rec
.mBinBlock
= btype
) then begin rect
:= rec
; break
; end;
2012 if (rect
= nil) then raise Exception
.Create(Format('block of type %d has no corresponding record', [btype
]));
2013 //writeln('found type ''', rec.mName, ''' for block type ', btype);
2014 if (rec
.mSize
= 0) or ((bsize
mod rec
.mSize
) <> 0) then raise Exception
.Create(Format('block of type %d has invalid number of records', [btype
]));
2016 if (rect
.mHeader
) then
2018 if (bsize
<> mSize
) then raise Exception
.Create(Format('header block of type %d has invalid number of records', [btype
]));
2020 st
.ReadBuffer(buf
^, bsize
);
2021 mst
.setup(buf
, mSize
);
2022 parseBinValue(mst
, true); // force parsing data
2026 // create list for this type
2027 fld
:= TDynField
.Create(rec
.mName
, TDynField
.TType
.TList
);
2033 st
.ReadBuffer(buf
^, bsize
);
2034 for f
:= 0 to (bsize
div rec
.mSize
)-1 do
2036 mst
.setup(buf
+f
*rec
.mSize
, rec
.mSize
);
2037 rec
:= rect
.clone();
2038 rec
.mHeaderRec
:= self
;
2039 rec
.parseBinValue(mst
);
2040 rec
.mId
:= Format('%s%d', [rec
.mName
, f
]);
2041 fld
.addListItem(rec
);
2042 //writeln('parsed ''', rec.mId, '''...');
2048 //st.position := st.position+bsize;
2051 for fld
in mFields
do
2053 if (fld
.mType
<> TDynField
.TType
.TList
) then continue
;
2054 for rec
in fld
.mRVal
do linkNames(rec
);
2060 if StrEqu(mName
, 'TriggerData') then mSize
:= Integer(st
.size
-st
.position
);
2061 if (mSize
< 1) then raise Exception
.Create(Format('cannot read record of type ''%s'' with unknown size', [mName
]));
2063 st
.ReadBuffer(buf
^, mSize
);
2064 for fld
in mFields
do
2066 if fld
.mInternal
then continue
;
2067 if (fld
.mBinOfs
< 0) then continue
;
2068 if (fld
.mBinOfs
>= st
.size
) then raise Exception
.Create(Format('record of type ''%s'' has invalid field ''%s''', [fld
.mName
]));
2069 mst
.setup(buf
+fld
.mBinOfs
, mSize
-fld
.mBinOfs
);
2070 //writeln('parsing ''', mName, '.', fld.mName, '''...');
2071 fld
.parseBinValue(mst
);
2075 if (buf
<> nil) then FreeMem(buf
);
2080 procedure TDynRecord
.writeBinTo (st
: TStream
; trigbufsz
: Integer=-1; onlyFields
: Boolean=false);
2083 rec
, rv
: TDynRecord
;
2086 blk
, blkmax
: Integer;
2091 if (trigbufsz
< 0) then
2093 if (mBinBlock
< 1) then raise Exception
.Create('cannot write binary record without block number');
2094 if (mSize
< 1) then raise Exception
.Create('cannot write binary record without size');
2103 FillChar(buf
^, bufsz
, 0);
2104 ws
:= TSFSMemoryChunkStream
.Create(buf
, bufsz
);
2106 // write normal fields
2107 for fld
in mFields
do
2110 if (fld
.mType
= fld
.TType
.TList
) then continue
; // later
2111 if fld
.mInternal
then continue
;
2112 if (fld
.mBinOfs
< 0) then continue
;
2113 if (fld
.mBinOfs
>= bufsz
) then raise Exception
.Create('binary value offset is outside of the buffer');
2114 TSFSMemoryChunkStream(ws
).setup(buf
+fld
.mBinOfs
, bufsz
-fld
.mBinOfs
);
2115 //writeln('writing field <', fld.mName, '>');
2119 // write block with normal fields
2120 if mHeader
and not onlyFields
then
2122 //writeln('writing header...');
2123 // signature and version
2124 writeIntBE(st
, LongWord($4D415001));
2125 writeInt(st
, Byte(mBinBlock
)); // type
2126 writeInt(st
, LongWord(0)); // reserved
2127 writeInt(st
, LongWord(bufsz
)); // size
2129 st
.WriteBuffer(buf
^, bufsz
);
2131 ws
.Free(); ws
:= nil;
2132 FreeMem(buf
); buf
:= nil;
2134 // write other blocks, if any
2135 if mHeader
and not onlyFields
then
2139 for fld
in mFields
do
2142 if (fld
.mType
= fld
.TType
.TList
) then
2144 if (fld
.mRVal
= nil) or (fld
.mRVal
.count
= 0) then continue
;
2145 rec
:= mOwner
.findRecType(fld
.mName
);
2146 if (rec
= nil) then continue
;
2147 if (rec
.mBinBlock
<= 0) then continue
;
2148 if (blkmax
< rec
.mBinBlock
) then blkmax
:= rec
.mBinBlock
;
2152 for blk
:= 1 to blkmax
do
2154 if (blk
= mBinBlock
) then continue
;
2156 for fld
in mFields
do
2159 if (fld
.mType
= fld
.TType
.TList
) then
2161 if (fld
.mRVal
= nil) or (fld
.mRVal
.count
= 0) then continue
;
2162 rec
:= mOwner
.findRecType(fld
.mName
);
2163 if (rec
= nil) then continue
;
2164 if (rec
.mBinBlock
<> blk
) then continue
;
2165 if (ws
= nil) then ws
:= TMemoryStream
.Create();
2166 for rv
in fld
.mRVal
do rv
.writeBinTo(ws
);
2172 blksz
:= Integer(ws
.position
);
2174 writeInt(st
, Byte(blk
)); // type
2175 writeInt(st
, LongWord(0)); // reserved
2176 writeInt(st
, LongWord(blksz
)); // size
2177 st
.CopyFrom(ws
, blksz
);
2183 writeInt(st
, Byte(0));
2184 writeInt(st
, LongWord(0));
2185 writeInt(st
, LongWord(0));
2189 if (buf
<> nil) then FreeMem(buf
);
2194 procedure TDynRecord
.writeTo (wr
: TTextWriter
; putHeader
: Boolean=true);
2202 if (Length(mId
) > 0) then begin wr
.put(' '); wr
.put(mId
); end;
2208 for fld
in mFields
do
2211 if (fld
.mType
= fld
.TType
.TList
) then
2213 if not mHeader
then raise Exception
.Create('record list in non-header record');
2214 if (fld
.mRVal
<> nil) then
2216 for rec
in fld
.mRVal
do
2218 if (Length(rec
.mId
) = 0) then continue
;
2220 rec
.writeTo(wr
, true);
2225 if fld
.mInternal
then continue
;
2226 if fld
.mOmitDef
and fld
.isDefaultValue
then continue
;
2238 {$IF DEFINED(D2D_DYNREC_PROFILER)}
2240 profCloneRec
: UInt64 = 0;
2241 profFindRecType
: UInt64 = 0;
2242 profFieldSearching
: UInt64 = 0;
2243 profListDupChecking
: UInt64 = 0;
2244 profAddRecByType
: UInt64 = 0;
2245 profFieldValParsing
: UInt64 = 0;
2246 profFixDefaults
: UInt64 = 0;
2247 profRecValParse
: UInt64 = 0;
2249 procedure xdynDumpProfiles ();
2251 writeln('=== XDYNREC PROFILES ===');
2252 writeln('record cloning: ', profCloneRec
div 1000, '.', profCloneRec
mod 1000, ' milliseconds');
2253 writeln('findRecType : ', profFindRecType
div 1000, '.', profFindRecType
mod 1000, ' milliseconds');
2254 writeln('field[] : ', profFieldSearching
div 1000, '.', profFieldSearching
mod 1000, ' milliseconds');
2255 writeln('list dup check: ', profListDupChecking
div 1000, '.', profListDupChecking
mod 1000, ' milliseconds');
2256 writeln('addRecByType : ', profAddRecByType
div 1000, '.', profAddRecByType
mod 1000, ' milliseconds');
2257 writeln('field valparse: ', profFieldValParsing
div 1000, '.', profFieldValParsing
mod 1000, ' milliseconds');
2258 writeln('fix defaults : ', profFixDefaults
div 1000, '.', profFixDefaults
mod 1000, ' milliseconds');
2259 writeln('recvalparse : ', profRecValParse
div 1000, '.', profRecValParse
mod 1000, ' milliseconds');
2264 procedure TDynRecord
.parseValue (pr
: TTextParser
; beginEaten
: Boolean=false);
2267 rec
: TDynRecord
= nil;
2268 trc
{, rv}: TDynRecord
;
2269 {$IF DEFINED(D2D_DYNREC_PROFILER)}
2273 if (mOwner
= nil) then raise Exception
.Create(Format('can''t parse record ''%s'' value without owner', [mName
]));
2275 {$IF DEFINED(D2D_DYNREC_PROFILER)}stall
:= curTimeMicro();{$ENDIF}
2281 if (not beginEaten
) and (pr
.tokType
= pr
.TTId
) then mId
:= pr
.expectId();
2285 assert(mHeaderRec
= self
);
2288 //writeln('parsing record <', mName, '>');
2289 if not beginEaten
then pr
.expectTT(pr
.TTBegin
);
2290 while (pr
.tokType
<> pr
.TTEnd
) do
2292 if (pr
.tokType
<> pr
.TTId
) then raise Exception
.Create('identifier expected');
2293 //writeln('<', mName, '.', pr.tokStr, '>');
2298 // add records with this type (if any)
2299 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2300 trc
:= mOwner
.findRecType(pr
.tokStr
);
2301 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFindRecType
:= curTimeMicro()-stt
;{$ENDIF}
2302 if (trc
<> nil) then
2304 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2306 {$IF DEFINED(D2D_DYNREC_PROFILER)}profCloneRec
:= curTimeMicro()-stt
;{$ENDIF}
2307 rec
.mHeaderRec
:= mHeaderRec
;
2312 if (Length(rec.mId) > 0) then
2314 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF}
2315 fld := field[pr.tokStr];
2316 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFieldSearching := curTimeMicro()-stt;{$ENDIF}
2318 if (fld <> nil) and (fld.mRVal <> nil) then
2320 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF}
2321 //idtmp := trc.mName+':'+rec.mId;
2322 //if ids.put(idtmp, 1) then raise Exception.Create(Format('duplicate thing ''%s'' in record ''%s''', [fld.mName, mName]));
2323 if fld.mRHash.has(rec.mId) then raise Exception.Create(Format('duplicate thing ''%s'' in record ''%s''', [fld.mName, mName]));
2324 {$IF DEFINED(D2D_DYNREC_PROFILER)}profListDupChecking := curTimeMicro()-stt;{$ENDIF}
2328 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2329 addRecordByType(rec
.mName
, rec
);
2330 {$IF DEFINED(D2D_DYNREC_PROFILER)}profAddRecByType
:= curTimeMicro()-stt
;{$ENDIF}
2340 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2341 fld
:= field
[pr
.tokStr
];
2342 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFieldSearching
:= curTimeMicro()-stt
;{$ENDIF}
2343 if (fld
<> nil) then
2345 if fld
.defined
then raise Exception
.Create(Format('duplicate field ''%s'' in record ''%s''', [fld
.mName
, mName
]));
2346 if fld
.internal
then raise Exception
.Create(Format('internal field ''%s'' in record ''%s''', [fld
.mName
, mName
]));
2348 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2350 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFieldValParsing
:= curTimeMicro()-stt
;{$ENDIF}
2354 // something is wrong
2355 raise Exception
.Create(Format('unknown field ''%s'' in record ''%s''', [pr
.tokStr
, mName
]));
2357 pr
.expectTT(pr
.TTEnd
);
2358 // fix field defaults
2359 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2360 for fld
in mFields
do fld
.fixDefaultValue();
2361 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFixDefaults
:= curTimeMicro()-stt
;{$ENDIF}
2362 //writeln('done parsing record <', mName, '>');
2363 //{$IF DEFINED(D2D_DYNREC_PROFILER)}writeln('stall: ', curTimeMicro()-stall);{$ENDIF}
2364 {$IF DEFINED(D2D_DYNREC_PROFILER)}profRecValParse
:= curTimeMicro()-stall
;{$ENDIF}
2368 // ////////////////////////////////////////////////////////////////////////// //
2369 constructor TDynEBS
.Create (pr
: TTextParser
);
2376 destructor TDynEBS
.Destroy ();
2383 procedure TDynEBS
.cleanup ();
2394 function TDynEBS
.findByName (const aname
: AnsiString): Integer;
2397 while (result
< Length(mIds
)) do
2399 if StrEqu(aname
, mIds
[result
]) then exit
;
2406 function TDynEBS
.hasByName (const aname
: AnsiString): Boolean; inline;
2408 result
:= (findByName(aname
) >= 0);
2412 function TDynEBS
.getFieldByName (const aname
: AnsiString): Integer; inline;
2416 f
:= findByName(aname
);
2417 if (f
>= 0) then result
:= mVals
[f
] else result
:= 0;
2421 function TDynEBS
.definition (): AnsiString;
2425 if mIsEnum
then result
:='enum ' else result
:= 'bitset ';
2429 if mIsEnum
then cv
:= 0 else cv
:= 1;
2430 for f
:= 0 to High(mIds
) do
2432 if (mIds
[f
] = mMaxName
) then continue
;
2433 result
+= ' '+mIds
[f
];
2434 if (mVals
[f
] <> cv
) then
2436 result
+= Format(' = %d', [mVals
[f
]]);
2437 if mIsEnum
then cv
:= mVals
[f
];
2442 result
+= Format(', // %d'#10, [mVals
[f
]]);
2444 if mIsEnum
then Inc(cv
) else if (mVals
[f
] = cv
) then cv
:= cv
shl 1;
2447 if (Length(mMaxName
) > 0) then result
+= ' '+mMaxName
+' = MAX,'#10;
2452 function TDynEBS
.pasdef (): AnsiString;
2456 result
:= '// '+mName
+#10'const'#10;
2458 for f
:= 0 to High(mIds
) do
2460 result
+= formatstrf(' %s = %d;'#10, [mIds
[f
], mVals
[f
]]);
2465 function TDynEBS
.nameByValue (v
: Integer): AnsiString;
2469 for f
:= 0 to High(mVals
) do
2471 if (mVals
[f
] = v
) then begin result
:= mIds
[f
]; exit
; end;
2477 procedure TDynEBS
.parseDef (pr
: TTextParser
);
2485 if pr
.eatId('enum') then mIsEnum
:= true
2486 else if pr
.eatId('bitset') then mIsEnum
:= false
2487 else pr
.expectId('enum');
2488 mName
:= pr
.expectId();
2489 mMaxVal
:= Integer($80000000);
2490 if mIsEnum
then cv
:= 0 else cv
:= 1;
2491 pr
.expectTT(pr
.TTBegin
);
2492 while (pr
.tokType
<> pr
.TTEnd
) do
2494 idname
:= pr
.expectId();
2495 for f
:= 0 to High(mIds
) do
2497 if StrEqu(mIds
[f
], idname
) then raise Exception
.Create(Format('duplicate field ''%s'' in enum/bitset ''%s''', [idname
, mName
]));
2499 if StrEqu(mMaxName
, idname
) then raise Exception
.Create(Format('duplicate field ''%s'' in enum/bitset ''%s''', [idname
, mName
]));
2504 if pr
.eatDelim('=') then
2506 if pr
.eatId('MAX') then
2508 if (Length(mMaxName
) > 0) then raise Exception
.Create(Format('duplicate max field ''%s'' in enum/bitset ''%s''', [idname
, mName
]));
2514 v
:= pr
.expectInt();
2515 if mIsEnum
then cv
:= v
;
2523 if mIsEnum
or (not hasV
) then
2525 if (mMaxVal
< v
) then mMaxVal
:= v
;
2527 SetLength(mIds
, Length(mIds
)+1);
2528 mIds
[High(mIds
)] := idname
;
2529 SetLength(mVals
, Length(mIds
));
2530 mVals
[High(mVals
)] := v
;
2532 if mIsEnum
or (not hasV
) then
2534 if mIsEnum
then Inc(cv
) else cv
:= cv
shl 1;
2537 if (pr
.tokType
= pr
.TTEnd
) then break
;
2538 pr
.expectTT(pr
.TTComma
);
2539 while pr
.eatTT(pr
.TTComma
) do begin end;
2541 pr
.expectTT(pr
.TTEnd
);
2543 if (Length(mMaxName
) > 0) then
2545 SetLength(mIds
, Length(mIds
)+1);
2546 mIds
[High(mIds
)] := mMaxName
;
2547 SetLength(mVals
, Length(mIds
));
2548 mVals
[High(mVals
)] := mMaxVal
;
2553 // ////////////////////////////////////////////////////////////////////////// //
2554 constructor TDynMapDef
.Create (pr
: TTextParser
);
2556 recTypes
:= TDynRecList
.Create();
2557 trigTypes
:= TDynRecList
.Create();
2558 ebsTypes
:= TDynEBSList
.Create();
2563 destructor TDynMapDef
.Destroy ();
2568 for rec
in recTypes
do rec
.Free();
2569 for rec
in trigTypes
do rec
.Free();
2570 for ebs
in ebsTypes
do ebs
.Free();
2581 function TDynMapDef
.getHeaderRecType (): TDynRecord
; inline;
2583 if (recTypes
.count
= 0) then raise Exception
.Create('no header in empty mapdef');
2584 result
:= recTypes
[0];
2588 function TDynMapDef
.findRecType (const aname
: AnsiString): TDynRecord
;
2592 for rec
in recTypes
do
2594 if StrEqu(rec
.name
, aname
) then begin result
:= rec
; exit
; end;
2600 function TDynMapDef
.findTrigFor (const aname
: AnsiString): TDynRecord
;
2604 for rec
in trigTypes
do
2606 if (rec
.isForTrig
[aname
]) then begin result
:= rec
; exit
; end;
2612 function TDynMapDef
.findEBSType (const aname
: AnsiString): TDynEBS
;
2616 for ebs
in ebsTypes
do
2618 if StrEqu(ebs
.name
, aname
) then begin result
:= ebs
; exit
; end;
2624 procedure TDynMapDef
.parseDef (pr
: TTextParser
);
2626 rec
, hdr
: TDynRecord
;
2630 // setup header links and type links
2631 procedure linkRecord (rec
: TDynRecord
);
2635 rec
.mHeaderRec
:= recTypes
[0];
2636 for fld
in rec
.mFields
do
2638 if (fld
.mType
= fld
.TType
.TTrigData
) then continue
;
2640 TDynField
.TEBS
.TNone
: begin end;
2641 TDynField
.TEBS
.TRec
:
2643 fld
.mEBSType
:= findRecType(fld
.mEBSTypeName
);
2644 if (fld
.mEBSType
= nil) then raise Exception
.Create(Format('field ''%s'' of type ''%s'' has no correcponding record definition', [fld
.mName
, fld
.mEBSTypeName
]));
2646 TDynField
.TEBS
.TEnum
,
2647 TDynField
.TEBS
.TBitSet
:
2649 fld
.mEBSType
:= findEBSType(fld
.mEBSTypeName
);
2650 if (fld
.mEBSType
= nil) then raise Exception
.Create(Format('field ''%s'' of type ''%s'' has no correcponding enum/bitset', [fld
.mName
, fld
.mEBSTypeName
]));
2651 if ((fld
.mEBS
= TDynField
.TEBS
.TEnum
) <> (fld
.mEBSType
as TDynEBS
).mIsEnum
) then raise Exception
.Create(Format('field ''%s'' of type ''%s'' enum/bitset type conflict', [fld
.mName
, fld
.mEBSTypeName
]));
2657 // setup default values
2658 procedure fixRecordDefaults (rec
: TDynRecord
);
2662 for fld
in rec
.mFields
do if fld
.mHasDefault
then fld
.parseDefaultValue();
2669 if not pr
.skipBlanks() then break
;
2670 if (pr
.tokType
<> pr
.TTId
) then raise Exception
.Create('identifier expected');
2672 if (pr
.tokStr
= 'enum') or (pr
.tokStr
= 'bitset') then
2674 eb
:= TDynEBS
.Create(pr
);
2675 if (findEBSType(eb
.name
) <> nil) then
2678 raise Exception
.Create(Format('duplicate enum/bitset ''%s''', [eb
.name
]));
2681 ebsTypes
.append(eb
);
2682 //writeln(eb.definition); writeln;
2686 if (pr
.tokStr
= 'TriggerData') then
2688 rec
:= TDynRecord
.Create(pr
);
2689 for f
:= 0 to High(rec
.mTrigTypes
) do
2691 if (findTrigFor(rec
.mTrigTypes
[f
]) <> nil) then
2694 raise Exception
.Create(Format('duplicate trigdata ''%s''', [rec
.mTrigTypes
[f
]]));
2698 trigTypes
.append(rec
);
2699 //writeln(dr.definition); writeln;
2703 rec
:= TDynRecord
.Create(pr
);
2704 //writeln(dr.definition); writeln;
2705 if (findRecType(rec
.name
) <> nil) then begin rec
.Free(); raise Exception
.Create(Format('duplicate record ''%s''', [rec
.name
])); end;
2706 if (hdr
<> nil) and StrEqu(rec
.name
, hdr
.name
) then begin rec
.Free(); raise Exception
.Create(Format('duplicate record ''%s''', [rec
.name
])); end;
2710 if (hdr
<> nil) then begin rec
.Free(); raise Exception
.Create(Format('duplicate header record ''%s'' (previous is ''%s'')', [rec
.name
, hdr
.name
])); end;
2715 recTypes
.append(rec
);
2719 // put header record to top
2720 if (hdr
= nil) then raise Exception
.Create('header definition not found in mapdef');
2721 recTypes
.append(nil);
2722 for f
:= recTypes
.count
-1 downto 1 do recTypes
[f
] := recTypes
[f
-1];
2725 // setup header links and type links
2726 for rec
in recTypes
do linkRecord(rec
);
2727 for rec
in trigTypes
do linkRecord(rec
);
2729 // setup default values
2730 for rec
in recTypes
do fixRecordDefaults(rec
);
2731 for rec
in trigTypes
do fixRecordDefaults(rec
);
2735 // ////////////////////////////////////////////////////////////////////////// //
2736 function TDynMapDef
.parseMap (pr
: TTextParser
): TDynRecord
;
2738 res
: TDynRecord
= nil;
2742 pr
.expectId(headerType
.name
);
2743 res
:= headerType
.clone();
2744 res
.mHeaderRec
:= res
;
2754 function TDynMapDef
.parseBinMap (st
: TStream
): TDynRecord
;
2756 res
: TDynRecord
= nil;
2760 res
:= headerType
.clone();
2761 res
.mHeaderRec
:= res
;
2762 res
.parseBinValue(st
);
2771 function TDynMapDef
.pasdef (): AnsiString;
2780 result
+= '// ////////////////////////////////////////////////////////////////////////// //'#10;
2781 result
+= '// enums and bitsets'#10;
2782 for ebs
in ebsTypes
do result
+= #10+ebs
.pasdef();
2783 result
+= #10#10'// ////////////////////////////////////////////////////////////////////////// //'#10;
2784 result
+= '// records'#10'type'#10;
2785 for rec
in recTypes
do
2787 if (rec
.mSize
< 1) then continue
;
2788 result
+= rec
.pasdef();
2791 result
+= #10#10'// ////////////////////////////////////////////////////////////////////////// //'#10;
2792 result
+= '// triggerdata'#10'type'#10;
2793 result
+= ' TTriggerData = record'#10;
2794 result
+= ' case Byte of'#10;
2795 result
+= ' 0: (Default: Byte128);'#10;
2796 for rec
in trigTypes
do
2800 for tn
in rec
.mTrigTypes
do
2802 if needComma
then result
+= ', ' else needComma
:= true;
2806 for fld
in rec
.mFields
do
2808 if fld
.mInternal
then continue
;
2809 if (fld
.mBinOfs
< 0) then continue
;
2810 result
+= ' '+fld
.pasdef
+#10;
2814 result
+= ' end;'#10;