DEADSOFTWARE

map records and fields can have optional tooltips ("tip") and help ("help") in mapdef...
[d2df-sdl.git] / src / shared / xdynrec.pas
1 (* Copyright (C) DooM 2D:Forever Developers
2 *
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.
7 *
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.
12 *
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/>.
15 *)
16 {$INCLUDE a_modes.inc}
17 {.$DEFINE XDYNREC_USE_FIELDHASH} // actually, it is SLOWER with this
18 unit xdynrec;
20 interface
22 uses
23 SysUtils, Variants, Classes,
24 xparser, xstreams, utils, hashtable;
27 // ////////////////////////////////////////////////////////////////////////// //
28 type
29 TDynRecException = class(Exception)
30 public
31 constructor Create (const amsg: AnsiString);
32 constructor CreateFmt (const afmt: AnsiString; const args: array of const);
33 end;
35 TDynParseException = class(TDynRecException)
36 public
37 tokLine, tokCol: Integer;
39 public
40 constructor Create (pr: TTextParser; const amsg: AnsiString);
41 constructor CreateFmt (pr: TTextParser; const afmt: AnsiString; const args: array of const);
42 end;
45 // ////////////////////////////////////////////////////////////////////////// //
46 type
47 TDynMapDef = class;
48 TDynRecord = class;
49 TDynField = class;
50 TDynEBS = class;
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)
57 TDynField = class
58 public
59 type
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)
67 private
68 type
69 TEBS = (TNone, TRec, TEnum, TBitSet);
71 private
72 mOwner: TDynRecord; // owner record
73 mName: AnsiString; // field name
74 mTip: AnsiString; // short tip
75 mHelp: AnsiString; // long help
76 mType: TType; // field type
77 mIVal: Integer; // for all integer types
78 mIVal2: Integer; // for point and size
79 mSVal: AnsiString; // string; for byte and char arrays
80 mRVal: TDynRecList; // for list
81 mRHash: THashStrInt; // id -> index in mRVal
82 mRecRef: TDynRecord; // for TEBS.TRec
83 mMaxDim: Integer; // for byte and char arrays; <0: not an array; 0: impossible value
84 mBinOfs: Integer; // offset in binary; <0 - none
85 mSepPosSize: Boolean; // for points and sizes, use separate fields
86 mAsT: Boolean; // for points and sizes, use separate fields, names starts with `t`
87 mDefined: Boolean;
88 mHasDefault: Boolean;
89 mWriteDef: Boolean;
90 mInternal: Boolean;
91 mNegBool: Boolean;
92 mBitSetUnique: Boolean; // bitset can contain only one value
93 mAsMonsterId: Boolean; // special hack for triggers: monster record number+1 in binary (so 0 means "none")
94 // default value
95 mDefUnparsed: AnsiString;
96 mDefSVal: AnsiString; // default string value
97 mDefIVal, mDefIVal2: Integer; // default integer values
98 mDefRecRef: TDynRecord;
99 mEBS: TEBS; // complex type type
100 mEBSTypeName: AnsiString; // name of enum, bitset or record
101 mEBSType: TObject; // either TDynRecord or TDynEBS; nil means "simple type"; nil for `TTrigData` too
103 // for binary parser
104 mRecRefId: AnsiString;
106 // for userdata
107 mTagInt: Integer;
108 mTagPtr: Pointer;
110 // for pasgen
111 mAlias: AnsiString;
113 private
114 procedure cleanup ();
116 procedure parseDefaultValue (); // parse `mDefUnparsed` to `mDefSVal`, `mDefIVal`, `mDefIVal2`, `mDefRecRef`
117 procedure fixDefaultValue (); // this will NOT clone `mDefRecRef`
118 function isDefaultValue (): Boolean;
120 function getListCount (): Integer; inline;
121 function getListItem (idx: Integer): TDynRecord; inline; overload;
122 function getListItem (const aname: AnsiString): TDynRecord; inline; overload;
124 function getRecRefIndex (): Integer;
126 function getVar (): Variant;
127 procedure setVar (val: Variant);
129 procedure setRecRef (arec: TDynRecord);
131 procedure parseDef (pr: TTextParser); // parse mapdef definition
132 function definition (): AnsiString; // generate mapdef definition
134 protected
135 // returns `true` for duplicate record id
136 function addListItem (rec: TDynRecord): Boolean; inline;
137 function removeListItem (const aid: AnsiString): TDynRecord; // returns nil or removed record
139 public
140 // get string name for the given type
141 class function getTypeName (t: TType): AnsiString;
143 public
144 constructor Create (const aname: AnsiString; atype: TType);
145 constructor Create (const aname: AnsiString; val: Variant);
146 constructor Create (pr: TTextParser);
147 destructor Destroy (); override;
149 // clone this field; register all list records in `registerIn`
150 // "registration" is required to manage record lifetime; use header record if in doubt
151 // owner will be set to `newOwner`, if it is not `nil`, or to `owner`
152 // for lists, cloning will clone all list members
153 function clone (newOwner: TDynRecord=nil; registerIn: TDynRecord=nil): TDynField;
155 // compare field values (including trigdata)
156 // WARNING: won't work for lists
157 function isSimpleEqu (fld: TDynField): Boolean;
159 // parse string value to appropriate type and set new field value
160 procedure setValue (const s: AnsiString);
162 // supports `for rec in field do` (for lists)
163 function GetEnumerator (): TDynRecList.TEnumerator; inline;
165 public
166 // text parser and writer
167 procedure parseValue (pr: TTextParser);
168 procedure writeTo (wr: TTextWriter);
170 // binary parser and writer (DO NOT USE!)
171 procedure parseBinValue (st: TStream);
172 procedure writeBinTo (var hasLostData: Boolean; st: TStream);
174 public
175 // the following functions are here only for 'mapgen'! DO NOT USE!
176 // build "alias name" for pascal code
177 function palias (firstUp: Boolean=false): AnsiString;
179 public
180 property owner: TDynRecord read mOwner;
181 property name: AnsiString read mName; // field name
182 property baseType: TType read mType; // field type (base for arrays)
183 property defined: Boolean read mDefined; // was field value set to something by external code?
184 property internal: Boolean read mInternal write mInternal; // internal field?
185 property ival: Integer read mIVal; // integer value for int field (for speed), first field (x/w) for `TPoint` and `TSize`
186 property ival2: Integer read mIVal2; // for `TPoint` and `TSize`, this is second field (y/h)
187 property sval: AnsiString read mSVal; // string value for string field (for speed)
188 property hasDefault: Boolean read mHasDefault; // `true` if this field has default value in mapdef
189 property defsval: AnsiString read mDefSVal; // string representation of default value
190 property ebs: TEBS read mEBS; // what kind of reference is this? none, enum, bitset, record
191 property ebstype: TObject read mEBSType; // reference type (nil, TDynRecord, TDynEBS); WARNING: don't modify type!
192 property ebstypename: AnsiString read mEBSTypeName; // enum/bitset name
193 property recref: TDynRecord read mRecRef write setRecRef; // referenced record (actual one, you can modify it)
194 property recrefIndex: Integer read getRecRefIndex; // index of referenced record in header; -1: not found
195 // for record lists
196 property count: Integer read getListCount;
197 property itemAt[idx: Integer]: TDynRecord read getListItem;
198 property item[const aname: AnsiString]: TDynRecord read getListItem; default; // alas, FPC 3+ lost property overloading feature
199 // field value as Variant
200 property value: Variant read getVar write setVar;
202 property tip: AnsiString read mTip;
203 property help: AnsiString read mHelp;
205 public
206 // userdata (you can use these properties as you want to; they won't be written or read to files)
207 property tagInt: Integer read mTagInt write mTagInt;
208 property tagPtr: Pointer read mTagPtr write mTagPtr;
210 public
211 // the following properties are here only for 'mapgen'! DO NOT USE!
212 property negbool: Boolean read mNegBool;
213 property hasTPrefix: Boolean read mAsT;
214 property separatePasFields: Boolean read mSepPosSize;
215 property binOfs: Integer read mBinOfs;
216 property equToDefault: Boolean read isDefaultValue;
217 end;
220 // record, either with actual values, or with type definitions
221 TDynRecord = class
222 private
223 mOwner: TDynMapDef;
224 mId: AnsiString;
225 mTypeName: AnsiString;
226 mTip: AnsiString; // short tip
227 mHelp: AnsiString; // long help
228 mSize: Integer;
229 mFields: TDynFieldList;
230 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
231 mFieldsHash: THashStrInt; // id -> index in mRVal
232 {$ENDIF}
233 mTrigTypes: array of AnsiString; // if this is triggerdata, we'll hold list of triggers here
234 mHeader: Boolean; // true for header record
235 mBinBlock: Integer; // -1: none
236 mHeaderRec: TDynRecord; // for "value" records this is header record with data, for "type" records this is header type record
238 // for userdata
239 mTagInt: Integer;
240 mTagPtr: Pointer;
242 mRec2Free: TDynRecList;
244 private
245 procedure parseDef (pr: TTextParser); // parse definition
246 function definition (): AnsiString;
248 function findByName (const aname: AnsiString): Integer; inline;
249 function hasByName (const aname: AnsiString): Boolean; inline;
250 function getFieldByName (const aname: AnsiString): TDynField; inline;
251 function getFieldAt (idx: Integer): TDynField; inline;
252 function getCount (): Integer; inline;
254 function getIsTrigData (): Boolean; inline;
255 function getIsForTrig (const aname: AnsiString): Boolean; inline;
257 function getForTrigCount (): Integer; inline;
258 function getForTrigAt (idx: Integer): AnsiString; inline;
260 procedure regrec (rec: TDynRecord);
262 function getUserVar (const aname: AnsiString): Variant;
263 procedure setUserVar (const aname: AnsiString; val: Variant);
265 procedure clearRefRecs (rec: TDynRecord);
267 protected
268 function findRecordByTypeId (const atypename, aid: AnsiString): TDynRecord;
269 function findRecordNumByType (const atypename: AnsiString; rc: TDynRecord): Integer;
270 function addRecordByType (const atypename: AnsiString; rc: TDynRecord): Boolean; // `true`: duplicate record id
272 procedure addField (fld: TDynField); inline;
273 function addFieldChecked (fld: TDynField): Boolean; inline; // `true`: duplicate name
275 public
276 constructor Create ();
277 constructor Create (pr: TTextParser); // parse definition
278 destructor Destroy (); override;
280 // clone this record; register all list records in `registerIn`
281 // "registration" is required to manage record lifetime; use header record if in doubt
282 // all fields are cloned too
283 function clone (registerIn: TDynRecord): TDynRecord;
285 // compare records (values of all fields, including trigdata)
286 // WARNING: won't work for records with list fields
287 function isSimpleEqu (rec: TDynRecord): Boolean;
289 // find field with `TriggerType` type
290 function trigTypeField (): TDynField;
292 // number of records of the given instance
293 function instanceCount (const atypename: AnsiString): Integer;
295 // only for headers: create new record with the given type
296 // will return cloned record ready for use, or `nil` on unknown type name
297 // `aid` must not be empty, and must be unique
298 function newTypedRecord (const atypename, aid: AnsiString): TDynRecord;
300 // remove record with the given type and id
301 // return `true` if record was successfully found and removed
302 // this will do all necessary recref cleanup too
303 // WARNING: not tested yet
304 function removeTypedRecord (const atypename, aid: AnsiString): Boolean;
306 //TODO:
307 // [.] API to create triggers
308 // [.] API to properly remove triggers (remove trigdata)
309 // [.] check if `removeTypedRecord()` does the right thing with inline records
310 // [.] for fields: assigning `recref` should remove previously assigned inline record (record without id)
311 // [.] other API i forgot
313 public
314 // text parser
315 // `beginEaten`: `true` if "{" was eaten
316 procedure parseValue (pr: TTextParser; beginEaten: Boolean=false);
318 // text writer
319 // `putHeader`: `true` to write complete header, otherwise only "{...}"
320 procedure writeTo (wr: TTextWriter; putHeader: Boolean=true);
322 // binary parser and writer (DO NOT USE!)
323 procedure parseBinValue (st: TStream; forceData: Boolean=false);
324 procedure writeBinTo (var hasLostData: Boolean; st: TStream; trigbufsz: Integer=-1; onlyFields: Boolean=false);
326 public
327 property mapdef: TDynMapDef read mOwner;
328 property id: AnsiString read mId; // record id in text map
329 property typeName: AnsiString read mTypeName; // record type name (like "panel", or "trigger")
330 property has[const aname: AnsiString]: Boolean read hasByName; // do we have field with the given name?
331 property count: Integer read getCount; // number of fields in this record
332 property field[const aname: AnsiString]: TDynField read getFieldByName; default; // get field by name
333 property fieldAt[idx: Integer]: TDynField read getFieldAt; // get field at the given index
334 property isTrigData: Boolean read getIsTrigData; // is this special "TriggerData" record?
335 property isForTrig[const aname: AnsiString]: Boolean read getIsForTrig; // can this "TriggerData" be used for the trigger with the given type?
336 property forTrigCount: Integer read getForTrigCount; // number of trigger type names for "TriggerData"
337 property forTrigAt[idx: Integer]: AnsiString read getForTrigAt; // trigger type name at the given index for "TriggerData"
338 property headerRec: TDynRecord read mHeaderRec; // get header record for this one (header contains all other records, enums, bitsets, etc.)
339 property isHeader: Boolean read mHeader; // is this a header record?
341 property tip: AnsiString read mTip;
342 property help: AnsiString read mHelp;
344 public
345 // user fields; user can add arbitrary custom fields
346 // by default, any user field will be marked as "internal"
347 // note: you can use this to manipulate non-user fields too
348 property user[const aname: AnsiString]: Variant read getUserVar write setUserVar;
350 public
351 // userdata (you can use these properties as you want to; they won't be written or read to files)
352 property tagInt: Integer read mTagInt write mTagInt;
353 property tagPtr: Pointer read mTagPtr write mTagPtr;
354 end;
357 // bitset/enum definition
358 TDynEBS = class
359 private
360 mOwner: TDynMapDef;
361 mIsEnum: Boolean;
362 mTypeName: AnsiString;
363 mTip: AnsiString; // short tip
364 mHelp: AnsiString; // long help
365 mIds: array of AnsiString;
366 mVals: array of Integer;
367 mMaxName: AnsiString; // MAX field
368 mMaxVal: Integer; // max value
370 private
371 procedure cleanup ();
373 procedure parseDef (pr: TTextParser); // parse definition
375 function findByName (const aname: AnsiString): Integer; inline;
376 function hasByName (const aname: AnsiString): Boolean; inline;
377 function getFieldByName (const aname: AnsiString): Integer; inline;
379 function definition (): AnsiString;
380 function pasdef (): AnsiString;
382 public
383 constructor Create (pr: TTextParser); // parse definition
384 destructor Destroy (); override;
386 // find name for the given value
387 // return empty string if not found
388 function nameByValue (v: Integer): AnsiString;
390 public
391 property mapdef: TDynMapDef read mOwner;
392 property typeName: AnsiString read mTypeName; // enum/bitset type name
393 property isEnum: Boolean read mIsEnum; // is this enum? `false` means "bitset"
394 property has[const aname: AnsiString]: Boolean read hasByName;
395 property field[const aname: AnsiString]: Integer read getFieldByName;
397 property tip: AnsiString read mTip;
398 property help: AnsiString read mHelp;
399 end;
402 // parsed "mapdef.txt"
403 TDynMapDef = class
404 public
405 recTypes: TDynRecList; // [0] is always header
406 trigTypes: TDynRecList; // trigdata
407 ebsTypes: TDynEBSList; // enums, bitsets
409 private
410 procedure parseDef (pr: TTextParser);
412 function getHeaderRecType (): TDynRecord; inline;
414 function getRecTypeCount (): Integer; inline;
415 function getRecTypeAt (idx: Integer): TDynRecord; inline;
417 function getEBSTypeCount (): Integer; inline;
418 function getEBSTypeAt (idx: Integer): TDynEBS; inline;
420 function getTrigTypeCount (): Integer; inline;
421 function getTrigTypeAt (idx: Integer): TDynRecord; inline;
423 // creates new header record
424 function parseTextMap (pr: TTextParser): TDynRecord;
426 // creates new header record
427 function parseBinMap (st: TStream): TDynRecord;
429 public
430 constructor Create (pr: TTextParser); // parses data definition
431 destructor Destroy (); override;
433 function findRecType (const aname: AnsiString): TDynRecord;
434 function findTrigFor (const aname: AnsiString): TDynRecord;
435 function findEBSType (const aname: AnsiString): TDynEBS;
437 public
438 // parse text or binary map, return new header record
439 // WARNING! stream must be seekable
440 function parseMap (st: TStream; wasBinary: PBoolean=nil): TDynRecord;
442 // returns `true` if the given stream can be a map file
443 // stream position is 0 on return
444 // WARNING! stream must be seekable
445 class function canBeMap (st: TStream): Boolean;
447 public
448 // the following functions are here only for 'mapgen'! DO NOT USE!
449 function pasdefconst (): AnsiString;
451 public
452 property headerType: TDynRecord read getHeaderRecType;
453 // for record types
454 property recTypeCount: Integer read getRecTypeCount;
455 property recTypeAt[idx: Integer]: TDynRecord read getRecTypeAt;
456 property recType[const aname: AnsiString]: TDynRecord read findRecType;
457 // for enum/bitset types
458 property ebsTypeCount: Integer read getEBSTypeCount;
459 property ebsTypeAt[idx: Integer]: TDynEBS read getEBSTypeAt;
460 property ebsType[const aname: AnsiString]: TDynEBS read findEBSType;
461 // for trigtypes
462 property trigTypeCount: Integer read getTrigTypeCount;
463 property trigTypeAt[idx: Integer]: TDynRecord read getTrigTypeAt;
464 property trigTypeFor[const aname: AnsiString]: TDynRecord read findTrigFor;
465 end;
468 {$IF DEFINED(D2D_DYNREC_PROFILER)}
469 procedure xdynDumpProfiles ();
470 {$ENDIF}
473 implementation
475 uses
476 e_log
477 {$IF DEFINED(D2D_DYNREC_PROFILER)},xprofiler{$ENDIF};
480 // ////////////////////////////////////////////////////////////////////////// //
481 function StrEqu (const a, b: AnsiString): Boolean; inline; begin result := (a = b); end;
484 // ////////////////////////////////////////////////////////////////////////// //
485 constructor TDynRecException.Create (const amsg: AnsiString);
486 begin
487 inherited Create(amsg);
488 end;
490 constructor TDynRecException.CreateFmt (const afmt: AnsiString; const args: array of const);
491 begin
492 inherited Create(formatstrf(afmt, args));
493 end;
496 // ////////////////////////////////////////////////////////////////////////// //
497 constructor TDynParseException.Create (pr: TTextParser; const amsg: AnsiString);
498 begin
499 if (pr <> nil) then begin tokLine := pr.tokLine; tokCol := pr.tokCol; end else begin tokLine := 0; tokCol := 0; end;
500 inherited Create(amsg);
501 end;
503 constructor TDynParseException.CreateFmt (pr: TTextParser; const afmt: AnsiString; const args: array of const);
504 begin
505 if (pr <> nil) then begin tokLine := pr.tokLine; tokCol := pr.tokCol; end else begin tokLine := 0; tokCol := 0; end;
506 inherited Create(formatstrf(afmt, args));
507 end;
510 // ////////////////////////////////////////////////////////////////////////// //
511 function TDynField.GetEnumerator (): TDynRecList.TEnumerator; inline;
512 begin
513 //result := TListEnumerator.Create(mRVal);
514 if (mRVal <> nil) then result := mRVal.GetEnumerator else result := TDynRecList.TEnumerator.Create(nil, 0);
515 end;
518 // ////////////////////////////////////////////////////////////////////////// //
519 constructor TDynField.Create (const aname: AnsiString; atype: TType);
520 begin
521 mRVal := nil;
522 mRecRef := nil;
523 mRHash := nil;
524 cleanup();
525 mName := aname;
526 mType := atype;
527 if (mType = TType.TList) then
528 begin
529 mRVal := TDynRecList.Create();
530 mRHash := hashNewStrInt();
531 end;
532 end;
535 constructor TDynField.Create (pr: TTextParser);
536 begin
537 cleanup();
538 parseDef(pr);
539 end;
542 constructor TDynField.Create (const aname: AnsiString; val: Variant);
543 procedure setInt32 (v: LongInt);
544 begin
545 case mType of
546 TType.TBool:
547 if (v = 0) then mIVal := 0
548 else if (v = 1) then mIVal := 1
549 else raise TDynRecException.Create('cannot convert shortint variant to field value');
550 TType.TByte:
551 if (v >= -128) and (v <= 127) then mIVal := v
552 else raise TDynRecException.Create('cannot convert shortint variant to field value');
553 TType.TUByte:
554 if (v >= 0) and (v <= 255) then mIVal := v
555 else raise TDynRecException.Create('cannot convert shortint variant to field value');
556 TType.TShort:
557 if (v >= -32768) and (v <= 32767) then mIVal := v
558 else raise TDynRecException.Create('cannot convert shortint variant to field value');
559 TType.TUShort:
560 if (v >= 0) and (v <= 65535) then mIVal := v
561 else raise TDynRecException.Create('cannot convert shortint variant to field value');
562 TType.TInt:
563 mIVal := v;
564 TType.TUInt:
565 mIVal := v;
566 TType.TString:
567 mSVal := formatstrf('%s', [v]);
568 else
569 raise TDynRecException.Create('cannot convert integral variant to field value');
570 end;
571 end;
572 begin
573 mRVal := nil;
574 mRecRef := nil;
575 mRHash := nil;
576 cleanup();
577 mName := aname;
578 case varType(val) of
579 varEmpty: raise TDynRecException.Create('cannot convert empty variant to field value');
580 varNull: raise TDynRecException.Create('cannot convert null variant to field value');
581 varSingle: raise TDynRecException.Create('cannot convert single variant to field value');
582 varDouble: raise TDynRecException.Create('cannot convert double variant to field value');
583 varDecimal: raise TDynRecException.Create('cannot convert decimal variant to field value');
584 varCurrency: raise TDynRecException.Create('cannot convert currency variant to field value');
585 varDate: raise TDynRecException.Create('cannot convert date variant to field value');
586 varOleStr: raise TDynRecException.Create('cannot convert olestr variant to field value');
587 varStrArg: raise TDynRecException.Create('cannot convert stdarg variant to field value');
588 varString: mType := TType.TString;
589 varDispatch: raise TDynRecException.Create('cannot convert dispatch variant to field value');
590 varBoolean: mType := TType.TBool;
591 varVariant: raise TDynRecException.Create('cannot convert variant variant to field value');
592 varUnknown: raise TDynRecException.Create('cannot convert unknown variant to field value');
593 varByte: mType := TType.TUByte;
594 varWord: mType := TType.TUShort;
595 varShortInt: mType := TType.TByte;
596 varSmallint: mType := TType.TShort;
597 varInteger: mType := TType.TInt;
598 varInt64: raise TDynRecException.Create('cannot convert int64 variant to field value');
599 varLongWord: raise TDynRecException.Create('cannot convert longword variant to field value');
600 varQWord: raise TDynRecException.Create('cannot convert uint64 variant to field value');
601 varError: raise TDynRecException.Create('cannot convert error variant to field value');
602 else raise TDynRecException.Create('cannot convert undetermined variant to field value');
603 end;
604 value := val;
605 end;
608 destructor TDynField.Destroy ();
609 begin
610 cleanup();
611 inherited;
612 end;
615 procedure TDynField.cleanup ();
616 begin
617 mName := '';
618 mTip := '';
619 mHelp := '';
620 mType := TType.TInt;
621 mIVal := 0;
622 mIVal2 := 0;
623 mSVal := '';
624 mRVal.Free();
625 mRVal := nil;
626 mRHash.Free();
627 mRHash := nil;
628 mRecRef := nil;
629 mMaxDim := -1;
630 mBinOfs := -1;
631 mSepPosSize := false;
632 mAsT := false;
633 mHasDefault := false;
634 mDefined := false;
635 mWriteDef := false;
636 mInternal := true;
637 mDefUnparsed := '';
638 mDefSVal := '';
639 mDefIVal := 0;
640 mDefIVal2 := 0;
641 mDefRecRef := nil;
642 mEBS := TEBS.TNone;
643 mEBSTypeName := '';
644 mEBSType := nil;
645 mBitSetUnique := false;
646 mAsMonsterId := false;
647 mNegBool := false;
648 mRecRefId := '';
649 mTagInt := 0;
650 mTagPtr := nil;
651 mAlias := '';
652 end;
655 function TDynField.clone (newOwner: TDynRecord=nil; registerIn: TDynRecord=nil): TDynField;
656 var
657 rec: TDynRecord;
658 begin
659 result := TDynField.Create(mName, mType);
660 result.mOwner := mOwner;
661 if (newOwner <> nil) then result.mOwner := newOwner else result.mOwner := mOwner;
662 result.mName := mName;
663 result.mTip := mTip;
664 result.mHelp := mHelp;
665 result.mType := mType;
666 result.mIVal := mIVal;
667 result.mIVal2 := mIVal2;
668 result.mSVal := mSVal;
669 if (mRVal <> nil) then
670 begin
671 if (result.mRVal = nil) then result.mRVal := TDynRecList.Create(mRVal.count);
672 if (result.mRHash = nil) then result.mRHash := hashNewStrInt();
673 for rec in mRVal do result.addListItem(rec.clone(registerIn));
674 end;
675 result.mRecRef := mRecRef;
676 result.mMaxDim := mMaxDim;
677 result.mBinOfs := mBinOfs;
678 result.mSepPosSize := mSepPosSize;
679 result.mAsT := mAsT;
680 result.mDefined := mDefined;
681 result.mHasDefault := mHasDefault;
682 result.mWriteDef := mWriteDef;
683 result.mInternal := mInternal;
684 result.mNegBool := mNegBool;
685 result.mBitSetUnique := mBitSetUnique;
686 result.mAsMonsterId := mAsMonsterId;
687 result.mDefUnparsed := mDefUnparsed;
688 result.mDefSVal := mDefSVal;
689 result.mDefIVal := mDefIVal;
690 result.mDefIVal2 := mDefIVal2;
691 result.mDefRecRef := mDefRecRef;
692 result.mEBS := mEBS;
693 result.mEBSTypeName := mEBSTypeName;
694 result.mEBSType := mEBSType;
695 result.mRecRefId := mRecRefId;
696 result.mTagInt := mTagInt;
697 result.mTagPtr := mTagPtr;
698 result.mAlias := mAlias;
699 end;
702 function TDynField.palias (firstUp: Boolean=false): AnsiString;
703 var
704 nextUp: Boolean;
705 ch: AnsiChar;
706 begin
707 if (Length(mAlias) > 0) then
708 begin
709 if firstUp then result := UpCase1251(mAlias[1])+Copy(mAlias, 2, Length(mAlias)-1) else result := mAlias;
710 end
711 else
712 begin
713 result := '';
714 nextUp := firstUp;
715 for ch in mName do
716 begin
717 if (ch = '_') then begin nextUp := true; continue; end;
718 if nextUp then result += UpCase1251(ch) else result += ch;
719 nextUp := false;
720 end;
721 end;
722 end;
725 procedure TDynField.setRecRef (arec: TDynRecord);
726 var
727 trc: TDynRecord = nil;
728 begin
729 case mEBS of
730 TEBS.TNone: raise TDynRecException.CreateFmt('cannot set refrec for non-reference field ''%s''', [mName]);
731 TEBS.TRec:
732 begin
733 if (arec <> nil) then
734 begin
735 if (mEBSType <> nil) and (mEBSType is TDynRecord) then trc := (mEBSType as TDynRecord);
736 if (trc = nil) then raise TDynRecException.CreateFmt('cannot set refrec for field ''%s'' (type conflict: improperly initialized field)', [mName]);
737 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]);
738 end;
739 mRecRef := arec;
740 mDefined := true;
741 exit;
742 end;
743 TEBS.TEnum: raise TDynRecException.CreateFmt('cannot set refrec for enum field ''%s''', [mName]);
744 TEBS.TBitSet: raise TDynRecException.CreateFmt('cannot set refrec for bitset field ''%s''', [mName]);
745 else raise TDynRecException.Create('ketmar forgot to process some reftypes');
746 end;
747 end;
750 function TDynField.getVar (): Variant;
751 begin
752 if (mEBS = TEBS.TRec) then begin result := LongInt(getRecRefIndex); exit; end;
753 case mType of
754 TType.TBool: result := (mIVal <> 0);
755 TType.TChar: result := mSVal;
756 TType.TByte: result := ShortInt(mIVal);
757 TType.TUByte: result := Byte(mIVal);
758 TType.TShort: result := SmallInt(mIVal);
759 TType.TUShort: result := Word(mIVal);
760 TType.TInt: result := LongInt(mIVal);
761 TType.TUInt: result := LongWord(mIVal);
762 TType.TString: result := mSVal;
763 TType.TPoint: raise TDynRecException.Create('cannot convert point field to variant');
764 TType.TSize: raise TDynRecException.Create('cannot convert size field to variant');
765 TType.TList: raise TDynRecException.Create('cannot convert list field to variant');
766 TType.TTrigData: raise TDynRecException.Create('cannot convert trigdata field to variant');
767 else result := Unassigned; raise TDynRecException.Create('ketmar forgot to handle some field type');
768 end;
769 end;
772 procedure TDynField.setVar (val: Variant);
773 procedure setInt32 (v: LongInt);
774 begin
775 case mType of
776 TType.TBool:
777 if (v = 0) then mIVal := 0
778 else if (v = 1) then mIVal := 1
779 else raise TDynRecException.Create('cannot convert shortint variant to field value');
780 TType.TByte:
781 if (v >= -128) and (v <= 127) then mIVal := v
782 else raise TDynRecException.Create('cannot convert shortint variant to field value');
783 TType.TUByte:
784 if (v >= 0) and (v <= 255) then mIVal := v
785 else raise TDynRecException.Create('cannot convert shortint variant to field value');
786 TType.TShort:
787 if (v >= -32768) and (v <= 32767) then mIVal := v
788 else raise TDynRecException.Create('cannot convert shortint variant to field value');
789 TType.TUShort:
790 if (v >= 0) and (v <= 65535) then mIVal := v
791 else raise TDynRecException.Create('cannot convert shortint variant to field value');
792 TType.TInt:
793 mIVal := v;
794 TType.TUInt:
795 mIVal := v;
796 TType.TString:
797 mSVal := formatstrf('%s', [v]);
798 else
799 raise TDynRecException.Create('cannot convert integral variant to field value');
800 end;
801 end;
802 begin
803 case varType(val) of
804 varEmpty: raise TDynRecException.Create('cannot convert empty variant to field value');
805 varNull: raise TDynRecException.Create('cannot convert null variant to field value');
806 varSingle: raise TDynRecException.Create('cannot convert single variant to field value');
807 varDouble: raise TDynRecException.Create('cannot convert double variant to field value');
808 varDecimal: raise TDynRecException.Create('cannot convert decimal variant to field value');
809 varCurrency: raise TDynRecException.Create('cannot convert currency variant to field value');
810 varDate: raise TDynRecException.Create('cannot convert date variant to field value');
811 varOleStr: raise TDynRecException.Create('cannot convert olestr variant to field value');
812 varStrArg: raise TDynRecException.Create('cannot convert stdarg variant to field value');
813 varString:
814 if (mType = TType.TChar) or (mType = TType.TString) then
815 begin
816 mSVal := val;
817 end
818 else
819 begin
820 raise TDynRecException.Create('cannot convert string variant to field value');
821 end;
822 varDispatch: raise TDynRecException.Create('cannot convert dispatch variant to field value');
823 varBoolean:
824 case mType of
825 TType.TBool,
826 TType.TByte,
827 TType.TUByte,
828 TType.TShort,
829 TType.TUShort,
830 TType.TInt,
831 TType.TUInt:
832 if val then mIVal := 1 else mIVal := 0;
833 TType.TString:
834 if val then mSVal := 'true' else mSVal := 'false';
835 else
836 raise TDynRecException.Create('cannot convert boolean variant to field value');
837 end;
838 varVariant: raise TDynRecException.Create('cannot convert variant variant to field value');
839 varUnknown: raise TDynRecException.Create('cannot convert unknown variant to field value');
840 varByte,
841 varWord,
842 varShortInt,
843 varSmallint,
844 varInteger:
845 setInt32(val);
846 varInt64:
847 if (val < Int64(LongInt($80000000))) or (val > LongInt($7FFFFFFF)) then
848 raise TDynRecException.Create('cannot convert boolean variant to field value')
849 else
850 mIVal := LongInt(val);
851 varLongWord:
852 if (val > LongWord($7FFFFFFF)) then raise TDynRecException.Create('cannot convert longword variant to field value')
853 else setInt32(Integer(val));
854 varQWord: raise TDynRecException.Create('cannot convert uint64 variant to field value');
855 varError: raise TDynRecException.Create('cannot convert error variant to field value');
856 else raise TDynRecException.Create('cannot convert undetermined variant to field value');
857 end;
858 mDefined := true;
859 end;
862 // won't work for lists
863 function TDynField.isSimpleEqu (fld: TDynField): Boolean;
864 begin
865 if (fld = nil) or (mType <> fld.mType) then begin result := false; exit; end;
866 case mType of
867 TType.TBool: result := ((mIVal <> 0) = (fld.mIVal <> 0));
868 TType.TChar: result := (mSVal = fld.mSVal);
869 TType.TByte,
870 TType.TUByte,
871 TType.TShort,
872 TType.TUShort,
873 TType.TInt,
874 TType.TUInt:
875 result := (mIVal = fld.mIVal);
876 TType.TString: result := (mSVal = fld.mSVal);
877 TType.TPoint,
878 TType.TSize:
879 result := ((mIVal = fld.mIVal) and (mIVal2 = fld.mIVal2));
880 TType.TList: result := false;
881 TType.TTrigData:
882 begin
883 if (mRecRef = nil) then begin result := (fld.mRecRef = nil); exit; end;
884 result := mRecRef.isSimpleEqu(fld.mRecRef);
885 end;
886 else raise TDynRecException.Create('ketmar forgot to handle some field type');
887 end;
888 end;
891 procedure TDynField.setValue (const s: AnsiString);
892 var
893 stp: TTextParser;
894 begin
895 stp := TStrTextParser.Create(s+';');
896 try
897 parseValue(stp);
898 finally
899 stp.Free();
900 end;
901 end;
904 procedure TDynField.parseDefaultValue ();
905 var
906 stp: TTextParser = nil;
907 oSVal: AnsiString;
908 oIVal, oIVal2: Integer;
909 oRRef: TDynRecord;
910 oDef: Boolean;
911 begin
912 if not mHasDefault then
913 begin
914 mDefSVal := '';
915 mDefIVal := 0;
916 mDefIVal2 := 0;
917 mDefRecRef := nil;
918 end
919 else
920 begin
921 oSVal := mSVal;
922 oIVal := mIVal;
923 oIVal2 := mIVal2;
924 oRRef := mRecRef;
925 oDef := mDefined;
926 try
927 stp := TStrTextParser.Create(mDefUnparsed+';');
928 parseValue(stp);
929 mDefSVal := mSVal;
930 mDefIVal := mIVal;
931 mDefIVal2 := mIVal2;
932 mDefRecRef := mRecRef;
933 finally
934 mSVal := oSVal;
935 mIVal := oIVal;
936 mIVal2 := oIVal2;
937 mRecRef := oRRef;
938 mDefined := oDef;
939 stp.Free();
940 end;
941 end;
942 end;
945 // default value should be parsed
946 procedure TDynField.fixDefaultValue ();
947 begin
948 if mDefined then exit;
949 if not mHasDefault then
950 begin
951 if mInternal then exit;
952 raise TDynRecException.CreateFmt('field ''%s'' in record ''%s'' of record type ''%s'' is not set', [mName, mOwner.mId, mOwner.mTypeName]);
953 end;
954 if (mEBS = TEBS.TRec) then mRecRef := mDefRecRef;
955 mSVal := mDefSVal;
956 mIVal := mDefIVal;
957 mIVal2 := mDefIVal2;
958 mDefined := true;
959 end;
962 // default value should be parsed
963 function TDynField.isDefaultValue (): Boolean;
964 begin
965 if not mHasDefault then begin result := false; exit; end;
966 if (mEBS = TEBS.TRec) then begin result := (mRecRef = mDefRecRef); exit; end;
967 case mType of
968 TType.TChar, TType.TString: result := (mSVal = mDefSVal);
969 TType.TPoint, TType.TSize: result := (mIVal = mDefIVal2) and (mIVal2 = mDefIVal2);
970 TType.TList, TType.TTrigData: result := false; // no default values for those types
971 else result := (mIVal = mDefIVal);
972 end;
973 end;
976 function TDynField.getListCount (): Integer; inline;
977 begin
978 if (mRVal <> nil) then result := mRVal.count else result := 0;
979 end;
982 function TDynField.getListItem (idx: Integer): TDynRecord; inline; overload;
983 begin
984 if (mRVal <> nil) and (idx >= 0) and (idx < mRVal.count) then result := mRVal[idx] else result := nil;
985 end;
988 function TDynField.getListItem (const aname: AnsiString): TDynRecord; inline; overload;
989 var
990 idx: Integer;
991 begin
992 if (mRVal <> nil) and mRHash.get(aname, idx) then result := mRVal[idx] else result := nil;
993 end;
996 function TDynField.addListItem (rec: TDynRecord): Boolean; inline;
997 begin
998 result := false;
999 if (mRVal <> nil) then
1000 begin
1001 mRVal.append(rec);
1002 if (Length(rec.mId) > 0) then result := mRHash.put(rec.mId, mRVal.count-1);
1003 end;
1004 end;
1007 function TDynField.removeListItem (const aid: AnsiString): TDynRecord;
1008 var
1009 f, idx: Integer;
1010 begin
1011 result := nil;
1012 if mRHash.get(aid, idx) then
1013 begin
1014 assert((idx >= 0) and (idx < mRVal.count));
1015 result := mRVal[idx];
1016 // fix hash and list
1017 for f := idx+1 to mRVal.count-1 do
1018 begin
1019 if (Length(mRVal[f].mId) > 0) then mRHash.put(mRVal[f].mId, f-1);
1020 end;
1021 mRHash.del(aid);
1022 mRVal.delete(idx);
1023 end;
1024 end;
1027 class function TDynField.getTypeName (t: TType): AnsiString;
1028 begin
1029 case t of
1030 TType.TBool: result := 'bool';
1031 TType.TChar: result := 'char';
1032 TType.TByte: result := 'byte';
1033 TType.TUByte: result := 'ubyte';
1034 TType.TShort: result := 'short';
1035 TType.TUShort: result := 'ushort';
1036 TType.TInt: result := 'int';
1037 TType.TUInt: result := 'uint';
1038 TType.TString: result := 'string';
1039 TType.TPoint: result := 'point';
1040 TType.TSize: result := 'size';
1041 TType.TList: result := 'array';
1042 TType.TTrigData: result := 'trigdata';
1043 else raise TDynRecException.Create('ketmar forgot to handle some field type');
1044 end;
1045 end;
1048 function TDynField.definition (): AnsiString;
1049 begin
1050 result := quoteStr(mName)+' type ';
1051 result += getTypeName(mType);
1052 if (Length(mAlias) > 0) then result += ' alias '+mAlias;
1053 if (mMaxDim >= 0) then result += Format('[%d]', [mMaxDim]);
1054 if (mBinOfs >= 0) then result += Format(' offset %d', [mBinOfs]);
1055 case mEBS of
1056 TEBS.TNone: begin end;
1057 TEBS.TRec: result += ' '+mEBSTypeName;
1058 TEBS.TEnum: result += ' enum '+mEBSTypeName;
1059 TEBS.TBitSet: begin result += ' bitset '; if mBitSetUnique then result += 'unique '; result += mEBSTypeName; end;
1060 end;
1061 if mAsMonsterId then result += ' as monsterid';
1062 if mHasDefault and (Length(mDefUnparsed) > 0) then result += ' default '+mDefUnparsed;
1063 if mSepPosSize then
1064 begin
1065 if (mType = TType.TPoint) then begin if (mAsT) then result += ' as txy' else result += ' as xy'; end
1066 else if (mType = TType.TSize) then begin if (mAsT) then result += ' as twh' else result += ' as wh'; end;
1067 end;
1068 if mWriteDef then result += ' writedefault';
1069 if mInternal then result += ' internal';
1070 end;
1073 procedure TDynField.parseDef (pr: TTextParser);
1074 var
1075 fldname: AnsiString;
1076 fldtype: AnsiString;
1077 fldofs: Integer;
1078 fldrecname: AnsiString;
1079 asxy, aswh, ast: Boolean;
1080 ainternal: Boolean;
1081 writedef: Boolean;
1082 defstr: AnsiString;
1083 defint, defint2: Integer;
1084 hasdefStr: Boolean;
1085 hasdefInt: Boolean;
1086 hasdefId: Boolean;
1087 lmaxdim: Integer;
1088 lebs: TDynField.TEBS;
1089 unique: Boolean;
1090 asmonid: Boolean;
1091 defech: AnsiChar;
1092 xalias: AnsiString;
1093 atip, ahelp: AnsiString;
1094 begin
1095 fldname := '';
1096 fldtype := '';
1097 fldofs := -1;
1098 fldrecname := '';
1099 asxy := false;
1100 aswh := false;
1101 ast := false;
1102 ainternal := false;
1103 writedef := false;
1104 defstr := '';
1105 defint := 0;
1106 defint2 := 0;
1107 hasdefStr := false;
1108 hasdefInt := false;
1109 hasdefId := false;
1110 unique := false;
1111 asmonid := false;
1112 lmaxdim := -1;
1113 lebs := TDynField.TEBS.TNone;
1114 xalias := '';
1115 atip := '';
1116 ahelp := '';
1118 // field name
1119 fldname := pr.expectStrOrId();
1121 while (pr.tokType <> pr.TTSemi) do
1122 begin
1123 if pr.eatId('type') then
1124 begin
1125 if (Length(fldtype) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate type definition for field ''%s''', [fldname]);
1126 // field type
1127 fldtype := pr.expectId();
1128 // fixed-size array?
1129 if pr.eatDelim('[') then
1130 begin
1131 lmaxdim := pr.expectInt();
1132 // arbitrary limits
1133 if (lmaxdim < 1) or (lmaxdim > 32768) then raise TDynParseException.CreateFmt(pr, 'invalid field ''%s'' array size', [fldname]);
1134 pr.expectDelim(']');
1135 end;
1136 continue;
1137 end;
1139 if pr.eatId('alias') then
1140 begin
1141 if (Length(xalias) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate alias definition for field ''%s''', [fldname]);
1142 xalias := pr.expectId();
1143 continue;
1144 end;
1146 if pr.eatId('tip') then
1147 begin
1148 if (Length(atip) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for field ''%s''', [fldname]);
1149 atip := pr.expectStr(false);
1150 continue;
1151 end;
1153 if pr.eatId('help') then
1154 begin
1155 if (Length(ahelp) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for field ''%s''', [fldname]);
1156 ahelp := pr.expectStr(false);
1157 continue;
1158 end;
1160 if pr.eatId('offset') then
1161 begin
1162 if (fldofs >= 0) then raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s'' offset', [fldname]);
1163 fldofs := pr.expectInt();
1164 if (fldofs < 0) then raise TDynParseException.CreateFmt(pr, 'invalid field ''%s'' offset', [fldname]);
1165 continue;
1166 end;
1168 if pr.eatId('as') then
1169 begin
1170 if pr.eatId('xy') then asxy := true
1171 else if pr.eatId('wh') then aswh := true
1172 else if pr.eatId('txy') then begin asxy := true; ast := true; end
1173 else if pr.eatId('twh') then begin aswh := true; ast := true; end
1174 else if pr.eatId('monsterid') then begin asmonid := true; end
1175 else raise TDynParseException.CreateFmt(pr, 'invalid field ''%s'' as what?', [fldname]);
1176 continue;
1177 end;
1179 if pr.eatId('enum') then
1180 begin
1181 lebs := TDynField.TEBS.TEnum;
1182 if (Length(fldrecname) <> 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' already typed as ''%s''', [fldname, fldrecname]);
1183 fldrecname := pr.expectId();
1184 continue;
1185 end;
1187 if pr.eatId('bitset') then
1188 begin
1189 lebs := TDynField.TEBS.TBitSet;
1190 if (Length(fldrecname) <> 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' already typed as ''%s''', [fldname, fldrecname]);
1191 unique := pr.eatId('unique');
1192 fldrecname := pr.expectId();
1193 continue;
1194 end;
1196 if pr.eatId('default') then
1197 begin
1198 if hasdefStr or hasdefInt or hasdefId then raise TDynParseException.CreateFmt(pr, 'field ''%s'' has duplicate default', [fldname]);
1199 case pr.tokType of
1200 pr.TTStr:
1201 begin
1202 hasdefStr := true;
1203 defstr := pr.expectStr(true); // allow empty strings
1204 end;
1205 pr.TTId:
1206 begin
1207 hasdefId := true;
1208 defstr := pr.expectId();
1209 end;
1210 pr.TTInt:
1211 begin
1212 hasdefInt := true;
1213 defint := pr.expectInt();
1214 end;
1215 pr.TTDelim:
1216 begin
1217 hasdefInt := true;
1218 if pr.eatDelim('[') then defech := ']' else begin pr.expectDelim('('); defech := ')'; end;
1219 defint := pr.expectInt();
1220 defint2 := pr.expectInt();
1221 pr.expectDelim(defech);
1222 end;
1223 else
1224 raise TDynParseException.CreateFmt(pr, 'field ''%s'' has invalid default', [fldname]);
1225 end;
1226 continue;
1227 end;
1229 if pr.eatId('writedefault') then
1230 begin
1231 writedef := true;
1232 continue;
1233 end;
1235 if pr.eatId('internal') then
1236 begin
1237 ainternal := true;
1238 continue;
1239 end;
1241 // record type, no special modifiers
1242 if (pr.tokType <> pr.TTId) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' has something unexpected in definition', [fldname]);
1244 if (Length(fldrecname) <> 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' already typed as ''%s''', [fldname, fldrecname]);
1245 fldrecname := pr.expectId();
1246 lebs := TDynField.TEBS.TRec;
1247 end;
1249 pr.expectTT(pr.TTSemi);
1251 // create field
1252 mName := fldname;
1253 if (fldtype = 'bool') then mType := TType.TBool
1254 else if (fldtype = 'negbool') then begin mType := TType.TBool; mNegBool := true; end
1255 else if (fldtype = 'char') then mType := TType.TChar
1256 else if (fldtype = 'byte') then mType := TType.TByte
1257 else if (fldtype = 'ubyte') then mType := TType.TUByte
1258 else if (fldtype = 'short') then mType := TType.TShort
1259 else if (fldtype = 'ushort') then mType := TType.TUShort
1260 else if (fldtype = 'int') then mType := TType.TInt
1261 else if (fldtype = 'uint') then mType := TType.TUInt
1262 else if (fldtype = 'string') then mType := TType.TString
1263 else if (fldtype = 'point') then mType := TType.TPoint
1264 else if (fldtype = 'size') then mType := TType.TSize
1265 else if (fldtype = 'trigdata') then mType := TType.TTrigData
1266 else
1267 begin
1268 // record types defaults to int
1269 if (Length(fldrecname) > 0) then
1270 begin
1271 mType := TType.TInt;
1272 end
1273 else
1274 begin
1275 if (Length(fldtype) = 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' has no type', [fldname])
1276 else raise TDynParseException.CreateFmt(pr, 'field ''%s'' has invalid type ''%s''', [fldname, fldtype]);
1277 end;
1278 end;
1280 // check for valid arrays
1281 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]);
1283 // check for valid trigdata or record type
1284 if (mType = TType.TTrigData) then
1285 begin
1286 // trigdata
1287 if (lmaxdim < 1) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' cannot be non-array', [fldname, 'trigdata']);
1288 if (Length(fldrecname) > 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' cannot have another type', [fldname, 'trigdata']);
1289 lebs := TDynField.TEBS.TRec;
1290 end
1291 else if (Length(fldrecname) > 0) then
1292 begin
1293 // record
1294 if not (mType in [TType.TByte, TType.TUByte, TType.TShort, TType.TUShort, TType.TInt, TType.TUInt]) then
1295 begin
1296 raise TDynParseException.CreateFmt(pr, 'field ''%s'' of record type ''%s'' cannot have type ''%s''', [fldname, fldrecname, fldtype]);
1297 end;
1298 end;
1300 // setup default value
1301 if hasdefStr then self.mDefUnparsed := quoteStr(defstr)
1302 else if hasdefId then self.mDefUnparsed := defstr
1303 else if hasdefInt then
1304 begin
1305 if (mType = TType.TPoint) then self.mDefUnparsed := Format('(%d %d)', [defint, defint2])
1306 else if (mType = TType.TSize) then self.mDefUnparsed := Format('[%d %d]', [defint, defint2])
1307 else self.mDefUnparsed := Format('%d', [defint]);
1308 end;
1310 self.mHasDefault := (hasdefStr or hasdefId or hasdefInt);
1311 self.mEBS := lebs;
1312 self.mEBSTypeName := fldrecname;
1313 self.mBitSetUnique := unique;
1314 self.mAsMonsterId := asmonid;
1315 self.mMaxDim := lmaxdim;
1316 self.mBinOfs := fldofs;
1317 self.mSepPosSize := (asxy or aswh);
1318 self.mAsT := ast;
1319 self.mWriteDef := writedef;
1320 self.mInternal := ainternal;
1321 self.mAlias := xalias;
1322 self.mTip := atip;
1323 self.mHelp := ahelp;
1324 end;
1327 function TDynField.getRecRefIndex (): Integer;
1328 begin
1329 if (mRecRef = nil) then begin result := -1; exit; end;
1330 result := mOwner.findRecordNumByType(mEBSTypeName, mRecRef);
1331 end;
1334 procedure TDynField.writeBinTo (var hasLostData: Boolean; st: TStream);
1335 var
1336 s: AnsiString;
1337 f: Integer;
1338 maxv: Integer;
1339 buf: PByte;
1340 ws: TStream = nil;
1341 begin
1342 case mEBS of
1343 TEBS.TNone: begin end;
1344 TEBS.TRec:
1345 begin
1346 if (mMaxDim >= 0) then
1347 begin
1348 // this must be triggerdata
1349 if (mType <> TType.TTrigData) then
1350 begin
1351 raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName, mName]);
1352 end;
1353 // write triggerdata
1354 GetMem(buf, mMaxDim);
1355 if (buf = nil) then raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName, mName]);
1356 try
1357 FillChar(buf^, mMaxDim, 0);
1358 if (mRecRef <> nil) then
1359 begin
1360 ws := TSFSMemoryChunkStream.Create(buf, mMaxDim);
1361 mRecRef.writeBinTo(hasLostData, ws, mMaxDim); // as trigdata
1362 end;
1363 st.WriteBuffer(buf^, mMaxDim);
1364 finally
1365 ws.Free();
1366 if (buf <> nil) then FreeMem(buf);
1367 end;
1368 exit;
1369 end;
1370 // record reference
1371 case mType of
1372 TType.TByte: maxv := 127;
1373 TType.TUByte: maxv := 254;
1374 TType.TShort: maxv := 32767;
1375 TType.TUShort: maxv := 65534;
1376 TType.TInt: maxv := $7fffffff;
1377 TType.TUInt: maxv := $7fffffff;
1378 else raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName, mName]);
1379 end;
1380 // find record number
1381 if (mRecRef <> nil) then
1382 begin
1383 f := mOwner.findRecordNumByType(mEBSTypeName, mRecRef);
1384 if (f < 0) then raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' not found in record list', [mEBSTypeName, mName]);
1385 if mAsMonsterId then Inc(f);
1386 if (f > maxv) then raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' has too big index', [mEBSTypeName, mName]);
1387 end
1388 else
1389 begin
1390 if mAsMonsterId then f := 0 else f := -1;
1391 end;
1392 case mType of
1393 TType.TByte, TType.TUByte: writeInt(st, Byte(f));
1394 TType.TShort, TType.TUShort: writeInt(st, SmallInt(f));
1395 TType.TInt, TType.TUInt: writeInt(st, LongWord(f));
1396 else raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName, mName]);
1397 end;
1398 exit;
1399 end;
1400 TEBS.TEnum: begin end;
1401 TEBS.TBitSet: begin end;
1402 else raise TDynRecException.Create('ketmar forgot to handle some EBS type');
1403 end;
1405 case mType of
1406 TType.TBool:
1407 begin
1408 if not mNegBool then
1409 begin
1410 if (mIVal <> 0) then writeInt(st, Byte(1)) else writeInt(st, Byte(0));
1411 end
1412 else
1413 begin
1414 if (mIVal = 0) then writeInt(st, Byte(1)) else writeInt(st, Byte(0));
1415 end;
1416 exit;
1417 end;
1418 TType.TChar:
1419 begin
1420 if (mMaxDim = 0) then raise TDynRecException.CreateFmt('invalid string size definition for field ''%s''', [mName]);
1421 if (mMaxDim < 0) then
1422 begin
1423 if (Length(mSVal) <> 1) then raise TDynRecException.CreateFmt('invalid string size definition for field ''%s''', [mName]);
1424 writeInt(st, Byte(mSVal[1]));
1425 end
1426 else
1427 begin
1428 if (Length(mSVal) > mMaxDim) then raise TDynRecException.CreateFmt('invalid string size definition for field ''%s''', [mName]);
1429 s := utf2win(mSVal);
1430 if (Length(s) > 0) then st.WriteBuffer(PChar(s)^, Length(s));
1431 for f := Length(s) to mMaxDim do writeInt(st, Byte(0));
1432 end;
1433 exit;
1434 end;
1435 TType.TByte,
1436 TType.TUByte:
1437 begin
1438 // triggerdata array was processed earlier
1439 if (mMaxDim >= 0) then TDynRecException.CreateFmt('byte array in field ''%s'' cannot be written', [mName]);
1440 writeInt(st, Byte(mIVal));
1441 exit;
1442 end;
1443 TType.TShort,
1444 TType.TUShort:
1445 begin
1446 if (mMaxDim >= 0) then raise TDynRecException.CreateFmt('short array in field ''%s'' cannot be written', [mName]);
1447 writeInt(st, Word(mIVal));
1448 exit;
1449 end;
1450 TType.TInt,
1451 TType.TUInt:
1452 begin
1453 if (mMaxDim >= 0) then raise TDynRecException.CreateFmt('int array in field ''%s'' cannot be written', [mName]);
1454 writeInt(st, LongWord(mIVal));
1455 exit;
1456 end;
1457 TType.TString:
1458 begin
1459 raise TDynRecException.CreateFmt('cannot write string field ''%s''', [mName]);
1460 end;
1461 TType.TPoint:
1462 begin
1463 if (mMaxDim >= 0) then raise TDynRecException.CreateFmt('pos/size array in field ''%s'' cannot be written', [mName]);
1464 writeInt(st, LongInt(mIVal));
1465 writeInt(st, LongInt(mIVal2));
1466 exit;
1467 end;
1468 TType.TSize:
1469 begin
1470 if (mMaxDim >= 0) then raise TDynRecException.CreateFmt('pos/size array in field ''%s'' cannot be written', [mName]);
1471 writeInt(st, Word(mIVal));
1472 writeInt(st, Word(mIVal2));
1473 exit;
1474 end;
1475 TType.TList:
1476 raise TDynRecException.Create('cannot write lists to binary format');
1477 TType.TTrigData:
1478 raise TDynRecException.Create('cannot write triggers to binary format (internal error)');
1479 else raise TDynRecException.Create('ketmar forgot to handle some field type');
1480 end;
1481 end;
1484 procedure TDynField.writeTo (wr: TTextWriter);
1485 var
1486 es: TDynEBS = nil;
1487 f, mask: Integer;
1488 first, found: Boolean;
1489 begin
1490 wr.put(mName);
1491 wr.put(' ');
1492 case mEBS of
1493 TEBS.TNone: begin end;
1494 TEBS.TRec:
1495 begin
1496 if (mRecRef = nil) then
1497 begin
1498 if (mType = TType.TTrigData) then wr.put('{}'#10) else wr.put('null;'#10);
1499 end
1500 else if (Length(mRecRef.mId) = 0) then
1501 begin
1502 mRecRef.writeTo(wr, false); // only data, no header
1503 end
1504 else
1505 begin
1506 wr.put(mRecRef.mId);
1507 wr.put(';'#10);
1508 end;
1509 exit;
1510 end;
1511 TEBS.TEnum:
1512 begin
1513 //def := mOwner.mOwner;
1514 //es := def.ebsType[mEBSTypeName];
1515 es := nil;
1516 if (mEBSType <> nil) and (mEBSType is TDynEBS) then es := (mEBSType as TDynEBS);
1517 if (es = nil) or (not es.mIsEnum) then raise TDynRecException.CreateFmt('record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1518 for f := 0 to High(es.mVals) do
1519 begin
1520 if (es.mVals[f] = mIVal) then
1521 begin
1522 wr.put(es.mIds[f]);
1523 wr.put(';'#10);
1524 exit;
1525 end;
1526 end;
1527 raise TDynRecException.CreateFmt('value %d in record enum type ''%s'' for field ''%s'' not found', [mIVal, mEBSTypeName, mName]);
1528 end;
1529 TEBS.TBitSet:
1530 begin
1531 //def := mOwner.mOwner;
1532 //es := def.ebsType[mEBSTypeName];
1533 es := nil;
1534 if (mEBSType <> nil) and (mEBSType is TDynEBS) then es := (mEBSType as TDynEBS);
1535 if (es = nil) or es.mIsEnum then raise TDynRecException.CreateFmt('record bitset type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1536 // none?
1537 if (mIVal = 0) then
1538 begin
1539 for f := 0 to High(es.mVals) do
1540 begin
1541 if (es.mVals[f] = 0) then
1542 begin
1543 wr.put(es.mIds[f]);
1544 wr.put(';'#10);
1545 exit;
1546 end;
1547 end;
1548 raise TDynRecException.CreateFmt('value %d in record bitset type ''%s'' for field ''%s'' not found', [0, mEBSTypeName, mName]);
1549 end;
1550 // not none
1551 mask := 1;
1552 first := true;
1553 while (mask <> 0) do
1554 begin
1555 if ((mIVal and mask) <> 0) then
1556 begin
1557 found := false;
1558 for f := 0 to High(es.mVals) do
1559 begin
1560 if (es.mVals[f] = mask) then
1561 begin
1562 if not first then wr.put(' | ') else first := false;
1563 wr.put(es.mIds[f]);
1564 found := true;
1565 break;
1566 end;
1567 end;
1568 if not found then raise TDynRecException.CreateFmt('value %d in record bitset type ''%s'' for field ''%s'' not found', [mask, mEBSTypeName, mName]);
1569 end;
1570 mask := mask shl 1;
1571 end;
1572 wr.put(';'#10);
1573 exit;
1574 end;
1575 else raise TDynRecException.Create('ketmar forgot to handle some EBS type');
1576 end;
1578 case mType of
1579 TType.TBool:
1580 begin
1581 if (mIVal = 0) then wr.put('false;'#10) else wr.put('true;'#10);
1582 exit;
1583 end;
1584 TType.TChar:
1585 begin
1586 if (mMaxDim = 0) then raise TDynRecException.CreateFmt('invalid string size definition for field ''%s''', [mName]);
1587 wr.put(quoteStr(mSVal));
1588 wr.put(';'#10);
1589 exit;
1590 end;
1591 TType.TByte,
1592 TType.TUByte,
1593 TType.TShort,
1594 TType.TUShort,
1595 TType.TInt,
1596 TType.TUInt:
1597 begin
1598 wr.put('%d;'#10, [mIVal]);
1599 exit;
1600 end;
1601 TType.TString:
1602 begin
1603 wr.put(quoteStr(mSVal));
1604 wr.put(';'#10);
1605 exit;
1606 end;
1607 TType.TPoint,
1608 TType.TSize:
1609 begin
1610 wr.put('(%d %d);'#10, [mIVal, mIVal2]);
1611 exit;
1612 end;
1613 TType.TList:
1614 begin
1615 assert(false);
1616 exit;
1617 end;
1618 TType.TTrigData:
1619 begin
1620 assert(false);
1621 exit;
1622 end;
1623 else raise TDynRecException.Create('ketmar forgot to handle some field type');
1624 end;
1625 raise TDynRecException.CreateFmt('cannot parse field ''%s'' yet', [mName]);
1626 end;
1629 procedure TDynField.parseBinValue (st: TStream);
1630 var
1631 rec, rc: TDynRecord;
1632 tfld: TDynField;
1633 es: TDynEBS = nil;
1634 tdata: PByte = nil;
1635 f, mask: Integer;
1636 s: AnsiString;
1637 begin
1638 case mEBS of
1639 TEBS.TNone: begin end;
1640 TEBS.TRec:
1641 begin
1642 // this must be triggerdata
1643 if (mType = TType.TTrigData) then
1644 begin
1645 assert(mMaxDim > 0);
1646 rec := mOwner;
1647 // find trigger definition
1648 tfld := rec.trigTypeField();
1649 if (tfld = nil) then raise TDynRecException.CreateFmt('triggerdata value for field ''%s'' in record ''%s'' without TriggerType field', [mName, rec.mTypeName]);
1650 rc := mOwner.mOwner.trigTypeFor[tfld.mSVal]; // find in mapdef
1651 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]);
1652 rc := rc.clone(mOwner.mHeaderRec);
1653 rc.mHeaderRec := mOwner.mHeaderRec;
1654 // on error, it will be freed by memowner
1655 rc.parseBinValue(st, true);
1656 mRecRef := rc;
1657 mDefined := true;
1658 exit;
1659 end
1660 else
1661 begin
1662 // not a trigger data
1663 case mType of
1664 TType.TByte: f := readShortInt(st);
1665 TType.TUByte: f := readByte(st);
1666 TType.TShort: f := readSmallInt(st);
1667 TType.TUShort: f := readWord(st);
1668 TType.TInt: f := readLongInt(st);
1669 TType.TUInt: f := readLongWord(st);
1670 else raise TDynRecException.CreateFmt('invalid non-numeric type ''%s'' for field ''%s'' of record ''%s''', [getTypeName(mType), mName, mEBSTypeName]);
1671 end;
1672 if mAsMonsterId then Dec(f);
1673 if (f < 0) then mRecRefId := '' else mRecRefId := Format('%s%d', [mEBSTypeName, f]);
1674 end;
1675 mDefined := true;
1676 exit;
1677 end;
1678 TEBS.TEnum,
1679 TEBS.TBitSet:
1680 begin
1681 assert(mMaxDim < 0);
1682 case mType of
1683 TType.TByte: f := readShortInt(st);
1684 TType.TUByte: f := readByte(st);
1685 TType.TShort: f := readSmallInt(st);
1686 TType.TUShort: f := readWord(st);
1687 TType.TInt: f := readLongInt(st);
1688 TType.TUInt: f := readLongWord(st);
1689 else raise TDynRecException.CreateFmt('invalid non-numeric type ''%s'' for field ''%s'' of record ''%s''', [getTypeName(mType), mName, mEBSTypeName]);
1690 end;
1691 es := nil;
1692 if (mEBSType <> nil) and (mEBSType is TDynEBS) then es := (mEBSType as TDynEBS);
1693 if (es = nil) or (es.mIsEnum <> (mEBS = TEBS.TEnum)) then raise TDynRecException.CreateFmt('record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1694 mIVal := f;
1695 // build enum/bitfield values
1696 if (mEBS = TEBS.TEnum) then
1697 begin
1698 mSVal := es.nameByValue(mIVal);
1699 if (Length(mSVal) = 0) then raise TDynRecException.CreateFmt('record enum type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName, mName, mIVal]);
1700 end
1701 else
1702 begin
1703 // special for 'none'
1704 if (mIVal = 0) then
1705 begin
1706 mSVal := es.nameByValue(mIVal);
1707 if (Length(mSVal) = 0) then raise TDynRecException.CreateFmt('record bitset type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName, mName, mIVal]);
1708 end
1709 else
1710 begin
1711 mSVal := '';
1712 mask := 1;
1713 while (mask <> 0) do
1714 begin
1715 if ((mIVal and mask) <> 0) then
1716 begin
1717 s := es.nameByValue(mask);
1718 if (Length(s) = 0) then raise TDynRecException.CreateFmt('record bitset type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName, mName, mask]);
1719 if (Length(mSVal) <> 0) then mSVal += '+';
1720 mSVal += s;
1721 end;
1722 mask := mask shl 1;
1723 end;
1724 end;
1725 end;
1726 //writeln('ebs <', es.mName, '>: ', mSVal);
1727 mDefined := true;
1728 exit;
1729 end;
1730 else raise TDynRecException.Create('ketmar forgot to handle some EBS type');
1731 end;
1733 case mType of
1734 TType.TBool:
1735 begin
1736 f := readByte(st);
1737 if (f <> 0) then f := 1;
1738 if mNegBool then f := 1-f;
1739 mIVal := f;
1740 mDefined := true;
1741 exit;
1742 end;
1743 TType.TChar:
1744 begin
1745 if (mMaxDim < 0) then
1746 begin
1747 mIVal := readByte(st);
1748 end
1749 else
1750 begin
1751 mSVal := '';
1752 GetMem(tdata, mMaxDim);
1753 try
1754 st.ReadBuffer(tdata^, mMaxDim);
1755 f := 0;
1756 while (f < mMaxDim) and (tdata[f] <> 0) do Inc(f);
1757 if (f > 0) then
1758 begin
1759 SetLength(mSVal, f);
1760 Move(tdata^, PChar(mSVal)^, f);
1761 mSVal := win2utf(mSVal);
1762 end;
1763 finally
1764 FreeMem(tdata);
1765 end;
1766 end;
1767 mDefined := true;
1768 exit;
1769 end;
1770 TType.TByte: begin mIVal := readShortInt(st); mDefined := true; exit; end;
1771 TType.TUByte: begin mIVal := readByte(st); mDefined := true; exit; end;
1772 TType.TShort: begin mIVal := readSmallInt(st); mDefined := true; exit; end;
1773 TType.TUShort: begin mIVal := readWord(st); mDefined := true; exit; end;
1774 TType.TInt: begin mIVal := readLongInt(st); mDefined := true; exit; end;
1775 TType.TUInt: begin mIVal := readLongWord(st); mDefined := true; exit; end;
1776 TType.TString:
1777 begin
1778 raise TDynRecException.Create('cannot read strings from binaries yet');
1779 exit;
1780 end;
1781 TType.TPoint:
1782 begin
1783 mIVal := readLongInt(st);
1784 mIVal2 := readLongInt(st);
1785 mDefined := true;
1786 exit;
1787 end;
1788 TType.TSize:
1789 begin
1790 mIVal := readWord(st);
1791 mIVal2 := readWord(st);
1792 mDefined := true;
1793 exit;
1794 end;
1795 TType.TList:
1796 begin
1797 assert(false);
1798 exit;
1799 end;
1800 TType.TTrigData:
1801 begin
1802 assert(false);
1803 exit;
1804 end;
1805 else raise TDynRecException.Create('ketmar forgot to handle some field type');
1806 end;
1807 raise TDynRecException.CreateFmt('cannot parse field ''%s'' yet', [mName]);
1808 end;
1811 procedure TDynField.parseValue (pr: TTextParser);
1813 procedure parseInt (min, max: Integer);
1814 begin
1815 mIVal := pr.expectInt();
1816 if (mIVal < min) or (mIVal > max) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
1817 mDefined := true;
1818 end;
1820 var
1821 rec, rc: TDynRecord;
1822 es: TDynEBS = nil;
1823 tfld: TDynField;
1824 tk: AnsiString;
1825 edim: AnsiChar;
1826 begin
1827 if (pr.tokType = pr.TTEOF) then raise TDynParseException.Create(pr, 'field value expected');
1828 if (pr.tokType = pr.TTSemi) then raise TDynParseException.Create(pr, 'extra semicolon');
1829 // if this field should contain struct, convert type and parse struct
1830 case mEBS of
1831 TEBS.TNone: begin end;
1832 TEBS.TRec:
1833 begin
1834 // ugly hack. sorry.
1835 if (mType = TType.TTrigData) then
1836 begin
1837 pr.expectTT(pr.TTBegin);
1838 if (pr.tokType = pr.TTEnd) then
1839 begin
1840 // '{}'
1841 mRecRef := nil;
1842 pr.expectTT(pr.TTEnd);
1843 end
1844 else
1845 begin
1846 rec := mOwner;
1847 // find trigger definition
1848 tfld := rec.trigTypeField();
1849 if (tfld = nil) then raise TDynParseException.CreateFmt(pr, 'triggerdata value for field ''%s'' in record ''%s'' without ''type'' field', [mName, rec.mTypeName]);
1850 rc := mOwner.mOwner.trigTypeFor[tfld.mSVal]; // find in mapdef
1851 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]);
1852 rc := rc.clone(mOwner.mHeaderRec);
1853 rc.mHeaderRec := mOwner.mHeaderRec;
1854 //writeln(rc.definition);
1855 // on error, it will be freed by memowner
1856 rc.parseValue(pr, true);
1857 mRecRef := rc;
1858 end;
1859 mDefined := true;
1860 pr.eatTT(pr.TTSemi); // hack: allow (but don't require) semicolon after inline records
1861 exit;
1862 end;
1863 // other record types
1864 if (pr.tokType = pr.TTId) then
1865 begin
1866 if pr.eatId('null') then
1867 begin
1868 mRecRef := nil;
1869 end
1870 else
1871 begin
1872 rec := mOwner.findRecordByTypeId(mEBSTypeName, pr.tokStr);
1873 if (rec = nil) then
1874 begin
1875 mRecRefId := pr.tokStr;
1876 end
1877 else
1878 begin
1879 mRecRef := rec;
1880 mRecRefId := '';
1881 end;
1882 pr.expectId();
1883 end;
1884 mDefined := true;
1885 pr.expectTT(pr.TTSemi);
1886 exit;
1887 end
1888 else if (pr.tokType = pr.TTBegin) then
1889 begin
1890 //rec := mOwner.mOwner.recType[mEBSTypeName]; // find in mapdef
1891 rec := nil;
1892 if (mEBSType <> nil) and (mEBSType is TDynRecord) then rec := (mEBSType as TDynRecord);
1893 if (rec = nil) then raise TDynParseException.CreateFmt(pr, 'record type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1894 rc := rec.clone(mOwner.mHeaderRec);
1895 rc.mHeaderRec := mOwner.mHeaderRec;
1896 rc.parseValue(pr);
1897 mRecRef := rc;
1898 mDefined := true;
1899 if mOwner.addRecordByType(mEBSTypeName, rc) then
1900 begin
1901 raise TDynParseException.CreateFmt(pr, 'duplicate record with id ''%s'' for field ''%s'' in record ''%s''', [rc.mId, mName, mOwner.mTypeName]);
1902 end;
1903 pr.eatTT(pr.TTSemi); // hack: allow (but don't require) semicolon after inline records
1904 exit;
1905 end;
1906 pr.expectTT(pr.TTBegin);
1907 end;
1908 TEBS.TEnum:
1909 begin
1910 //es := mOwner.mOwner.ebsType[mEBSTypeName]; // find in mapdef
1911 es := nil;
1912 if (mEBSType <> nil) and (mEBSType is TDynEBS) then es := (mEBSType as TDynEBS);
1913 if (es = nil) or (not es.mIsEnum) then raise TDynParseException.CreateFmt(pr, 'record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1914 tk := pr.expectId();
1915 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]);
1916 mIVal := es.field[tk];
1917 mSVal := tk;
1918 //writeln('ENUM ', mEBSName, '; element <', mSVal, '> with value ', mIVal);
1919 mDefined := true;
1920 pr.expectTT(pr.TTSemi);
1921 exit;
1922 end;
1923 TEBS.TBitSet:
1924 begin
1925 //es := mOwner.mOwner.ebsType[mEBSTypeName]; // find in mapdef
1926 es := nil;
1927 if (mEBSType <> nil) and (mEBSType is TDynEBS) then es := (mEBSType as TDynEBS);
1928 if (es = nil) or es.mIsEnum then raise TDynParseException.CreateFmt(pr, 'record bitset type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1929 mIVal := 0;
1930 while true do
1931 begin
1932 tk := pr.expectId();
1933 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]);
1934 mIVal := mIVal or es.field[tk];
1935 mSVal := tk;
1936 if (pr.tokType <> pr.TTDelim) or ((pr.tokChar <> '|') and (pr.tokChar <> '+')) then break;
1937 if mBitSetUnique then raise TDynParseException.CreateFmt(pr, 'record bitset of type ''%s'' for field ''%s'' expects only one value', [tk, mEBSTypeName, mName]);
1938 pr.skipToken(); // plus or pipe
1939 end;
1940 mDefined := true;
1941 pr.expectTT(pr.TTSemi);
1942 exit;
1943 end;
1944 else raise TDynParseException.Create(pr, 'ketmar forgot to handle some EBS type');
1945 end;
1947 case mType of
1948 TType.TBool:
1949 begin
1950 if pr.eatId('true') or pr.eatId('tan') or pr.eatId('yes') then mIVal := 1
1951 else if pr.eatId('false') or pr.eatId('ona') or pr.eatId('no') then mIVal := 0
1952 else raise TDynParseException.CreateFmt(pr, 'invalid bool value for field ''%s''', [mName]);
1953 mDefined := true;
1954 pr.expectTT(pr.TTSemi);
1955 exit;
1956 end;
1957 TType.TChar:
1958 begin
1959 if (mMaxDim = 0) then raise TDynParseException.CreateFmt(pr, 'invalid string size definition for field ''%s''', [mName]);
1960 mSVal := pr.expectStr(true);
1961 if (mMaxDim < 0) then
1962 begin
1963 // single char
1964 if (Length(mSVal) <> 1) then raise TDynParseException.CreateFmt(pr, 'invalid string size for field ''%s''', [mName]);
1965 mIVal := Integer(mSVal[1]);
1966 mSVal := '';
1967 end
1968 else
1969 begin
1970 // string
1971 if (Length(mSVal) > mMaxDim) then raise TDynParseException.CreateFmt(pr, 'invalid string size for field ''%s''', [mName]);
1972 end;
1973 mDefined := true;
1974 pr.expectTT(pr.TTSemi);
1975 exit;
1976 end;
1977 TType.TByte:
1978 begin
1979 parseInt(-128, 127);
1980 pr.expectTT(pr.TTSemi);
1981 exit;
1982 end;
1983 TType.TUByte:
1984 begin
1985 parseInt(0, 255);
1986 pr.expectTT(pr.TTSemi);
1987 exit;
1988 end;
1989 TType.TShort:
1990 begin
1991 parseInt(-32768, 32768);
1992 pr.expectTT(pr.TTSemi);
1993 exit;
1994 end;
1995 TType.TUShort:
1996 begin
1997 parseInt(0, 65535);
1998 pr.expectTT(pr.TTSemi);
1999 exit;
2000 end;
2001 TType.TInt:
2002 begin
2003 parseInt(Integer($80000000), $7fffffff);
2004 pr.expectTT(pr.TTSemi);
2005 exit;
2006 end;
2007 TType.TUInt:
2008 begin
2009 parseInt(0, $7fffffff); //FIXME
2010 pr.expectTT(pr.TTSemi);
2011 exit;
2012 end;
2013 TType.TString:
2014 begin
2015 mSVal := pr.expectStr(true);
2016 mDefined := true;
2017 pr.expectTT(pr.TTSemi);
2018 exit;
2019 end;
2020 TType.TPoint,
2021 TType.TSize:
2022 begin
2023 if pr.eatDelim('[') then edim := ']' else begin pr.expectDelim('('); edim := ')'; end;
2024 mIVal := pr.expectInt();
2025 if (mType = TType.TSize) then
2026 begin
2027 if (mIVal < 0) or (mIVal > 32767) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
2028 end;
2029 mIVal2 := pr.expectInt();
2030 if (mType = TType.TSize) then
2031 begin
2032 if (mIVal2 < 0) or (mIVal2 > 32767) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
2033 end;
2034 mDefined := true;
2035 pr.expectDelim(edim);
2036 pr.expectTT(pr.TTSemi);
2037 exit;
2038 end;
2039 TType.TList:
2040 begin
2041 assert(false);
2042 exit;
2043 end;
2044 TType.TTrigData:
2045 begin
2046 assert(false);
2047 exit;
2048 end;
2049 else raise TDynParseException.Create(pr, 'ketmar forgot to handle some field type');
2050 end;
2051 raise TDynParseException.CreateFmt(pr, 'cannot parse field ''%s'' yet', [mName]);
2052 end;
2055 // ////////////////////////////////////////////////////////////////////////// //
2056 constructor TDynRecord.Create (pr: TTextParser);
2057 begin
2058 if (pr = nil) then raise TDynParseException.Create(pr, 'cannot create record type without type definition');
2059 mId := '';
2060 mTypeName := '';
2061 mSize := 0;
2062 mFields := TDynFieldList.Create();
2063 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2064 mFieldsHash := hashNewStrInt();
2065 {$ENDIF}
2066 mTrigTypes := nil;
2067 mHeader := false;
2068 mHeaderRec := nil;
2069 mBinBlock := -1;
2070 mTagInt := 0;
2071 mTagPtr := nil;
2072 parseDef(pr);
2073 end;
2076 constructor TDynRecord.Create ();
2077 begin
2078 mTypeName := '';
2079 mSize := 0;
2080 mFields := TDynFieldList.Create();
2081 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2082 mFieldsHash := hashNewStrInt();
2083 {$ENDIF}
2084 mTrigTypes := nil;
2085 mHeader := false;
2086 mHeaderRec := nil;
2087 mTagInt := 0;
2088 mTagPtr := nil;
2089 mRec2Free := nil;
2090 end;
2093 destructor TDynRecord.Destroy ();
2094 var
2095 fld: TDynField;
2096 rec: TDynRecord;
2097 begin
2098 if (mRec2Free <> nil) then
2099 begin
2100 for rec in mRec2Free do
2101 begin
2102 if (rec <> self) then
2103 begin
2104 //writeln(formatstrf('freeing: 0x%08x; name=%s; id=%s', [Pointer(rec), rec.mName, rec.mId]));
2105 rec.Free();
2106 end;
2107 end;
2108 mRec2Free.Free();
2109 mRec2Free := nil;
2110 end;
2111 mTypeName := '';
2112 for fld in mFields do fld.Free();
2113 mFields.Free();
2114 mFields := nil;
2115 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2116 mFieldsHash.Free();
2117 mFieldsHash := nil;
2118 {$ENDIF}
2119 mTrigTypes := nil;
2120 mHeaderRec := nil;
2121 mTagInt := 0;
2122 mTagPtr := nil;
2123 inherited;
2124 end;
2127 procedure TDynRecord.regrec (rec: TDynRecord);
2128 begin
2129 if (rec <> nil) and (rec <> self) then
2130 begin
2131 if (mRec2Free = nil) then mRec2Free := TDynRecList.Create();
2132 mRec2Free.append(rec);
2133 end;
2134 end;
2137 procedure TDynRecord.addField (fld: TDynField); inline;
2138 begin
2139 if (fld = nil) then raise TDynRecException.Create('cannot append nil field to record');
2140 mFields.append(fld);
2141 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2142 if (Length(fld.mName) > 0) then mFieldsHash.put(fld.mName, mFields.count-1);
2143 {$ENDIF}
2144 end;
2147 function TDynRecord.addFieldChecked (fld: TDynField): Boolean; inline; // `true`: duplicate name
2148 begin
2149 result := false;
2150 if (fld = nil) then raise TDynRecException.Create('cannot append nil field to record');
2151 {$IF not DEFINED(XDYNREC_USE_FIELDHASH)}
2152 if (Length(fld.mName) > 0) then result := hasByName(fld.mName);
2153 {$ENDIF}
2154 mFields.append(fld);
2155 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2156 if (Length(fld.mName) > 0) then result := mFieldsHash.put(fld.mName, mFields.count-1);
2157 {$ENDIF}
2158 end;
2161 function TDynRecord.findByName (const aname: AnsiString): Integer; inline;
2162 begin
2163 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2164 if not mFieldsHash.get(aname, result) then result := -1;
2165 {$ELSE}
2166 result := 0;
2167 while (result < mFields.count) do
2168 begin
2169 if StrEqu(aname, mFields[result].mName) then exit;
2170 Inc(result);
2171 end;
2172 result := -1;
2173 {$ENDIF}
2174 end;
2177 function TDynRecord.hasByName (const aname: AnsiString): Boolean; inline;
2178 begin
2179 result := (findByName(aname) >= 0);
2180 end;
2183 function TDynRecord.getFieldByName (const aname: AnsiString): TDynField; inline;
2184 var
2185 f: Integer;
2186 begin
2187 f := findByName(aname);
2188 if (f >= 0) then result := mFields[f] else result := nil;
2189 end;
2192 function TDynRecord.getFieldAt (idx: Integer): TDynField; inline;
2193 begin
2194 if (idx >= 0) and (idx < mFields.count) then result := mFields[idx] else result := nil;
2195 end;
2198 function TDynRecord.getCount (): Integer; inline;
2199 begin
2200 result := mFields.count;
2201 end;
2204 function TDynRecord.getIsTrigData (): Boolean; inline;
2205 begin
2206 result := (Length(mTrigTypes) > 0);
2207 end;
2210 function TDynRecord.getIsForTrig (const aname: AnsiString): Boolean; inline;
2211 var
2212 f: Integer;
2213 begin
2214 result := true;
2215 for f := 0 to High(mTrigTypes) do if StrEqu(mTrigTypes[f], aname) then exit;
2216 result := false;
2217 end;
2220 function TDynRecord.getForTrigCount (): Integer; inline;
2221 begin
2222 result := Length(mTrigTypes);
2223 end;
2226 function TDynRecord.getForTrigAt (idx: Integer): AnsiString; inline;
2227 begin
2228 if (idx >= 0) and (idx < Length(mTrigTypes)) then result := mTrigTypes[idx] else result := '';
2229 end;
2232 function TDynRecord.clone (registerIn: TDynRecord): TDynRecord;
2233 var
2234 fld: TDynField;
2235 f: Integer;
2236 begin
2237 result := TDynRecord.Create();
2238 result.mOwner := mOwner;
2239 result.mId := mId;
2240 result.mTypeName := mTypeName;
2241 result.mTip := mTip;
2242 result.mHelp := mHelp;
2243 result.mSize := mSize;
2244 result.mHeader := mHeader;
2245 result.mBinBlock := mBinBlock;
2246 result.mHeaderRec := mHeaderRec;
2247 result.mTagInt := mTagInt;
2248 result.mTagPtr := mTagPtr;
2249 if (mFields.count > 0) then
2250 begin
2251 result.mFields.capacity := mFields.count;
2252 for fld in mFields do result.addField(fld.clone(result, registerIn));
2253 end;
2254 SetLength(result.mTrigTypes, Length(mTrigTypes));
2255 for f := 0 to High(mTrigTypes) do result.mTrigTypes[f] := mTrigTypes[f];
2256 if (registerIn <> nil) then registerIn.regrec(result);
2257 end;
2260 function TDynRecord.findRecordByTypeId (const atypename, aid: AnsiString): TDynRecord;
2261 var
2262 fld: TDynField;
2263 idx: Integer;
2264 begin
2265 result := nil;
2266 if (Length(aid) = 0) then exit;
2267 // find record data
2268 fld := mHeaderRec.field[atypename];
2269 if (fld = nil) then exit;
2270 if (fld.mType <> fld.TType.TList) then raise TDynRecException.CreateFmt('cannot get record of type ''%s'' due to name conflict with ordinary field', [atypename]);
2271 // find by id
2272 if (fld.mRVal <> nil) then
2273 begin
2274 if fld.mRHash.get(aid, idx) then begin result := fld.mRVal[idx]; exit; end;
2275 end;
2276 // alas
2277 end;
2280 function TDynRecord.findRecordNumByType (const atypename: AnsiString; rc: TDynRecord): Integer;
2281 var
2282 fld: TDynField;
2283 idx: Integer;
2284 begin
2285 result := -1;
2286 // find record data
2287 fld := mHeaderRec.field[atypename];
2288 if (fld = nil) then exit;
2289 if (fld.mType <> fld.TType.TList) then raise TDynRecException.CreateFmt('cannot get record of type ''%s'' due to name conflict with ordinary field', [atypename]);
2290 // find by ref
2291 if (fld.mRVal <> nil) then
2292 begin
2293 for idx := 0 to fld.mRVal.count-1 do
2294 begin
2295 if (fld.mRVal[idx] = rc) then begin result := idx; exit; end;
2296 end;
2297 end;
2298 // alas
2299 end;
2302 function TDynRecord.addRecordByType (const atypename: AnsiString; rc: TDynRecord): Boolean;
2303 var
2304 fld: TDynField;
2305 begin
2306 // find record data
2307 fld := mHeaderRec.field[atypename];
2308 if (fld = nil) then
2309 begin
2310 // first record
2311 fld := TDynField.Create(atypename, TDynField.TType.TList);
2312 fld.mOwner := mHeaderRec;
2313 mHeaderRec.addField(fld);
2314 end;
2315 if (fld.mType <> fld.TType.TList) then raise TDynRecException.CreateFmt('cannot append record of type ''%s'' due to name conflict with ordinary field', [atypename]);
2316 // append
2317 if (fld.mRVal = nil) then
2318 begin
2319 fld.mRVal := TDynRecList.Create();
2320 fld.mRHash := hashNewStrInt();
2321 end;
2322 result := fld.addListItem(rc);
2323 end;
2326 function TDynRecord.isSimpleEqu (rec: TDynRecord): Boolean;
2327 var
2328 f: Integer;
2329 begin
2330 if (rec = nil) then begin result := false; exit; end; // self.mRecRef can't be `nil` here
2331 if (rec = self) then begin result := true; exit; end;
2332 if (mFields.count <> rec.mFields.count) then begin result := false; exit; end;
2333 result := false;
2334 for f := 0 to mFields.count-1 do
2335 begin
2336 if not mFields[f].isSimpleEqu(rec.mFields[f]) then exit;
2337 end;
2338 result := true;
2339 end;
2342 function TDynRecord.trigTypeField (): TDynField;
2343 var
2344 fld: TDynField;
2345 es: TDynEBS = nil;
2346 begin
2347 for fld in mFields do
2348 begin
2349 if (fld.mEBS <> TDynField.TEBS.TEnum) then continue;
2350 if not (fld.mEBSType is TDynEBS) then continue;
2351 es := (fld.mEBSType as TDynEBS);
2352 assert(es <> nil);
2353 if StrEqu(es.mTypeName, 'TriggerType') then begin result := fld; exit; end;
2354 end;
2355 result := nil;
2356 end;
2359 // number of records of the given instance
2360 function TDynRecord.instanceCount (const atypename: AnsiString): Integer;
2361 var
2362 fld: TDynField;
2363 begin
2364 result := 0;
2365 fld := field[atypename];
2366 if (fld <> nil) and (fld.mType = fld.TType.TList) then result := fld.mRVal.count;
2367 end;
2370 function TDynRecord.newTypedRecord (const atypename, aid: AnsiString): TDynRecord;
2371 var
2372 trc: TDynRecord;
2373 fld: TDynField;
2374 begin
2375 if not mHeader then raise TDynRecException.Create('cannot create new records with non-header');
2376 if (Length(aid) = 0) then raise TDynRecException.CreateFmt('cannot create new record of type ''%s'' without id', [atypename]);
2377 trc := mapdef.recType[atypename];
2378 if (trc = nil) then begin result := nil; exit; end;
2379 // check if aid is unique
2380 fld := field[atypename];
2381 if (fld <> nil) and (fld.getListItem(aid) <> nil) then raise TDynRecException.CreateFmt('cannot create record of type ''%s'' with duplicate id ''%s''', [atypename, aid]);
2382 result := trc.clone(self);
2383 result.mId := aid;
2384 addRecordByType(atypename, result);
2385 end;
2388 procedure TDynRecord.clearRefRecs (rec: TDynRecord);
2389 procedure clearRefs (fld: TDynField);
2390 var
2391 rc: TDynRecord;
2392 begin
2393 if (fld = nil) then exit;
2394 if (fld.mRecRef = rec) then fld.mRecRef := nil;
2395 if (fld.mType = fld.TType.TList) then for rc in fld.mRVal do rc.clearRefRecs(rec);
2396 end;
2397 var
2398 fld: TDynField;
2399 begin
2400 if (rec = nil) or (mFields = nil) then exit;
2401 for fld in mFields do clearRefs(fld);
2402 end;
2405 // remove record with the given type and id
2406 // return `true` if record was successfully found and removed
2407 // this will do all necessary recref cleanup too
2408 function TDynRecord.removeTypedRecord (const atypename, aid: AnsiString): Boolean;
2409 var
2410 trc, rec: TDynRecord;
2411 fld: TDynField;
2412 f: Integer;
2413 doFree: Boolean = false;
2414 begin
2415 result := false;
2416 if not mHeader then raise TDynRecException.Create('cannot remove records with non-header');
2417 if (Length(aid) = 0) then exit;
2418 trc := mapdef.recType[atypename];
2419 if (trc = nil) then exit;
2420 fld := field[atypename];
2421 if (fld = nil) then exit;
2422 rec := fld.removeListItem(aid);
2423 if (rec = nil) then exit;
2424 clearRefRecs(rec);
2425 for f := 0 to mRec2Free.count-1 do
2426 begin
2427 if (mRec2Free[f] = rec) then
2428 begin
2429 mRec2Free[f] := nil;
2430 doFree := true;
2431 end;
2432 end;
2433 if doFree then rec.Free();
2434 end;
2437 function TDynRecord.getUserVar (const aname: AnsiString): Variant;
2438 var
2439 fld: TDynField;
2440 begin
2441 fld := getFieldByName(aname);
2442 if (fld = nil) then result := Unassigned else result := fld.value;
2443 end;
2446 procedure TDynRecord.setUserVar (const aname: AnsiString; val: Variant);
2447 var
2448 fld: TDynField;
2449 begin
2450 fld := getFieldByName(aname);
2451 if (fld = nil) then
2452 begin
2453 if (Length(aname) = 0) then raise TDynRecException.Create('cannot create nameless user field');
2454 fld := TDynField.Create(aname, val);
2455 fld.mOwner := self;
2456 fld.mInternal := true;
2457 addField(fld);
2458 end
2459 else
2460 begin
2461 fld.value := val;
2462 end;
2463 end;
2466 procedure TDynRecord.parseDef (pr: TTextParser);
2467 var
2468 fld: TDynField;
2469 tdn: AnsiString;
2470 begin
2471 if pr.eatId('TriggerData') then
2472 begin
2473 pr.expectId('for');
2474 if pr.eatDelim('(') then
2475 begin
2476 while true do
2477 begin
2478 while pr.eatTT(pr.TTComma) do begin end;
2479 if pr.eatDelim(')') then break;
2480 tdn := pr.expectId();
2481 if isForTrig[tdn] then raise TDynParseException.CreateFmt(pr, 'duplicate trigdata ''%s'' trigtype ''%s''', [mTypeName, tdn]);
2482 SetLength(mTrigTypes, Length(mTrigTypes)+1);
2483 mTrigTypes[High(mTrigTypes)] := tdn;
2484 end;
2485 end
2486 else
2487 begin
2488 tdn := pr.expectId();
2489 SetLength(mTrigTypes, 1);
2490 mTrigTypes[0] := tdn;
2491 end;
2492 mTypeName := 'TriggerData';
2493 end
2494 else
2495 begin
2496 mTypeName := pr.expectStrOrId();
2497 while (pr.tokType <> pr.TTBegin) do
2498 begin
2499 if pr.eatId('header') then begin mHeader := true; continue; end;
2500 if pr.eatId('size') then
2501 begin
2502 if (mSize > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate `size` in record ''%s''', [mTypeName]);
2503 mSize := pr.expectInt();
2504 if (mSize < 1) then raise TDynParseException.CreateFmt(pr, 'invalid record ''%s'' size: %d', [mTypeName, mSize]);
2505 pr.expectId('bytes');
2506 continue;
2507 end;
2508 if pr.eatId('binblock') then
2509 begin
2510 if (mBinBlock >= 0) then raise TDynParseException.CreateFmt(pr, 'duplicate `binblock` in record ''%s''', [mTypeName]);
2511 mBinBlock := pr.expectInt();
2512 if (mBinBlock < 1) then raise TDynParseException.CreateFmt(pr, 'invalid record ''%s'' binblock: %d', [mTypeName, mBinBlock]);
2513 continue;
2514 end;
2515 if pr.eatId('tip') then
2516 begin
2517 if (Length(mTip) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for record ''%s''', [mTypeName]);
2518 mTip := pr.expectStr(false);
2519 continue;
2520 end;
2521 if pr.eatId('help') then
2522 begin
2523 if (Length(mHelp) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate help definition for record ''%s''', [mTypeName]);
2524 mHelp := pr.expectStr(false);
2525 continue;
2526 end;
2527 end;
2528 end;
2530 pr.expectTT(pr.TTBegin);
2531 // load fields
2532 while (pr.tokType <> pr.TTEnd) do
2533 begin
2534 fld := TDynField.Create(pr);
2535 // append
2536 fld.mOwner := self;
2537 if addFieldChecked(fld) then
2538 begin
2539 fld.Free();
2540 raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s''', [fld.name]);
2541 end;
2542 // done with field
2543 end;
2544 pr.expectTT(pr.TTEnd);
2545 end;
2548 function TDynRecord.definition (): AnsiString;
2549 var
2550 f: Integer;
2551 begin
2552 if isTrigData then
2553 begin
2554 // trigger data
2555 result := 'TriggerData for ';
2556 if (Length(mTrigTypes) > 1) then
2557 begin
2558 result += '(';
2559 for f := 0 to High(mTrigTypes) do
2560 begin
2561 if (f <> 0) then result += ', ';
2562 result += mTrigTypes[f];
2563 end;
2564 result += ')';
2565 end
2566 else
2567 begin
2568 result += mTrigTypes[0];
2569 end;
2570 end
2571 else
2572 begin
2573 // record
2574 result := quoteStr(mTypeName);
2575 if (mSize >= 0) then result += Format(' size %d bytes', [mSize]);
2576 if mHeader then result += ' header';
2577 end;
2578 result += ' {'#10;
2579 for f := 0 to mFields.count-1 do
2580 begin
2581 result += ' ';
2582 result += mFields[f].definition;
2583 result += ';'#10;
2584 end;
2585 result += '}';
2586 end;
2589 procedure TDynRecord.parseBinValue (st: TStream; forceData: Boolean=false);
2590 var
2591 sign: string[4];
2592 btype: Integer;
2593 bsize: Integer;
2594 buf: PByte = nil;
2595 loaded: array[0..255] of Boolean;
2596 rec, rect: TDynRecord;
2597 fld: TDynField;
2598 f: Integer;
2599 mst: TSFSMemoryChunkStream = nil;
2601 procedure linkNames (rec: TDynRecord);
2602 var
2603 fld: TDynField;
2604 rt: TDynRecord;
2605 begin
2606 //writeln('*** rec: ', rec.mName, '.', rec.mId, ' (', rec.mFields.count, ')');
2607 for fld in rec.mFields do
2608 begin
2609 if (fld.mType = TDynField.TType.TTrigData) then
2610 begin
2611 if (fld.mRecRef <> nil) then linkNames(fld.mRecRef);
2612 continue;
2613 end;
2614 if (Length(fld.mRecRefId) = 0) then continue;
2615 assert(fld.mEBSType <> nil);
2616 rt := findRecordByTypeId(fld.mEBSTypeName, fld.mRecRefId);
2617 if (rt = nil) then
2618 begin
2619 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);
2620 //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]);
2621 end;
2622 //writeln(' ', rec.mName, '.', rec.mId, ':', fld.mName, ' -> ', rt.mName, '.', rt.mId, ' (', fld.mEBSTypeName, '.', fld.mRecRefId, ')');
2623 fld.mRecRefId := '';
2624 fld.mRecRef := rt;
2625 fld.mDefined := true;
2626 end;
2627 for fld in rec.mFields do
2628 begin
2629 //writeln(' ', fld.mName);
2630 fld.fixDefaultValue(); // just in case
2631 end;
2632 end;
2634 begin
2635 for f := 0 to High(loaded) do loaded[f] := false;
2636 mst := TSFSMemoryChunkStream.Create(nil, 0);
2637 try
2638 if mHeader and not forceData then
2639 begin
2640 // parse map file as sequence of blocks
2641 sign[0] := #4;
2642 st.ReadBuffer(sign[1], 4);
2643 if (sign <> 'MAP'#1) then raise TDynRecException.Create('invalid binary map signature');
2644 // parse blocks
2645 while (st.position < st.size) do
2646 begin
2647 btype := readByte(st);
2648 if (btype = 0) then break; // no more blocks
2649 readLongWord(st); // reserved
2650 bsize := readLongInt(st);
2651 {$IF DEFINED(D2D_XDYN_DEBUG)}writeln('btype=', btype, '; bsize=', bsize);{$ENDIF}
2652 if (bsize < 0) or (bsize > $1fffffff) then raise TDynRecException.CreateFmt('block of type %d has invalid size %d', [btype, bsize]);
2653 if loaded[btype] then raise TDynRecException.CreateFmt('block of type %d already loaded', [btype]);
2654 loaded[btype] := true;
2655 // find record type for this block
2656 rect := nil;
2657 for rec in mOwner.recTypes do if (rec.mBinBlock = btype) then begin rect := rec; break; end;
2658 if (rect = nil) then raise TDynRecException.CreateFmt('block of type %d has no corresponding record', [btype]);
2659 //writeln('found type ''', rec.mName, ''' for block type ', btype);
2660 if (rec.mSize = 0) or ((bsize mod rec.mSize) <> 0) then raise TDynRecException.CreateFmt('block of type %d has invalid number of records', [btype]);
2661 // header?
2662 if (rect.mHeader) then
2663 begin
2664 if (bsize <> mSize) then raise TDynRecException.CreateFmt('header block of type %d has invalid number of records', [btype]);
2665 GetMem(buf, bsize);
2666 st.ReadBuffer(buf^, bsize);
2667 mst.setup(buf, mSize);
2668 parseBinValue(mst, true); // force parsing data
2669 end
2670 else
2671 begin
2672 // create list for this type
2673 fld := TDynField.Create(rec.mTypeName, TDynField.TType.TList);
2674 fld.mOwner := self;
2675 addField(fld);
2676 if (bsize > 0) then
2677 begin
2678 GetMem(buf, bsize);
2679 st.ReadBuffer(buf^, bsize);
2680 for f := 0 to (bsize div rec.mSize)-1 do
2681 begin
2682 mst.setup(buf+f*rec.mSize, rec.mSize);
2683 rec := rect.clone(self);
2684 rec.mHeaderRec := self;
2685 rec.parseBinValue(mst);
2686 rec.mId := Format('%s%d', [rec.mTypeName, f]);
2687 fld.addListItem(rec);
2688 //writeln('parsed ''', rec.mId, '''...');
2689 end;
2690 end;
2691 end;
2692 FreeMem(buf);
2693 buf := nil;
2694 //st.position := st.position+bsize;
2695 end;
2696 // link fields
2697 for fld in mFields do
2698 begin
2699 if (fld.mType <> TDynField.TType.TList) then continue;
2700 for rec in fld.mRVal do linkNames(rec);
2701 end;
2702 exit;
2703 end;
2705 // read fields
2706 if StrEqu(mTypeName, 'TriggerData') then mSize := Integer(st.size-st.position);
2707 if (mSize < 1) then raise TDynRecException.CreateFmt('cannot read record of type ''%s'' with unknown size', [mTypeName]);
2708 GetMem(buf, mSize);
2709 st.ReadBuffer(buf^, mSize);
2710 for fld in mFields do
2711 begin
2712 if fld.mInternal then continue;
2713 if (fld.mBinOfs < 0) then continue;
2714 if (fld.mBinOfs >= st.size) then raise TDynRecException.CreateFmt('record of type ''%s'' has invalid field ''%s''', [fld.mName]);
2715 mst.setup(buf+fld.mBinOfs, mSize-fld.mBinOfs);
2716 //writeln('parsing ''', mName, '.', fld.mName, '''...');
2717 fld.parseBinValue(mst);
2718 end;
2719 finally
2720 mst.Free();
2721 if (buf <> nil) then FreeMem(buf);
2722 end;
2723 end;
2726 procedure TDynRecord.writeBinTo (var hasLostData: Boolean; st: TStream; trigbufsz: Integer=-1; onlyFields: Boolean=false);
2727 var
2728 fld: TDynField;
2729 rec, rv: TDynRecord;
2730 buf: PByte = nil;
2731 ws: TStream = nil;
2732 blk, blkmax: Integer;
2733 bufsz: Integer = 0;
2734 blksz: Integer;
2735 begin
2736 if (trigbufsz < 0) then
2737 begin
2738 if (mBinBlock < 1) then raise TDynRecException.Create('cannot write binary record without block number');
2739 if (mSize < 1) then raise TDynRecException.Create('cannot write binary record without size');
2740 bufsz := mSize;
2741 end
2742 else
2743 begin
2744 bufsz := trigbufsz;
2745 end;
2746 try
2747 GetMem(buf, bufsz);
2748 FillChar(buf^, bufsz, 0);
2749 ws := TSFSMemoryChunkStream.Create(buf, bufsz);
2751 // write normal fields
2752 for fld in mFields do
2753 begin
2754 // record list?
2755 if (fld.mType = fld.TType.TList) then continue; // later
2756 if fld.mInternal then continue;
2757 if (fld.mBinOfs < 0) then
2758 begin
2759 if not fld.equToDefault then hasLostData := true;
2760 continue;
2761 end;
2762 if (fld.mBinOfs >= bufsz) then raise TDynRecException.Create('binary value offset is outside of the buffer');
2763 TSFSMemoryChunkStream(ws).setup(buf+fld.mBinOfs, bufsz-fld.mBinOfs);
2764 //writeln('writing field <', fld.mName, '>');
2765 fld.writeBinTo(hasLostData, ws);
2766 end;
2768 // write block with normal fields
2769 if mHeader and not onlyFields then
2770 begin
2771 //writeln('writing header...');
2772 // signature and version
2773 writeIntBE(st, LongWord($4D415001));
2774 writeInt(st, Byte(mBinBlock)); // type
2775 writeInt(st, LongWord(0)); // reserved
2776 writeInt(st, LongWord(bufsz)); // size
2777 end;
2778 st.WriteBuffer(buf^, bufsz);
2780 ws.Free(); ws := nil;
2781 FreeMem(buf); buf := nil;
2783 // write other blocks, if any
2784 if mHeader and not onlyFields then
2785 begin
2786 // calculate blkmax
2787 blkmax := 0;
2788 for fld in mFields do
2789 begin
2790 // record list?
2791 if (fld.mType = fld.TType.TList) then
2792 begin
2793 if (fld.mRVal = nil) or (fld.mRVal.count = 0) then continue;
2794 rec := mOwner.recType[fld.mName];
2795 if (rec = nil) then continue;
2796 if (rec.mBinBlock <= 0) then continue;
2797 if (blkmax < rec.mBinBlock) then blkmax := rec.mBinBlock;
2798 end;
2799 end;
2800 // write blocks
2801 for blk := 1 to blkmax do
2802 begin
2803 if (blk = mBinBlock) then continue;
2804 ws := nil;
2805 for fld in mFields do
2806 begin
2807 // record list?
2808 if (fld.mType = fld.TType.TList) then
2809 begin
2810 if (fld.mRVal = nil) or (fld.mRVal.count = 0) then continue;
2811 rec := mOwner.recType[fld.mName];
2812 if (rec = nil) then continue;
2813 if (rec.mBinBlock <> blk) then continue;
2814 if (ws = nil) then ws := TMemoryStream.Create();
2815 for rv in fld.mRVal do rv.writeBinTo(hasLostData, ws);
2816 end;
2817 end;
2818 // flush block
2819 if (ws <> nil) then
2820 begin
2821 blksz := Integer(ws.position);
2822 ws.position := 0;
2823 writeInt(st, Byte(blk)); // type
2824 writeInt(st, LongWord(0)); // reserved
2825 writeInt(st, LongWord(blksz)); // size
2826 st.CopyFrom(ws, blksz);
2827 ws.Free();
2828 ws := nil;
2829 end;
2830 end;
2831 // write end marker
2832 writeInt(st, Byte(0));
2833 writeInt(st, LongWord(0));
2834 writeInt(st, LongWord(0));
2835 end;
2836 finally
2837 ws.Free();
2838 if (buf <> nil) then FreeMem(buf);
2839 end;
2840 end;
2843 procedure TDynRecord.writeTo (wr: TTextWriter; putHeader: Boolean=true);
2844 var
2845 fld: TDynField;
2846 rec: TDynRecord;
2847 putTypeComment: Boolean;
2848 f: Integer;
2849 begin
2850 if putHeader then
2851 begin
2852 wr.put(mTypeName);
2853 if (Length(mId) > 0) then begin wr.put(' '); wr.put(mId); end;
2854 wr.put(' ');
2855 end;
2856 wr.put('{'#10);
2857 wr.indent();
2858 try
2859 for fld in mFields do
2860 begin
2861 // record list?
2862 if (fld.mType = fld.TType.TList) then
2863 begin
2864 if not mHeader then raise TDynRecException.Create('record list in non-header record');
2865 if (fld.mRVal <> nil) and (fld.mRVal.count > 0) then
2866 begin
2867 putTypeComment := true;
2868 for rec in fld.mRVal do
2869 begin
2870 if (rec = nil) or (Length(rec.mId) = 0) then continue;
2871 if putTypeComment then
2872 begin
2873 wr.put(#10);
2874 if (80-wr.curIndent*2 >= 2) then
2875 begin
2876 wr.putIndent();
2877 for f := wr.curIndent to 80-wr.curIndent do wr.put('/');
2878 wr.put(#10);
2879 end;
2880 putTypeComment := false;
2881 wr.putIndent();
2882 wr.put('// ');
2883 wr.put(fld.name);
2884 wr.put(#10);
2885 end
2886 else
2887 begin
2888 wr.put(#10);
2889 end;
2890 wr.putIndent();
2891 rec.writeTo(wr, true);
2892 end;
2893 end;
2894 continue;
2895 end;
2896 if fld.mInternal then continue;
2897 if (not fld.mWriteDef) and fld.isDefaultValue then continue;
2898 wr.putIndent();
2899 fld.writeTo(wr);
2900 end;
2901 finally
2902 wr.unindent();
2903 end;
2904 wr.putIndent();
2905 wr.put('}'#10);
2906 end;
2909 {$IF DEFINED(D2D_DYNREC_PROFILER)}
2910 var
2911 profCloneRec: UInt64 = 0;
2912 profFindRecType: UInt64 = 0;
2913 profFieldSearching: UInt64 = 0;
2914 profListDupChecking: UInt64 = 0;
2915 profAddRecByType: UInt64 = 0;
2916 profFieldValParsing: UInt64 = 0;
2917 profFixDefaults: UInt64 = 0;
2918 profRecValParse: UInt64 = 0;
2920 procedure xdynDumpProfiles ();
2921 begin
2922 writeln('=== XDYNREC PROFILES ===');
2923 writeln('record cloning: ', profCloneRec div 1000, '.', profCloneRec mod 1000, ' milliseconds');
2924 writeln('findRecType : ', profFindRecType div 1000, '.', profFindRecType mod 1000, ' milliseconds');
2925 writeln('field[] : ', profFieldSearching div 1000, '.', profFieldSearching mod 1000, ' milliseconds');
2926 writeln('list dup check: ', profListDupChecking div 1000, '.', profListDupChecking mod 1000, ' milliseconds');
2927 writeln('addRecByType : ', profAddRecByType div 1000, '.', profAddRecByType mod 1000, ' milliseconds');
2928 writeln('field valparse: ', profFieldValParsing div 1000, '.', profFieldValParsing mod 1000, ' milliseconds');
2929 writeln('fix defaults : ', profFixDefaults div 1000, '.', profFixDefaults mod 1000, ' milliseconds');
2930 writeln('recvalparse : ', profRecValParse div 1000, '.', profRecValParse mod 1000, ' milliseconds');
2931 end;
2932 {$ENDIF}
2935 procedure TDynRecord.parseValue (pr: TTextParser; beginEaten: Boolean=false);
2936 var
2937 fld: TDynField;
2938 rec: TDynRecord = nil;
2939 trc{, rv}: TDynRecord;
2940 {$IF DEFINED(D2D_DYNREC_PROFILER)}
2941 stt, stall: UInt64;
2942 {$ENDIF}
2944 procedure linkNames (rec: TDynRecord);
2945 var
2946 fld: TDynField;
2947 rt: TDynRecord;
2948 begin
2949 //writeln('*** rec: ', rec.mName, '.', rec.mId, ' (', rec.mFields.count, ')');
2950 for fld in rec.mFields do
2951 begin
2952 if (fld.mType = TDynField.TType.TTrigData) then
2953 begin
2954 if (fld.mRecRef <> nil) then linkNames(fld.mRecRef);
2955 continue;
2956 end;
2957 if (Length(fld.mRecRefId) = 0) then continue;
2958 assert(fld.mEBSType <> nil);
2959 rt := findRecordByTypeId(fld.mEBSTypeName, fld.mRecRefId);
2960 if (rt = nil) then
2961 begin
2962 //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);
2963 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]);
2964 end;
2965 //writeln(' ', rec.mName, '.', rec.mId, ':', fld.mName, ' -> ', rt.mName, '.', rt.mId, ' (', fld.mEBSTypeName, '.', fld.mRecRefId, ')');
2966 fld.mRecRefId := '';
2967 fld.mRecRef := rt;
2968 fld.mDefined := true;
2969 end;
2970 for fld in rec.mFields do
2971 begin
2972 //writeln(' ', fld.mName);
2973 fld.fixDefaultValue(); // just in case
2974 end;
2975 end;
2977 begin
2978 if (mOwner = nil) then raise TDynParseException.CreateFmt(pr, 'can''t parse record ''%s'' value without owner', [mTypeName]);
2980 {$IF DEFINED(D2D_DYNREC_PROFILER)}stall := curTimeMicro();{$ENDIF}
2982 // not a header?
2983 if not mHeader then
2984 begin
2985 // id?
2986 if (not beginEaten) and (pr.tokType = pr.TTId) then mId := pr.expectId();
2987 end
2988 else
2989 begin
2990 assert(mHeaderRec = self);
2991 end;
2993 //writeln('parsing record <', mName, '>');
2994 if not beginEaten then pr.expectTT(pr.TTBegin);
2995 while (pr.tokType <> pr.TTEnd) do
2996 begin
2997 if (pr.tokType <> pr.TTId) then raise TDynParseException.Create(pr, 'identifier expected');
2998 //writeln('<', mName, '.', pr.tokStr, '>');
3000 // records
3001 if mHeader then
3002 begin
3003 // add records with this type (if any)
3004 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF}
3005 trc := mOwner.recType[pr.tokStr];
3006 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFindRecType := curTimeMicro()-stt;{$ENDIF}
3007 if (trc <> nil) then
3008 begin
3009 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF}
3010 rec := trc.clone(mHeaderRec);
3011 {$IF DEFINED(D2D_DYNREC_PROFILER)}profCloneRec := curTimeMicro()-stt;{$ENDIF}
3012 rec.mHeaderRec := mHeaderRec;
3013 // on error, it will be freed by memowner
3014 pr.skipToken();
3015 rec.parseValue(pr);
3016 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF}
3017 addRecordByType(rec.mTypeName, rec);
3018 {$IF DEFINED(D2D_DYNREC_PROFILER)}profAddRecByType := curTimeMicro()-stt;{$ENDIF}
3019 continue;
3020 end;
3021 end;
3023 // fields
3024 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF}
3025 //writeln('0: <', mName, '.', pr.tokStr, '>');
3026 fld := field[pr.tokStr];
3027 //writeln('1: <', mName, '.', pr.tokStr, '>');
3028 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFieldSearching := curTimeMicro()-stt;{$ENDIF}
3029 if (fld <> nil) then
3030 begin
3031 //writeln('2: <', mName, '.', pr.tokStr, '>');
3032 if fld.defined then raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s'' in record ''%s''', [fld.mName, mTypeName]);
3033 if fld.internal then raise TDynParseException.CreateFmt(pr, 'internal field ''%s'' in record ''%s''', [fld.mName, mTypeName]);
3034 pr.skipToken(); // skip field name
3035 //writeln('3: <', mName, '.', pr.tokStr, '>:', pr.tokType);
3036 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF}
3037 fld.parseValue(pr);
3038 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFieldValParsing := curTimeMicro()-stt;{$ENDIF}
3039 continue;
3040 end;
3042 // something is wrong
3043 raise TDynParseException.CreateFmt(pr, 'unknown field ''%s'' in record ''%s''', [pr.tokStr, mTypeName]);
3044 end;
3045 pr.expectTT(pr.TTEnd);
3047 if mHeader then
3048 begin
3049 // link fields
3050 for fld in mFields do
3051 begin
3052 if (fld.mType <> TDynField.TType.TList) then continue;
3053 for rec in fld.mRVal do linkNames(rec);
3054 end;
3055 end;
3057 // fix field defaults
3058 {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF}
3059 for fld in mFields do fld.fixDefaultValue();
3060 {$IF DEFINED(D2D_DYNREC_PROFILER)}profFixDefaults := curTimeMicro()-stt;{$ENDIF}
3061 //writeln('done parsing record <', mName, '>');
3062 //{$IF DEFINED(D2D_DYNREC_PROFILER)}writeln('stall: ', curTimeMicro()-stall);{$ENDIF}
3063 {$IF DEFINED(D2D_DYNREC_PROFILER)}profRecValParse := curTimeMicro()-stall;{$ENDIF}
3064 end;
3067 // ////////////////////////////////////////////////////////////////////////// //
3068 constructor TDynEBS.Create (pr: TTextParser);
3069 begin
3070 cleanup();
3071 parseDef(pr);
3072 end;
3075 destructor TDynEBS.Destroy ();
3076 begin
3077 cleanup();
3078 inherited;
3079 end;
3082 procedure TDynEBS.cleanup ();
3083 begin
3084 mIsEnum := false;
3085 mTypeName := '';
3086 mTip := '';
3087 mHelp := '';
3088 mIds := nil;
3089 mVals := nil;
3090 mMaxName := '';
3091 mMaxVal := 0;
3092 end;
3095 function TDynEBS.findByName (const aname: AnsiString): Integer;
3096 begin
3097 result := 0;
3098 while (result < Length(mIds)) do
3099 begin
3100 if StrEqu(aname, mIds[result]) then exit;
3101 Inc(result);
3102 end;
3103 result := -1;
3104 end;
3107 function TDynEBS.hasByName (const aname: AnsiString): Boolean; inline;
3108 begin
3109 result := (findByName(aname) >= 0);
3110 end;
3113 function TDynEBS.getFieldByName (const aname: AnsiString): Integer; inline;
3114 var
3115 f: Integer;
3116 begin
3117 f := findByName(aname);
3118 if (f >= 0) then result := mVals[f] else result := 0;
3119 end;
3122 function TDynEBS.definition (): AnsiString;
3123 var
3124 f, cv: Integer;
3125 begin
3126 if mIsEnum then result :='enum ' else result := 'bitset ';
3127 result += mTypeName;
3128 result += ' {'#10;
3129 // fields
3130 if mIsEnum then cv := 0 else cv := 1;
3131 for f := 0 to High(mIds) do
3132 begin
3133 if (mIds[f] = mMaxName) then continue;
3134 result += ' '+mIds[f];
3135 if (mVals[f] <> cv) then
3136 begin
3137 result += Format(' = %d', [mVals[f]]);
3138 if mIsEnum then cv := mVals[f];
3139 result += ','#10;
3140 end
3141 else
3142 begin
3143 result += Format(', // %d'#10, [mVals[f]]);
3144 end;
3145 if mIsEnum then Inc(cv) else if (mVals[f] = cv) then cv := cv shl 1;
3146 end;
3147 // max field
3148 if (Length(mMaxName) > 0) then result += ' '+mMaxName+' = MAX,'#10;
3149 result += '}';
3150 end;
3153 function TDynEBS.pasdef (): AnsiString;
3154 var
3155 f: Integer;
3156 begin
3157 result := '// '+mTypeName+#10'const'#10;
3158 // fields
3159 for f := 0 to High(mIds) do
3160 begin
3161 result += formatstrf(' %s = %d;'#10, [mIds[f], mVals[f]]);
3162 end;
3163 end;
3166 function TDynEBS.nameByValue (v: Integer): AnsiString;
3167 var
3168 f: Integer;
3169 begin
3170 for f := 0 to High(mVals) do
3171 begin
3172 if (mVals[f] = v) then begin result := mIds[f]; exit; end;
3173 end;
3174 result := '';
3175 end;
3178 procedure TDynEBS.parseDef (pr: TTextParser);
3179 var
3180 idname: AnsiString;
3181 cv, v: Integer;
3182 f: Integer;
3183 skipAdd: Boolean;
3184 hasV: Boolean;
3185 begin
3186 if pr.eatId('enum') then mIsEnum := true
3187 else if pr.eatId('bitset') then mIsEnum := false
3188 else pr.expectId('enum');
3189 mTypeName := pr.expectId();
3190 mMaxVal := Integer($80000000);
3191 if mIsEnum then cv := 0 else cv := 1;
3192 while (pr.tokType <> pr.TTBegin) do
3193 begin
3194 if pr.eatId('tip') then
3195 begin
3196 if (Length(mTip) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for enum/bitset ''%s''', [mTypeName]);
3197 mTip := pr.expectStr(false);
3198 continue;
3199 end;
3200 if pr.eatId('help') then
3201 begin
3202 if (Length(mHelp) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate help definition for enum/bitset ''%s''', [mTypeName]);
3203 mHelp := pr.expectStr(false);
3204 continue;
3205 end;
3206 break;
3207 end;
3208 pr.expectTT(pr.TTBegin);
3209 while (pr.tokType <> pr.TTEnd) do
3210 begin
3211 idname := pr.expectId();
3212 for f := 0 to High(mIds) do
3213 begin
3214 if StrEqu(mIds[f], idname) then raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s'' in enum/bitset ''%s''', [idname, mTypeName]);
3215 end;
3216 if StrEqu(mMaxName, idname) then raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s'' in enum/bitset ''%s''', [idname, mTypeName]);
3217 skipAdd := false;
3218 hasV := false;
3219 v := cv;
3220 // has value?
3221 if pr.eatDelim('=') then
3222 begin
3223 if pr.eatId('MAX') then
3224 begin
3225 if (Length(mMaxName) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate max field ''%s'' in enum/bitset ''%s''', [idname, mTypeName]);
3226 mMaxName := idname;
3227 skipAdd := true;
3228 end
3229 else
3230 begin
3231 v := pr.expectInt();
3232 if mIsEnum then cv := v;
3233 hasV := true;
3234 end;
3235 end;
3236 // append it?
3237 if not skipAdd then
3238 begin
3239 // fix maxvalue
3240 if mIsEnum or (not hasV) then
3241 begin
3242 if (mMaxVal < v) then mMaxVal := v;
3243 end;
3244 SetLength(mIds, Length(mIds)+1);
3245 mIds[High(mIds)] := idname;
3246 SetLength(mVals, Length(mIds));
3247 mVals[High(mVals)] := v;
3248 // next cv
3249 if mIsEnum or (not hasV) then
3250 begin
3251 if mIsEnum then Inc(cv) else cv := cv shl 1;
3252 end;
3253 end;
3254 if (pr.tokType = pr.TTEnd) then break;
3255 pr.expectTT(pr.TTComma);
3256 while pr.eatTT(pr.TTComma) do begin end;
3257 end;
3258 pr.expectTT(pr.TTEnd);
3259 // add max field
3260 if (Length(mMaxName) > 0) then
3261 begin
3262 SetLength(mIds, Length(mIds)+1);
3263 mIds[High(mIds)] := mMaxName;
3264 SetLength(mVals, Length(mIds));
3265 mVals[High(mVals)] := mMaxVal;
3266 end;
3267 end;
3270 // ////////////////////////////////////////////////////////////////////////// //
3271 constructor TDynMapDef.Create (pr: TTextParser);
3272 begin
3273 recTypes := TDynRecList.Create();
3274 trigTypes := TDynRecList.Create();
3275 ebsTypes := TDynEBSList.Create();
3276 parseDef(pr);
3277 end;
3280 destructor TDynMapDef.Destroy ();
3281 var
3282 rec: TDynRecord;
3283 ebs: TDynEBS;
3284 begin
3285 //!!!FIXME!!! check who owns trigs and recs!
3286 for rec in recTypes do rec.Free();
3287 for rec in trigTypes do rec.Free();
3288 for ebs in ebsTypes do ebs.Free();
3289 recTypes.Free();
3290 trigTypes.Free();
3291 ebsTypes.Free();
3292 recTypes := nil;
3293 trigTypes := nil;
3294 ebsTypes := nil;
3295 inherited;
3296 end;
3299 function TDynMapDef.getHeaderRecType (): TDynRecord; inline;
3300 begin
3301 if (recTypes.count = 0) then raise TDynRecException.Create('no header in empty mapdef');
3302 result := recTypes[0];
3303 end;
3306 function TDynMapDef.findRecType (const aname: AnsiString): TDynRecord;
3307 var
3308 rec: TDynRecord;
3309 begin
3310 for rec in recTypes do
3311 begin
3312 if StrEqu(rec.typeName, aname) then begin result := rec; exit; end;
3313 end;
3314 result := nil;
3315 end;
3318 function TDynMapDef.findTrigFor (const aname: AnsiString): TDynRecord;
3319 var
3320 rec: TDynRecord;
3321 begin
3322 for rec in trigTypes do
3323 begin
3324 if (rec.isForTrig[aname]) then begin result := rec; exit; end;
3325 end;
3326 result := nil;
3327 end;
3330 function TDynMapDef.findEBSType (const aname: AnsiString): TDynEBS;
3331 var
3332 ebs: TDynEBS;
3333 begin
3334 for ebs in ebsTypes do
3335 begin
3336 if StrEqu(ebs.typeName, aname) then begin result := ebs; exit; end;
3337 end;
3338 result := nil;
3339 end;
3342 procedure TDynMapDef.parseDef (pr: TTextParser);
3343 var
3344 rec, hdr: TDynRecord;
3345 eb: TDynEBS;
3346 f: Integer;
3348 // setup header links and type links
3349 procedure linkRecord (rec: TDynRecord);
3350 var
3351 fld: TDynField;
3352 begin
3353 rec.mHeaderRec := recTypes[0];
3354 for fld in rec.mFields do
3355 begin
3356 if (fld.mType = fld.TType.TTrigData) then continue;
3357 case fld.mEBS of
3358 TDynField.TEBS.TNone: begin end;
3359 TDynField.TEBS.TRec:
3360 begin
3361 fld.mEBSType := findRecType(fld.mEBSTypeName);
3362 if (fld.mEBSType = nil) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' has no correcponding record definition', [fld.mName, fld.mEBSTypeName]);
3363 end;
3364 TDynField.TEBS.TEnum,
3365 TDynField.TEBS.TBitSet:
3366 begin
3367 fld.mEBSType := findEBSType(fld.mEBSTypeName);
3368 if (fld.mEBSType = nil) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' has no correcponding enum/bitset', [fld.mName, fld.mEBSTypeName]);
3369 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]);
3370 end;
3371 end;
3372 end;
3373 end;
3375 // setup default values
3376 procedure fixRecordDefaults (rec: TDynRecord);
3377 var
3378 fld: TDynField;
3379 begin
3380 for fld in rec.mFields do if fld.mHasDefault then fld.parseDefaultValue();
3381 end;
3383 begin
3384 hdr := nil;
3385 while true do
3386 begin
3387 if not pr.skipBlanks() then break;
3389 if (pr.tokType = pr.TTId) then
3390 begin
3391 // enum or bitset
3392 if (pr.tokStr = 'enum') or (pr.tokStr = 'bitset') then
3393 begin
3394 eb := TDynEBS.Create(pr);
3395 if (findEBSType(eb.typeName) <> nil) then
3396 begin
3397 eb.Free();
3398 raise TDynParseException.CreateFmt(pr, 'duplicate enum/bitset ''%s''', [eb.typeName]);
3399 end;
3400 eb.mOwner := self;
3401 ebsTypes.append(eb);
3402 //writeln(eb.definition); writeln;
3403 continue;
3404 end;
3406 // triggerdata
3407 if (pr.tokStr = 'TriggerData') then
3408 begin
3409 rec := TDynRecord.Create(pr);
3410 for f := 0 to High(rec.mTrigTypes) do
3411 begin
3412 if (findTrigFor(rec.mTrigTypes[f]) <> nil) then
3413 begin
3414 rec.Free();
3415 raise TDynParseException.CreateFmt(pr, 'duplicate trigdata ''%s''', [rec.mTrigTypes[f]]);
3416 end;
3417 end;
3418 rec.mOwner := self;
3419 trigTypes.append(rec);
3420 //writeln(dr.definition); writeln;
3421 continue;
3422 end;
3423 end;
3425 rec := TDynRecord.Create(pr);
3426 //writeln(dr.definition); writeln;
3427 if (findRecType(rec.typeName) <> nil) then begin rec.Free(); raise TDynParseException.CreateFmt(pr, 'duplicate record ''%s''', [rec.typeName]); end;
3428 if (hdr <> nil) and StrEqu(rec.typeName, hdr.typeName) then begin rec.Free(); raise TDynParseException.CreateFmt(pr, 'duplicate record ''%s''', [rec.typeName]); end;
3429 rec.mOwner := self;
3430 if rec.mHeader then
3431 begin
3432 if (hdr <> nil) then begin rec.Free(); raise TDynParseException.CreateFmt(pr, 'duplicate header record ''%s'' (previous is ''%s'')', [rec.typeName, hdr.typeName]); end;
3433 hdr := rec;
3434 end
3435 else
3436 begin
3437 recTypes.append(rec);
3438 end;
3439 end;
3441 // put header record to top
3442 if (hdr = nil) then raise TDynParseException.Create(pr, 'header definition not found in mapdef');
3443 recTypes.append(nil);
3444 for f := recTypes.count-1 downto 1 do recTypes[f] := recTypes[f-1];
3445 recTypes[0] := hdr;
3447 // setup header links and type links
3448 for rec in recTypes do linkRecord(rec);
3449 for rec in trigTypes do linkRecord(rec);
3451 // setup default values
3452 for rec in recTypes do fixRecordDefaults(rec);
3453 for rec in trigTypes do fixRecordDefaults(rec);
3454 end;
3457 // ////////////////////////////////////////////////////////////////////////// //
3458 function TDynMapDef.parseTextMap (pr: TTextParser): TDynRecord;
3459 var
3460 res: TDynRecord = nil;
3461 begin
3462 result := nil;
3463 try
3464 pr.expectId(headerType.typeName);
3465 res := headerType.clone(nil);
3466 res.mHeaderRec := res;
3467 res.parseValue(pr);
3468 result := res;
3469 res := nil;
3470 finally
3471 res.Free();
3472 end;
3473 end;
3476 function TDynMapDef.parseBinMap (st: TStream): TDynRecord;
3477 var
3478 res: TDynRecord = nil;
3479 begin
3480 result := nil;
3481 try
3482 res := headerType.clone(nil);
3483 res.mHeaderRec := res;
3484 res.parseBinValue(st);
3485 result := res;
3486 res := nil;
3487 finally
3488 res.Free();
3489 end;
3490 end;
3493 // WARNING! stream must be seekable
3494 function TDynMapDef.parseMap (st: TStream; wasBinary: PBoolean=nil): TDynRecord;
3495 var
3496 sign: packed array[0..3] of AnsiChar;
3497 pr: TTextParser;
3498 begin
3499 if (wasBinary <> nil) then wasBinary^ := false;
3500 st.position := 0;
3501 st.ReadBuffer(sign[0], 4);
3502 st.position := 0;
3503 if (sign[0] = 'M') and (sign[1] = 'A') and (sign[2] = 'P') then
3504 begin
3505 if (sign[3] = #1) then
3506 begin
3507 if (wasBinary <> nil) then wasBinary^ := true;
3508 result := parseBinMap(st);
3509 exit;
3510 end;
3511 raise TDynRecException.Create('invalid binary map version');
3512 end
3513 else
3514 begin
3515 pr := TFileTextParser.Create(st, false); // `st` is not owned
3516 try
3517 try
3518 result := parseTextMap(pr);
3519 except on e: Exception do
3520 raise TDynParseException.Create(pr, e.message);
3521 end;
3522 finally
3523 pr.Free();
3524 end;
3525 end;
3526 end;
3529 // returns `true` if the given stream can be a map file
3530 // stream position is 0 on return
3531 // WARNING! stream must be seekable
3532 class function TDynMapDef.canBeMap (st: TStream): Boolean;
3533 var
3534 sign: packed array[0..3] of AnsiChar;
3535 pr: TTextParser;
3536 begin
3537 result := false;
3538 st.position := 0;
3539 st.ReadBuffer(sign[0], 4);
3540 if (sign[0] = 'M') and (sign[1] = 'A') and (sign[2] = 'P') then
3541 begin
3542 result := (sign[3] = #1);
3543 end
3544 else
3545 begin
3546 st.position := 0;
3547 pr := TFileTextParser.Create(st, false); // `st` is not owned
3548 result := (pr.tokType = pr.TTId) and (pr.tokStr = 'map');
3549 pr.Free();
3550 end;
3551 st.position := 0;
3552 end;
3555 function TDynMapDef.pasdefconst (): AnsiString;
3556 var
3557 ebs: TDynEBS;
3558 begin
3559 result := '';
3560 result += '// ////////////////////////////////////////////////////////////////////////// //'#10;
3561 result += '// enums and bitsets'#10;
3562 for ebs in ebsTypes do result += #10+ebs.pasdef();
3563 end;
3566 function TDynMapDef.getRecTypeCount (): Integer; inline; begin result := recTypes.count; end;
3567 function TDynMapDef.getRecTypeAt (idx: Integer): TDynRecord; inline; begin if (idx >= 0) and (idx < recTypes.count) then result := recTypes[idx] else result := nil; end;
3569 function TDynMapDef.getEBSTypeCount (): Integer; inline; begin result := ebsTypes.count; end;
3570 function TDynMapDef.getEBSTypeAt (idx: Integer): TDynEBS; inline; begin if (idx >= 0) and (idx < ebsTypes.count) then result := ebsTypes[idx] else result := nil; end;
3572 function TDynMapDef.getTrigTypeCount (): Integer; inline; begin result := trigTypes.count; end;
3573 function TDynMapDef.getTrigTypeAt (idx: Integer): TDynRecord; inline; begin if (idx >= 0) and (idx < trigTypes.count) then result := trigTypes[idx] else result := nil; end;
3576 end.