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
23 SysUtils
, Variants
, Classes
,
24 xparser
, xstreams
, utils
, hashtable
;
27 // ////////////////////////////////////////////////////////////////////////// //
29 TDynRecException
= class(Exception
)
31 constructor Create (const amsg
: AnsiString);
32 constructor CreateFmt (const afmt
: AnsiString; const args
: array of const);
35 TDynParseException
= class(TDynRecException
)
37 tokLine
, tokCol
: Integer;
40 constructor Create (pr
: TTextParser
; const amsg
: AnsiString);
41 constructor CreateFmt (pr
: TTextParser
; const afmt
: AnsiString; const args
: array of const);
45 // ////////////////////////////////////////////////////////////////////////// //
52 TDynFieldList
= specialize TSimpleList
<TDynField
>;
53 TDynRecList
= specialize TSimpleList
<TDynRecord
>;
54 TDynEBSList
= specialize TSimpleList
<TDynEBS
>;
56 // this is base type for all scalars (and arrays)
60 TType
= (TBool
, TChar
, TByte
, TUByte
, TShort
, TUShort
, TInt
, TUInt
, TString
, TPoint
, TSize
, TList
, TTrigData
);
61 // TPoint: pair of Integers
62 // TSize: pair of UShorts
63 // TList: actually, array of records
64 // TTrigData: array of mMaxDim bytes, but internally a record (mRecRef)
65 // in binary: arrays of chars are pascal shortstrings (with counter in the first byte)
69 TEBS
= (TNone
, TRec
, TEnum
, TBitSet
);
72 mOwner
: TDynRecord
; // owner record
73 mName
: AnsiString; // field name
74 mType
: TType
; // field type
75 mIVal
: Integer; // for all integer types
76 mIVal2
: Integer; // for point and size
77 mSVal
: AnsiString; // string; for byte and char arrays
78 mRVal
: TDynRecList
; // for list
79 mRHash
: THashStrInt
; // id -> index in mRVal
80 mRecRef
: TDynRecord
; // for TEBS.TRec
81 mMaxDim
: Integer; // for byte and char arrays; <0: not an array; 0: impossible value
82 mBinOfs
: Integer; // offset in binary; <0 - none
83 mSepPosSize
: Boolean; // for points and sizes, use separate fields
84 mAsT
: Boolean; // for points and sizes, use separate fields, names starts with `t`
90 mBitSetUnique
: Boolean; // bitset can contain only one value
91 mAsMonsterId
: Boolean; // special hack for triggers: monster record number+1 in binary (so 0 means "none")
93 mDefUnparsed
: AnsiString;
94 mDefSVal
: AnsiString; // default string value
95 mDefIVal
, mDefIVal2
: Integer; // default integer values
96 mDefRecRef
: TDynRecord
;
97 mEBS
: TEBS
; // complex type type
98 mEBSTypeName
: AnsiString; // name of enum, bitset or record
99 mEBSType
: TObject
; // either TDynRecord or TDynEBS; nil means "simple type"; nil for `TTrigData` too
102 mRecRefId
: AnsiString;
112 procedure cleanup ();
114 procedure parseDefaultValue (); // parse `mDefUnparsed` to `mDefSVal`, `mDefIVal`, `mDefIVal2`, `mDefRecRef`
115 procedure fixDefaultValue (); // this will NOT clone `mDefRecRef`
116 function isDefaultValue (): Boolean;
118 function getListCount (): Integer; inline;
119 function getListItem (idx
: Integer): TDynRecord
; inline; overload
;
120 function getListItem (const aname
: AnsiString): TDynRecord
; inline; overload
;
122 function getRecRefIndex (): Integer;
124 function getVar (): Variant;
125 procedure setVar (val
: Variant);
127 procedure setRecRef (arec
: TDynRecord
);
129 procedure parseDef (pr
: TTextParser
); // parse mapdef definition
130 function definition (): AnsiString; // generate mapdef definition
133 // returns `true` for duplicate record id
134 function addListItem (rec
: TDynRecord
): Boolean; inline;
135 function removeListItem (const aid
: AnsiString): TDynRecord
; // returns nil or removed record
138 // get string name for the given type
139 class function getTypeName (t
: TType
): AnsiString;
142 constructor Create (const aname
: AnsiString; atype
: TType
);
143 constructor Create (const aname
: AnsiString; val
: Variant);
144 constructor Create (pr
: TTextParser
);
145 destructor Destroy (); override;
147 // clone this field; register all list records in `registerIn`
148 // "registration" is required to manage record lifetime; use header record if in doubt
149 // owner will be set to `newOwner`, if it is not `nil`, or to `owner`
150 // for lists, cloning will clone all list members
151 function clone (newOwner
: TDynRecord
=nil; registerIn
: TDynRecord
=nil): TDynField
;
153 // compare field values (including trigdata)
154 // WARNING: won't work for lists
155 function isSimpleEqu (fld
: TDynField
): Boolean;
157 // parse string value to appropriate type and set new field value
158 procedure setValue (const s
: AnsiString);
160 // supports `for rec in field do` (for lists)
161 function GetEnumerator (): TDynRecList
.TEnumerator
; inline;
164 // text parser and writer
165 procedure parseValue (pr
: TTextParser
);
166 procedure writeTo (wr
: TTextWriter
);
168 // binary parser and writer (DO NOT USE!)
169 procedure parseBinValue (st
: TStream
);
170 procedure writeBinTo (st
: TStream
);
173 // the following functions are here only for 'mapgen'! DO NOT USE!
174 // build "alias name" for pascal code
175 function palias (firstUp
: Boolean=false): AnsiString;
178 property owner
: TDynRecord read mOwner
;
179 property name
: AnsiString read mName
; // field name
180 property baseType
: TType read mType
; // field type (base for arrays)
181 property defined
: Boolean read mDefined
; // was field value set to something by external code?
182 property internal
: Boolean read mInternal write mInternal
; // internal field?
183 property ival
: Integer read mIVal
; // integer value for int field (for speed), first field (x/w) for `TPoint` and `TSize`
184 property ival2
: Integer read mIVal2
; // for `TPoint` and `TSize`, this is second field (y/h)
185 property sval
: AnsiString read mSVal
; // string value for string field (for speed)
186 property hasDefault
: Boolean read mHasDefault
; // `true` if this field has default value in mapdef
187 property defsval
: AnsiString read mDefSVal
; // string representation of default value
188 property ebs
: TEBS read mEBS
; // what kind of reference is this? none, enum, bitset, record
189 property ebstype
: TObject read mEBSType
; // reference type (nil, TDynRecord, TDynEBS); WARNING: don't modify type!
190 property ebstypename
: AnsiString read mEBSTypeName
; // enum/bitset name
191 property recref
: TDynRecord read mRecRef write setRecRef
; // referenced record (actual one, you can modify it)
192 property recrefIndex
: Integer read getRecRefIndex
; // index of referenced record in header; -1: not found
194 property count
: Integer read getListCount
;
195 property itemAt
[idx
: Integer]: TDynRecord read getListItem
;
196 property item
[const aname
: AnsiString]: TDynRecord read getListItem
; default
; // alas, FPC 3+ lost property overloading feature
197 // field value as Variant
198 property value
: Variant read getVar write setVar
;
201 // userdata (you can use these properties as you want to; they won't be written or read to files)
202 property tagInt
: Integer read mTagInt write mTagInt
;
203 property tagPtr
: Pointer read mTagPtr write mTagPtr
;
206 // the following properties are here only for 'mapgen'! DO NOT USE!
207 property negbool
: Boolean read mNegBool
;
208 property hasTPrefix
: Boolean read mAsT
;
209 property separatePasFields
: Boolean read mSepPosSize
;
210 property binOfs
: Integer read mBinOfs
;
214 // record, either with actual values, or with type definitions
219 mTypeName
: AnsiString;
221 mFields
: TDynFieldList
;
222 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
223 mFieldsHash
: THashStrInt
; // id -> index in mRVal
225 mTrigTypes
: array of AnsiString; // if this is triggerdata, we'll hold list of triggers here
226 mHeader
: Boolean; // true for header record
227 mBinBlock
: Integer; // -1: none
228 mHeaderRec
: TDynRecord
; // for "value" records this is header record with data, for "type" records this is header type record
234 mRec2Free
: TDynRecList
;
237 procedure parseDef (pr
: TTextParser
); // parse definition
238 function definition (): AnsiString;
240 function findByName (const aname
: AnsiString): Integer; inline;
241 function hasByName (const aname
: AnsiString): Boolean; inline;
242 function getFieldByName (const aname
: AnsiString): TDynField
; inline;
243 function getFieldAt (idx
: Integer): TDynField
; inline;
244 function getCount (): Integer; inline;
246 function getIsTrigData (): Boolean; inline;
247 function getIsForTrig (const aname
: AnsiString): Boolean; inline;
249 function getForTrigCount (): Integer; inline;
250 function getForTrigAt (idx
: Integer): AnsiString; inline;
252 procedure regrec (rec
: TDynRecord
);
254 function getUserVar (const aname
: AnsiString): Variant;
255 procedure setUserVar (const aname
: AnsiString; val
: Variant);
257 procedure clearRefRecs (rec
: TDynRecord
);
260 function findRecordByTypeId (const atypename
, aid
: AnsiString): TDynRecord
;
261 function findRecordNumByType (const atypename
: AnsiString; rc
: TDynRecord
): Integer;
262 function addRecordByType (const atypename
: AnsiString; rc
: TDynRecord
): Boolean; // `true`: duplicate record id
264 procedure addField (fld
: TDynField
); inline;
265 function addFieldChecked (fld
: TDynField
): Boolean; inline; // `true`: duplicate name
268 constructor Create ();
269 constructor Create (pr
: TTextParser
); // parse definition
270 destructor Destroy (); override;
272 // clone this record; register all list records in `registerIn`
273 // "registration" is required to manage record lifetime; use header record if in doubt
274 // all fields are cloned too
275 function clone (registerIn
: TDynRecord
): TDynRecord
;
277 // compare records (values of all fields, including trigdata)
278 // WARNING: won't work for records with list fields
279 function isSimpleEqu (rec
: TDynRecord
): Boolean;
281 // find field with `TriggerType` type
282 function trigTypeField (): TDynField
;
284 // number of records of the given instance
285 function instanceCount (const atypename
: AnsiString): Integer;
287 // only for headers: create new record with the given type
288 // will return cloned record ready for use, or `nil` on unknown type name
289 // `aid` must not be empty, and must be unique
290 function newTypedRecord (const atypename
, aid
: AnsiString): TDynRecord
;
292 // remove record with the given type and id
293 // return `true` if record was successfully found and removed
294 // this will do all necessary recref cleanup too
295 // WARNING: not tested yet
296 function removeTypedRecord (const atypename
, aid
: AnsiString): Boolean;
299 // [.] API to create triggers
300 // [.] API to properly remove triggers (remove trigdata)
301 // [.] check if `removeTypedRecord()` does the right thing with inline records
302 // [.] for fields: assigning `recref` should remove previously assigned inline record (record without id)
303 // [.] other API i forgot
307 // `beginEaten`: `true` if "{" was eaten
308 procedure parseValue (pr
: TTextParser
; beginEaten
: Boolean=false);
311 // `putHeader`: `true` to write complete header, otherwise only "{...}"
312 procedure writeTo (wr
: TTextWriter
; putHeader
: Boolean=true);
314 // binary parser and writer (DO NOT USE!)
315 procedure parseBinValue (st
: TStream
; forceData
: Boolean=false);
316 procedure writeBinTo (st
: TStream
; trigbufsz
: Integer=-1; onlyFields
: Boolean=false);
319 property mapdef
: TDynMapDef read mOwner
;
320 property id
: AnsiString read mId
; // record id in text map
321 property typeName
: AnsiString read mTypeName
; // record type name (like "panel", or "trigger")
322 property has
[const aname
: AnsiString]: Boolean read hasByName
; // do we have field with the given name?
323 property count
: Integer read getCount
; // number of fields in this record
324 property field
[const aname
: AnsiString]: TDynField read getFieldByName
; default
; // get field by name
325 property fieldAt
[idx
: Integer]: TDynField read getFieldAt
; // get field at the given index
326 property isTrigData
: Boolean read getIsTrigData
; // is this special "TriggerData" record?
327 property isForTrig
[const aname
: AnsiString]: Boolean read getIsForTrig
; // can this "TriggerData" be used for the trigger with the given type?
328 property forTrigCount
: Integer read getForTrigCount
; // number of trigger type names for "TriggerData"
329 property forTrigAt
[idx
: Integer]: AnsiString read getForTrigAt
; // trigger type name at the given index for "TriggerData"
330 property headerRec
: TDynRecord read mHeaderRec
; // get header record for this one (header contains all other records, enums, bitsets, etc.)
331 property isHeader
: Boolean read mHeader
; // is this a header record?
334 // user fields; user can add arbitrary custom fields
335 // by default, any user field will be marked as "internal"
336 // note: you can use this to manipulate non-user fields too
337 property user
[const aname
: AnsiString]: Variant read getUserVar write setUserVar
;
340 // userdata (you can use these properties as you want to; they won't be written or read to files)
341 property tagInt
: Integer read mTagInt write mTagInt
;
342 property tagPtr
: Pointer read mTagPtr write mTagPtr
;
346 // bitset/enum definition
351 mTypeName
: AnsiString;
352 mIds
: array of AnsiString;
353 mVals
: array of Integer;
354 mMaxName
: AnsiString; // MAX field
355 mMaxVal
: Integer; // max value
358 procedure cleanup ();
360 procedure parseDef (pr
: TTextParser
); // parse definition
362 function findByName (const aname
: AnsiString): Integer; inline;
363 function hasByName (const aname
: AnsiString): Boolean; inline;
364 function getFieldByName (const aname
: AnsiString): Integer; inline;
366 function definition (): AnsiString;
367 function pasdef (): AnsiString;
370 constructor Create (pr
: TTextParser
); // parse definition
371 destructor Destroy (); override;
373 // find name for the given value
374 // return empty string if not found
375 function nameByValue (v
: Integer): AnsiString;
378 property mapdef
: TDynMapDef read mOwner
;
379 property typeName
: AnsiString read mTypeName
; // enum/bitset type name
380 property isEnum
: Boolean read mIsEnum
; // is this enum? `false` means "bitset"
381 property has
[const aname
: AnsiString]: Boolean read hasByName
;
382 property field
[const aname
: AnsiString]: Integer read getFieldByName
;
386 // parsed "mapdef.txt"
389 recTypes
: TDynRecList
; // [0] is always header
390 trigTypes
: TDynRecList
; // trigdata
391 ebsTypes
: TDynEBSList
; // enums, bitsets
394 procedure parseDef (pr
: TTextParser
);
396 function getHeaderRecType (): TDynRecord
; inline;
398 function getRecTypeCount (): Integer; inline;
399 function getRecTypeAt (idx
: Integer): TDynRecord
; inline;
401 function getEBSTypeCount (): Integer; inline;
402 function getEBSTypeAt (idx
: Integer): TDynEBS
; inline;
404 function getTrigTypeCount (): Integer; inline;
405 function getTrigTypeAt (idx
: Integer): TDynRecord
; inline;
407 // creates new header record
408 function parseTextMap (pr
: TTextParser
): TDynRecord
;
410 // creates new header record
411 function parseBinMap (st
: TStream
): TDynRecord
;
414 constructor Create (pr
: TTextParser
); // parses data definition
415 destructor Destroy (); override;
417 function findRecType (const aname
: AnsiString): TDynRecord
;
418 function findTrigFor (const aname
: AnsiString): TDynRecord
;
419 function findEBSType (const aname
: AnsiString): TDynEBS
;
422 // parse text or binary map, return new header record
423 // WARNING! stream must be seekable
424 function parseMap (st
: TStream
): TDynRecord
;
426 // returns `true` if the given stream can be a map file
427 // stream position is 0 on return
428 // WARNING! stream must be seekable
429 class function canBeMap (st
: TStream
): Boolean;
432 // the following functions are here only for 'mapgen'! DO NOT USE!
433 function pasdefconst (): AnsiString;
436 property headerType
: TDynRecord read getHeaderRecType
;
438 property recTypeCount
: Integer read getRecTypeCount
;
439 property recTypeAt
[idx
: Integer]: TDynRecord read getRecTypeAt
;
440 property recType
[const aname
: AnsiString]: TDynRecord read findRecType
;
441 // for enum/bitset types
442 property ebsTypeCount
: Integer read getEBSTypeCount
;
443 property ebsTypeAt
[idx
: Integer]: TDynEBS read getEBSTypeAt
;
444 property ebsType
[const aname
: AnsiString]: TDynEBS read findEBSType
;
446 property trigTypeCount
: Integer read getTrigTypeCount
;
447 property trigTypeAt
[idx
: Integer]: TDynRecord read getTrigTypeAt
;
448 property trigTypeFor
[const aname
: AnsiString]: TDynRecord read findTrigFor
;
452 {$IF DEFINED(D2D_DYNREC_PROFILER)}
453 procedure xdynDumpProfiles ();
461 {$IF DEFINED(D2D_DYNREC_PROFILER)},xprofiler
{$ENDIF};
464 // ////////////////////////////////////////////////////////////////////////// //
465 function StrEqu (const a
, b
: AnsiString): Boolean; inline; begin result
:= (a
= b
); end;
468 // ////////////////////////////////////////////////////////////////////////// //
469 constructor TDynRecException
.Create (const amsg
: AnsiString);
471 inherited Create(amsg
);
474 constructor TDynRecException
.CreateFmt (const afmt
: AnsiString; const args
: array of const);
476 inherited Create(formatstrf(afmt
, args
));
480 // ////////////////////////////////////////////////////////////////////////// //
481 constructor TDynParseException
.Create (pr
: TTextParser
; const amsg
: AnsiString);
483 if (pr
<> nil) then begin tokLine
:= pr
.tokLine
; tokCol
:= pr
.tokCol
; end else begin tokLine
:= 0; tokCol
:= 0; end;
484 inherited Create(amsg
);
487 constructor TDynParseException
.CreateFmt (pr
: TTextParser
; const afmt
: AnsiString; const args
: array of const);
489 if (pr
<> nil) then begin tokLine
:= pr
.tokLine
; tokCol
:= pr
.tokCol
; end else begin tokLine
:= 0; tokCol
:= 0; end;
490 inherited Create(formatstrf(afmt
, args
));
494 // ////////////////////////////////////////////////////////////////////////// //
495 function TDynField
.GetEnumerator (): TDynRecList
.TEnumerator
; inline;
497 //result := TListEnumerator.Create(mRVal);
498 if (mRVal
<> nil) then result
:= mRVal
.GetEnumerator
else result
:= TDynRecList
.TEnumerator
.Create(nil, 0);
502 // ////////////////////////////////////////////////////////////////////////// //
503 constructor TDynField
.Create (const aname
: AnsiString; atype
: TType
);
511 if (mType
= TType
.TList
) then
513 mRVal
:= TDynRecList
.Create();
514 mRHash
:= hashNewStrInt();
519 constructor TDynField
.Create (pr
: TTextParser
);
526 constructor TDynField
.Create (const aname
: AnsiString; val
: Variant);
527 procedure setInt32 (v
: LongInt);
531 if (v
= 0) then mIVal
:= 0
532 else if (v
= 1) then mIVal
:= 1
533 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
535 if (v
>= -128) and (v
<= 127) then mIVal
:= v
536 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
538 if (v
>= 0) and (v
<= 255) then mIVal
:= v
539 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
541 if (v
>= -32768) and (v
<= 32767) then mIVal
:= v
542 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
544 if (v
>= 0) and (v
<= 65535) then mIVal
:= v
545 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
551 mSVal
:= formatstrf('%s', [v
]);
553 raise TDynRecException
.Create('cannot convert integral variant to field value');
563 varEmpty
: raise TDynRecException
.Create('cannot convert empty variant to field value');
564 varNull
: raise TDynRecException
.Create('cannot convert null variant to field value');
565 varSingle
: raise TDynRecException
.Create('cannot convert single variant to field value');
566 varDouble
: raise TDynRecException
.Create('cannot convert double variant to field value');
567 varDecimal
: raise TDynRecException
.Create('cannot convert decimal variant to field value');
568 varCurrency
: raise TDynRecException
.Create('cannot convert currency variant to field value');
569 varDate
: raise TDynRecException
.Create('cannot convert date variant to field value');
570 varOleStr
: raise TDynRecException
.Create('cannot convert olestr variant to field value');
571 varStrArg
: raise TDynRecException
.Create('cannot convert stdarg variant to field value');
572 varString
: mType
:= TType
.TString
;
573 varDispatch
: raise TDynRecException
.Create('cannot convert dispatch variant to field value');
574 varBoolean
: mType
:= TType
.TBool
;
575 varVariant
: raise TDynRecException
.Create('cannot convert variant variant to field value');
576 varUnknown
: raise TDynRecException
.Create('cannot convert unknown variant to field value');
577 varByte
: mType
:= TType
.TUByte
;
578 varWord
: mType
:= TType
.TUShort
;
579 varShortInt
: mType
:= TType
.TByte
;
580 varSmallint
: mType
:= TType
.TShort
;
581 varInteger
: mType
:= TType
.TInt
;
582 varInt64
: raise TDynRecException
.Create('cannot convert int64 variant to field value');
583 varLongWord
: raise TDynRecException
.Create('cannot convert longword variant to field value');
584 varQWord
: raise TDynRecException
.Create('cannot convert uint64 variant to field value');
585 varError
: raise TDynRecException
.Create('cannot convert error variant to field value');
586 else raise TDynRecException
.Create('cannot convert undetermined variant to field value');
592 destructor TDynField
.Destroy ();
599 procedure TDynField
.cleanup ();
613 mSepPosSize
:= false;
615 mHasDefault
:= false;
627 mBitSetUnique
:= false;
628 mAsMonsterId
:= false;
637 function TDynField
.clone (newOwner
: TDynRecord
=nil; registerIn
: TDynRecord
=nil): TDynField
;
641 result
:= TDynField
.Create(mName
, mType
);
642 result
.mOwner
:= mOwner
;
643 if (newOwner
<> nil) then result
.mOwner
:= newOwner
else result
.mOwner
:= mOwner
;
644 result
.mName
:= mName
;
645 result
.mType
:= mType
;
646 result
.mIVal
:= mIVal
;
647 result
.mIVal2
:= mIVal2
;
648 result
.mSVal
:= mSVal
;
649 if (mRVal
<> nil) then
651 if (result
.mRVal
= nil) then result
.mRVal
:= TDynRecList
.Create(mRVal
.count
);
652 if (result
.mRHash
= nil) then result
.mRHash
:= hashNewStrInt();
653 for rec
in mRVal
do result
.addListItem(rec
.clone(registerIn
));
655 result
.mRecRef
:= mRecRef
;
656 result
.mMaxDim
:= mMaxDim
;
657 result
.mBinOfs
:= mBinOfs
;
658 result
.mSepPosSize
:= mSepPosSize
;
660 result
.mDefined
:= mDefined
;
661 result
.mHasDefault
:= mHasDefault
;
662 result
.mWriteDef
:= mWriteDef
;
663 result
.mInternal
:= mInternal
;
664 result
.mNegBool
:= mNegBool
;
665 result
.mBitSetUnique
:= mBitSetUnique
;
666 result
.mAsMonsterId
:= mAsMonsterId
;
667 result
.mDefUnparsed
:= mDefUnparsed
;
668 result
.mDefSVal
:= mDefSVal
;
669 result
.mDefIVal
:= mDefIVal
;
670 result
.mDefIVal2
:= mDefIVal2
;
671 result
.mDefRecRef
:= mDefRecRef
;
673 result
.mEBSTypeName
:= mEBSTypeName
;
674 result
.mEBSType
:= mEBSType
;
675 result
.mRecRefId
:= mRecRefId
;
676 result
.mTagInt
:= mTagInt
;
677 result
.mTagPtr
:= mTagPtr
;
678 result
.mAlias
:= mAlias
;
682 function TDynField
.palias (firstUp
: Boolean=false): AnsiString;
687 if (Length(mAlias
) > 0) then
689 if firstUp
then result
:= UpCase1251(mAlias
[1])+Copy(mAlias
, 2, Length(mAlias
)-1) else result
:= mAlias
;
697 if (ch
= '_') then begin nextUp
:= true; continue
; end;
698 if nextUp
then result
+= UpCase1251(ch
) else result
+= ch
;
705 procedure TDynField
.setRecRef (arec
: TDynRecord
);
707 trc
: TDynRecord
= nil;
710 TEBS
.TNone
: raise TDynRecException
.CreateFmt('cannot set refrec for non-reference field ''%s''', [mName
]);
713 if (arec
<> nil) then
715 if (mEBSType
<> nil) and (mEBSType
is TDynRecord
) then trc
:= (mEBSType
as TDynRecord
);
716 if (trc
= nil) then raise TDynRecException
.CreateFmt('cannot set refrec for field ''%s'' (type conflict: improperly initialized field)', [mName
]);
717 if (trc
.typeName
<> arec
.typeName
) then raise TDynRecException
.CreateFmt('cannot set refrec for field ''%s'' (type conflict: expected ''%s'' got ''%s'')', [mName
, trc
.typeName
, arec
.typeName
]);
723 TEBS
.TEnum
: raise TDynRecException
.CreateFmt('cannot set refrec for enum field ''%s''', [mName
]);
724 TEBS
.TBitSet
: raise TDynRecException
.CreateFmt('cannot set refrec for bitset field ''%s''', [mName
]);
725 else raise TDynRecException
.Create('ketmar forgot to process some reftypes');
730 function TDynField
.getVar (): Variant;
732 if (mEBS
= TEBS
.TRec
) then begin result
:= LongInt(getRecRefIndex
); exit
; end;
734 TType
.TBool
: result
:= (mIVal
<> 0);
735 TType
.TChar
: result
:= mSVal
;
736 TType
.TByte
: result
:= ShortInt(mIVal
);
737 TType
.TUByte
: result
:= Byte(mIVal
);
738 TType
.TShort
: result
:= SmallInt(mIVal
);
739 TType
.TUShort
: result
:= Word(mIVal
);
740 TType
.TInt
: result
:= LongInt(mIVal
);
741 TType
.TUInt
: result
:= LongWord(mIVal
);
742 TType
.TString
: result
:= mSVal
;
743 TType
.TPoint
: raise TDynRecException
.Create('cannot convert point field to variant');
744 TType
.TSize
: raise TDynRecException
.Create('cannot convert size field to variant');
745 TType
.TList
: raise TDynRecException
.Create('cannot convert list field to variant');
746 TType
.TTrigData
: raise TDynRecException
.Create('cannot convert trigdata field to variant');
747 else result
:= Unassigned
; raise TDynRecException
.Create('ketmar forgot to handle some field type');
752 procedure TDynField
.setVar (val
: Variant);
753 procedure setInt32 (v
: LongInt);
757 if (v
= 0) then mIVal
:= 0
758 else if (v
= 1) then mIVal
:= 1
759 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
761 if (v
>= -128) and (v
<= 127) then mIVal
:= v
762 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
764 if (v
>= 0) and (v
<= 255) then mIVal
:= v
765 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
767 if (v
>= -32768) and (v
<= 32767) then mIVal
:= v
768 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
770 if (v
>= 0) and (v
<= 65535) then mIVal
:= v
771 else raise TDynRecException
.Create('cannot convert shortint variant to field value');
777 mSVal
:= formatstrf('%s', [v
]);
779 raise TDynRecException
.Create('cannot convert integral variant to field value');
784 varEmpty
: raise TDynRecException
.Create('cannot convert empty variant to field value');
785 varNull
: raise TDynRecException
.Create('cannot convert null variant to field value');
786 varSingle
: raise TDynRecException
.Create('cannot convert single variant to field value');
787 varDouble
: raise TDynRecException
.Create('cannot convert double variant to field value');
788 varDecimal
: raise TDynRecException
.Create('cannot convert decimal variant to field value');
789 varCurrency
: raise TDynRecException
.Create('cannot convert currency variant to field value');
790 varDate
: raise TDynRecException
.Create('cannot convert date variant to field value');
791 varOleStr
: raise TDynRecException
.Create('cannot convert olestr variant to field value');
792 varStrArg
: raise TDynRecException
.Create('cannot convert stdarg variant to field value');
794 if (mType
= TType
.TChar
) or (mType
= TType
.TString
) then
800 raise TDynRecException
.Create('cannot convert string variant to field value');
802 varDispatch
: raise TDynRecException
.Create('cannot convert dispatch variant to field value');
812 if val
then mIVal
:= 1 else mIVal
:= 0;
814 if val
then mSVal
:= 'true' else mSVal
:= 'false';
816 raise TDynRecException
.Create('cannot convert boolean variant to field value');
818 varVariant
: raise TDynRecException
.Create('cannot convert variant variant to field value');
819 varUnknown
: raise TDynRecException
.Create('cannot convert unknown variant to field value');
827 if (val
< Int64(LongInt($80000000))) or (val
> LongInt($7FFFFFFF)) then
828 raise TDynRecException
.Create('cannot convert boolean variant to field value')
830 mIVal
:= LongInt(val
);
832 if (val
> LongWord($7FFFFFFF)) then raise TDynRecException
.Create('cannot convert longword variant to field value')
833 else setInt32(Integer(val
));
834 varQWord
: raise TDynRecException
.Create('cannot convert uint64 variant to field value');
835 varError
: raise TDynRecException
.Create('cannot convert error variant to field value');
836 else raise TDynRecException
.Create('cannot convert undetermined variant to field value');
842 // won't work for lists
843 function TDynField
.isSimpleEqu (fld
: TDynField
): Boolean;
845 if (fld
= nil) or (mType
<> fld
.mType
) then begin result
:= false; exit
; end;
847 TType
.TBool
: result
:= ((mIVal
<> 0) = (fld
.mIVal
<> 0));
848 TType
.TChar
: result
:= (mSVal
= fld
.mSVal
);
855 result
:= (mIVal
= fld
.mIVal
);
856 TType
.TString
: result
:= (mSVal
= fld
.mSVal
);
859 result
:= ((mIVal
= fld
.mIVal
) and (mIVal2
= fld
.mIVal2
));
860 TType
.TList
: result
:= false;
863 if (mRecRef
= nil) then begin result
:= (fld
.mRecRef
= nil); exit
; end;
864 result
:= mRecRef
.isSimpleEqu(fld
.mRecRef
);
866 else raise TDynRecException
.Create('ketmar forgot to handle some field type');
871 procedure TDynField
.setValue (const s
: AnsiString);
875 stp
:= TStrTextParser
.Create(s
+';');
884 procedure TDynField
.parseDefaultValue ();
886 stp
: TTextParser
= nil;
888 oIVal
, oIVal2
: Integer;
892 if not mHasDefault
then
907 stp
:= TStrTextParser
.Create(mDefUnparsed
+';');
912 mDefRecRef
:= mRecRef
;
925 // default value should be parsed
926 procedure TDynField
.fixDefaultValue ();
928 if mDefined
then exit
;
929 if not mHasDefault
then
931 if mInternal
then exit
;
932 raise TDynRecException
.CreateFmt('field ''%s'' in record ''%s'' of record type ''%s'' is not set', [mName
, mOwner
.mId
, mOwner
.mTypeName
]);
934 if (mEBS
= TEBS
.TRec
) then mRecRef
:= mDefRecRef
;
942 // default value should be parsed
943 function TDynField
.isDefaultValue (): Boolean;
945 if not mHasDefault
then begin result
:= false; exit
; end;
946 if (mEBS
= TEBS
.TRec
) then begin result
:= (mRecRef
= mDefRecRef
); exit
; end;
948 TType
.TChar
, TType
.TString
: result
:= (mSVal
= mDefSVal
);
949 TType
.TPoint
, TType
.TSize
: result
:= (mIVal
= mDefIVal2
) and (mIVal2
= mDefIVal2
);
950 TType
.TList
, TType
.TTrigData
: result
:= false; // no default values for those types
951 else result
:= (mIVal
= mDefIVal
);
956 function TDynField
.getListCount (): Integer; inline;
958 if (mRVal
<> nil) then result
:= mRVal
.count
else result
:= 0;
962 function TDynField
.getListItem (idx
: Integer): TDynRecord
; inline; overload
;
964 if (mRVal
<> nil) and (idx
>= 0) and (idx
< mRVal
.count
) then result
:= mRVal
[idx
] else result
:= nil;
968 function TDynField
.getListItem (const aname
: AnsiString): TDynRecord
; inline; overload
;
972 if (mRVal
<> nil) and mRHash
.get(aname
, idx
) then result
:= mRVal
[idx
] else result
:= nil;
976 function TDynField
.addListItem (rec
: TDynRecord
): Boolean; inline;
979 if (mRVal
<> nil) then
982 if (Length(rec
.mId
) > 0) then result
:= mRHash
.put(rec
.mId
, mRVal
.count
-1);
987 function TDynField
.removeListItem (const aid
: AnsiString): TDynRecord
;
992 if mRHash
.get(aid
, idx
) then
994 assert((idx
>= 0) and (idx
< mRVal
.count
));
995 result
:= mRVal
[idx
];
997 for f
:= idx
+1 to mRVal
.count
-1 do
999 if (Length(mRVal
[f
].mId
) > 0) then mRHash
.put(mRVal
[f
].mId
, f
-1);
1007 class function TDynField
.getTypeName (t
: TType
): AnsiString;
1010 TType
.TBool
: result
:= 'bool';
1011 TType
.TChar
: result
:= 'char';
1012 TType
.TByte
: result
:= 'byte';
1013 TType
.TUByte
: result
:= 'ubyte';
1014 TType
.TShort
: result
:= 'short';
1015 TType
.TUShort
: result
:= 'ushort';
1016 TType
.TInt
: result
:= 'int';
1017 TType
.TUInt
: result
:= 'uint';
1018 TType
.TString
: result
:= 'string';
1019 TType
.TPoint
: result
:= 'point';
1020 TType
.TSize
: result
:= 'size';
1021 TType
.TList
: result
:= 'array';
1022 TType
.TTrigData
: result
:= 'trigdata';
1023 else raise TDynRecException
.Create('ketmar forgot to handle some field type');
1028 function TDynField
.definition (): AnsiString;
1030 result
:= quoteStr(mName
)+' type ';
1031 result
+= getTypeName(mType
);
1032 if (Length(mAlias
) > 0) then result
+= ' alias '+mAlias
;
1033 if (mMaxDim
>= 0) then result
+= Format('[%d]', [mMaxDim
]);
1034 if (mBinOfs
>= 0) then result
+= Format(' offset %d', [mBinOfs
]);
1036 TEBS
.TNone
: begin end;
1037 TEBS
.TRec
: result
+= ' '+mEBSTypeName
;
1038 TEBS
.TEnum
: result
+= ' enum '+mEBSTypeName
;
1039 TEBS
.TBitSet
: begin result
+= ' bitset '; if mBitSetUnique
then result
+= 'unique '; result
+= mEBSTypeName
; end;
1041 if mAsMonsterId
then result
+= ' as monsterid';
1042 if mHasDefault
and (Length(mDefUnparsed
) > 0) then result
+= ' default '+mDefUnparsed
;
1045 if (mType
= TType
.TPoint
) then begin if (mAsT
) then result
+= ' as txy' else result
+= ' as xy'; end
1046 else if (mType
= TType
.TSize
) then begin if (mAsT
) then result
+= ' as twh' else result
+= ' as wh'; end;
1048 if mWriteDef
then result
+= ' writedefault';
1049 if mInternal
then result
+= ' internal';
1053 procedure TDynField
.parseDef (pr
: TTextParser
);
1055 fldname
: AnsiString;
1056 fldtype
: AnsiString;
1058 fldrecname
: AnsiString;
1059 asxy
, aswh
, ast
: Boolean;
1063 defint
, defint2
: Integer;
1068 lebs
: TDynField
.TEBS
;
1092 lebs
:= TDynField.TEBS.TNone
;
1096 fldname
:= pr.expectStrOrId
();
1098 while
(pr.tokType
<> pr.TTSemi
) do
1100 if pr.eatId
('type') then
1102 if
(Length(fldtype
) > 0) then raise TDynParseException.CreateFmt
(pr
, 'duplicate type definition for field ''%s''', [fldname
]);
1104 fldtype
:= pr.expectId
();
1105 // fixed
-size array
?
1106 if pr.eatDelim
('[') then
1108 lmaxdim
:= pr.expectInt
();
1110 if
(lmaxdim
< 1) or (lmaxdim
> 32768) then raise TDynParseException.CreateFmt
(pr
, 'invalid field ''%s'' array size', [fldname
]);
1111 pr.expectDelim
(']');
1116 if pr
.eatId('alias') then
1118 if (Length(xalias
) > 0) then raise TDynParseException
.CreateFmt(pr
, 'duplicate alias definition for field ''%s''', [fldname
]);
1119 xalias
:= pr
.expectId();
1123 if pr
.eatId('offset') then
1125 if (fldofs
>= 0) then raise TDynParseException
.CreateFmt(pr
, 'duplicate field ''%s'' offset', [fldname
]);
1126 fldofs
:= pr
.expectInt();
1127 if (fldofs
< 0) then raise TDynParseException
.CreateFmt(pr
, 'invalid field ''%s'' offset', [fldname
]);
1131 if pr
.eatId('as') then
1133 if pr
.eatId('xy') then asxy
:= true
1134 else if pr
.eatId('wh') then aswh
:= true
1135 else if pr
.eatId('txy') then begin asxy
:= true; ast
:= true; end
1136 else if pr
.eatId('twh') then begin aswh
:= true; ast
:= true; end
1137 else if pr
.eatId('monsterid') then begin asmonid
:= true
; end
1138 else raise TDynParseException.CreateFmt
(pr
, 'invalid field ''%s'' as what?', [fldname
]);
1142 if pr
.eatId('enum') then
1144 lebs
:= TDynField
.TEBS
.TEnum
;
1145 if (Length(fldrecname
) <> 0) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' already typed as ''%s''', [fldname
, fldrecname
]);
1146 fldrecname
:= pr
.expectId();
1150 if pr
.eatId('bitset') then
1152 lebs
:= TDynField
.TEBS
.TBitSet
;
1153 if (Length(fldrecname
) <> 0) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' already typed as ''%s''', [fldname
, fldrecname
]);
1154 unique
:= pr
.eatId('unique');
1155 fldrecname
:= pr
.expectId();
1159 if pr
.eatId('default') then
1161 if hasdefStr
or hasdefInt
or hasdefId
then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' has duplicate default', [fldname
]);
1166 defstr
:= pr
.expectStr(true); // allow empty strings
1171 defstr
:= pr
.expectId();
1176 defint
:= pr
.expectInt();
1181 if pr
.eatDelim('[') then defech
:= ']' else begin pr
.expectDelim('('); defech
:= ')'; end;
1182 defint
:= pr
.expectInt();
1183 defint2
:= pr
.expectInt();
1184 pr
.expectDelim(defech
);
1187 raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' has invalid default', [fldname
]);
1192 if pr
.eatId('writedefault') then
1198 if pr
.eatId('internal') then
1204 // record type, no special modifiers
1205 if (pr
.tokType
<> pr
.TTId
) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' has something unexpected in definition', [fldname
]);
1207 if (Length(fldrecname
) <> 0) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' already typed as ''%s''', [fldname
, fldrecname
]);
1208 fldrecname
:= pr
.expectId();
1209 lebs
:= TDynField
.TEBS
.TRec
;
1212 pr
.expectTT(pr
.TTSemi
);
1216 if (fldtype
= 'bool') then mType
:= TType
.TBool
1217 else if (fldtype
= 'negbool') then begin mType
:= TType
.TBool
; mNegBool
:= true; end
1218 else if (fldtype
= 'char') then mType
:= TType
.TChar
1219 else if (fldtype
= 'byte') then mType
:= TType
.TByte
1220 else if (fldtype
= 'ubyte') then mType
:= TType
.TUByte
1221 else if (fldtype
= 'short') then mType
:= TType
.TShort
1222 else if (fldtype
= 'ushort') then mType
:= TType
.TUShort
1223 else if (fldtype
= 'int') then mType
:= TType
.TInt
1224 else if (fldtype
= 'uint') then mType
:= TType
.TUInt
1225 else if (fldtype
= 'string') then mType
:= TType
.TString
1226 else if (fldtype
= 'point') then mType
:= TType
.TPoint
1227 else if (fldtype
= 'size') then mType
:= TType
.TSize
1228 else if (fldtype
= 'trigdata') then mType
:= TType
.TTrigData
1231 // record types defaults to int
1232 if (Length(fldrecname
) > 0) then
1234 mType
:= TType
.TInt
;
1238 if (Length(fldtype
) = 0) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' has no type', [fldname
])
1239 else raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' has invalid type ''%s''', [fldname
, fldtype
]);
1243 // check for valid arrays
1244 if (lmaxdim
> 0) and (mType
<> TType
.TChar
) and (mType
<> TType
.TTrigData
) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' of type ''%s'' cannot be array', [fldname
, fldtype
]);
1246 // check for valid trigdata or record type
1247 if (mType
= TType
.TTrigData
) then
1250 if (lmaxdim
< 1) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' of type ''%s'' cannot be non-array', [fldname
, 'trigdata']);
1251 if (Length(fldrecname
) > 0) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' of type ''%s'' cannot have another type', [fldname
, 'trigdata']);
1252 lebs
:= TDynField
.TEBS
.TRec
;
1254 else if (Length(fldrecname
) > 0) then
1257 if not (mType
in [TType
.TByte
, TType
.TUByte
, TType
.TShort
, TType
.TUShort
, TType
.TInt
, TType
.TUInt
]) then
1259 raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' of record type ''%s'' cannot have type ''%s''', [fldname
, fldrecname
, fldtype
]);
1263 // setup default value
1264 if hasdefStr
then self
.mDefUnparsed
:= quoteStr(defstr
)
1265 else if hasdefId
then self
.mDefUnparsed
:= defstr
1266 else if hasdefInt
then
1268 if (mType
= TType
.TPoint
) then self
.mDefUnparsed
:= Format('(%d %d)', [defint
, defint2
])
1269 else if (mType
= TType
.TSize
) then self
.mDefUnparsed
:= Format('[%d %d]', [defint
, defint2
])
1270 else self
.mDefUnparsed
:= Format('%d', [defint
]);
1273 self
.mHasDefault
:= (hasdefStr
or hasdefId
or hasdefInt
);
1275 self
.mEBSTypeName
:= fldrecname
;
1276 self
.mBitSetUnique
:= unique
;
1277 self
.mAsMonsterId
:= asmonid
;
1278 self.mMaxDim
:= lmaxdim
;
1279 self.mBinOfs
:= fldofs
;
1280 self.mSepPosSize
:= (asxy
or aswh
);
1282 self.mWriteDef
:= writedef
;
1283 self.mInternal
:= ainternal
;
1284 self.mAlias
:= xalias
;
1288 function TDynField
.getRecRefIndex (): Integer;
1290 if (mRecRef
= nil) then begin result
:= -1; exit
; end;
1291 result
:= mOwner
.findRecordNumByType(mEBSTypeName
, mRecRef
);
1295 procedure TDynField
.writeBinTo (st
: TStream
);
1304 TEBS
.TNone
: begin end;
1307 if (mMaxDim
>= 0) then
1309 // this must be triggerdata
1310 if (mType
<> TType
.TTrigData
) then
1312 raise TDynRecException
.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName
, mName
]);
1314 // write triggerdata
1315 GetMem(buf
, mMaxDim
);
1316 if (buf
= nil) then raise TDynRecException
.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName
, mName
]);
1318 FillChar(buf
^, mMaxDim
, 0);
1319 if (mRecRef
<> nil) then
1321 ws
:= TSFSMemoryChunkStream
.Create(buf
, mMaxDim
);
1322 mRecRef
.writeBinTo(ws
, mMaxDim
); // as trigdata
1324 st
.WriteBuffer(buf
^, mMaxDim
);
1327 if (buf
<> nil) then FreeMem(buf
);
1333 TType
.TByte
: maxv
:= 127;
1334 TType
.TUByte
: maxv
:= 254;
1335 TType
.TShort
: maxv
:= 32767;
1336 TType
.TUShort
: maxv
:= 65534;
1337 TType
.TInt
: maxv
:= $7fffffff;
1338 TType
.TUInt
: maxv
:= $7fffffff;
1339 else raise TDynRecException
.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName
, mName
]);
1341 // find record number
1342 if (mRecRef
<> nil) then
1344 f
:= mOwner
.findRecordNumByType(mEBSTypeName
, mRecRef
);
1345 if (f
< 0) then raise TDynRecException
.CreateFmt('record reference type ''%s'' in field ''%s'' not found in record list', [mEBSTypeName
, mName
]);
1346 if mAsMonsterId
then Inc(f
);
1347 if (f
> maxv
) then raise TDynRecException
.CreateFmt('record reference type ''%s'' in field ''%s'' has too big index', [mEBSTypeName
, mName
]);
1351 if mAsMonsterId
then f
:= 0 else f
:= -1;
1354 TType
.TByte
, TType
.TUByte
: writeInt(st
, Byte(f
));
1355 TType
.TShort
, TType
.TUShort
: writeInt(st
, SmallInt(f
));
1356 TType
.TInt
, TType
.TUInt
: writeInt(st
, LongWord(f
));
1357 else raise TDynRecException
.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName
, mName
]);
1361 TEBS
.TEnum
: begin end;
1362 TEBS
.TBitSet
: begin end;
1363 else raise TDynRecException
.Create('ketmar forgot to handle some EBS type');
1369 if not mNegBool
then
1371 if (mIVal
<> 0) then writeInt(st
, Byte(1)) else writeInt(st
, Byte(0));
1375 if (mIVal
= 0) then writeInt(st
, Byte(1)) else writeInt(st
, Byte(0));
1381 if (mMaxDim
= 0) then raise TDynRecException
.CreateFmt('invalid string size definition for field ''%s''', [mName
]);
1382 if (mMaxDim
< 0) then
1384 if (Length(mSVal
) <> 1) then raise TDynRecException
.CreateFmt('invalid string size definition for field ''%s''', [mName
]);
1385 writeInt(st
, Byte(mSVal
[1]));
1389 if (Length(mSVal
) > mMaxDim
) then raise TDynRecException
.CreateFmt('invalid string size definition for field ''%s''', [mName
]);
1390 s
:= utf2win(mSVal
);
1391 if (Length(s
) > 0) then st
.WriteBuffer(PChar(s
)^, Length(s
));
1392 for f
:= Length(s
) to mMaxDim
do writeInt(st
, Byte(0));
1399 // triggerdata array was processed earlier
1400 if (mMaxDim
>= 0) then TDynRecException
.CreateFmt('byte array in field ''%s'' cannot be written', [mName
]);
1401 writeInt(st
, Byte(mIVal
));
1407 if (mMaxDim
>= 0) then raise TDynRecException
.CreateFmt('short array in field ''%s'' cannot be written', [mName
]);
1408 writeInt(st
, Word(mIVal
));
1414 if (mMaxDim
>= 0) then raise TDynRecException
.CreateFmt('int array in field ''%s'' cannot be written', [mName
]);
1415 writeInt(st
, LongWord(mIVal
));
1420 raise TDynRecException
.CreateFmt('cannot write string field ''%s''', [mName
]);
1424 if (mMaxDim
>= 0) then raise TDynRecException
.CreateFmt('pos/size array in field ''%s'' cannot be written', [mName
]);
1425 writeInt(st
, LongInt(mIVal
));
1426 writeInt(st
, LongInt(mIVal2
));
1431 if (mMaxDim
>= 0) then raise TDynRecException
.CreateFmt('pos/size array in field ''%s'' cannot be written', [mName
]);
1432 writeInt(st
, Word(mIVal
));
1433 writeInt(st
, Word(mIVal2
));
1446 else raise TDynRecException
.Create('ketmar forgot to handle some field type');
1451 procedure TDynField
.writeTo (wr
: TTextWriter
);
1455 first
, found
: Boolean;
1460 TEBS
.TNone
: begin end;
1463 if (mRecRef
= nil) then
1465 if (mType
= TType
.TTrigData
) then wr
.put('{}'#10) else wr
.put('null;'#10);
1467 else if (Length(mRecRef
.mId
) = 0) then
1469 mRecRef
.writeTo(wr
, false); // only data, no header
1473 wr
.put(mRecRef
.mId
);
1480 //def := mOwner.mOwner;
1481 //es := def.ebsType[mEBSTypeName];
1483 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1484 if (es
= nil) or (not es
.mIsEnum
) then raise TDynRecException
.CreateFmt('record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]);
1485 for f
:= 0 to High(es
.mVals
) do
1487 if (es
.mVals
[f
] = mIVal
) then
1494 raise TDynRecException
.CreateFmt('value %d in record enum type ''%s'' for field ''%s'' not found', [mIVal
, mEBSTypeName
, mName
]);
1498 //def := mOwner.mOwner;
1499 //es := def.ebsType[mEBSTypeName];
1501 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1502 if (es
= nil) or es
.mIsEnum
then raise TDynRecException
.CreateFmt('record bitset type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]);
1506 for f
:= 0 to High(es
.mVals
) do
1508 if (es
.mVals
[f
] = 0) then
1515 raise TDynRecException
.CreateFmt('value %d in record bitset type ''%s'' for field ''%s'' not found', [0, mEBSTypeName
, mName
]);
1520 while (mask
<> 0) do
1522 if ((mIVal
and mask
) <> 0) then
1525 for f
:= 0 to High(es
.mVals
) do
1527 if (es
.mVals
[f
] = mask
) then
1529 if not first
then wr
.put('+') else first
:= false;
1535 if not found
then raise TDynRecException
.CreateFmt('value %d in record bitset type ''%s'' for field ''%s'' not found', [mask
, mEBSTypeName
, mName
]);
1542 else raise TDynRecException
.Create('ketmar forgot to handle some EBS type');
1548 if (mIVal
= 0) then wr
.put('false;'#10) else wr
.put('true;'#10);
1553 if (mMaxDim
= 0) then raise TDynRecException
.CreateFmt('invalid string size definition for field ''%s''', [mName
]);
1554 wr
.put(quoteStr(mSVal
));
1565 wr
.put('%d;'#10, [mIVal
]);
1570 wr
.put(quoteStr(mSVal
));
1577 wr
.put('(%d %d);'#10, [mIVal
, mIVal2
]);
1590 else raise TDynRecException
.Create('ketmar forgot to handle some field type');
1592 raise TDynRecException
.CreateFmt('cannot parse field ''%s'' yet', [mName
]);
1596 procedure TDynField
.parseBinValue (st
: TStream
);
1598 rec
, rc
: TDynRecord
;
1606 TEBS
.TNone
: begin end;
1609 // this must be triggerdata
1610 if (mType
= TType
.TTrigData
) then
1612 assert(mMaxDim
> 0);
1614 // find trigger definition
1615 tfld
:= rec
.trigTypeField();
1616 if (tfld
= nil) then raise TDynRecException
.CreateFmt('triggerdata value for field ''%s'' in record ''%s'' without TriggerType field', [mName
, rec
.mTypeName
]);
1617 rc
:= mOwner
.mOwner
.trigTypeFor
[tfld
.mSVal
]; // find in mapdef
1618 if (rc
= nil) then raise TDynRecException
.CreateFmt('triggerdata definition for field ''%s'' in record ''%s'' with type ''%s'' not found', [mName
, rec
.mTypeName
, tfld
.mSVal
]);
1619 rc
:= rc
.clone(mOwner
.mHeaderRec
);
1620 rc
.mHeaderRec
:= mOwner
.mHeaderRec
;
1621 // on error, it will be freed by memowner
1622 rc
.parseBinValue(st
, true);
1629 // not a trigger data
1631 TType
.TByte
: f
:= readShortInt(st
);
1632 TType
.TUByte
: f
:= readByte(st
);
1633 TType
.TShort
: f
:= readSmallInt(st
);
1634 TType
.TUShort
: f
:= readWord(st
);
1635 TType
.TInt
: f
:= readLongInt(st
);
1636 TType
.TUInt
: f
:= readLongWord(st
);
1637 else raise TDynRecException
.CreateFmt('invalid non-numeric type ''%s'' for field ''%s'' of record ''%s''', [getTypeName(mType
), mName
, mEBSTypeName
]);
1639 if mAsMonsterId
then Dec(f
);
1640 if (f
< 0) then mRecRefId
:= '' else mRecRefId
:= Format('%s%d', [mEBSTypeName
, f
]);
1648 assert(mMaxDim
< 0);
1650 TType
.TByte
: f
:= readShortInt(st
);
1651 TType
.TUByte
: f
:= readByte(st
);
1652 TType
.TShort
: f
:= readSmallInt(st
);
1653 TType
.TUShort
: f
:= readWord(st
);
1654 TType
.TInt
: f
:= readLongInt(st
);
1655 TType
.TUInt
: f
:= readLongWord(st
);
1656 else raise TDynRecException
.CreateFmt('invalid non-numeric type ''%s'' for field ''%s'' of record ''%s''', [getTypeName(mType
), mName
, mEBSTypeName
]);
1659 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1660 if (es
= nil) or (es
.mIsEnum
<> (mEBS
= TEBS
.TEnum
)) then raise TDynRecException
.CreateFmt('record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]);
1662 // build enum/bitfield values
1663 if (mEBS
= TEBS
.TEnum
) then
1665 mSVal
:= es
.nameByValue(mIVal
);
1666 if (Length(mSVal
) = 0) then raise TDynRecException
.CreateFmt('record enum type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName
, mName
, mIVal
]);
1670 // special for 'none'
1673 mSVal
:= es
.nameByValue(mIVal
);
1674 if (Length(mSVal
) = 0) then raise TDynRecException
.CreateFmt('record bitset type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName
, mName
, mIVal
]);
1680 while (mask
<> 0) do
1682 if ((mIVal
and mask
) <> 0) then
1684 s
:= es
.nameByValue(mask
);
1685 if (Length(s
) = 0) then raise TDynRecException
.CreateFmt('record bitset type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName
, mName
, mask
]);
1686 if (Length(mSVal
) <> 0) then mSVal
+= '+';
1693 //writeln('ebs <', es.mName, '>: ', mSVal);
1697 else raise TDynRecException
.Create('ketmar forgot to handle some EBS type');
1704 if (f
<> 0) then f
:= 1;
1705 if mNegBool
then f
:= 1-f
;
1712 if (mMaxDim
< 0) then
1714 mIVal
:= readByte(st
);
1719 GetMem(tdata
, mMaxDim
);
1721 st
.ReadBuffer(tdata
^, mMaxDim
);
1723 while (f
< mMaxDim
) and (tdata
[f
] <> 0) do Inc(f
);
1726 SetLength(mSVal
, f
);
1727 Move(tdata
^, PChar(mSVal
)^, f
);
1728 mSVal
:= win2utf(mSVal
);
1737 TType
.TByte
: begin mIVal
:= readShortInt(st
); mDefined
:= true; exit
; end;
1738 TType
.TUByte
: begin mIVal
:= readByte(st
); mDefined
:= true; exit
; end;
1739 TType
.TShort
: begin mIVal
:= readSmallInt(st
); mDefined
:= true; exit
; end;
1740 TType
.TUShort
: begin mIVal
:= readWord(st
); mDefined
:= true; exit
; end;
1741 TType
.TInt
: begin mIVal
:= readLongInt(st
); mDefined
:= true; exit
; end;
1742 TType
.TUInt
: begin mIVal
:= readLongWord(st
); mDefined
:= true; exit
; end;
1745 raise TDynRecException
.Create('cannot read strings from binaries yet');
1750 mIVal
:= readLongInt(st
);
1751 mIVal2
:= readLongInt(st
);
1757 mIVal
:= readWord(st
);
1758 mIVal2
:= readWord(st
);
1772 else raise TDynRecException
.Create('ketmar forgot to handle some field type');
1774 raise TDynRecException
.CreateFmt('cannot parse field ''%s'' yet', [mName
]);
1778 procedure TDynField
.parseValue (pr
: TTextParser
);
1780 procedure parseInt (min
, max
: Integer);
1782 mIVal
:= pr
.expectInt();
1783 if (mIVal
< min
) or (mIVal
> max
) then raise TDynParseException
.CreateFmt(pr
, 'invalid %s value for field ''%s''', [getTypeName(mType
), mName
]);
1788 rec
, rc
: TDynRecord
;
1794 if (pr
.tokType
= pr
.TTEOF
) then raise TDynParseException
.Create(pr
, 'field value expected');
1795 if (pr
.tokType
= pr
.TTSemi
) then raise TDynParseException
.Create(pr
, 'extra semicolon');
1796 // if this field should contain struct, convert type and parse struct
1798 TEBS
.TNone
: begin end;
1801 // ugly hack. sorry.
1802 if (mType
= TType
.TTrigData
) then
1804 pr
.expectTT(pr
.TTBegin
);
1805 if (pr
.tokType
= pr
.TTEnd
) then
1809 pr
.expectTT(pr
.TTEnd
);
1814 // find trigger definition
1815 tfld
:= rec
.trigTypeField();
1816 if (tfld
= nil) then raise TDynParseException
.CreateFmt(pr
, 'triggerdata value for field ''%s'' in record ''%s'' without ''type'' field', [mName
, rec
.mTypeName
]);
1817 rc
:= mOwner
.mOwner
.trigTypeFor
[tfld
.mSVal
]; // find in mapdef
1818 if (rc
= nil) then raise TDynParseException
.CreateFmt(pr
, 'triggerdata definition for field ''%s'' in record ''%s'' with type ''%s'' not found', [mName
, rec
.mTypeName
, tfld
.mSVal
]);
1819 rc
:= rc
.clone(mOwner
.mHeaderRec
);
1820 rc
.mHeaderRec
:= mOwner
.mHeaderRec
;
1821 //writeln(rc.definition);
1822 // on error, it will be freed by memowner
1823 rc
.parseValue(pr
, true);
1827 pr
.eatTT(pr
.TTSemi
); // hack: allow (but don't require) semicolon after inline records
1830 // other record types
1831 if (pr
.tokType
= pr
.TTId
) then
1833 if pr
.eatId('null') then
1839 rec
:= mOwner
.findRecordByTypeId(mEBSTypeName
, pr
.tokStr
);
1842 mRecRefId
:= pr
.tokStr
;
1852 pr
.expectTT(pr
.TTSemi
);
1855 else if (pr
.tokType
= pr
.TTBegin
) then
1857 //rec := mOwner.mOwner.recType[mEBSTypeName]; // find in mapdef
1859 if (mEBSType
<> nil) and (mEBSType
is TDynRecord
) then rec
:= (mEBSType
as TDynRecord
);
1860 if (rec
= nil) then raise TDynParseException
.CreateFmt(pr
, 'record type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]);
1861 rc
:= rec
.clone(mOwner
.mHeaderRec
);
1862 rc
.mHeaderRec
:= mOwner
.mHeaderRec
;
1866 if mOwner
.addRecordByType(mEBSTypeName
, rc
) then
1868 raise TDynParseException
.CreateFmt(pr
, 'duplicate record with id ''%s'' for field ''%s'' in record ''%s''', [rc
.mId
, mName
, mOwner
.mTypeName
]);
1870 pr
.eatTT(pr
.TTSemi
); // hack: allow (but don't require) semicolon after inline records
1873 pr
.expectTT(pr
.TTBegin
);
1877 //es := mOwner.mOwner.ebsType[mEBSTypeName]; // find in mapdef
1879 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1880 if (es
= nil) or (not es
.mIsEnum
) then raise TDynParseException
.CreateFmt(pr
, 'record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]);
1881 tk
:= pr
.expectId();
1882 if not es
.has
[tk
] then raise TDynParseException
.CreateFmt(pr
, 'record enum value ''%s'' of type ''%s'' for field ''%s'' not found', [tk
, mEBSTypeName
, mName
]);
1883 mIVal
:= es
.field
[tk
];
1885 //writeln('ENUM ', mEBSName, '; element <', mSVal, '> with value ', mIVal);
1887 pr
.expectTT(pr
.TTSemi
);
1892 //es := mOwner.mOwner.ebsType[mEBSTypeName]; // find in mapdef
1894 if (mEBSType
<> nil) and (mEBSType
is TDynEBS
) then es
:= (mEBSType
as TDynEBS
);
1895 if (es
= nil) or es
.mIsEnum
then raise TDynParseException
.CreateFmt(pr
, 'record bitset type ''%s'' for field ''%s'' not found', [mEBSTypeName
, mName
]);
1899 tk
:= pr
.expectId();
1900 if not es
.has
[tk
] then raise TDynParseException
.CreateFmt(pr
, 'record bitset value ''%s'' of type ''%s'' for field ''%s'' not found', [tk
, mEBSTypeName
, mName
]);
1901 mIVal
:= mIVal
or es
.field
[tk
];
1903 if (pr
.tokType
<> pr
.TTDelim
) or ((pr
.tokChar
<> '|') and (pr
.tokChar
<> '+')) then break
;
1904 if mBitSetUnique
then raise TDynParseException
.CreateFmt(pr
, 'record bitset of type ''%s'' for field ''%s'' expects only one value', [tk
, mEBSTypeName
, mName
]);
1905 pr
.skipToken(); // plus or pipe
1908 pr
.expectTT(pr
.TTSemi
);
1911 else raise TDynParseException
.Create(pr
, 'ketmar forgot to handle some EBS type');
1917 if pr
.eatId('true') or pr
.eatId('tan') or pr
.eatId('yes') then mIVal
:= 1
1918 else if pr
.eatId('false') or pr
.eatId('ona') or pr
.eatId('no') then mIVal
:= 0
1919 else raise TDynParseException
.CreateFmt(pr
, 'invalid bool value for field ''%s''', [mName
]);
1921 pr
.expectTT(pr
.TTSemi
);
1926 if (mMaxDim
= 0) then raise TDynParseException
.CreateFmt(pr
, 'invalid string size definition for field ''%s''', [mName
]);
1927 mSVal
:= pr
.expectStr(true);
1928 if (mMaxDim
< 0) then
1931 if (Length(mSVal
) <> 1) then raise TDynParseException
.CreateFmt(pr
, 'invalid string size for field ''%s''', [mName
]);
1932 mIVal
:= Integer(mSVal
[1]);
1938 if (Length(mSVal
) > mMaxDim
) then raise TDynParseException
.CreateFmt(pr
, 'invalid string size for field ''%s''', [mName
]);
1941 pr
.expectTT(pr
.TTSemi
);
1946 parseInt(-128, 127);
1947 pr
.expectTT(pr
.TTSemi
);
1953 pr
.expectTT(pr
.TTSemi
);
1958 parseInt(-32768, 32768);
1959 pr
.expectTT(pr
.TTSemi
);
1965 pr
.expectTT(pr
.TTSemi
);
1970 parseInt(Integer($80000000), $7fffffff);
1971 pr
.expectTT(pr
.TTSemi
);
1976 parseInt(0, $7fffffff); //FIXME
1977 pr
.expectTT(pr
.TTSemi
);
1982 mSVal
:= pr
.expectStr(true);
1984 pr
.expectTT(pr
.TTSemi
);
1990 if pr
.eatDelim('[') then edim
:= ']' else begin pr
.expectDelim('('); edim
:= ')'; end;
1991 mIVal
:= pr
.expectInt();
1992 if (mType
= TType
.TSize
) then
1994 if (mIVal
< 0) or (mIVal
> 32767) then raise TDynParseException
.CreateFmt(pr
, 'invalid %s value for field ''%s''', [getTypeName(mType
), mName
]);
1996 mIVal2
:= pr
.expectInt();
1997 if (mType
= TType
.TSize
) then
1999 if (mIVal2
< 0) or (mIVal2
> 32767) then raise TDynParseException
.CreateFmt(pr
, 'invalid %s value for field ''%s''', [getTypeName(mType
), mName
]);
2002 pr
.expectDelim(edim
);
2003 pr
.expectTT(pr
.TTSemi
);
2016 else raise TDynParseException
.Create(pr
, 'ketmar forgot to handle some field type');
2018 raise TDynParseException
.CreateFmt(pr
, 'cannot parse field ''%s'' yet', [mName
]);
2022 // ////////////////////////////////////////////////////////////////////////// //
2023 constructor TDynRecord
.Create (pr
: TTextParser
);
2025 if (pr
= nil) then raise TDynParseException
.Create(pr
, 'cannot create record type without type definition');
2029 mFields
:= TDynFieldList
.Create();
2030 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2031 mFieldsHash
:= hashNewStrInt();
2043 constructor TDynRecord
.Create ();
2047 mFields
:= TDynFieldList
.Create();
2048 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2049 mFieldsHash
:= hashNewStrInt();
2060 destructor TDynRecord
.Destroy ();
2065 if (mRec2Free
<> nil) then
2067 for rec
in mRec2Free
do
2069 if (rec
<> self
) then
2071 //writeln(formatstrf('freeing: 0x%08x; name=%s; id=%s', [Pointer(rec), rec.mName, rec.mId]));
2079 for fld
in mFields
do fld
.Free();
2082 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2094 procedure TDynRecord
.regrec (rec
: TDynRecord
);
2096 if (rec
<> nil) and (rec
<> self
) then
2098 if (mRec2Free
= nil) then mRec2Free
:= TDynRecList
.Create();
2099 mRec2Free
.append(rec
);
2104 procedure TDynRecord
.addField (fld
: TDynField
); inline;
2106 if (fld
= nil) then raise TDynRecException
.Create('cannot append nil field to record');
2107 mFields
.append(fld
);
2108 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2109 if (Length(fld
.mName
) > 0) then mFieldsHash
.put(fld
.mName
, mFields
.count
-1);
2114 function TDynRecord
.addFieldChecked (fld
: TDynField
): Boolean; inline; // `true`: duplicate name
2117 if (fld
= nil) then raise TDynRecException
.Create('cannot append nil field to record');
2118 {$IF not DEFINED(XDYNREC_USE_FIELDHASH)}
2119 if (Length(fld
.mName
) > 0) then result
:= hasByName(fld
.mName
);
2121 mFields
.append(fld
);
2122 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2123 if (Length(fld
.mName
) > 0) then result
:= mFieldsHash
.put(fld
.mName
, mFields
.count
-1);
2128 function TDynRecord
.findByName (const aname
: AnsiString): Integer; inline;
2130 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2131 if not mFieldsHash
.get(aname
, result
) then result
:= -1;
2134 while (result
< mFields
.count
) do
2136 if StrEqu(aname
, mFields
[result
].mName
) then exit
;
2144 function TDynRecord
.hasByName (const aname
: AnsiString): Boolean; inline;
2146 result
:= (findByName(aname
) >= 0);
2150 function TDynRecord
.getFieldByName (const aname
: AnsiString): TDynField
; inline;
2154 f
:= findByName(aname
);
2155 if (f
>= 0) then result
:= mFields
[f
] else result
:= nil;
2159 function TDynRecord
.getFieldAt (idx
: Integer): TDynField
; inline;
2161 if (idx
>= 0) and (idx
< mFields
.count
) then result
:= mFields
[idx
] else result
:= nil;
2165 function TDynRecord
.getCount (): Integer; inline;
2167 result
:= mFields
.count
;
2171 function TDynRecord
.getIsTrigData (): Boolean; inline;
2173 result
:= (Length(mTrigTypes
) > 0);
2177 function TDynRecord
.getIsForTrig (const aname
: AnsiString): Boolean; inline;
2182 for f
:= 0 to High(mTrigTypes
) do if StrEqu(mTrigTypes
[f
], aname
) then exit
;
2187 function TDynRecord
.getForTrigCount (): Integer; inline;
2189 result
:= Length(mTrigTypes
);
2193 function TDynRecord
.getForTrigAt (idx
: Integer): AnsiString; inline;
2195 if (idx
>= 0) and (idx
< Length(mTrigTypes
)) then result
:= mTrigTypes
[idx
] else result
:= '';
2199 function TDynRecord
.clone (registerIn
: TDynRecord
): TDynRecord
;
2204 result
:= TDynRecord
.Create();
2205 result
.mOwner
:= mOwner
;
2207 result
.mTypeName
:= mTypeName
;
2208 result
.mSize
:= mSize
;
2209 result
.mHeader
:= mHeader
;
2210 result
.mBinBlock
:= mBinBlock
;
2211 result
.mHeaderRec
:= mHeaderRec
;
2212 result
.mTagInt
:= mTagInt
;
2213 result
.mTagPtr
:= mTagPtr
;
2214 if (mFields
.count
> 0) then
2216 result
.mFields
.capacity
:= mFields
.count
;
2217 for fld
in mFields
do result
.addField(fld
.clone(result
, registerIn
));
2219 SetLength(result
.mTrigTypes
, Length(mTrigTypes
));
2220 for f
:= 0 to High(mTrigTypes
) do result
.mTrigTypes
[f
] := mTrigTypes
[f
];
2221 if (registerIn
<> nil) then registerIn
.regrec(result
);
2225 function TDynRecord
.findRecordByTypeId (const atypename
, aid
: AnsiString): TDynRecord
;
2231 if (Length(aid
) = 0) then exit
;
2233 fld
:= mHeaderRec
.field
[atypename
];
2234 if (fld
= nil) then exit
;
2235 if (fld
.mType
<> fld
.TType
.TList
) then raise TDynRecException
.CreateFmt('cannot get record of type ''%s'' due to name conflict with ordinary field', [atypename
]);
2237 if (fld
.mRVal
<> nil) then
2239 if fld
.mRHash
.get(aid
, idx
) then begin result
:= fld
.mRVal
[idx
]; exit
; end;
2245 function TDynRecord
.findRecordNumByType (const atypename
: AnsiString; rc
: TDynRecord
): Integer;
2252 fld
:= mHeaderRec
.field
[atypename
];
2253 if (fld
= nil) then exit
;
2254 if (fld
.mType
<> fld
.TType
.TList
) then raise TDynRecException
.CreateFmt('cannot get record of type ''%s'' due to name conflict with ordinary field', [atypename
]);
2256 if (fld
.mRVal
<> nil) then
2258 for idx
:= 0 to fld
.mRVal
.count
-1 do
2260 if (fld
.mRVal
[idx
] = rc
) then begin result
:= idx
; exit
; end;
2267 function TDynRecord
.addRecordByType (const atypename
: AnsiString; rc
: TDynRecord
): Boolean;
2272 fld
:= mHeaderRec
.field
[atypename
];
2276 fld
:= TDynField
.Create(atypename
, TDynField
.TType
.TList
);
2277 fld
.mOwner
:= mHeaderRec
;
2278 mHeaderRec
.addField(fld
);
2280 if (fld
.mType
<> fld
.TType
.TList
) then raise TDynRecException
.CreateFmt('cannot append record of type ''%s'' due to name conflict with ordinary field', [atypename
]);
2282 if (fld
.mRVal
= nil) then
2284 fld
.mRVal
:= TDynRecList
.Create();
2285 fld
.mRHash
:= hashNewStrInt();
2287 result
:= fld
.addListItem(rc
);
2291 function TDynRecord
.isSimpleEqu (rec
: TDynRecord
): Boolean;
2295 if (rec
= nil) then begin result
:= false; exit
; end; // self.mRecRef can't be `nil` here
2296 if (rec
= self
) then begin result
:= true; exit
; end;
2297 if (mFields
.count
<> rec
.mFields
.count
) then begin result
:= false; exit
; end;
2299 for f
:= 0 to mFields
.count
-1 do
2301 if not mFields
[f
].isSimpleEqu(rec
.mFields
[f
]) then exit
;
2307 function TDynRecord
.trigTypeField (): TDynField
;
2312 for fld
in mFields
do
2314 if (fld
.mEBS
<> TDynField
.TEBS
.TEnum
) then continue
;
2315 if not (fld
.mEBSType
is TDynEBS
) then continue
;
2316 es
:= (fld
.mEBSType
as TDynEBS
);
2318 if StrEqu(es
.mTypeName
, 'TriggerType') then begin result
:= fld
; exit
; end;
2324 // number of records of the given instance
2325 function TDynRecord
.instanceCount (const atypename
: AnsiString): Integer;
2330 fld
:= field
[atypename
];
2331 if (fld
<> nil) and (fld
.mType
= fld
.TType
.TList
) then result
:= fld
.mRVal
.count
;
2335 function TDynRecord
.newTypedRecord (const atypename
, aid
: AnsiString): TDynRecord
;
2340 if not mHeader
then raise TDynRecException
.Create('cannot create new records with non-header');
2341 if (Length(aid
) = 0) then raise TDynRecException
.CreateFmt('cannot create new record of type ''%s'' without id', [atypename
]);
2342 trc
:= mapdef
.recType
[atypename
];
2343 if (trc
= nil) then begin result
:= nil; exit
; end;
2344 // check if aid is unique
2345 fld
:= field
[atypename
];
2346 if (fld
<> nil) and (fld
.getListItem(aid
) <> nil) then raise TDynRecException
.CreateFmt('cannot create record of type ''%s'' with duplicate id ''%s''', [atypename
, aid
]);
2347 result
:= trc
.clone(self
);
2349 addRecordByType(atypename
, result
);
2353 procedure TDynRecord
.clearRefRecs (rec
: TDynRecord
);
2354 procedure clearRefs (fld
: TDynField
);
2358 if (fld
= nil) then exit
;
2359 if (fld
.mRecRef
= rec
) then fld
.mRecRef
:= nil;
2360 if (fld
.mType
= fld
.TType
.TList
) then for rc
in fld
.mRVal
do rc
.clearRefRecs(rec
);
2365 if (rec
= nil) or (mFields
= nil) then exit
;
2366 for fld
in mFields
do clearRefs(fld
);
2370 // remove record with the given type and id
2371 // return `true` if record was successfully found and removed
2372 // this will do all necessary recref cleanup too
2373 function TDynRecord
.removeTypedRecord (const atypename
, aid
: AnsiString): Boolean;
2375 trc
, rec
: TDynRecord
;
2378 doFree
: Boolean = false;
2381 if not mHeader
then raise TDynRecException
.Create('cannot remove records with non-header');
2382 if (Length(aid
) = 0) then exit
;
2383 trc
:= mapdef
.recType
[atypename
];
2384 if (trc
= nil) then exit
;
2385 fld
:= field
[atypename
];
2386 if (fld
= nil) then exit
;
2387 rec
:= fld
.removeListItem(aid
);
2388 if (rec
= nil) then exit
;
2390 for f
:= 0 to mRec2Free
.count
-1 do
2392 if (mRec2Free
[f
] = rec
) then
2394 mRec2Free
[f
] := nil;
2398 if doFree
then rec
.Free();
2402 function TDynRecord
.getUserVar (const aname
: AnsiString): Variant;
2406 fld
:= getFieldByName(aname
);
2407 if (fld
= nil) then result
:= Unassigned
else result
:= fld
.value
;
2411 procedure TDynRecord
.setUserVar (const aname
: AnsiString; val
: Variant);
2415 fld
:= getFieldByName(aname
);
2418 if (Length(aname
) = 0) then raise TDynRecException
.Create('cannot create nameless user field');
2419 fld
:= TDynField
.Create(aname
, val
);
2421 fld
.mInternal
:= true;
2431 procedure TDynRecord
.parseDef (pr
: TTextParser
);
2436 if pr
.eatId('TriggerData') then
2439 if pr
.eatDelim('(') then
2443 while pr
.eatTT(pr
.TTComma
) do begin end;
2444 if pr
.eatDelim(')') then break
;
2445 tdn
:= pr
.expectId();
2446 if isForTrig
[tdn
] then raise TDynParseException
.CreateFmt(pr
, 'duplicate trigdata ''%s'' trigtype ''%s''', [mTypeName
, tdn
]);
2447 SetLength(mTrigTypes
, Length(mTrigTypes
)+1);
2448 mTrigTypes
[High(mTrigTypes
)] := tdn
;
2453 tdn
:= pr
.expectId();
2454 SetLength(mTrigTypes
, 1);
2455 mTrigTypes
[0] := tdn
;
2457 mTypeName
:= 'TriggerData';
2461 mTypeName
:= pr
.expectStrOrId();
2462 while (pr
.tokType
<> pr
.TTBegin
) do
2464 if pr
.eatId('header') then begin mHeader
:= true; continue
; end;
2465 if pr
.eatId('size') then
2467 if (mSize
> 0) then raise TDynParseException
.CreateFmt(pr
, 'duplicate `size` in record ''%s''', [mTypeName
]);
2468 mSize
:= pr
.expectInt();
2469 if (mSize
< 1) then raise TDynParseException
.CreateFmt(pr
, 'invalid record ''%s'' size: %d', [mTypeName
, mSize
]);
2470 pr
.expectId('bytes');
2473 if pr
.eatId('binblock') then
2475 if (mBinBlock
>= 0) then raise TDynParseException
.CreateFmt(pr
, 'duplicate `binblock` in record ''%s''', [mTypeName
]);
2476 mBinBlock
:= pr
.expectInt();
2477 if (mBinBlock
< 1) then raise TDynParseException
.CreateFmt(pr
, 'invalid record ''%s'' binblock: %d', [mTypeName
, mBinBlock
]);
2483 pr
.expectTT(pr
.TTBegin
);
2485 while (pr
.tokType
<> pr
.TTEnd
) do
2487 fld
:= TDynField
.Create(pr
);
2490 if addFieldChecked(fld
) then
2493 raise TDynParseException
.CreateFmt(pr
, 'duplicate field ''%s''', [fld
.name
]);
2497 pr
.expectTT(pr
.TTEnd
);
2501 function TDynRecord
.definition (): AnsiString;
2508 result
:= 'TriggerData for ';
2509 if (Length(mTrigTypes
) > 1) then
2512 for f
:= 0 to High(mTrigTypes
) do
2514 if (f
<> 0) then result
+= ', ';
2515 result
+= mTrigTypes
[f
];
2521 result
+= mTrigTypes
[0];
2527 result
:= quoteStr(mTypeName
);
2528 if (mSize
>= 0) then result
+= Format(' size %d bytes', [mSize
]);
2529 if mHeader
then result
+= ' header';
2532 for f
:= 0 to mFields
.count
-1 do
2535 result
+= mFields
[f
].definition
;
2542 procedure TDynRecord
.parseBinValue (st
: TStream
; forceData
: Boolean=false);
2548 loaded
: array[0..255] of Boolean;
2549 rec
, rect
: TDynRecord
;
2552 mst
: TSFSMemoryChunkStream
= nil;
2554 procedure linkNames (rec
: TDynRecord
);
2559 //writeln('*** rec: ', rec.mName, '.', rec.mId, ' (', rec.mFields.count, ')');
2560 for fld
in rec
.mFields
do
2562 if (fld
.mType
= TDynField
.TType
.TTrigData
) then
2564 if (fld
.mRecRef
<> nil) then linkNames(fld
.mRecRef
);
2567 if (Length(fld
.mRecRefId
) = 0) then continue
;
2568 assert(fld
.mEBSType
<> nil);
2569 rt
:= findRecordByTypeId(fld
.mEBSTypeName
, fld
.mRecRefId
);
2572 e_LogWritefln('record of type ''%s'' with id ''%s'' links to inexistant record of type ''%s'' with id ''%s''', [rec
.mTypeName
, rec
.mId
, fld
.mEBSTypeName
, fld
.mRecRefId
], MSG_WARNING
);
2573 //raise TDynRecException.CreateFmt('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]);
2575 //writeln(' ', rec.mName, '.', rec.mId, ':', fld.mName, ' -> ', rt.mName, '.', rt.mId, ' (', fld.mEBSTypeName, '.', fld.mRecRefId, ')');
2576 fld
.mRecRefId
:= '';
2578 fld
.mDefined
:= true;
2580 for fld
in rec
.mFields
do
2582 //writeln(' ', fld.mName);
2583 fld
.fixDefaultValue(); // just in case
2588 for f
:= 0 to High(loaded
) do loaded
[f
] := false;
2589 mst
:= TSFSMemoryChunkStream
.Create(nil, 0);
2591 if mHeader
and not forceData
then
2593 // parse map file as sequence of blocks
2595 st
.ReadBuffer(sign
[1], 4);
2596 if (sign
<> 'MAP'#1) then raise TDynRecException
.Create('invalid binary map signature');
2598 while (st
.position
< st
.size
) do
2600 btype
:= readByte(st
);
2601 if (btype
= 0) then break
; // no more blocks
2602 readLongWord(st
); // reserved
2603 bsize
:= readLongInt(st
);
2604 {$IF DEFINED(D2D_XDYN_DEBUG)}writeln('btype=', btype
, '; bsize=', bsize
);{$ENDIF}
2605 if (bsize
< 0) or (bsize
> $1fffffff) then raise TDynRecException
.CreateFmt('block of type %d has invalid size %d', [btype
, bsize
]);
2606 if loaded
[btype
] then raise TDynRecException
.CreateFmt('block of type %d already loaded', [btype
]);
2607 loaded
[btype
] := true;
2608 // find record type for this block
2610 for rec
in mOwner
.recTypes
do if (rec
.mBinBlock
= btype
) then begin rect
:= rec
; break
; end;
2611 if (rect
= nil) then raise TDynRecException
.CreateFmt('block of type %d has no corresponding record', [btype
]);
2612 //writeln('found type ''', rec.mName, ''' for block type ', btype);
2613 if (rec
.mSize
= 0) or ((bsize
mod rec
.mSize
) <> 0) then raise TDynRecException
.CreateFmt('block of type %d has invalid number of records', [btype
]);
2615 if (rect
.mHeader
) then
2617 if (bsize
<> mSize
) then raise TDynRecException
.CreateFmt('header block of type %d has invalid number of records', [btype
]);
2619 st
.ReadBuffer(buf
^, bsize
);
2620 mst
.setup(buf
, mSize
);
2621 parseBinValue(mst
, true); // force parsing data
2625 // create list for this type
2626 fld
:= TDynField
.Create(rec
.mTypeName
, TDynField
.TType
.TList
);
2632 st
.ReadBuffer(buf
^, bsize
);
2633 for f
:= 0 to (bsize
div rec
.mSize
)-1 do
2635 mst
.setup(buf
+f
*rec
.mSize
, rec
.mSize
);
2636 rec
:= rect
.clone(self
);
2637 rec
.mHeaderRec
:= self
;
2638 rec
.parseBinValue(mst
);
2639 rec
.mId
:= Format('%s%d', [rec
.mTypeName
, f
]);
2640 fld
.addListItem(rec
);
2641 //writeln('parsed ''', rec.mId, '''...');
2647 //st.position := st.position+bsize;
2650 for fld
in mFields
do
2652 if (fld
.mType
<> TDynField
.TType
.TList
) then continue
;
2653 for rec
in fld
.mRVal
do linkNames(rec
);
2659 if StrEqu(mTypeName
, 'TriggerData') then mSize
:= Integer(st
.size
-st
.position
);
2660 if (mSize
< 1) then raise TDynRecException
.CreateFmt('cannot read record of type ''%s'' with unknown size', [mTypeName
]);
2662 st
.ReadBuffer(buf
^, mSize
);
2663 for fld
in mFields
do
2665 if fld
.mInternal
then continue
;
2666 if (fld
.mBinOfs
< 0) then continue
;
2667 if (fld
.mBinOfs
>= st
.size
) then raise TDynRecException
.CreateFmt('record of type ''%s'' has invalid field ''%s''', [fld
.mName
]);
2668 mst
.setup(buf
+fld
.mBinOfs
, mSize
-fld
.mBinOfs
);
2669 //writeln('parsing ''', mName, '.', fld.mName, '''...');
2670 fld
.parseBinValue(mst
);
2674 if (buf
<> nil) then FreeMem(buf
);
2679 procedure TDynRecord
.writeBinTo (st
: TStream
; trigbufsz
: Integer=-1; onlyFields
: Boolean=false);
2682 rec
, rv
: TDynRecord
;
2685 blk
, blkmax
: Integer;
2690 if (trigbufsz
< 0) then
2692 if (mBinBlock
< 1) then raise TDynRecException
.Create('cannot write binary record without block number');
2693 if (mSize
< 1) then raise TDynRecException
.Create('cannot write binary record without size');
2702 FillChar(buf
^, bufsz
, 0);
2703 ws
:= TSFSMemoryChunkStream
.Create(buf
, bufsz
);
2705 // write normal fields
2706 for fld
in mFields
do
2709 if (fld
.mType
= fld
.TType
.TList
) then continue
; // later
2710 if fld
.mInternal
then continue
;
2711 if (fld
.mBinOfs
< 0) then continue
;
2712 if (fld
.mBinOfs
>= bufsz
) then raise TDynRecException
.Create('binary value offset is outside of the buffer');
2713 TSFSMemoryChunkStream(ws
).setup(buf
+fld
.mBinOfs
, bufsz
-fld
.mBinOfs
);
2714 //writeln('writing field <', fld.mName, '>');
2718 // write block with normal fields
2719 if mHeader
and not onlyFields
then
2721 //writeln('writing header...');
2722 // signature and version
2723 writeIntBE(st
, LongWord($4D415001));
2724 writeInt(st
, Byte(mBinBlock
)); // type
2725 writeInt(st
, LongWord(0)); // reserved
2726 writeInt(st
, LongWord(bufsz
)); // size
2728 st
.WriteBuffer(buf
^, bufsz
);
2730 ws
.Free(); ws
:= nil;
2731 FreeMem(buf
); buf
:= nil;
2733 // write other blocks, if any
2734 if mHeader
and not onlyFields
then
2738 for fld
in mFields
do
2741 if (fld
.mType
= fld
.TType
.TList
) then
2743 if (fld
.mRVal
= nil) or (fld
.mRVal
.count
= 0) then continue
;
2744 rec
:= mOwner
.recType
[fld
.mName
];
2745 if (rec
= nil) then continue
;
2746 if (rec
.mBinBlock
<= 0) then continue
;
2747 if (blkmax
< rec
.mBinBlock
) then blkmax
:= rec
.mBinBlock
;
2751 for blk
:= 1 to blkmax
do
2753 if (blk
= mBinBlock
) then continue
;
2755 for fld
in mFields
do
2758 if (fld
.mType
= fld
.TType
.TList
) then
2760 if (fld
.mRVal
= nil) or (fld
.mRVal
.count
= 0) then continue
;
2761 rec
:= mOwner
.recType
[fld
.mName
];
2762 if (rec
= nil) then continue
;
2763 if (rec
.mBinBlock
<> blk
) then continue
;
2764 if (ws
= nil) then ws
:= TMemoryStream
.Create();
2765 for rv
in fld
.mRVal
do rv
.writeBinTo(ws
);
2771 blksz
:= Integer(ws
.position
);
2773 writeInt(st
, Byte(blk
)); // type
2774 writeInt(st
, LongWord(0)); // reserved
2775 writeInt(st
, LongWord(blksz
)); // size
2776 st
.CopyFrom(ws
, blksz
);
2782 writeInt(st
, Byte(0));
2783 writeInt(st
, LongWord(0));
2784 writeInt(st
, LongWord(0));
2788 if (buf
<> nil) then FreeMem(buf
);
2793 procedure TDynRecord
.writeTo (wr
: TTextWriter
; putHeader
: Boolean=true);
2801 if (Length(mId
) > 0) then begin wr
.put(' '); wr
.put(mId
); end;
2807 for fld
in mFields
do
2810 if (fld
.mType
= fld
.TType
.TList
) then
2812 if not mHeader
then raise TDynRecException
.Create('record list in non-header record');
2813 if (fld
.mRVal
<> nil) then
2815 for rec
in fld
.mRVal
do
2817 if (Length(rec
.mId
) = 0) then continue
;
2819 rec
.writeTo(wr
, true);
2824 if fld
.mInternal
then continue
;
2825 if (not fld
.mWriteDef
) and fld
.isDefaultValue
then continue
;
2837 {$IF DEFINED(D2D_DYNREC_PROFILER)}
2839 profCloneRec
: UInt64 = 0;
2840 profFindRecType
: UInt64 = 0;
2841 profFieldSearching
: UInt64 = 0;
2842 profListDupChecking
: UInt64 = 0;
2843 profAddRecByType
: UInt64 = 0;
2844 profFieldValParsing
: UInt64 = 0;
2845 profFixDefaults
: UInt64 = 0;
2846 profRecValParse
: UInt64 = 0;
2848 procedure xdynDumpProfiles ();
2850 writeln('=== XDYNREC PROFILES ===');
2851 writeln('record cloning: ', profCloneRec
div 1000, '.', profCloneRec
mod 1000, ' milliseconds');
2852 writeln('findRecType : ', profFindRecType
div 1000, '.', profFindRecType
mod 1000, ' milliseconds');
2853 writeln('field[] : ', profFieldSearching
div 1000, '.', profFieldSearching
mod 1000, ' milliseconds');
2854 writeln('list dup check: ', profListDupChecking
div 1000, '.', profListDupChecking
mod 1000, ' milliseconds');
2855 writeln('addRecByType : ', profAddRecByType
div 1000, '.', profAddRecByType
mod 1000, ' milliseconds');
2856 writeln('field valparse: ', profFieldValParsing
div 1000, '.', profFieldValParsing
mod 1000, ' milliseconds');
2857 writeln('fix defaults : ', profFixDefaults
div 1000, '.', profFixDefaults
mod 1000, ' milliseconds');
2858 writeln('recvalparse : ', profRecValParse
div 1000, '.', profRecValParse
mod 1000, ' milliseconds');
2863 procedure TDynRecord
.parseValue (pr
: TTextParser
; beginEaten
: Boolean=false);
2866 rec
: TDynRecord
= nil;
2867 trc
{, rv}: TDynRecord
;
2868 {$IF DEFINED(D2D_DYNREC_PROFILER)}
2872 procedure linkNames (rec
: TDynRecord
);
2877 //writeln('*** rec: ', rec.mName, '.', rec.mId, ' (', rec.mFields.count, ')');
2878 for fld
in rec
.mFields
do
2880 if (fld
.mType
= TDynField
.TType
.TTrigData
) then
2882 if (fld
.mRecRef
<> nil) then linkNames(fld
.mRecRef
);
2885 if (Length(fld
.mRecRefId
) = 0) then continue
;
2886 assert(fld
.mEBSType
<> nil);
2887 rt
:= findRecordByTypeId(fld
.mEBSTypeName
, fld
.mRecRefId
);
2890 //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);
2891 raise TDynParseException
.CreateFmt(pr
, 'record of type ''%s'' with id ''%s'' links to inexistant record of type ''%s'' with id ''%s''', [rec
.mTypeName
, rec
.mId
, fld
.mEBSTypeName
, fld
.mRecRefId
]);
2893 //writeln(' ', rec.mName, '.', rec.mId, ':', fld.mName, ' -> ', rt.mName, '.', rt.mId, ' (', fld.mEBSTypeName, '.', fld.mRecRefId, ')');
2894 fld
.mRecRefId
:= '';
2896 fld
.mDefined
:= true;
2898 for fld
in rec
.mFields
do
2900 //writeln(' ', fld.mName);
2901 fld
.fixDefaultValue(); // just in case
2906 if (mOwner
= nil) then raise TDynParseException
.CreateFmt(pr
, 'can''t parse record ''%s'' value without owner', [mTypeName
]);
2908 {$IF DEFINED(D2D_DYNREC_PROFILER)}stall
:= curTimeMicro();{$ENDIF}
2914 if (not beginEaten
) and (pr
.tokType
= pr
.TTId
) then mId
:= pr
.expectId();
2918 assert(mHeaderRec
= self
);
2921 //writeln('parsing record <', mName, '>');
2922 if not beginEaten
then pr
.expectTT(pr
.TTBegin
);
2923 while (pr
.tokType
<> pr
.TTEnd
) do
2925 if (pr
.tokType
<> pr
.TTId
) then raise TDynParseException
.Create(pr
, 'identifier expected');
2926 //writeln('<', mName, '.', pr.tokStr, '>');
2931 // add records with this type (if any)
2932 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2933 trc
:= mOwner
.recType
[pr
.tokStr
];
2934 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFindRecType
:= curTimeMicro()-stt
;{$ENDIF}
2935 if (trc
<> nil) then
2937 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2938 rec
:= trc
.clone(mHeaderRec
);
2939 {$IF DEFINED(D2D_DYNREC_PROFILER)}profCloneRec
:= curTimeMicro()-stt
;{$ENDIF}
2940 rec
.mHeaderRec
:= mHeaderRec
;
2941 // on error, it will be freed by memowner
2944 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2945 addRecordByType(rec
.mTypeName
, rec
);
2946 {$IF DEFINED(D2D_DYNREC_PROFILER)}profAddRecByType
:= curTimeMicro()-stt
;{$ENDIF}
2952 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2953 //writeln('0: <', mName, '.', pr.tokStr, '>');
2954 fld
:= field
[pr
.tokStr
];
2955 //writeln('1: <', mName, '.', pr.tokStr, '>');
2956 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFieldSearching
:= curTimeMicro()-stt
;{$ENDIF}
2957 if (fld
<> nil) then
2959 //writeln('2: <', mName, '.', pr.tokStr, '>');
2960 if fld
.defined
then raise TDynParseException
.CreateFmt(pr
, 'duplicate field ''%s'' in record ''%s''', [fld
.mName
, mTypeName
]);
2961 if fld
.internal
then raise TDynParseException
.CreateFmt(pr
, 'internal field ''%s'' in record ''%s''', [fld
.mName
, mTypeName
]);
2962 pr
.skipToken(); // skip field name
2963 //writeln('3: <', mName, '.', pr.tokStr, '>:', pr.tokType);
2964 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2966 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFieldValParsing
:= curTimeMicro()-stt
;{$ENDIF}
2970 // something is wrong
2971 raise TDynParseException
.CreateFmt(pr
, 'unknown field ''%s'' in record ''%s''', [pr
.tokStr
, mTypeName
]);
2973 pr
.expectTT(pr
.TTEnd
);
2978 for fld
in mFields
do
2980 if (fld
.mType
<> TDynField
.TType
.TList
) then continue
;
2981 for rec
in fld
.mRVal
do linkNames(rec
);
2985 // fix field defaults
2986 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt
:= curTimeMicro();{$ENDIF}
2987 for fld
in mFields
do fld
.fixDefaultValue();
2988 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFixDefaults
:= curTimeMicro()-stt
;{$ENDIF}
2989 //writeln('done parsing record <', mName, '>');
2990 //{$IF DEFINED(D2D_DYNREC_PROFILER)}writeln('stall: ', curTimeMicro()-stall);{$ENDIF}
2991 {$IF DEFINED(D2D_DYNREC_PROFILER)}profRecValParse
:= curTimeMicro()-stall
;{$ENDIF}
2995 // ////////////////////////////////////////////////////////////////////////// //
2996 constructor TDynEBS
.Create (pr
: TTextParser
);
3003 destructor TDynEBS
.Destroy ();
3010 procedure TDynEBS
.cleanup ();
3021 function TDynEBS
.findByName (const aname
: AnsiString): Integer;
3024 while (result
< Length(mIds
)) do
3026 if StrEqu(aname
, mIds
[result
]) then exit
;
3033 function TDynEBS
.hasByName (const aname
: AnsiString): Boolean; inline;
3035 result
:= (findByName(aname
) >= 0);
3039 function TDynEBS
.getFieldByName (const aname
: AnsiString): Integer; inline;
3043 f
:= findByName(aname
);
3044 if (f
>= 0) then result
:= mVals
[f
] else result
:= 0;
3048 function TDynEBS
.definition (): AnsiString;
3052 if mIsEnum
then result
:='enum ' else result
:= 'bitset ';
3053 result
+= mTypeName
;
3056 if mIsEnum
then cv
:= 0 else cv
:= 1;
3057 for f
:= 0 to High(mIds
) do
3059 if (mIds
[f
] = mMaxName
) then continue
;
3060 result
+= ' '+mIds
[f
];
3061 if (mVals
[f
] <> cv
) then
3063 result
+= Format(' = %d', [mVals
[f
]]);
3064 if mIsEnum
then cv
:= mVals
[f
];
3069 result
+= Format(', // %d'#10, [mVals
[f
]]);
3071 if mIsEnum
then Inc(cv
) else if (mVals
[f
] = cv
) then cv
:= cv
shl 1;
3074 if (Length(mMaxName
) > 0) then result
+= ' '+mMaxName
+' = MAX,'#10;
3079 function TDynEBS
.pasdef (): AnsiString;
3083 result
:= '// '+mTypeName
+#10'const'#10;
3085 for f
:= 0 to High(mIds
) do
3087 result
+= formatstrf(' %s = %d;'#10, [mIds
[f
], mVals
[f
]]);
3092 function TDynEBS
.nameByValue (v
: Integer): AnsiString;
3096 for f
:= 0 to High(mVals
) do
3098 if (mVals
[f
] = v
) then begin result
:= mIds
[f
]; exit
; end;
3104 procedure TDynEBS
.parseDef (pr
: TTextParser
);
3112 if pr
.eatId('enum') then mIsEnum
:= true
3113 else if pr
.eatId('bitset') then mIsEnum
:= false
3114 else pr
.expectId('enum');
3115 mTypeName
:= pr
.expectId();
3116 mMaxVal
:= Integer($80000000);
3117 if mIsEnum
then cv
:= 0 else cv
:= 1;
3118 pr
.expectTT(pr
.TTBegin
);
3119 while (pr
.tokType
<> pr
.TTEnd
) do
3121 idname
:= pr
.expectId();
3122 for f
:= 0 to High(mIds
) do
3124 if StrEqu(mIds
[f
], idname
) then raise TDynParseException
.CreateFmt(pr
, 'duplicate field ''%s'' in enum/bitset ''%s''', [idname
, mTypeName
]);
3126 if StrEqu(mMaxName
, idname
) then raise TDynParseException
.CreateFmt(pr
, 'duplicate field ''%s'' in enum/bitset ''%s''', [idname
, mTypeName
]);
3131 if pr
.eatDelim('=') then
3133 if pr
.eatId('MAX') then
3135 if (Length(mMaxName
) > 0) then raise TDynParseException
.CreateFmt(pr
, 'duplicate max field ''%s'' in enum/bitset ''%s''', [idname
, mTypeName
]);
3141 v
:= pr
.expectInt();
3142 if mIsEnum
then cv
:= v
;
3150 if mIsEnum
or (not hasV
) then
3152 if (mMaxVal
< v
) then mMaxVal
:= v
;
3154 SetLength(mIds
, Length(mIds
)+1);
3155 mIds
[High(mIds
)] := idname
;
3156 SetLength(mVals
, Length(mIds
));
3157 mVals
[High(mVals
)] := v
;
3159 if mIsEnum
or (not hasV
) then
3161 if mIsEnum
then Inc(cv
) else cv
:= cv
shl 1;
3164 if (pr
.tokType
= pr
.TTEnd
) then break
;
3165 pr
.expectTT(pr
.TTComma
);
3166 while pr
.eatTT(pr
.TTComma
) do begin end;
3168 pr
.expectTT(pr
.TTEnd
);
3170 if (Length(mMaxName
) > 0) then
3172 SetLength(mIds
, Length(mIds
)+1);
3173 mIds
[High(mIds
)] := mMaxName
;
3174 SetLength(mVals
, Length(mIds
));
3175 mVals
[High(mVals
)] := mMaxVal
;
3180 // ////////////////////////////////////////////////////////////////////////// //
3181 constructor TDynMapDef
.Create (pr
: TTextParser
);
3183 recTypes
:= TDynRecList
.Create();
3184 trigTypes
:= TDynRecList
.Create();
3185 ebsTypes
:= TDynEBSList
.Create();
3190 destructor TDynMapDef
.Destroy ();
3195 //!!!FIXME!!! check who owns trigs and recs!
3196 for rec
in recTypes
do rec
.Free();
3197 for rec
in trigTypes
do rec
.Free();
3198 for ebs
in ebsTypes
do ebs
.Free();
3209 function TDynMapDef
.getHeaderRecType (): TDynRecord
; inline;
3211 if (recTypes
.count
= 0) then raise TDynRecException
.Create('no header in empty mapdef');
3212 result
:= recTypes
[0];
3216 function TDynMapDef
.findRecType (const aname
: AnsiString): TDynRecord
;
3220 for rec
in recTypes
do
3222 if StrEqu(rec
.typeName
, aname
) then begin result
:= rec
; exit
; end;
3228 function TDynMapDef
.findTrigFor (const aname
: AnsiString): TDynRecord
;
3232 for rec
in trigTypes
do
3234 if (rec
.isForTrig
[aname
]) then begin result
:= rec
; exit
; end;
3240 function TDynMapDef
.findEBSType (const aname
: AnsiString): TDynEBS
;
3244 for ebs
in ebsTypes
do
3246 if StrEqu(ebs
.typeName
, aname
) then begin result
:= ebs
; exit
; end;
3252 procedure TDynMapDef
.parseDef (pr
: TTextParser
);
3254 rec
, hdr
: TDynRecord
;
3258 // setup header links and type links
3259 procedure linkRecord (rec
: TDynRecord
);
3263 rec
.mHeaderRec
:= recTypes
[0];
3264 for fld
in rec
.mFields
do
3266 if (fld
.mType
= fld
.TType
.TTrigData
) then continue
;
3268 TDynField
.TEBS
.TNone
: begin end;
3269 TDynField
.TEBS
.TRec
:
3271 fld
.mEBSType
:= findRecType(fld
.mEBSTypeName
);
3272 if (fld
.mEBSType
= nil) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' of type ''%s'' has no correcponding record definition', [fld
.mName
, fld
.mEBSTypeName
]);
3274 TDynField
.TEBS
.TEnum
,
3275 TDynField
.TEBS
.TBitSet
:
3277 fld
.mEBSType
:= findEBSType(fld
.mEBSTypeName
);
3278 if (fld
.mEBSType
= nil) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' of type ''%s'' has no correcponding enum/bitset', [fld
.mName
, fld
.mEBSTypeName
]);
3279 if ((fld
.mEBS
= TDynField
.TEBS
.TEnum
) <> (fld
.mEBSType
as TDynEBS
).mIsEnum
) then raise TDynParseException
.CreateFmt(pr
, 'field ''%s'' of type ''%s'' enum/bitset type conflict', [fld
.mName
, fld
.mEBSTypeName
]);
3285 // setup default values
3286 procedure fixRecordDefaults (rec
: TDynRecord
);
3290 for fld
in rec
.mFields
do if fld
.mHasDefault
then fld
.parseDefaultValue();
3297 if not pr
.skipBlanks() then break
;
3299 if (pr
.tokType
= pr
.TTId
) then
3302 if (pr
.tokStr
= 'enum') or (pr
.tokStr
= 'bitset') then
3304 eb
:= TDynEBS
.Create(pr
);
3305 if (findEBSType(eb
.typeName
) <> nil) then
3308 raise TDynParseException
.CreateFmt(pr
, 'duplicate enum/bitset ''%s''', [eb
.typeName
]);
3311 ebsTypes
.append(eb
);
3312 //writeln(eb.definition); writeln;
3317 if (pr
.tokStr
= 'TriggerData') then
3319 rec
:= TDynRecord
.Create(pr
);
3320 for f
:= 0 to High(rec
.mTrigTypes
) do
3322 if (findTrigFor(rec
.mTrigTypes
[f
]) <> nil) then
3325 raise TDynParseException
.CreateFmt(pr
, 'duplicate trigdata ''%s''', [rec
.mTrigTypes
[f
]]);
3329 trigTypes
.append(rec
);
3330 //writeln(dr.definition); writeln;
3335 rec
:= TDynRecord
.Create(pr
);
3336 //writeln(dr.definition); writeln;
3337 if (findRecType(rec
.typeName
) <> nil) then begin rec
.Free(); raise TDynParseException
.CreateFmt(pr
, 'duplicate record ''%s''', [rec
.typeName
]); end;
3338 if (hdr
<> nil) and StrEqu(rec
.typeName
, hdr
.typeName
) then begin rec
.Free(); raise TDynParseException
.CreateFmt(pr
, 'duplicate record ''%s''', [rec
.typeName
]); end;
3342 if (hdr
<> nil) then begin rec
.Free(); raise TDynParseException
.CreateFmt(pr
, 'duplicate header record ''%s'' (previous is ''%s'')', [rec
.typeName
, hdr
.typeName
]); end;
3347 recTypes
.append(rec
);
3351 // put header record to top
3352 if (hdr
= nil) then raise TDynParseException
.Create(pr
, 'header definition not found in mapdef');
3353 recTypes
.append(nil);
3354 for f
:= recTypes
.count
-1 downto 1 do recTypes
[f
] := recTypes
[f
-1];
3357 // setup header links and type links
3358 for rec
in recTypes
do linkRecord(rec
);
3359 for rec
in trigTypes
do linkRecord(rec
);
3361 // setup default values
3362 for rec
in recTypes
do fixRecordDefaults(rec
);
3363 for rec
in trigTypes
do fixRecordDefaults(rec
);
3367 // ////////////////////////////////////////////////////////////////////////// //
3368 function TDynMapDef
.parseTextMap (pr
: TTextParser
): TDynRecord
;
3370 res
: TDynRecord
= nil;
3374 pr
.expectId(headerType
.typeName
);
3375 res
:= headerType
.clone(nil);
3376 res
.mHeaderRec
:= res
;
3386 function TDynMapDef
.parseBinMap (st
: TStream
): TDynRecord
;
3388 res
: TDynRecord
= nil;
3392 res
:= headerType
.clone(nil);
3393 res
.mHeaderRec
:= res
;
3394 res
.parseBinValue(st
);
3403 // WARNING! stream must be seekable
3404 function TDynMapDef
.parseMap (st
: TStream
): TDynRecord
;
3406 sign
: packed array[0..3] of AnsiChar;
3410 st
.ReadBuffer(sign
[0], 4);
3412 if (sign
[0] = 'M') and (sign
[1] = 'A') and (sign
[2] = 'P') then
3414 if (sign
[3] = #1) then
3416 result
:= parseBinMap(st
);
3419 raise TDynRecException
.Create('invalid binary map version');
3423 pr
:= TFileTextParser
.Create(st
, false); // `st` is not owned
3426 result
:= parseTextMap(pr
);
3427 except on e
: Exception
do
3428 raise TDynParseException
.Create(pr
, e
.message);
3437 // returns `true` if the given stream can be a map file
3438 // stream position is 0 on return
3439 // WARNING! stream must be seekable
3440 class function TDynMapDef
.canBeMap (st
: TStream
): Boolean;
3442 sign
: packed array[0..3] of AnsiChar;
3447 st
.ReadBuffer(sign
[0], 4);
3448 if (sign
[0] = 'M') and (sign
[1] = 'A') and (sign
[2] = 'P') then
3450 result
:= (sign
[3] = #1);
3455 pr
:= TFileTextParser
.Create(st
, false); // `st` is not owned
3456 result
:= (pr
.tokType
= pr
.TTId
) and (pr
.tokStr
= 'map');
3463 function TDynMapDef
.pasdefconst (): AnsiString;
3468 result
+= '// ////////////////////////////////////////////////////////////////////////// //'#10;
3469 result
+= '// enums and bitsets'#10;
3470 for ebs
in ebsTypes
do result
+= #10+ebs
.pasdef();
3474 function TDynMapDef
.getRecTypeCount (): Integer; inline; begin result
:= recTypes
.count
; end;
3475 function TDynMapDef
.getRecTypeAt (idx
: Integer): TDynRecord
; inline; begin if (idx
>= 0) and (idx
< recTypes
.count
) then result
:= recTypes
[idx
] else result
:= nil; end;
3477 function TDynMapDef
.getEBSTypeCount (): Integer; inline; begin result
:= ebsTypes
.count
; end;
3478 function TDynMapDef
.getEBSTypeAt (idx
: Integer): TDynEBS
; inline; begin if (idx
>= 0) and (idx
< ebsTypes
.count
) then result
:= ebsTypes
[idx
] else result
:= nil; end;
3480 function TDynMapDef
.getTrigTypeCount (): Integer; inline; begin result
:= trigTypes
.count
; end;
3481 function TDynMapDef
.getTrigTypeAt (idx
: Integer): TDynRecord
; inline; begin if (idx
>= 0) and (idx
< trigTypes
.count
) then result
:= trigTypes
[idx
] else result
:= nil; end;