1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2009-2016, The OpenClonk 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 "script/C4Value.h"
19
20 #include "game/C4GameScript.h"
21 #include "object/C4Def.h"
22 #include "object/C4DefList.h"
23 #include "object/C4GameObjects.h"
24 #include "object/C4Object.h"
25 #include "script/C4AulExec.h"
26 #include "script/C4Effect.h"
27 #include "script/C4StringTable.h"
28 #include "script/C4ValueArray.h"
29
30 const C4Value C4VNull;
31
GetC4VName(const C4V_Type Type)32 const char* GetC4VName(const C4V_Type Type)
33 {
34 switch (Type)
35 {
36 case C4V_Nil:
37 return "nil";
38 case C4V_Int:
39 return "int";
40 case C4V_Bool:
41 return "bool";
42 case C4V_String:
43 return "string";
44 case C4V_Array:
45 return "array";
46 case C4V_PropList:
47 return "proplist";
48 case C4V_Any:
49 return "any";
50 case C4V_Object:
51 return "object";
52 case C4V_Def:
53 return "def";
54 case C4V_Effect:
55 return "effect";
56 case C4V_Function:
57 return "function";
58 default:
59 return "!Fehler!";
60 }
61 }
62
C4Value(C4PropListStatic * p)63 C4Value::C4Value(C4PropListStatic * p): C4Value(static_cast<C4PropList *>(p)) {}
C4Value(C4Def * p)64 C4Value::C4Value(C4Def * p): C4Value(static_cast<C4PropList *>(p)) {}
C4Value(C4Object * p)65 C4Value::C4Value(C4Object * p): C4Value(static_cast<C4PropList *>(p)) {}
C4Value(C4Effect * p)66 C4Value::C4Value(C4Effect * p): C4Value(static_cast<C4PropList *>(p)) {}
67
getObj() const68 C4Object * C4Value::getObj() const
69 {
70 return CheckConversion(C4V_Object) ? Data.PropList->GetObject() : nullptr;
71 }
72
_getObj() const73 C4Object * C4Value::_getObj() const
74 {
75 return Data.PropList ? Data.PropList->GetObject() : nullptr;
76 }
77
getDef() const78 C4Def * C4Value::getDef() const
79 {
80 return CheckConversion(C4V_Def) ? Data.PropList->GetDef() : nullptr;
81 }
82
_getDef() const83 C4Def * C4Value::_getDef() const
84 {
85 return Data.PropList ? Data.PropList->GetDef() : nullptr;
86 }
87
C4VObj(C4Object * pObj)88 C4Value C4VObj(C4Object *pObj) { return C4Value(static_cast<C4PropList*>(pObj)); }
89
FnCnvObject() const90 bool C4Value::FnCnvObject() const
91 {
92 // try casting
93 if (Data.PropList->GetObject()) return true;
94 return false;
95 }
96
FnCnvDef() const97 bool C4Value::FnCnvDef() const
98 {
99 // try casting
100 if (Data.PropList->GetDef()) return true;
101 return false;
102 }
103
FnCnvEffect() const104 bool C4Value::FnCnvEffect() const
105 {
106 // try casting
107 if (Data.PropList->GetEffect()) return true;
108 return false;
109 }
110
WarnAboutConversion(C4V_Type Type,C4V_Type vtToType)111 bool C4Value::WarnAboutConversion(C4V_Type Type, C4V_Type vtToType)
112 {
113 switch (vtToType)
114 {
115 case C4V_Nil: return Type != C4V_Nil && Type != C4V_Any;
116 case C4V_Int: return Type != C4V_Int && Type != C4V_Nil && Type != C4V_Bool && Type != C4V_Any;
117 case C4V_Bool: return false;
118 case C4V_PropList: return Type != C4V_PropList && Type != C4V_Effect && Type != C4V_Def && Type != C4V_Object && Type != C4V_Nil && Type != C4V_Any;
119 case C4V_String: return Type != C4V_String && Type != C4V_Nil && Type != C4V_Any;
120 case C4V_Array: return Type != C4V_Array && Type != C4V_Nil && Type != C4V_Any;
121 case C4V_Function: return Type != C4V_Function && Type != C4V_Nil && Type != C4V_Any;
122 case C4V_Any: return false;
123 case C4V_Def: return Type != C4V_Def && Type != C4V_Object && Type != C4V_PropList && Type != C4V_Nil && Type != C4V_Any;
124 case C4V_Object: return Type != C4V_Object && Type != C4V_PropList && Type != C4V_Nil && Type != C4V_Any;
125 case C4V_Effect: return Type != C4V_Effect && Type != C4V_PropList && Type != C4V_Nil && Type != C4V_Any;
126 default: assert(!"C4Value::ConvertTo: impossible conversion target"); return false;
127 }
128 }
129
130 // Humanreadable debug output
GetDataString(int depth,const C4PropListStatic * ignore_reference_parent) const131 StdStrBuf C4Value::GetDataString(int depth, const C4PropListStatic *ignore_reference_parent) const
132 {
133 // ouput by type info
134 switch (GetType())
135 {
136 case C4V_Int:
137 return FormatString("%ld", static_cast<long>(Data.Int));
138 case C4V_Bool:
139 return StdStrBuf(Data ? "true" : "false");
140 case C4V_PropList:
141 {
142 if (Data.PropList == ScriptEngine.GetPropList())
143 return StdStrBuf("Global");
144 C4Object * Obj = Data.PropList->GetObject();
145 if (Obj == Data.PropList)
146 return FormatString("Object(%d)", Obj->Number);
147 const C4PropListStatic * Def = Data.PropList->IsStatic();
148 if (Def)
149 if (!ignore_reference_parent || Def->GetParent() != ignore_reference_parent)
150 return Def->GetDataString();
151 C4Effect * fx = Data.PropList->GetEffect();
152 StdStrBuf DataString;
153 DataString = (fx ? "effect {" : "{");
154 Data.PropList->AppendDataString(&DataString, ", ", depth, Def && ignore_reference_parent);
155 DataString.AppendChar('}');
156 return DataString;
157 }
158 case C4V_String:
159 return (Data.Str && Data.Str->GetCStr()) ? FormatString(R"("%s")", Data.Str->GetCStr()) : StdStrBuf("(nullstring)");
160 case C4V_Array:
161 {
162 if (depth <= 0 && Data.Array->GetSize())
163 {
164 return StdStrBuf("[...]");
165 }
166 StdStrBuf DataString;
167 DataString = "[";
168 for (int32_t i = 0; i < Data.Array->GetSize(); i++)
169 {
170 if (i) DataString.Append(", ");
171 DataString.Append(std::move(Data.Array->GetItem(i).GetDataString(depth - 1)));
172 }
173 DataString.AppendChar(']');
174 return DataString;
175 }
176 case C4V_Function:
177 return Data.Fn->GetFullName();
178 case C4V_Nil:
179 return StdStrBuf("nil");
180 default:
181 return StdStrBuf("-unknown type- ");
182 }
183 }
184
185 // JSON serialization.
186 // Only plain data values can be serialized. Throws a C4JSONSerializationError
187 // when encountering values that cannot be represented in JSON or when the
188 // maximum depth is reached.
ToJSON(int depth,const C4PropListStatic * ignore_reference_parent) const189 StdStrBuf C4Value::ToJSON(int depth, const C4PropListStatic *ignore_reference_parent) const
190 {
191 // ouput by type info
192 switch (GetType())
193 {
194 case C4V_Int:
195 return FormatString("%ld", static_cast<long>(Data.Int));
196 case C4V_Bool:
197 return StdStrBuf(Data ? "true" : "false");
198 case C4V_PropList:
199 {
200 const C4PropListStatic * Def = Data.PropList->IsStatic();
201 if (Def)
202 if (!ignore_reference_parent || Def->GetParent() != ignore_reference_parent)
203 return Def->ToJSON();
204 return Data.PropList->ToJSON(depth, Def && ignore_reference_parent);
205 }
206 case C4V_String:
207 if (Data.Str && Data.Str->GetCStr())
208 {
209 StdStrBuf str = Data.Str->GetData();
210 str.EscapeString();
211 str.Replace("\n", R"(\n)");
212 return FormatString(R"("%s")", str.getData());
213 }
214 else
215 {
216 return StdStrBuf("null");
217 }
218 case C4V_Array:
219 {
220 if (depth <= 0 && Data.Array->GetSize())
221 {
222 throw C4JSONSerializationError("maximum depth reached");
223 }
224 StdStrBuf DataString;
225 DataString = "[";
226 for (int32_t i = 0; i < Data.Array->GetSize(); i++)
227 {
228 if (i) DataString.Append(",");
229 DataString.Append(std::move(Data.Array->GetItem(i).ToJSON(depth - 1)));
230 }
231 DataString.AppendChar(']');
232 return DataString;
233 }
234 case C4V_Function:
235 throw C4JSONSerializationError("cannot serialize function");
236 case C4V_Nil:
237 return StdStrBuf("null");
238 default:
239 throw C4JSONSerializationError("unknown type");
240 }
241 }
242
GetValue(uint32_t n)243 const C4Value & C4ValueNumbers::GetValue(uint32_t n)
244 {
245 if (n <= LoadedValues.size())
246 return LoadedValues[n - 1];
247 LogF("ERROR: Value number %d is missing.", n);
248 return C4VNull;
249 }
250
Denumerate(class C4ValueNumbers * numbers)251 void C4Value::Denumerate(class C4ValueNumbers * numbers)
252 {
253 switch (Type)
254 {
255 case C4V_Enum:
256 Set(numbers->GetValue(Data.Int)); break;
257 case C4V_Array:
258 Data.Array->Denumerate(numbers); break;
259 case C4V_PropList:
260 // objects and effects are denumerated via the main object list
261 if (!Data.PropList->IsNumbered() && !Data.PropList->IsStatic())
262 Data.PropList->Denumerate(numbers);
263 break;
264 case C4V_C4ObjectEnum:
265 {
266 C4PropList *pObj = C4PropListNumbered::GetByNumber(Data.Int);
267 if (pObj)
268 // set
269 SetPropList(pObj);
270 else
271 {
272 // object: invalid value - set to zero
273 LogF("ERROR: Object number %d is missing.", int(Data.Int));
274 Set0();
275 }
276 }
277 default: break;
278 }
279 }
280
Denumerate()281 void C4ValueNumbers::Denumerate()
282 {
283 for (auto & LoadedValue : LoadedValues)
284 LoadedValue.Denumerate(this);
285 }
286
GetNumberForValue(C4Value * v)287 uint32_t C4ValueNumbers::GetNumberForValue(C4Value * v)
288 {
289 // This is only used for C4Values containing pointers
290 // Assume that all pointers have the same size
291 if (ValueNumbers.find(v->GetData()) == ValueNumbers.end())
292 {
293 ValuesToSave.push_back(v);
294 ValueNumbers[v->GetData()] = ValuesToSave.size();
295 return ValuesToSave.size();
296 }
297 return ValueNumbers[v->GetData()];
298 }
299
CompileFunc(StdCompiler * pComp,C4ValueNumbers * numbers)300 void C4Value::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers)
301 {
302 // Type
303 bool deserializing = pComp->isDeserializer();
304 char cC4VID;
305 if (!deserializing)
306 {
307 assert(Type != C4V_Nil || !Data);
308 switch (Type)
309 {
310 case C4V_Nil:
311 cC4VID = 'n'; break;
312 case C4V_Int:
313 cC4VID = 'i'; break;
314 case C4V_Bool:
315 cC4VID = 'b'; break;
316 case C4V_PropList:
317 if (getPropList()->IsStatic())
318 cC4VID = 'D';
319 else if (getPropList()->IsNumbered())
320 cC4VID = 'O';
321 else
322 cC4VID = 'E';
323 break;
324 case C4V_Array:
325 cC4VID = 'E'; break;
326 case C4V_Function:
327 cC4VID = 'D'; break;
328 case C4V_String:
329 cC4VID = 's'; break;
330 default:
331 assert(false);
332 }
333 }
334 pComp->Character(cC4VID);
335 // Data
336 int32_t iTmp;
337 switch (cC4VID)
338 {
339 case 'i':
340 iTmp = Data.Int;
341 pComp->Value(iTmp);
342 SetInt(iTmp);
343 break;
344
345 case 'b':
346 iTmp = Data.Int;
347 pComp->Value(iTmp);
348 SetBool(!!iTmp);
349 break;
350
351 case 'E':
352 if (!deserializing)
353 iTmp = numbers->GetNumberForValue(this);
354 pComp->Value(iTmp);
355 if (deserializing)
356 {
357 Data.Int = iTmp; // must be denumerated later
358 Type = C4V_Enum;
359 }
360 break;
361
362 case 'O':
363 if (!deserializing)
364 iTmp = getPropList()->GetPropListNumbered()->Number;
365 pComp->Value(iTmp);
366 if (deserializing)
367 {
368 Data.Int = iTmp; // must be denumerated later
369 Type = C4V_C4ObjectEnum;
370 }
371 break;
372
373 case 'D':
374 {
375 if (!pComp->isDeserializer())
376 {
377 const C4PropList * p = getPropList();
378 if (getFunction())
379 {
380 p = Data.Fn->Parent;
381 assert(p);
382 assert(p->GetFunc(Data.Fn->GetName()) == Data.Fn);
383 assert(p->IsStatic());
384 }
385 p->IsStatic()->RefCompileFunc(pComp, numbers);
386 if (getFunction())
387 {
388 pComp->Separator(StdCompiler::SEP_PART);
389 StdStrBuf s; s.Ref(Data.Fn->GetName());
390 pComp->Value(mkParAdapt(s, StdCompiler::RCT_ID));
391 }
392 }
393 else
394 {
395 StdStrBuf s;
396 C4Value temp;
397 pComp->Value(mkParAdapt(s, StdCompiler::RCT_ID));
398 if (!::ScriptEngine.GetGlobalConstant(s.getData(), &temp))
399 pComp->excCorrupt("Cannot find global constant %s", s.getData());
400 while(pComp->Separator(StdCompiler::SEP_PART))
401 {
402 C4PropList * p = temp.getPropList();
403 if (!p)
404 pComp->excCorrupt("static proplist %s is not a proplist anymore", s.getData());
405 pComp->Value(mkParAdapt(s, StdCompiler::RCT_ID));
406 C4String * c4s = ::Strings.FindString(s.getData());
407 if (!c4s || !p->GetPropertyByS(c4s, &temp))
408 pComp->excCorrupt("Cannot find property %s in %s", s.getData(), GetDataString().getData());
409 }
410 Set(temp);
411 }
412 break;
413 }
414
415 case 's':
416 {
417 StdStrBuf s;
418 if (!deserializing)
419 s = Data.Str->GetData();
420 pComp->Value(s);
421 if (deserializing)
422 SetString(::Strings.RegString(s));
423 break;
424 }
425
426 // FIXME: remove these three once Game.txt were re-saved with current version
427 case 'c':
428 if (deserializing)
429 Set(GameScript.ScenPropList);
430 break;
431
432 case 't':
433 if (deserializing)
434 Set(GameScript.ScenPrototype);
435 break;
436
437 case 'g':
438 if (deserializing)
439 SetPropList(ScriptEngine.GetPropList());
440 break;
441
442 case 'n':
443 case 'A': // compat with OC 5.1
444 if (deserializing)
445 Set0();
446 // doesn't have a value, so nothing to store
447 break;
448
449 default:
450 // shouldn't happen
451 pComp->excCorrupt("unknown C4Value type tag '%c'", cC4VID);
452 break;
453 }
454 }
455
CompileValue(StdCompiler * pComp,C4Value * v)456 void C4ValueNumbers::CompileValue(StdCompiler * pComp, C4Value * v)
457 {
458 // Type
459 bool deserializing = pComp->isDeserializer();
460 char cC4VID;
461 switch(v->GetType())
462 {
463 case C4V_PropList: cC4VID = 'p'; break;
464 case C4V_Array: cC4VID = 'a'; break;
465 default: assert(deserializing); break;
466 }
467 pComp->Character(cC4VID);
468 pComp->Separator(StdCompiler::SEP_START);
469 switch(cC4VID)
470 {
471 case 'p':
472 {
473 C4PropList * p = v->_getPropList();
474 pComp->Value(mkParAdapt(mkPtrAdaptNoNull(p), this));
475 if (deserializing) v->SetPropList(p);
476 }
477 break;
478 case 'a':
479 {
480 C4ValueArray * a = v->_getArray();
481 pComp->Value(mkParAdapt(mkPtrAdaptNoNull(a), this));
482 if (deserializing) v->SetArray(a);
483 }
484 break;
485 default:
486 pComp->excCorrupt("Unexpected character '%c'", cC4VID);
487 break;
488 }
489 pComp->Separator(StdCompiler::SEP_END);
490 }
491
CompileFunc(StdCompiler * pComp)492 void C4ValueNumbers::CompileFunc(StdCompiler * pComp)
493 {
494 bool deserializing = pComp->isDeserializer();
495 bool fNaming = pComp->hasNaming();
496 if (deserializing)
497 {
498 uint32_t iSize;
499 if (!fNaming) pComp->Value(iSize);
500 // Read new
501 do
502 {
503 // No entries left to read?
504 if (!fNaming && !iSize--)
505 break;
506 // Read entries
507 try
508 {
509 LoadedValues.emplace_back();
510 CompileValue(pComp, &LoadedValues.back());
511 }
512 catch (StdCompiler::NotFoundException *pEx)
513 {
514 // No value found: Stop reading loop
515 delete pEx;
516 break;
517 }
518 }
519 while (pComp->Separator(StdCompiler::SEP_SEP));
520 }
521 else
522 {
523 // Note: the list grows during this loop due to nested data structures.
524 // Data structures with loops are fine because the beginning of the loop
525 // will be found in the map and not saved again.
526 // This may still work with the binary compilers due to double-compiling
527 if (!fNaming)
528 {
529 int32_t iSize = ValuesToSave.size();
530 pComp->Value(iSize);
531 }
532 for(std::list<C4Value *>::iterator i = ValuesToSave.begin(); i != ValuesToSave.end(); ++i)
533 {
534 CompileValue(pComp, *i);
535 if (i != ValuesToSave.end()) pComp->Separator(StdCompiler::SEP_SEP);
536 }
537 }
538 }
539
ComparisonImpl(const C4Value & Value1,const C4Value & Value2)540 inline bool ComparisonImpl(const C4Value &Value1, const C4Value &Value2)
541 {
542 C4V_Type Type1 = Value1.GetType();
543 C4V_Data Data1 = Value1.GetData();
544 C4V_Type Type2 = Value2.GetType();
545 C4V_Data Data2 = Value2.GetData();
546 switch (Type1)
547 {
548 case C4V_Nil:
549 assert(!Data1);
550 return Type1 == Type2;
551 case C4V_Int:
552 case C4V_Bool:
553 return (Type2 == C4V_Int || Type2 == C4V_Bool) &&
554 Data1.Int == Data2.Int;
555 case C4V_PropList:
556 return Type1 == Type2 && *Data1.PropList == *Data2.PropList;
557 case C4V_String:
558 return Type1 == Type2 && Data1.Str == Data2.Str;
559 case C4V_Array:
560 return Type1 == Type2 &&
561 (Data1.Array == Data2.Array || *(Data1.Array) == *(Data2.Array));
562 case C4V_Function:
563 return Type1 == Type2 && Data1.Fn == Data2.Fn;
564 default:
565 assert(!"Unexpected C4Value type (denumeration missing?)");
566 return Data1 == Data2;
567 }
568 }
569
operator ==(const C4Value & Value2) const570 bool C4Value::operator == (const C4Value& Value2) const
571 {
572 // recursion guard using a linked list of Seen structures on the stack
573 // NOT thread-safe
574 struct Seen
575 {
576 Seen *prev;
577 const C4Value *left;
578 const C4Value *right;
579 inline Seen(Seen *prev, const C4Value *left, const C4Value *right):
580 prev(prev), left(left), right(right) {}
581 inline bool operator == (const Seen& other)
582 {
583 return left == other.left && right == other.right;
584 }
585 inline bool recursion(Seen *new_top)
586 {
587 for (Seen *s = this; s; s = s->prev)
588 if (*s == *new_top)
589 return true;
590 return false;
591 }
592 inline Seen *first()
593 {
594 Seen *s = this;
595 while (s->prev) s = s->prev;
596 return s;
597 }
598 };
599 static Seen *top = nullptr;
600 Seen here(top, this, &Value2);
601
602 bool recursion = top && top->recursion(&here);
603 if (recursion)
604 {
605 Seen *first = top->first();
606 // GetDataString is fine for circular values
607 LogF("Caught infinite recursion comparing %s and %s",
608 first->left->GetDataString().getData(),
609 first->right->GetDataString().getData());
610 return false;
611 }
612 top = &here;
613 bool result = ComparisonImpl(*this, Value2);
614 top = here.prev;
615 return result;
616 }
617
operator !=(const C4Value & Value2) const618 bool C4Value::operator != (const C4Value& Value2) const
619 {
620 return !(*this == Value2);
621 }
622
GetTypeEx() const623 C4V_Type C4Value::GetTypeEx() const
624 {
625 // Return type including types derived from prop list types (such as C4V_Def)
626 if (Type == C4V_PropList)
627 {
628 if (FnCnvEffect()) return C4V_Effect;
629 if (FnCnvObject()) return C4V_Object;
630 if (FnCnvDef()) return C4V_Def;
631 }
632 return Type;
633 }
634
LogDeletedObjectWarning(C4PropList * p)635 void C4Value::LogDeletedObjectWarning(C4PropList * p)
636 {
637 if (p->GetPropListNumbered())
638 LogF("Warning: using deleted object (#%d) (%s)!", p->GetPropListNumbered()->Number, p->GetName());
639 else
640 LogF("Warning: using deleted proplist %p (%s)!", static_cast<void*>(p), p->GetName());
641 AulExec.LogCallStack();
642 }
643