| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2017-2022, The LegacyClonk Team and contributors |
| 6 | * |
| 7 | * Distributed under the terms of the ISC license; see accompanying file |
| 8 | * "COPYING" for details. |
| 9 | * |
| 10 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 11 | * See accompanying file "TRADEMARK" for details. |
| 12 | * |
| 13 | * To redistribute this file separately, substitute the full license texts |
| 14 | * for the above references. |
| 15 | */ |
| 16 | |
| 17 | #include <C4Include.h> |
| 18 | #include <C4Value.h> |
| 19 | #include <C4Aul.h> |
| 20 | #include <C4StringTable.h> |
| 21 | #include <C4ValueList.h> |
| 22 | #include <C4ValueHash.h> |
| 23 | |
| 24 | #include <cinttypes> |
| 25 | #include <functional> |
| 26 | #include <format> |
| 27 | #include <string_view> |
| 28 | |
| 29 | #include <C4Game.h> |
| 30 | #include <C4Object.h> |
| 31 | #include <C4Log.h> |
| 32 | |
| 33 | const C4Value C4VNull{}; |
| 34 | const C4Value C4VTrue{C4VBool(fVal: true)}; |
| 35 | const C4Value C4VFalse{C4VBool(fVal: false)}; |
| 36 | |
| 37 | C4Value::~C4Value() |
| 38 | { |
| 39 | // resolve all C4Values referencing this Value |
| 40 | while (FirstRef) |
| 41 | FirstRef->Set(*this); |
| 42 | |
| 43 | // delete contents |
| 44 | DelDataRef(Data, Type, pNextRef: GetNextRef(), pBaseContainer: GetBaseContainer()); |
| 45 | } |
| 46 | |
| 47 | std::optional<StdStrBuf> C4Value::toString() const |
| 48 | { |
| 49 | const C4Value &val = GetRefVal(); |
| 50 | switch (val.Type) |
| 51 | { |
| 52 | case C4V_String: |
| 53 | return {val._getStr()->Data}; |
| 54 | |
| 55 | case C4V_Bool: |
| 56 | case C4V_Int: |
| 57 | return {StdStrBuf{std::format(fmt: "{}" , args: val._getInt()).c_str()}}; |
| 58 | |
| 59 | case C4V_C4ID: |
| 60 | return {StdStrBuf(C4IdText(id: val._getC4ID()))}; |
| 61 | |
| 62 | default: |
| 63 | return {}; |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | C4Value &C4Value::operator=(const C4Value &nValue) |
| 68 | { |
| 69 | // set referenced value |
| 70 | if (Type == C4V_pC4Value) |
| 71 | GetRefVal().operator=(nValue); |
| 72 | else |
| 73 | Set(nValue.GetRefVal()); |
| 74 | |
| 75 | return *this; |
| 76 | } |
| 77 | |
| 78 | void C4Value::AddDataRef() |
| 79 | { |
| 80 | switch (Type) |
| 81 | { |
| 82 | case C4V_pC4Value: Data.Ref->AddRef(pRef: this); break; |
| 83 | case C4V_Any: if (Data) { GuessType(); } break; |
| 84 | case C4V_Array: case C4V_Map: Data.Container = Data.Container->IncRef(); break; |
| 85 | case C4V_String: Data.Str->IncRef(); break; |
| 86 | case C4V_C4Object: |
| 87 | Data.Obj->AddRef(pRef: this); |
| 88 | #ifndef NDEBUG |
| 89 | // check if the object actually exists |
| 90 | if (!Game.Objects.ObjectNumber(Data.Obj)) |
| 91 | { |
| 92 | LogNTr(spdlog::level::warn, "using wild object ptr {}!" , static_cast<void *>(Data.Obj)); |
| 93 | } |
| 94 | else if (!Data.Obj->Status) |
| 95 | { |
| 96 | LogNTr(spdlog::level::warn, "using ptr on deleted object {} ({})!" , static_cast<void *>(Data.Obj), Data.Obj->GetName()); |
| 97 | } |
| 98 | #endif |
| 99 | break; |
| 100 | default: break; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | void C4Value::DelDataRef(C4V_Data Data, C4V_Type Type, C4Value *pNextRef, C4ValueContainer *pBaseContainer) |
| 105 | { |
| 106 | // clean up |
| 107 | switch (Type) |
| 108 | { |
| 109 | case C4V_pC4Value: |
| 110 | // Save because AddDataRef does not set this flag |
| 111 | HasBaseContainer = false; |
| 112 | Data.Ref->DelRef(pRef: this, pNextRef, pBaseContainer); |
| 113 | break; |
| 114 | case C4V_C4Object: Data.Obj->DelRef(pRef: this, pNextRef); break; |
| 115 | case C4V_Array: case C4V_Map: Data.Container->DecRef(); break; |
| 116 | case C4V_String: Data.Str->DecRef(); break; |
| 117 | default: break; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | void C4Value::Set(C4V_Data nData, C4V_Type nType) |
| 122 | { |
| 123 | // Do not add this to the same linked list twice. |
| 124 | if (Data == nData && Type == nType) return; |
| 125 | |
| 126 | C4V_Data oData = Data; |
| 127 | C4V_Type oType = Type; |
| 128 | C4Value *oNextRef = NextRef; |
| 129 | auto *oBaseContainer = BaseContainer; |
| 130 | auto oHasBaseContainer = HasBaseContainer; |
| 131 | |
| 132 | // change |
| 133 | Data = nData; |
| 134 | Type = (nData || nType == C4V_Int || nType == C4V_Bool) ? nType : C4V_Any; |
| 135 | |
| 136 | // hold |
| 137 | AddDataRef(); |
| 138 | |
| 139 | // clean up |
| 140 | DelDataRef(Data: oData, Type: oType, pNextRef: oHasBaseContainer ? nullptr : oNextRef, pBaseContainer: oHasBaseContainer ? oBaseContainer : nullptr); |
| 141 | |
| 142 | CheckRemoveFromMap(); |
| 143 | } |
| 144 | |
| 145 | void C4Value::Set0() |
| 146 | { |
| 147 | C4V_Data oData = Data; |
| 148 | C4V_Type oType = Type; |
| 149 | |
| 150 | // change |
| 151 | Data.Raw = 0; |
| 152 | Type = C4V_Any; |
| 153 | |
| 154 | // clean up (save even if Data was 0 before) |
| 155 | DelDataRef(Data: oData, Type: oType, pNextRef: HasBaseContainer ? nullptr : NextRef, pBaseContainer: HasBaseContainer ? BaseContainer : nullptr); |
| 156 | |
| 157 | CheckRemoveFromMap(); |
| 158 | } |
| 159 | |
| 160 | void C4Value::CheckRemoveFromMap() |
| 161 | { |
| 162 | if (Type == C4V_Any && Data.Raw == 0 && OwningMap) |
| 163 | { |
| 164 | OwningMap->removeValue(value: this); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | void C4Value::Move(C4Value *nValue) |
| 169 | { |
| 170 | nValue->Set(*this); |
| 171 | |
| 172 | // change references |
| 173 | for (C4Value *pVal = FirstRef; pVal; pVal = pVal->GetNextRef()) |
| 174 | pVal->Data.Ref = nValue; |
| 175 | |
| 176 | // copy ref list |
| 177 | assert(!nValue->FirstRef); |
| 178 | nValue->FirstRef = FirstRef; |
| 179 | |
| 180 | // delete usself |
| 181 | FirstRef = nullptr; |
| 182 | Set0(); |
| 183 | } |
| 184 | |
| 185 | void C4Value::GetContainerElement(C4Value *index, C4Value &target, C4AulContext *pctx, bool noref) |
| 186 | { |
| 187 | try |
| 188 | { |
| 189 | C4Value &Ref = GetRefVal(); |
| 190 | if (Ref.Type == C4V_C4Object) |
| 191 | { |
| 192 | if (index->ConvertTo(vtToType: C4V_String) && index->_getStr()) |
| 193 | { |
| 194 | auto var = Ref.Data.Obj->LocalNamed.GetItem(strName: index->_getStr()->Data.getData()); |
| 195 | if (var) target.SetRef(var); |
| 196 | else target.Set0(); |
| 197 | } |
| 198 | else |
| 199 | throw C4AulExecError(pctx->Obj, "indexed access on object: only string keys are allowed" ); |
| 200 | return; |
| 201 | } |
| 202 | // No array (and no nullpointer because Data==0 => Type==any) |
| 203 | if (Ref.Type != C4V_Array && Ref.Type != C4V_Map) |
| 204 | throw C4AulExecError(pctx->Obj, "indexed access: array or map expected" ); |
| 205 | if (noref) |
| 206 | { |
| 207 | // Get the item, but do not resize the array - it might be used more than once |
| 208 | if (Ref.Data.Container->hasIndex(index: *index)) |
| 209 | target.Set((*Ref.Data.Container)[*index]); |
| 210 | else |
| 211 | target.Set0(); |
| 212 | } |
| 213 | else |
| 214 | { |
| 215 | index->Deref(); |
| 216 | // Is target the first ref? |
| 217 | if (!Ref.Data.Container->hasIndex(index: *index) || !(*Ref.Data.Container)[*index].FirstRef) |
| 218 | { |
| 219 | Ref.Data.Container = Ref.Data.Container->IncElementRef(); |
| 220 | target.SetRef(&(*Ref.Data.Container)[*index]); |
| 221 | if (target.Type == C4V_pC4Value) |
| 222 | { |
| 223 | assert(!target.NextRef); |
| 224 | target.BaseContainer = Ref.Data.Container; |
| 225 | target.HasBaseContainer = true; |
| 226 | } |
| 227 | // else target apparently owned the last reference to the array |
| 228 | } |
| 229 | else |
| 230 | { |
| 231 | target.SetRef(&(*Ref.Data.Container)[*index]); |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | catch (const std::runtime_error &e) |
| 236 | { |
| 237 | throw C4AulExecError(pctx->Obj, e.what()); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | void C4Value::SetArrayLength(int32_t size, C4AulContext *cthr) |
| 242 | { |
| 243 | C4Value &Ref = GetRefVal(); |
| 244 | // No array |
| 245 | if (Ref.Type != C4V_Array) |
| 246 | throw C4AulExecError(cthr->Obj, "SetLength: array expected" ); |
| 247 | Ref.Data.Array = Ref.Data.Array->SetLength(size); |
| 248 | } |
| 249 | |
| 250 | const C4Value &C4Value::GetRefVal() const |
| 251 | { |
| 252 | const C4Value *pVal = this; |
| 253 | while (pVal->Type == C4V_pC4Value) |
| 254 | pVal = pVal->Data.Ref; |
| 255 | return *pVal; |
| 256 | } |
| 257 | |
| 258 | C4Value &C4Value::GetRefVal() |
| 259 | { |
| 260 | C4Value *pVal = this; |
| 261 | while (pVal->Type == C4V_pC4Value) |
| 262 | pVal = pVal->Data.Ref; |
| 263 | return *pVal; |
| 264 | } |
| 265 | |
| 266 | void C4Value::AddRef(C4Value *pRef) |
| 267 | { |
| 268 | pRef->NextRef = FirstRef; |
| 269 | FirstRef = pRef; |
| 270 | } |
| 271 | |
| 272 | void C4Value::DelRef(const C4Value *pRef, C4Value *pNextRef, C4ValueContainer *pBaseContainer) |
| 273 | { |
| 274 | if (pRef == FirstRef) |
| 275 | FirstRef = pNextRef; |
| 276 | else |
| 277 | { |
| 278 | C4Value *pVal = FirstRef; |
| 279 | while (pVal->NextRef != pRef) |
| 280 | { |
| 281 | // assert that pRef really was in the list |
| 282 | assert(pVal->NextRef && !pVal->HasBaseContainer); |
| 283 | pVal = pVal->NextRef; |
| 284 | } |
| 285 | pVal->NextRef = pNextRef; |
| 286 | if (pBaseContainer) |
| 287 | { |
| 288 | pVal->HasBaseContainer = true; |
| 289 | pVal->BaseContainer = pBaseContainer; |
| 290 | } |
| 291 | } |
| 292 | // Was pRef the last ref to an array element? |
| 293 | if (pBaseContainer && !FirstRef) |
| 294 | { |
| 295 | pBaseContainer->DecElementRef(); |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | C4V_Type C4Value::GuessType() |
| 300 | { |
| 301 | // guaranteed by the caller |
| 302 | assert(Data); |
| 303 | |
| 304 | if (Type != C4V_Any) return Type; |
| 305 | |
| 306 | // C4ID? |
| 307 | if (LooksLikeID(id: Data.ID) && Data.ID >= 10000) |
| 308 | return Type = C4V_C4ID; |
| 309 | |
| 310 | // object? |
| 311 | if (Game.Objects.ObjectNumber(pObj: Data.Obj)) |
| 312 | { |
| 313 | Type = C4V_C4Object; |
| 314 | // With the type now known, the destructor will clean up the reference |
| 315 | // which only works if the reference is added first |
| 316 | AddDataRef(); |
| 317 | return Type; |
| 318 | } |
| 319 | |
| 320 | // string? |
| 321 | if (Game.ScriptEngine.Strings.FindString(pString: Data.Str)) |
| 322 | { |
| 323 | Type = C4V_String; |
| 324 | // see above |
| 325 | AddDataRef(); |
| 326 | return Type; |
| 327 | } |
| 328 | |
| 329 | // must be int now |
| 330 | return Type = C4V_Int; |
| 331 | } |
| 332 | |
| 333 | void C4Value::HintType(C4V_Type type) |
| 334 | { |
| 335 | auto &ref = GetRefVal(); |
| 336 | if (ref.Data.Int != 0 || type == C4V_Bool || type == C4V_Int || type == C4V_C4ID) |
| 337 | ref.Type = type; |
| 338 | } |
| 339 | |
| 340 | const char *GetC4VName(const C4V_Type Type) |
| 341 | { |
| 342 | switch (Type) |
| 343 | { |
| 344 | case C4V_Any: |
| 345 | return "any" ; |
| 346 | case C4V_Int: |
| 347 | return "int" ; |
| 348 | case C4V_Bool: |
| 349 | return "bool" ; |
| 350 | case C4V_C4Object: |
| 351 | return "object" ; |
| 352 | case C4V_C4ID: |
| 353 | return "id" ; |
| 354 | case C4V_String: |
| 355 | return "string" ; |
| 356 | case C4V_Array: |
| 357 | return "array" ; |
| 358 | case C4V_Map: |
| 359 | return "map" ; |
| 360 | case C4V_pC4Value: |
| 361 | return "&" ; |
| 362 | case C4V_C4ObjectEnum: |
| 363 | ; // fallthrough |
| 364 | } |
| 365 | return "!Fehler!" ; |
| 366 | } |
| 367 | |
| 368 | char GetC4VID(const C4V_Type Type) |
| 369 | { |
| 370 | switch (Type) |
| 371 | { |
| 372 | case C4V_Any: |
| 373 | return 'A'; |
| 374 | case C4V_Int: |
| 375 | return 'i'; |
| 376 | case C4V_Bool: |
| 377 | return 'b'; |
| 378 | case C4V_C4Object: |
| 379 | return 'o'; |
| 380 | case C4V_C4ID: |
| 381 | return 'I'; |
| 382 | case C4V_String: |
| 383 | return 'S'; |
| 384 | case C4V_pC4Value: |
| 385 | return 'V'; // should never happen |
| 386 | case C4V_C4ObjectEnum: |
| 387 | return 'O'; |
| 388 | case C4V_Array: |
| 389 | return 'a'; |
| 390 | case C4V_Map: |
| 391 | return 'm'; |
| 392 | } |
| 393 | return ' '; |
| 394 | } |
| 395 | |
| 396 | C4V_Type GetC4VFromID(const char C4VID) |
| 397 | { |
| 398 | switch (C4VID) |
| 399 | { |
| 400 | case 'A': |
| 401 | return C4V_Any; |
| 402 | case 'i': |
| 403 | return C4V_Int; |
| 404 | case 'b': |
| 405 | return C4V_Bool; |
| 406 | case 'o': |
| 407 | return C4V_C4Object; |
| 408 | case 'I': |
| 409 | return C4V_C4ID; |
| 410 | case 'S': |
| 411 | return C4V_String; |
| 412 | case 'V': |
| 413 | return C4V_pC4Value; |
| 414 | case 'O': |
| 415 | return C4V_C4ObjectEnum; |
| 416 | case 'a': |
| 417 | return C4V_Array; |
| 418 | case 'm': |
| 419 | return C4V_Map; |
| 420 | } |
| 421 | return C4V_Any; |
| 422 | } |
| 423 | |
| 424 | const char *C4Value::GetTypeInfo() |
| 425 | { |
| 426 | return GetC4VName(Type: GetType()); |
| 427 | } |
| 428 | |
| 429 | // converter functions |
| 430 | |
| 431 | static bool FnCnvDirectOld(C4Value *Val, C4V_Type toType, bool fStrict) |
| 432 | { |
| 433 | // new syntax: failure |
| 434 | if (fStrict) return false; |
| 435 | // old syntax: do nothing |
| 436 | return true; |
| 437 | } |
| 438 | |
| 439 | static bool FnCnvError(C4Value *Val, C4V_Type toType, bool fStrict) |
| 440 | { |
| 441 | // deny convert |
| 442 | return false; |
| 443 | } |
| 444 | |
| 445 | static bool FnCnvDeref(C4Value *Val, C4V_Type toType, bool fStrict) |
| 446 | { |
| 447 | // resolve reference of Value |
| 448 | Val->Deref(); |
| 449 | // retry to check convert |
| 450 | return Val->ConvertTo(vtToType: toType, fStrict); |
| 451 | } |
| 452 | |
| 453 | bool C4Value::FnCnvGuess(C4Value *Val, C4V_Type toType, bool fStrict) |
| 454 | { |
| 455 | if (Val->Data) |
| 456 | { |
| 457 | // guess type (always possible because data is not 0) |
| 458 | Val->GuessType(); |
| 459 | // try to convert new type |
| 460 | return Val->ConvertTo(vtToType: toType, fStrict); |
| 461 | } |
| 462 | else |
| 463 | { |
| 464 | // nil is every possible type except a reference at the same time |
| 465 | return true; |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | bool C4Value::FnCnvInt2Id(C4Value *Val, C4V_Type toType, bool fStrict) |
| 470 | { |
| 471 | // inside range? |
| 472 | const auto i = Val->Data.Int; |
| 473 | if (!Inside<decltype(i)>(ival: i, lbound: 0, rbound: 9999)) return false; |
| 474 | // convert |
| 475 | Val->Type = C4V_C4ID; |
| 476 | Val->Data.ID = static_cast<C4ID>(i); |
| 477 | return true; |
| 478 | } |
| 479 | |
| 480 | // Type conversion table |
| 481 | #define CnvOK nullptr, false // allow conversion by same value |
| 482 | #define CnvError FnCnvError, true |
| 483 | #define CnvGuess C4Value::FnCnvGuess, false |
| 484 | #define CnvInt2Id C4Value::FnCnvInt2Id, false |
| 485 | #define CnvDirectOld FnCnvDirectOld, true |
| 486 | #define CnvDeref FnCnvDeref, false |
| 487 | |
| 488 | C4VCnvFn C4Value::C4ScriptCnvMap[C4V_Last + 1][C4V_Last + 1] = |
| 489 | { |
| 490 | { |
| 491 | // C4V_Any - always try guess |
| 492 | { CnvOK }, // any same |
| 493 | { CnvGuess }, // int |
| 494 | { CnvGuess }, // Bool |
| 495 | { CnvGuess }, // C4ID |
| 496 | { CnvGuess }, // C4Object |
| 497 | { CnvGuess }, // String |
| 498 | { CnvGuess }, // Array |
| 499 | { CnvGuess }, // Map |
| 500 | { CnvError }, // pC4Value |
| 501 | }, |
| 502 | { |
| 503 | // C4V_Int |
| 504 | { CnvOK }, // any |
| 505 | { CnvOK }, // int same |
| 506 | { CnvOK }, // Bool |
| 507 | { CnvInt2Id }, // C4ID numerical ID? |
| 508 | { CnvError }, // C4Object NEVER! |
| 509 | { CnvError }, // String NEVER! |
| 510 | { CnvError }, // Array NEVER! |
| 511 | { CnvError }, // Map NEVER! |
| 512 | { CnvError }, // pC4Value |
| 513 | }, |
| 514 | { |
| 515 | // C4V_Bool |
| 516 | { CnvOK }, // any |
| 517 | { CnvOK }, // int might be used |
| 518 | { CnvOK }, // Bool same |
| 519 | { CnvDirectOld }, // C4ID #strict forbid |
| 520 | { CnvError }, // C4Object NEVER! |
| 521 | { CnvError }, // String NEVER! |
| 522 | { CnvError }, // Array NEVER! |
| 523 | { CnvError }, // Map NEVER! |
| 524 | { CnvError }, // pC4Value |
| 525 | }, |
| 526 | { |
| 527 | // C4V_C4ID |
| 528 | { CnvOK }, // any |
| 529 | { CnvDirectOld }, // int #strict forbid |
| 530 | { CnvOK }, // Bool |
| 531 | { CnvOK }, // C4ID same |
| 532 | { CnvError }, // C4Object NEVER! |
| 533 | { CnvError }, // String NEVER! |
| 534 | { CnvError }, // Array NEVER! |
| 535 | { CnvError }, // Map NEVER! |
| 536 | { CnvError }, // pC4Value |
| 537 | }, |
| 538 | { |
| 539 | // C4V_Object |
| 540 | { CnvOK }, // any |
| 541 | { CnvDirectOld }, // int #strict forbid |
| 542 | { CnvOK }, // Bool |
| 543 | { CnvError }, // C4ID Senseless, thus error |
| 544 | { CnvOK }, // C4Object same |
| 545 | { CnvError }, // String NEVER! |
| 546 | { CnvError }, // Array NEVER! |
| 547 | { CnvError }, // Map NEVER! |
| 548 | { CnvError }, // pC4Value |
| 549 | }, |
| 550 | { |
| 551 | // C4V_String |
| 552 | { CnvOK }, // any |
| 553 | { CnvDirectOld }, // int #strict forbid |
| 554 | { CnvOK }, // Bool |
| 555 | { CnvError }, // C4ID Sensless, thus error |
| 556 | { CnvError }, // C4Object NEVER! |
| 557 | { CnvOK }, // String same |
| 558 | { CnvError }, // Array NEVER! |
| 559 | { CnvError }, // Map NEVER! |
| 560 | { CnvError }, // pC4Value |
| 561 | }, |
| 562 | { |
| 563 | // C4V_Array |
| 564 | { CnvOK }, // any |
| 565 | { CnvError }, // int NEVER! |
| 566 | { CnvOK }, // Bool |
| 567 | { CnvError }, // C4ID NEVER! |
| 568 | { CnvError }, // C4Object NEVER! |
| 569 | { CnvError }, // String NEVER! |
| 570 | { CnvOK }, // Array same |
| 571 | { CnvError }, // Map NEVER! |
| 572 | { CnvError }, // pC4Value NEVER! |
| 573 | }, |
| 574 | { |
| 575 | // C4V_Map |
| 576 | { CnvOK }, // any |
| 577 | { CnvError }, // int NEVER! |
| 578 | { CnvOK }, // Bool |
| 579 | { CnvError }, // C4ID NEVER! |
| 580 | { CnvError }, // C4Object NEVER! |
| 581 | { CnvError }, // String NEVER! |
| 582 | { CnvError }, // Array NEVER! |
| 583 | { CnvOK }, // Map same |
| 584 | { CnvError }, // pC4Value NEVER! |
| 585 | }, |
| 586 | { |
| 587 | // C4V_pC4Value - resolve reference and retry type check |
| 588 | { CnvDeref }, // any |
| 589 | { CnvDeref }, // int |
| 590 | { CnvDeref }, // Bool |
| 591 | { CnvDeref }, // C4ID |
| 592 | { CnvDeref }, // C4Object |
| 593 | { CnvDeref }, // String |
| 594 | { CnvDeref }, // Array |
| 595 | { CnvDeref }, // Map |
| 596 | { CnvOK }, // pC4Value same |
| 597 | }, |
| 598 | }; |
| 599 | |
| 600 | #undef CnvOK |
| 601 | #undef CvnError |
| 602 | #undef CnvGuess |
| 603 | #undef CnvInt2Id |
| 604 | #undef CnvDirectOld |
| 605 | #undef CnvDeref |
| 606 | |
| 607 | // Humanreadable debug output |
| 608 | std::string C4Value::GetDataString() const |
| 609 | { |
| 610 | if (Type == C4V_pC4Value) |
| 611 | return GetRefVal().GetDataString() + "*" ; |
| 612 | |
| 613 | // ouput by type info |
| 614 | switch (GetType()) |
| 615 | { |
| 616 | case C4V_Any: |
| 617 | return "nil" ; |
| 618 | case C4V_Int: |
| 619 | return std::to_string(val: Data.Int); |
| 620 | case C4V_Bool: |
| 621 | return Data ? "true" : "false" ; |
| 622 | case C4V_C4ID: |
| 623 | return C4IdText(id: Data.ID); |
| 624 | case C4V_C4Object: |
| 625 | { |
| 626 | // obj exists? |
| 627 | if (!Game.Objects.ObjectNumber(pObj: Data.Obj) && !Game.Objects.InactiveObjects.ObjectNumber(pObj: Data.Obj)) |
| 628 | return std::to_string(val: Data.Raw); |
| 629 | else if (Data.Obj) |
| 630 | if (Data.Obj->Status == C4OS_NORMAL) |
| 631 | return std::format(fmt: "{} #{}" , args: Data.Obj->GetName(), args: static_cast<int>(Data.Obj->Number)); |
| 632 | else |
| 633 | return std::format(fmt: "{{{} #{}}}" , args: Data.Obj->GetName(), args: static_cast<int>(Data.Obj->Number)); |
| 634 | else |
| 635 | return "0" ; // (impossible) |
| 636 | } |
| 637 | case C4V_String: |
| 638 | return (Data.Str && Data.Str->Data.getData()) ? std::format(fmt: "\"{}\"" , args: Data.Str->Data.getData()) : "(nullstring)" ; |
| 639 | case C4V_Array: |
| 640 | { |
| 641 | std::string dataString{"[" }; |
| 642 | for (int32_t i = 0; i < Data.Array->GetSize(); i++) |
| 643 | { |
| 644 | if (i) dataString += ", " ; |
| 645 | dataString += Data.Array->GetItem(index: i).GetDataString(); |
| 646 | } |
| 647 | dataString += ']'; |
| 648 | return dataString; |
| 649 | } |
| 650 | case C4V_Map: |
| 651 | { |
| 652 | if (Data.Map->size() == 0) return "{}" ; |
| 653 | std::string dataString{"{ " }; |
| 654 | bool first = true; |
| 655 | for (auto [key, value] : *Data.Map) |
| 656 | { |
| 657 | if (!first) dataString += ", " ; |
| 658 | else first = false; |
| 659 | |
| 660 | dataString += std::format(fmt: "{} = {}" , args: key.GetDataString(), args: value.GetDataString()); |
| 661 | } |
| 662 | dataString += " }" ; |
| 663 | return dataString; |
| 664 | } |
| 665 | default: |
| 666 | return "-unknown type- " ; |
| 667 | } |
| 668 | } |
| 669 | |
| 670 | C4Value C4VString(const char *strString) |
| 671 | { |
| 672 | // safety |
| 673 | if (!strString) return C4Value(); |
| 674 | return C4Value(new C4String(strString, &Game.ScriptEngine.Strings)); |
| 675 | } |
| 676 | |
| 677 | C4Value C4VString(StdStrBuf &&Str) |
| 678 | { |
| 679 | // safety |
| 680 | if (Str.isNull()) return C4Value(); |
| 681 | return C4Value(new C4String(std::forward<StdStrBuf>(t&: Str), &Game.ScriptEngine.Strings)); |
| 682 | } |
| 683 | |
| 684 | void C4Value::DenumeratePointer() |
| 685 | { |
| 686 | // array? |
| 687 | if (Type == C4V_Array || Type == C4V_Map) |
| 688 | { |
| 689 | Data.Container->DenumeratePointers(); |
| 690 | return; |
| 691 | } |
| 692 | // object types only |
| 693 | if (Type != C4V_C4ObjectEnum && Type != C4V_Any) return; |
| 694 | // in range? |
| 695 | if (Type != C4V_C4ObjectEnum && !Inside<std::intptr_t>(ival: Data.Raw, lbound: C4EnumPointer1, rbound: C4EnumPointer2)) return; |
| 696 | // get obj id, search object |
| 697 | const auto iObjID = (Data.Int >= C4EnumPointer1 ? Data.Int - C4EnumPointer1 : Data.Int); |
| 698 | C4Object *pObj = Game.Objects.ObjectPointer(iNumber: iObjID); |
| 699 | if (!pObj) |
| 700 | pObj = Game.Objects.InactiveObjects.ObjectPointer(iNumber: iObjID); |
| 701 | if (pObj) |
| 702 | // set |
| 703 | SetObject(pObj); |
| 704 | else |
| 705 | { |
| 706 | // any: guess type |
| 707 | if (Type == C4V_Any) |
| 708 | { |
| 709 | if (Data) GuessType(); |
| 710 | } |
| 711 | // object: invalid value - set to zero |
| 712 | else |
| 713 | Set0(); |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | void C4Value::CompileFunc(StdCompiler *pComp) |
| 718 | { |
| 719 | // Type |
| 720 | bool fCompiler = pComp->isCompiler(); |
| 721 | if (!fCompiler) |
| 722 | { |
| 723 | // Get type |
| 724 | if (Type == C4V_Any && Data) GuessType(); |
| 725 | char cC4VID = GetC4VID(Type); |
| 726 | // Object reference is saved enumerated |
| 727 | if (Type == C4V_C4Object) |
| 728 | cC4VID = GetC4VID(Type: C4V_C4ObjectEnum); |
| 729 | // Write |
| 730 | pComp->Character(rChar&: cC4VID); |
| 731 | } |
| 732 | else |
| 733 | { |
| 734 | // Clear |
| 735 | Set0(); |
| 736 | // Read type |
| 737 | char cC4VID; |
| 738 | try |
| 739 | { |
| 740 | pComp->Character(rChar&: cC4VID); |
| 741 | } |
| 742 | catch (const StdCompiler::NotFoundException &) |
| 743 | { |
| 744 | cC4VID = 'A'; |
| 745 | } |
| 746 | Type = GetC4VFromID(C4VID: cC4VID); |
| 747 | } |
| 748 | // Data |
| 749 | int32_t iTmp; |
| 750 | switch (Type) |
| 751 | { |
| 752 | // simple data types: just save |
| 753 | case C4V_Any: |
| 754 | case C4V_Int: |
| 755 | case C4V_Bool: |
| 756 | // these are 32-bit integers |
| 757 | iTmp = Data.Int; |
| 758 | pComp->Value(rInt&: iTmp); |
| 759 | Data.Int = iTmp; |
| 760 | return; |
| 761 | |
| 762 | case C4V_C4ID: |
| 763 | iTmp = static_cast<int32_t>(Data.ID); |
| 764 | pComp->Value(rInt&: iTmp); |
| 765 | Data.ID = static_cast<C4ID>(iTmp); |
| 766 | return; |
| 767 | |
| 768 | // object: save object number instead |
| 769 | case C4V_C4Object: |
| 770 | if (!fCompiler) |
| 771 | iTmp = Game.Objects.ObjectNumber(pObj: getObj()); |
| 772 | case C4V_C4ObjectEnum: |
| 773 | if (!fCompiler) if (Type == C4V_C4ObjectEnum) |
| 774 | iTmp = Data.Int; |
| 775 | pComp->Value(rInt&: iTmp); |
| 776 | if (fCompiler) |
| 777 | { |
| 778 | Type = C4V_C4ObjectEnum; |
| 779 | Data.Int = iTmp; // must be denumerated later |
| 780 | } |
| 781 | return; |
| 782 | |
| 783 | // string: save string number |
| 784 | case C4V_String: |
| 785 | if (!fCompiler) iTmp = getStr()->iEnumID; |
| 786 | pComp->Value(rInt&: iTmp); |
| 787 | // search |
| 788 | if (fCompiler) |
| 789 | { |
| 790 | C4String *pString = Game.ScriptEngine.Strings.FindString(iEnumID: iTmp); |
| 791 | if (pString) |
| 792 | { |
| 793 | Data.Str = pString; |
| 794 | pString->IncRef(); |
| 795 | } |
| 796 | else |
| 797 | Type = C4V_Any; |
| 798 | } |
| 799 | return; |
| 800 | |
| 801 | case C4V_Array: |
| 802 | pComp->Separator(eSep: StdCompiler::SEP_START2); |
| 803 | pComp->Value(rStruct: mkPtrAdapt(rpObj&: Data.Array, fAllowNull: false)); |
| 804 | if (fCompiler) Data.Container = Data.Array->IncRef(); |
| 805 | pComp->Separator(eSep: StdCompiler::SEP_END2); |
| 806 | return; |
| 807 | |
| 808 | case C4V_Map: |
| 809 | pComp->Separator(eSep: StdCompiler::SEP_START2); |
| 810 | pComp->Value(rStruct: mkPtrAdapt(rpObj&: Data.Map, fAllowNull: false)); |
| 811 | if (fCompiler) Data.Container = Data.Map->IncRef(); |
| 812 | pComp->Separator(eSep: StdCompiler::SEP_END2); |
| 813 | return; |
| 814 | |
| 815 | // shouldn't happen |
| 816 | case C4V_pC4Value: |
| 817 | ; // fallthrough |
| 818 | } |
| 819 | |
| 820 | assert(false); |
| 821 | } |
| 822 | |
| 823 | bool C4Value::Equals(const C4Value &other, C4AulScriptStrict strict) const |
| 824 | { |
| 825 | switch (strict) |
| 826 | { |
| 827 | case C4AulScriptStrict::NONSTRICT: case C4AulScriptStrict::STRICT1: |
| 828 | return _getRaw() == other._getRaw(); |
| 829 | case C4AulScriptStrict::STRICT2: |
| 830 | return *this == other; |
| 831 | case C4AulScriptStrict::STRICT3: |
| 832 | if (Type != other.Type) return false; |
| 833 | switch (Type) |
| 834 | { |
| 835 | case C4V_Any: |
| 836 | return true; |
| 837 | // object enum is needed for deserialization of C4ValueHash with objects as keys! |
| 838 | case C4V_C4ObjectEnum: |
| 839 | case C4V_Int: |
| 840 | return Data.Int == other.Data.Int; |
| 841 | case C4V_C4Object: |
| 842 | return Data.Obj == other.Data.Obj; |
| 843 | case C4V_C4ID: |
| 844 | return Data.ID == other.Data.ID; |
| 845 | case C4V_Bool: |
| 846 | return _getBool() == other._getBool(); |
| 847 | case C4V_String: |
| 848 | return Data.Str->Data == other.Data.Str->Data; |
| 849 | case C4V_Array: |
| 850 | return *Data.Array == *other.Data.Array; |
| 851 | case C4V_Map: |
| 852 | return *Data.Map == *other.Data.Map; |
| 853 | case C4V_pC4Value: |
| 854 | assert(!"Comparison between variables of types which should never be compared!" ); |
| 855 | // fallthrough |
| 856 | } |
| 857 | return false; |
| 858 | } |
| 859 | return false; |
| 860 | } |
| 861 | |
| 862 | bool C4Value::operator==(const C4Value &Value2) const |
| 863 | { |
| 864 | switch (Type) |
| 865 | { |
| 866 | case C4V_Any: |
| 867 | assert(!Data); |
| 868 | return Data == Value2.Data; |
| 869 | case C4V_Int: |
| 870 | switch (Value2.Type) |
| 871 | { |
| 872 | case C4V_Any: |
| 873 | assert(!Value2.Data); |
| 874 | return Data == Value2.Data; |
| 875 | case C4V_Int: |
| 876 | case C4V_Bool: |
| 877 | case C4V_C4ID: |
| 878 | return Data == Value2.Data; |
| 879 | default: |
| 880 | return false; |
| 881 | } |
| 882 | case C4V_Bool: |
| 883 | switch (Value2.Type) |
| 884 | { |
| 885 | case C4V_Any: |
| 886 | assert(!Value2.Data); |
| 887 | return Data == Value2.Data; |
| 888 | case C4V_Int: |
| 889 | case C4V_Bool: |
| 890 | return Data == Value2.Data; |
| 891 | default: |
| 892 | return false; |
| 893 | } |
| 894 | case C4V_C4ID: |
| 895 | switch (Value2.Type) |
| 896 | { |
| 897 | case C4V_Any: |
| 898 | assert(!Value2.Data); |
| 899 | [[fallthrough]]; |
| 900 | case C4V_Int: |
| 901 | case C4V_C4ID: |
| 902 | return Data == Value2.Data; |
| 903 | default: |
| 904 | return false; |
| 905 | } |
| 906 | case C4V_C4Object: |
| 907 | return Data == Value2.Data && Type == Value2.Type; |
| 908 | case C4V_String: |
| 909 | return Type == Value2.Type && Data.Str->Data == Value2.Data.Str->Data; |
| 910 | case C4V_Array: |
| 911 | return Type == Value2.Type && *(Data.Array) == *(Value2.Data.Array); |
| 912 | break; |
| 913 | case C4V_Map: |
| 914 | return Type == Value2.Type && *(Data.Map) == *(Value2.Data.Map); |
| 915 | default: |
| 916 | // C4AulExec should have dereferenced both values, no need to implement comparison here |
| 917 | return Data == Value2.Data; |
| 918 | } |
| 919 | return GetData() == Value2.GetData(); |
| 920 | } |
| 921 | |
| 922 | namespace |
| 923 | { |
| 924 | // based on boost container_hash's hashCombine |
| 925 | constexpr void hashCombine(std::size_t &hash, std::size_t nextHash) |
| 926 | { |
| 927 | if constexpr (sizeof(std::size_t) == 4) |
| 928 | { |
| 929 | #define rotateLeft32(x, r) (x << r) | (x >> (32 - r)) |
| 930 | constexpr std::size_t c1 = 0xcc9e2d51; |
| 931 | constexpr std::size_t c2 = 0x1b873593; |
| 932 | |
| 933 | nextHash *= c1; |
| 934 | nextHash = rotateLeft32(nextHash, 15); |
| 935 | nextHash *= c2; |
| 936 | |
| 937 | hash ^= nextHash; |
| 938 | hash = rotateLeft32(hash, 13); |
| 939 | hash = hash * 5 + 0xe6546b64; |
| 940 | #undef rotateLeft32 |
| 941 | } |
| 942 | else if constexpr (sizeof(std::size_t) == 8) |
| 943 | { |
| 944 | constexpr std::size_t m = 0xc6a4a7935bd1e995; |
| 945 | constexpr int r = 47; |
| 946 | |
| 947 | nextHash *= m; |
| 948 | nextHash ^= nextHash >> r; |
| 949 | nextHash *= m; |
| 950 | |
| 951 | hash ^= nextHash; |
| 952 | hash *= m; |
| 953 | |
| 954 | // Completely arbitrary number, to prevent 0's |
| 955 | // from hashing to 0. |
| 956 | hash += 0xe6546b64; |
| 957 | } |
| 958 | else |
| 959 | { |
| 960 | hash ^= nextHash + 0x9e3779b9 + (hash << 6) + (hash >> 2); |
| 961 | } |
| 962 | } |
| 963 | } |
| 964 | |
| 965 | std::size_t std::hash<C4Value>::operator()(C4Value value) const |
| 966 | { |
| 967 | C4Value &ref = value.GetRefVal(); |
| 968 | std::size_t hash = std::hash<C4V_Type>{}(ref.GetType()); |
| 969 | |
| 970 | if (ref.GetType() == C4V_C4ObjectEnum) |
| 971 | { |
| 972 | hash = std::hash<C4V_Type>{}(C4V_C4Object); |
| 973 | hashCombine(hash, nextHash: std::hash<C4ValueInt>{}(ref._getInt())); |
| 974 | return hash; |
| 975 | } |
| 976 | |
| 977 | switch (ref.GetType()) |
| 978 | { |
| 979 | case C4V_Any: |
| 980 | break; |
| 981 | |
| 982 | case C4V_Int: case C4V_C4ID: |
| 983 | hashCombine(hash, nextHash: std::hash<C4ValueInt>{}(ref._getInt())); |
| 984 | break; |
| 985 | |
| 986 | case C4V_Bool: |
| 987 | hashCombine(hash, nextHash: std::hash<bool>{}(ref._getBool())); |
| 988 | break; |
| 989 | |
| 990 | case C4V_C4Object: |
| 991 | hashCombine(hash, nextHash: std::hash<int32_t>{}(ref._getObj()->Number)); |
| 992 | break; |
| 993 | |
| 994 | case C4V_String: |
| 995 | { |
| 996 | const auto &str = ref._getStr()->Data; |
| 997 | hashCombine(hash, nextHash: std::hash<std::string_view>{}({str.getData(), str.getLength()})); |
| 998 | break; |
| 999 | } |
| 1000 | case C4V_Array: |
| 1001 | { |
| 1002 | const auto &array = *ref._getArray(); |
| 1003 | for (size_t i = 0; i < array.GetSize(); ++i) |
| 1004 | { |
| 1005 | hashCombine(hash, nextHash: (*this)(array.GetItem(index: i))); |
| 1006 | } |
| 1007 | break; |
| 1008 | } |
| 1009 | case C4V_Map: |
| 1010 | { |
| 1011 | std::size_t contentHash = 0; |
| 1012 | auto &map = *ref._getMap(); |
| 1013 | for (const auto &it : map) |
| 1014 | { |
| 1015 | std::size_t itemHash = (*this)(it.first); |
| 1016 | hashCombine(hash&: itemHash, nextHash: (*this)(it.second)); |
| 1017 | |
| 1018 | // order mustn't matter |
| 1019 | contentHash ^= itemHash; |
| 1020 | } |
| 1021 | hashCombine(hash, nextHash: contentHash); |
| 1022 | break; |
| 1023 | } |
| 1024 | default: |
| 1025 | throw std::runtime_error("Invalid value type for hashing C4Value" ); |
| 1026 | } |
| 1027 | |
| 1028 | return hash; |
| 1029 | } |
| 1030 | |