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
33const C4Value C4VNull{};
34const C4Value C4VTrue{C4VBool(fVal: true)};
35const C4Value C4VFalse{C4VBool(fVal: false)};
36
37C4Value::~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
47std::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
67C4Value &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
78void 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
104void 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
121void 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
145void 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
160void C4Value::CheckRemoveFromMap()
161{
162 if (Type == C4V_Any && Data.Raw == 0 && OwningMap)
163 {
164 OwningMap->removeValue(value: this);
165 }
166}
167
168void 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
185void 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
241void 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
250const 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
258C4Value &C4Value::GetRefVal()
259{
260 C4Value *pVal = this;
261 while (pVal->Type == C4V_pC4Value)
262 pVal = pVal->Data.Ref;
263 return *pVal;
264}
265
266void C4Value::AddRef(C4Value *pRef)
267{
268 pRef->NextRef = FirstRef;
269 FirstRef = pRef;
270}
271
272void 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
299C4V_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
333void 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
340const 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
368char 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
396C4V_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
424const char *C4Value::GetTypeInfo()
425{
426 return GetC4VName(Type: GetType());
427}
428
429// converter functions
430
431static 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
439static bool FnCnvError(C4Value *Val, C4V_Type toType, bool fStrict)
440{
441 // deny convert
442 return false;
443}
444
445static 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
453bool 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
469bool 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
488C4VCnvFn 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
608std::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
670C4Value C4VString(const char *strString)
671{
672 // safety
673 if (!strString) return C4Value();
674 return C4Value(new C4String(strString, &Game.ScriptEngine.Strings));
675}
676
677C4Value 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
684void 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
717void 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
823bool 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
862bool 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
922namespace
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
965std::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