engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / decorate / vc_decorate_ast.cpp
blobba1321276b9deb5eeeb37891fd17dd2e1769f9bb
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 // this directly included from "vc_decorate.cpp"
29 //==========================================================================
31 // VDecorateInvocation
33 //==========================================================================
34 class VDecorateInvocation : public VExpression {
35 public:
36 VName Name;
37 VMethod *Func; // if this is not null, use it instead of searching by `Name` (yet `Name` still be valid!)
38 int NumArgs;
39 VState *CallerState; // used for `Func`
40 VExpression *Args[VMethod::MAX_PARAMS+1];
42 VDecorateInvocation (VName AName, const TLocation &ALoc, int ANumArgs, VExpression *AArgs[]);
43 VDecorateInvocation (VMethod *AFunc, VName AName, const TLocation &ALoc, int ANumArgs, VExpression *AArgs[]);
44 virtual ~VDecorateInvocation () override;
45 virtual VExpression *SyntaxCopy () override;
46 virtual VExpression *DoResolve (VEmitContext &) override;
47 virtual void Emit (VEmitContext &) override;
49 virtual bool HasSideEffects () override;
50 virtual void VisitChildren (VExprVisitor *v) override;
52 virtual VStr toString () const override;
54 void FixKnownShit (VEmitContext &ec, const char *FuncName);
56 protected:
57 VDecorateInvocation () {}
58 virtual void DoSyntaxCopyTo (VExpression *e) override;
60 bool HasFloatArg (VEmitContext &ec);
64 //==========================================================================
66 // VDecorateUserVar
68 // this generates call to _get/_set uservar methods
70 //==========================================================================
71 class VDecorateUserVar : public VExpression {
72 public:
73 VStr fldname;
74 VExpression *index; // array index, if not nullptr
76 VDecorateUserVar (VStr afldname, const TLocation &aloc);
77 VDecorateUserVar (VStr afldname, VExpression *aindex, const TLocation &aloc);
78 virtual ~VDecorateUserVar () override;
79 virtual VExpression *SyntaxCopy () override;
80 virtual VExpression *DoResolve (VEmitContext &ec) override;
81 //virtual VExpression *ResolveAssignmentTarget (VEmitContext &ec) override;
82 virtual VExpression *ResolveCompleteAssign (VEmitContext &ec, VExpression *val, bool &resolved) override;
83 virtual void Emit (VEmitContext &ec) override;
84 virtual bool IsDecorateUserVar () const override;
85 virtual bool HasSideEffects () override;
86 virtual void VisitChildren (VExprVisitor *v) override;
87 virtual VStr toString () const override;
89 protected:
90 VDecorateUserVar () {}
91 virtual void DoSyntaxCopyTo (VExpression *e) override;
95 //==========================================================================
97 // VDecorateAJump
99 // as `A_Jump()` can have insane amounts of arguments, we'll generate
100 // VM code directly instead
102 //==========================================================================
103 class VDecorateAJump : public VExpression {
104 private:
105 //VExpression *xstc; // bool(`XLevel.StateCall`) access expression
106 //VExpression *xass; // XLevel.StateCall->Result = false
107 VExpression *crnd0; // first call to P_Random()
108 VExpression *crnd1; // second call to P_Random()
110 public:
111 VExpression *prob; // probability
112 TArray<VExpression *> labels; // jump labels
113 VState *CallerState;
115 public:
116 VDecorateAJump (const TLocation &aloc);
117 virtual ~VDecorateAJump () override;
118 virtual VExpression *SyntaxCopy () override;
119 virtual VExpression *DoResolve (VEmitContext &ec) override;
120 virtual void Emit (VEmitContext &ec) override;
121 virtual bool HasSideEffects () override;
122 virtual void VisitChildren (VExprVisitor *v) override;
123 virtual VStr toString () const override;
125 protected:
126 VDecorateAJump () {}
127 virtual void DoSyntaxCopyTo (VExpression *e) override;
132 //==========================================================================
134 // VDecorateRndPick
136 // as `[f]randompick()` can have insane amounts of arguments, we'll
137 // generate VM code directly instead
139 //==========================================================================
140 class VDecorateRndPick : public VExpression {
141 private:
142 VExpression *crnd0; // first call to P_Random()
144 public:
145 TArray<VExpression *> numbers; // jump labels
146 bool asFloat;
148 public:
149 VDecorateRndPick (bool aAsFloat, const TLocation &aloc);
150 virtual ~VDecorateRndPick () override;
151 virtual VExpression *SyntaxCopy () override;
152 virtual VExpression *DoResolve (VEmitContext &ec) override;
153 virtual void Emit (VEmitContext &ec) override;
154 virtual bool HasSideEffects () override;
155 virtual void VisitChildren (VExprVisitor *v) override;
156 virtual VStr toString () const override;
158 protected:
159 VDecorateRndPick () {}
160 virtual void DoSyntaxCopyTo (VExpression *e) override;
165 //==========================================================================
167 // tryStringAsFloat
169 //==========================================================================
170 static bool tryStringAsFloat (float &resf, const char *str) {
171 resf = 0.0f;
172 if (!str || !str[0]) return false;
173 if (VStr::convertFloat(str, &resf)) {
174 if (isFiniteF(resf)) {
175 return true;
178 resf = 0.0f;
179 return false;
183 //==========================================================================
185 // tryStringAsInt
187 //==========================================================================
188 static bool tryStringAsInt (int &resi, const char *str, const VFieldType &destType) {
189 // try int
190 resi = 0;
191 if (!str || !str[0]) return false;
192 // loose conversion
193 if (VStr::convertInt(str, &resi, true)) {
194 switch (destType.Type) {
195 case TYPE_Byte: resi = clampToByte(resi); break;
196 case TYPE_Bool: resi = (resi ? 1 : 0); break;
198 return true;
200 resi = 0;
201 return false;
205 //==========================================================================
207 // tryStringAsFloat2Int
209 //==========================================================================
210 static bool tryStringAsFloat2Int (int &resi, const char *str, const VFieldType &destType) {
211 resi = 0;
212 if (!str || !str[0]) return false;
213 // try float (sigh)
214 float resf = 0.0f;
215 if (VStr::convertFloat(str, &resf)) {
216 if (isFiniteF(resf)) {
217 // arbitrary limits
218 if (resf < -0x3fffffff) resi = -0x3fffffff;
219 else if (resf > +0x3fffffff) resi = +0x3fffffff;
220 else resi = (int)resf;
221 switch (destType.Type) {
222 case TYPE_Byte: resi = clampToByte(resi); break;
223 case TYPE_Bool: resi = (resi ? 1 : 0); break;
225 return true;
228 return false;
232 //==========================================================================
234 // VInvocation::MassageDecorateArg
236 // this will try to coerce some decorate argument to something sensible
238 // `argnum` starts with `1`
240 //==========================================================================
241 VExpression *VExpression::MassageDecorateArg (VEmitContext &ec, VInvocation *invokation, VState *CallerState, const char *funcName,
242 int argnum, const VFieldType &destType, bool isOptional,
243 const TLocation *aloc, bool *massaged)
245 //FIXME: move this to separate method
246 // simplify a little:
247 // replace `+number` with `number`
248 if (IsUnaryMath()) {
249 VUnary *un = (VUnary *)this;
250 if (un->op) {
251 if (un->Oper == VUnary::Plus && (un->op->IsIntConst() || un->op->IsFloatConst())) {
252 VExpression *enew = un->op;
253 //fprintf(stderr, "SIMPLIFIED! <%s> -> <%s>\n", *un->toString(), *etmp->toString());
254 un->op = nullptr;
255 delete this;
256 return enew->MassageDecorateArg(ec, invokation, CallerState, funcName, argnum, destType, isOptional, aloc, massaged);
261 // flags
262 // note that `invokation` may be `nullptr` if we called from `VDecorateAJump()`, for example
263 if (invokation) {
264 for (auto &&FlagName : invokation->Func->Params[argnum-1].NamedFlags) {
265 // hack for idiotic mod authors (hello, LCA!)
266 // [soundchannel]
267 if (FlagName == "soundchannel") {
268 if (destType.Type == TYPE_Int) {
269 if (IsStrConst() || IsNameConst() || IsDecorateSingleName()) {
270 VStr s = (IsDecorateSingleName() ? *((VDecorateSingleName *)this)->Name : IsStrConst() ? GetStrConst(ec.Package) : VStr(GetNameConst()));
271 int v = 0;
272 if (!VStr::convertInt(*s, &v)) {
273 // convert digits
274 const char *str = *s;
275 v = 0;
276 while (*str && digitInBase(*str, 10) < 0) ++str;
277 if (*str) {
278 while (*str) {
279 int d = digitInBase(*str, 10);
280 if (d < 0) break;
281 v = v*10+d;
282 ++str;
284 } else {
285 VStr t = s;
286 while (t.length()) {
287 if (t[0] == '_' || (vuint8)(t[0]) <= ' ') { t.chopLeft(1); continue; }
288 if (t.startsWithNoCase("CHANNEL")) { t.chopLeft(7); continue; }
289 if (t.startsWithNoCase("CHAN")) { t.chopLeft(4); continue; }
290 if (t.startsWithNoCase("SoundSlot")) { t.chopLeft(9); continue; }
291 if (t.startsWithNoCase("Slot")) { t.chopLeft(4); continue; }
292 if (t.startsWithNoCase("Sound")) { t.chopLeft(5); continue; }
293 break;
295 if (t.ICmp("Auto") == 0) v = 0;
296 else if (t.ICmp("Voice") == 0) v = 1;
297 else if (t.ICmp("Weapon") == 0) v = 2;
298 else if (t.ICmp("Item") == 0) v = 3;
299 else if (t.ICmp("Body") == 0) v = 4;
300 else v = 0;
303 if (v < 0 || v > 127) v = 0;
304 //GLog.Logf(NAME_Debug, "*** FUNC:`%s`; arg #%d (%s): soundchannel! '%s' -> %d", *Func->GetFullName(), i+1, *Func->Params[i].Name, *s, v);
305 VExpression *enew = new VIntLiteral(v, Loc);
306 delete this;
307 return enew->MassageDecorateArg(ec, invokation, CallerState, funcName, argnum, destType, isOptional, aloc, massaged);
310 continue;
312 // [color] or [color0]
313 if (FlagName == "color0" || FlagName == "color") {
314 if (destType.Type == TYPE_String) {
315 // integer zero? pass empty string
316 if (IsIntConst()) {
317 if (GetIntConst() == 0 && FlagName == "color0") {
318 VExpression *enew = new VStringLiteral(VStr(), ec.Package->FindString(""), Loc);
319 delete this;
320 return enew->MassageDecorateArg(ec, invokation, CallerState, funcName, argnum, destType, isOptional, aloc, massaged);
321 } else {
322 // hex colors
323 VStr s = va("#%06x", GetIntConst()&0xffffff);
324 VExpression *enew = new VStringLiteral(s, ec.Package->FindString(*s), Loc);
325 delete this;
326 return enew->MassageDecorateArg(ec, invokation, CallerState, funcName, argnum, destType, isOptional, aloc, massaged);
330 continue;
332 // unknown flag
333 ParseWarningAsError(Loc, "function `%s`, argument #%d has unknown argument flag `%s`!", funcName, argnum, *FlagName);
337 if (massaged) *massaged = true;
338 switch (destType.Type) {
339 case TYPE_Int:
340 case TYPE_Byte:
341 case TYPE_Float:
342 case TYPE_Bool:
343 if (IsStrConst()) {
344 VStr str = GetStrConst(ec.Package);
345 if (str.isEmpty() || str.strEquCI("none") || str.strEquCI("null") || str.strEquCI("nil") || str.strEquCI("false")) {
346 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be a number (replaced with 0); PLEASE, FIX THE CODE!", funcName, argnum);
347 VExpression *enew = new VIntLiteral(0, Loc);
348 delete this;
349 return enew;
351 if (str.strEquCI("true")) {
352 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be a number (replaced with 1); PLEASE, FIX THE CODE!", funcName, argnum);
353 VExpression *enew = new VIntLiteral(1, Loc);
354 delete this;
355 return enew;
357 // doomrl arsenal author is a... well, you know it
358 if (argnum == 3 && VStr::strEqu(funcName, "decorate_A_CheckFlag") && str.strEquCI("Nope")) {
359 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be an aptr (replaced with AAPTR_DEFAULT, mod author is a mo...dder)!", funcName, argnum);
360 VExpression *enew = new VIntLiteral(0, Loc);
361 delete this;
362 return enew;
364 // `A_SpawnParticle()` color
365 if (argnum == 1 && VStr::strEqu(funcName, "A_SpawnParticle")) {
366 vuint32 clr = M_ParseColor(*str, /*retZeroIfInvalid*/true);
367 if (clr == 0) {
368 ParseWarning((aloc ? *aloc : Loc), "color argument \"%s\" to `%s` is not a valid color (replaced with black)!", *str, funcName);
370 VExpression *enew = new VIntLiteral((vint32)clr, Loc);
371 delete this;
372 return enew;
374 // ok, try to convert the string to a number... please, Invisible Pink Unicorn, why did you created so many morons?!
375 if (destType.Type == TYPE_Float) {
376 float resf = 0.0f;
377 if (tryStringAsFloat(resf, *str)) {
378 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be `float`, not a string (coerced, mod author is a mo...dder)!", funcName, argnum);
379 VExpression *enew = new VFloatLiteral(resf, Loc);
380 delete this;
381 return enew;
383 } else {
384 // try int
385 int resi = 0;
386 if (tryStringAsInt(resi, *str, destType)) {
387 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be `%s`, not a string (coerced, mod author is a mo...dder)!", funcName, argnum, *destType.GetName());
388 VExpression *enew = new VIntLiteral(resi, Loc);
389 delete this;
390 return enew;
392 if (tryStringAsFloat2Int(resi, *str, destType)) {
393 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be `%s`, not a float-as-string (coerced, mod author is a mo...dder)!", funcName, argnum, *destType.GetName());
394 VExpression *enew = new VIntLiteral(resi, Loc);
395 delete this;
396 return enew;
400 // `A_SpawnParticle()` color
401 if (argnum == 1 && IsDecorateSingleName() && VStr::strEqu(funcName, "A_SpawnParticle")) {
402 VDecorateSingleName *e = (VDecorateSingleName *)this;
403 vuint32 clr = M_ParseColor(*e->Name, /*retZeroIfInvalid*/true);
404 if (clr == 0) {
405 ParseWarning((aloc ? *aloc : Loc), "color argument \"%s\" to `%s` is not a valid color (replaced with black)!", *e->Name, funcName);
407 VExpression *enew = new VIntLiteral((vint32)clr, Loc);
408 delete this;
409 return enew;
411 // none as literal?
412 if (IsNoneLiteral()) {
413 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be a number (replaced with 0); PLEASE, FIX THE CODE!", funcName, argnum);
414 VExpression *enew = new VIntLiteral(0, Loc);
415 delete this;
416 return enew;
419 if (IsDecorateSingleName()) {
420 VDecorateSingleName *e = (VDecorateSingleName *)this;
421 VStr str = VStr(e->Name);
422 // ok, try to convert the string to a number... please, Invisible Pink Unicorn, why did you created so many morons?!
423 if (destType.Type == TYPE_Float) {
424 float resf = 0.0f;
425 if (tryStringAsFloat(resf, *str)) {
426 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be `float`, not string (coerced, mod author is a mo...dder)!", funcName, argnum);
427 VExpression *enew = new VFloatLiteral(resf, Loc);
428 delete this;
429 return enew;
431 } else {
432 // try int
433 int resi = 0;
434 if (tryStringAsInt(resi, *str, destType)) {
435 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be `%s`, not string (coerced, mod author is a mo...dder)!", funcName, argnum, *destType.GetName());
436 VExpression *enew = new VIntLiteral(resi, Loc);
437 delete this;
438 return enew;
440 if (tryStringAsFloat2Int(resi, *str, destType)) {
441 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be `%s`, not float-as-string (coerced, mod author is a mo...dder)!", funcName, argnum, *destType.GetName());
442 VExpression *enew = new VIntLiteral(resi, Loc);
443 delete this;
444 return enew;
449 // convert string literal to identifier
450 if (IsStrConst()) {
451 VStr str = GetStrConst(ec.Package);
452 if (!str.isEmpty()) {
453 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be `%s`! (trying constant `%s`)", funcName, argnum, *destType.GetName(), *str);
454 VDecorateSingleName *enew = new VDecorateSingleName(str, Loc);
455 delete this;
456 return enew;
460 break;
462 case TYPE_Name:
463 // identifier?
464 if (IsDecorateSingleName()) {
465 VDecorateSingleName *e = (VDecorateSingleName *)this;
466 VExpression *enew = new VNameLiteral(*e->Name, Loc);
467 delete this;
468 return enew;
470 // string?
471 if (IsStrConst()) {
472 VStr val = GetStrConst(ec.Package);
473 VExpression *enew = new VNameLiteral(*val, Loc);
474 delete this;
475 return enew;
477 // integer zero?
478 if (IsIntConst() && GetIntConst() == 0) {
479 // "false" or "0" means "empty"
480 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be a string (replaced `0` with empty string); PLEASE, FIX THE CODE!", funcName, argnum);
481 VExpression *enew = new VNameLiteral(NAME_None, Loc);
482 delete this;
483 return enew;
485 // idiotic call (forgotten "damagetype")
486 if (invokation && argnum == invokation->NumArgs && argnum < 16 && IsIntConst() && (GetIntConst() == 1 || GetIntConst() == 0) &&
487 (VStr::strEqu(funcName, "A_CustomMeleeAttack") || VStr::strEqu(funcName, "A_CustomComboAttack")))
489 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be a DamageType (inserted default value); MOD AUTHOR IS A MO...DDER!", funcName, argnum);
490 VExpression *enew = new VNameLiteral(NAME_None, Loc);
491 // append "bleed"
492 invokation->Args[invokation->NumArgs] = new VIntLiteral(GetIntConst(), Loc);
493 ++invokation->NumArgs;
494 delete this;
495 return enew;
497 // none as literal?
498 if (IsNoneLiteral()) {
499 VExpression *enew = new VNameLiteral("none", Loc);
500 delete this;
501 return enew;
503 break;
505 case TYPE_String:
506 // identifier?
507 if (IsDecorateSingleName()) {
508 VDecorateSingleName *e = (VDecorateSingleName *)this;
509 VExpression *enew = new VStringLiteral(VStr(e->Name), ec.Package->FindString(*e->Name), Loc);
510 delete this;
511 return enew;
513 // integer zero?
514 if (IsIntConst() && GetIntConst() == 0) {
515 // "false" or "0" means "empty"
516 // don't warn for `A_CustomRailgun()`, number `0` seems to be a valid value there
518 if ((argnum == 3 || argnum == 4) && VStr::strEquCI(funcName, "A_CustomRailgun")) {
519 // do nothing
520 } else
523 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be a string (replaced `0` with empty string); PLEASE, FIX THE CODE!", funcName, argnum);
525 VExpression *enew = new VStringLiteral(VStr(), ec.Package->FindString(""), Loc);
526 delete this;
527 return enew;
529 // none as literal?
530 if (IsNoneLiteral()) {
531 VExpression *enew = new VStringLiteral("none", ec.Package->FindString(""), Loc);
532 delete this;
533 return enew;
535 // `A_CustomRailgun()` hex colors
537 if (IsIntConst() && (argnum == 3 || argnum == 4) && VStr::strEquCI(funcName, "A_CustomRailgun")) {
538 VStr s = va("#%06x", GetIntConst()&0xffffff);
539 VExpression *enew = new VStringLiteral(s, ec.Package->FindString(*s), Loc);
540 delete this;
541 return enew;
544 break;
546 case TYPE_Class:
547 // identifier?
548 if (IsDecorateSingleName()) {
549 VDecorateSingleName *e = (VDecorateSingleName *)this;
550 VExpression *enew = new VStringLiteral(VStr(e->Name), ec.Package->FindString(*e->Name), Loc);
551 delete this;
552 return enew->MassageDecorateArg(ec, invokation, CallerState, funcName, argnum, destType, isOptional, aloc, massaged);
554 // string?
555 if (IsStrConst()) {
556 VStr CName = GetStrConst(ec.Package);
557 VStr CNameStp = CName.xstrip();
558 //TLocation ALoc = Loc;
559 if (CNameStp.isEmpty() || CNameStp.strEquCI("None") || CNameStp.strEquCI("nil") || CNameStp.strEquCI("null")) {
560 //ParseWarning(ALoc, "NONE CLASS `%s`", CName);
561 VExpression *enew = new VNoneLiteral(Loc);
562 delete this;
563 return enew;
564 } else {
565 VClass *Cls = VClass::FindClassNoCase(*CName);
566 if (!Cls && CNameStp.length() && CNameStp.length() != CName.length()) Cls = VClass::FindClassNoCase(*CNameStp);
567 if (!Cls) {
568 if (!destType.Class) {
569 ParseWarningAsError((aloc ? *aloc : Loc), "No such class `%s` for argument #%d of `%s`", *CName, argnum, funcName);
570 VExpression *enew = new VNoneLiteral(Loc);
571 delete this;
572 return enew;
574 if (cli_ShowClassRTRouting > 0) ParseWarning((aloc ? *aloc : Loc), "No such class `%s` for argument #%d of `%s` (rt-routed)", *CName, argnum, funcName);
575 //k8: hack it with runtime class searching
577 VExpression *enew = new VNoneLiteral(Loc);
578 delete this;
579 return enew;
581 //GLog.Logf("********* %s (%s)", *destType.GetName(), (destType.Class ? destType.Class->GetName() : "<none>"));
582 VExpression *TmpArgs[2];
583 TmpArgs[0] = new VClassConstant(destType.Class, Loc);
584 TmpArgs[1] = new VNameLiteral(VName(*CName), Loc);
585 VExpression *enew = new VInvocation(nullptr, ec.SelfClass->FindMethodChecked("FindClassNoCaseEx"), nullptr, false, false, Loc, 2, TmpArgs);
586 delete this;
587 return enew;
589 if (destType.Class && !Cls->IsChildOf(destType.Class)) {
590 // exception: allow `RandomSpawner` for inventories
591 bool stillError = true;
592 if (argnum == 1 && VStr::strEqu("A_GiveInventory", funcName)) {
593 static VClass *invCls = nullptr;
594 if (invCls == nullptr) { invCls = VClass::FindClass("Inventory"); vassert(invCls); }
595 static VClass *rndCls = nullptr;
596 if (rndCls == nullptr) { rndCls = VClass::FindClass("RandomSpawner"); vassert(rndCls); }
597 if (destType.Class->IsChildOf(invCls) && Cls->IsChildOf(rndCls)) {
598 stillError = false;
599 ParseWarningAsError((aloc ? *aloc : Loc), "using class `%s` (RandomSpawner) for argument #%d of `%s`", Cls->GetName(), argnum, funcName);
602 if (stillError) {
603 ParseWarningAsError((aloc ? *aloc : Loc), "Class `%s` is not a descendant of `%s` for argument #%d of `%s`", *CName, destType.Class->GetName(), argnum, funcName);
604 //for (VClass *cc = Cls; cc; cc = cc->GetSuperClass()) GLog.Logf(NAME_Debug, " %s", cc->GetName());
605 VExpression *enew = new VNoneLiteral(Loc);
606 delete this;
607 return enew;
610 VExpression *enew = new VClassConstant(Cls, Loc);
611 delete this;
612 return enew;
614 break;
616 // integer zero?
617 if (IsIntConst() && GetIntConst() == 0) {
618 // "false" or "0" means "empty"
619 ParseWarningAsError((aloc ? *aloc : Loc), "`%s` argument #%d should be class (replaced with `none`); PLEASE, FIX THE CODE!", funcName, argnum);
620 VExpression *enew = new VNoneLiteral(Loc);
621 delete this;
622 return enew;
624 break;
626 case TYPE_State:
627 // some very bright person does this: `A_JumpIfTargetInLOS("1")` -- brilliant!
628 // string?
629 if (IsStrConst() || IsDecorateSingleName()) {
630 const TLocation ALoc = Loc;
631 VStr lblName;
632 if (IsStrConst()) lblName = GetStrConst(ec.Package); else { VDecorateSingleName *e = (VDecorateSingleName *)this; lblName = VStr(e->Name); }
633 lblName = lblName.xstrip();
634 // any special names will be resolved by rt-router
635 // try to convert a string to a number
636 int lbl = -1;
637 // string-as-a-number?
638 if (lblName.convertInt(&lbl)) {
639 // try to find the corresponding label
640 bool useNumber = true;
641 if (ec.SelfClass) {
642 VStateLabel *StLbl = ec.SelfClass->FindStateLabel(VName(*lblName), NAME_None, true);
643 if (StLbl) {
644 ParseWarningAsError((aloc ? *aloc : ALoc), "do not use numeric labels in `%s` (argument #%d, label '%s')", funcName, argnum, *lblName);
645 useNumber = false;
648 if (useNumber) {
649 if (lbl < 0) {
650 ParseError((aloc ? *aloc : ALoc), "Negative state jumps are not allowed (function `%s`, argument #%d)", funcName, argnum);
651 delete this;
652 return nullptr;
654 ParseWarningAsError((aloc ? *aloc : ALoc), "`%s` argument #%d should be number %d instead of string \"%s\"; PLEASE, FIX THE CODE!", funcName, argnum, lbl, *lblName.quote());
655 VExpression *TmpArgs[2];
656 TmpArgs[0] = new VIntLiteral(lbl, ALoc);
657 TmpArgs[1] = new VIntLiteral(GetRTRouteUId(), ALoc);
658 delete this;
659 return new VInvocation(nullptr, ec.SelfClass->FindMethodChecked("FindJumpStateOfs"), nullptr, false, false, ALoc, 2, TmpArgs);
662 //k8: don't resolve any "::" here, our dynamic resolver will do this for us
663 // it's a virtual state jump
664 //ParseWarning(Args[i]->Loc, "***VSJMP `%s`: <%s>", Func->GetName(), *lblName);
665 VExpression *TmpArgs[2];
666 //FIXME: support user arrays here!
667 if (lblName.startsWithCI("user_")) {
668 //ParseWarningAsError(ALoc, "uservars for state jumps are not supported (arg #%d for action `%s`). please, don't do this, this is a misfeature!", argnum, funcName);
669 // uservar; route with `FindJumpStateOfs()`
670 //GCon->Logf(NAME_Debug, "DECORATE: %s: routing uservar (%s) state with `FindJumpStateOfs()`", *ALoc.toStringNoCol(), *lblName);
671 delete this;
672 TmpArgs[0] = new VDecorateUserVar(lblName, ALoc);
673 TmpArgs[1] = new VIntLiteral(GetRTRouteUId(), ALoc);
674 return new VInvocation(nullptr, ec.SelfClass->FindMethodChecked("FindJumpStateOfs"), nullptr, false, false, ALoc, 2, TmpArgs);
675 } else {
676 // final state FindJumpState (name Label, int uniqueId);
677 //TmpArgs[0] = this;
678 delete this;
679 TmpArgs[0] = new VNameLiteral(VName(*lblName), ALoc);
680 TmpArgs[1] = new VIntLiteral(GetRTRouteUId(), ALoc);
681 return new VInvocation(nullptr, ec.SelfClass->FindMethodChecked("FindJumpState"), nullptr, false, false, ALoc, 2, TmpArgs);
684 // integer?
685 if (IsIntConst()) {
686 const TLocation ALoc = Loc;
687 int Offs = GetIntConst();
688 if (Offs < 0) {
689 ParseError((aloc ? *aloc : ALoc), "Negative state jumps are not allowed (function `%s`, argument #%d)", funcName, argnum);
690 delete this;
691 return nullptr;
693 VExpression *TmpArgs[2];
694 TmpArgs[0] = this;
695 TmpArgs[1] = new VIntLiteral(GetRTRouteUId(), ALoc);
696 return new VInvocation(nullptr, ec.SelfClass->FindMethodChecked("FindJumpStateOfs"), nullptr, false, false, ALoc, 2, TmpArgs);
698 // none as literal? this usually means "no jump"
699 // but let rt-router decide
700 if (IsNoneLiteral()) {
701 const TLocation ALoc = Loc;
702 VExpression *TmpArgs[2];
703 // final state FindJumpState (name Label, int uniqueId);
704 delete this;
705 TmpArgs[0] = new VNameLiteral(VName("none"), ALoc);
706 TmpArgs[1] = new VIntLiteral(GetRTRouteUId(), ALoc);
707 return new VInvocation(nullptr, ec.SelfClass->FindMethodChecked("FindJumpState"), nullptr, false, false, ALoc, 2, TmpArgs);
709 // support things like `A_Jump(n, func())`
710 if (Type.Type != TYPE_State) {
711 const TLocation ALoc = Loc;
712 //GCon->Logf("A_Jump: type=%s; expr=<%s>", *lbl->Type.GetName(), *lbl->toString());
713 VGagErrors gag;
714 VExpression *lx = this->SyntaxCopy()->Resolve(ec);
715 if (lx) {
716 if (lx->Type.Type != TYPE_State) {
717 const bool isGoodType = (lx->Type.Type == TYPE_Int || lx->Type.Type == TYPE_Byte || lx->Type.Type == TYPE_Bool || lx->Type.Type == TYPE_Float);
718 if (isGoodType) {
719 //GCon->Logf("A_Jump: type=%s; expr=<%s>", *lbl->Type.GetName(), *lbl->toString());
720 VExpression *TmpArgs[2];
721 TmpArgs[0] = this->SyntaxCopy();
722 if (lx->Type.Type == TYPE_Float) {
723 ParseWarningAsError(ALoc, "jump offset argument #%d for `%s` should be integer, not float! PLEASE, FIX THE CODE!", argnum, funcName);
724 TmpArgs[0] = new VScalarToInt(TmpArgs[0], false); // not resolved
726 TmpArgs[1] = new VIntLiteral(GetRTRouteUId(), ALoc);
727 VExpression *eres = new VInvocation(nullptr, ec.SelfClass->FindMethodChecked("FindJumpStateOfs"), nullptr, false, false, ALoc, 2, TmpArgs);
728 //GCon->Logf(" NEW: type=%s; expr=<%s>", *lbl->Type.GetName(), *lbl->toString());
729 delete this;
730 return eres;
733 delete lx;
736 break;
738 if (massaged) *massaged = false;
739 return this;
743 //==========================================================================
745 // VDecorateInvocation::VDecorateInvocation
747 //==========================================================================
748 VDecorateInvocation::VDecorateInvocation (VName AName, const TLocation &ALoc, int ANumArgs, VExpression **AArgs)
749 : VExpression(ALoc)
750 , Name(AName)
751 , Func(nullptr)
752 , NumArgs(ANumArgs)
753 , CallerState(nullptr)
755 memset(Args, 0, sizeof(Args));
756 for (int i = 0; i < NumArgs; ++i) Args[i] = AArgs[i];
760 //==========================================================================
762 // VDecorateInvocation::VDecorateInvocation
764 //==========================================================================
765 VDecorateInvocation::VDecorateInvocation (VMethod *AFunc, VName AName, const TLocation &ALoc, int ANumArgs, VExpression *AArgs[])
766 : VExpression(ALoc)
767 , Name(AName)
768 , Func(AFunc)
769 , NumArgs(ANumArgs)
770 , CallerState(nullptr)
772 memset(Args, 0, sizeof(Args));
773 for (int i = 0; i < NumArgs; ++i) Args[i] = AArgs[i];
777 //==========================================================================
779 // VDecorateInvocation::~VDecorateInvocation
781 //==========================================================================
782 VDecorateInvocation::~VDecorateInvocation () {
783 for (int i = 0; i < NumArgs; ++i) {
784 if (Args[i]) {
785 delete Args[i];
786 Args[i] = nullptr;
792 //==========================================================================
794 // VDecorateInvocation::HasSideEffects
796 //==========================================================================
797 bool VDecorateInvocation::HasSideEffects () {
798 return true;
802 //==========================================================================
804 // VDecorateInvocation::VisitChildren
806 //==========================================================================
807 void VDecorateInvocation::VisitChildren (VExprVisitor *v) {
808 for (int f = 0; !v->stopIt && f < NumArgs; f += 1) {
809 if (Args[f]) Args[f]->Visit(v);
814 //==========================================================================
816 // VDecorateInvocation::toString
818 //==========================================================================
819 VStr VDecorateInvocation::toString () const {
820 VStr res = *Name;
821 res += "(";
822 for (int f = 0; f < NumArgs; ++f) {
823 if (f != 0) res += ", ";
824 if (Args[f]) res += Args[f]->toString(); else res += "default";
826 res += ")";
827 return res;
831 //==========================================================================
833 // VDecorateInvocation::SyntaxCopy
835 //==========================================================================
836 VExpression *VDecorateInvocation::SyntaxCopy () {
837 auto res = new VDecorateInvocation();
838 DoSyntaxCopyTo(res);
839 return res;
843 //==========================================================================
845 // VDecorateInvocation::DoSyntaxCopyTo
847 //==========================================================================
848 void VDecorateInvocation::DoSyntaxCopyTo (VExpression *e) {
849 VExpression::DoSyntaxCopyTo(e);
850 auto res = (VDecorateInvocation *)e;
851 memset(res->Args, 0, sizeof(res->Args));
852 res->Name = Name;
853 res->NumArgs = NumArgs;
854 for (int f = 0; f < NumArgs; ++f) res->Args[f] = (Args[f] ? Args[f]->SyntaxCopy() : nullptr);
858 //==========================================================================
860 // VDecorateInvocation::HasFloatArg
862 //==========================================================================
863 bool VDecorateInvocation::HasFloatArg (VEmitContext &ec) {
864 for (int i = 0; i < NumArgs; ++i) {
865 if (!Args[i] || Args[i]->IsDefaultArg()) continue;
866 VGagErrors gag;
867 VExpression *e = Args[i]->SyntaxCopy()->Resolve(ec);
868 const bool isFloat = (e && e->Type.Type == TYPE_Float);
869 delete e;
870 if (isFloat) return true;
872 return false;
876 //==========================================================================
878 // VDecorateInvocation::DoResolve
880 //==========================================================================
881 void VDecorateInvocation::FixKnownShit (VEmitContext &ec, const char *FuncName) {
882 // a lot of dumbfucks cannot into wiki
883 if (NumArgs == 4 && Args[3] && VStr::strEquCI(FuncName, "A_CustomMeleeAttack")) {
884 VExpression *e;
886 VGagErrors gag;
887 e = Args[3]->SyntaxCopy()->Resolve(ec);
889 bool doit = (e && e->Type.CheckMatch(false/*asRef*/, e->Loc, VFieldType(TYPE_Bool), false/*raiseError*/));
890 delete e;
891 if (doit) {
892 // missing argument; morons!
893 ParseWarningAsError(Loc, "`%s` is missing argument `missound`; who cares about stupid wikis?!", FuncName);
894 // insert dummy empty string argument
895 for (int f = 3; f >= 2; --f) Args[f+1] = Args[f];
896 VStr ns = VStr("");
897 int val = ec.Package->FindString(*ns);
898 Args[2] = new VStringLiteral(ns, val, Args[2]->Loc);
899 ++NumArgs;
900 //GCon->Logf(NAME_Debug, "*** %s", *this->toString());
902 return;
907 //==========================================================================
909 // VDecorateInvocation::DoResolve
911 //==========================================================================
912 VExpression *VDecorateInvocation::DoResolve (VEmitContext &ec) {
913 if (ec.SelfClass) {
914 //FIXME: sanitize this!
915 VMethod *M = Func;
916 if (!M) {
917 if (VStr::strEquCI(*Name, "va") ||
918 VStr::strEquCI(*Name, "fmin") || VStr::strEquCI(*Name, "fmax") ||
919 VStr::strEquCI(*Name, "fclamp") || VStr::strEquCI(*Name, "fabs"))
921 Name = Name.GetLower();
922 M = ec.SelfClass->FindMethod(Name);
923 } else if (VStr::strEquCI(*Name, "min") || VStr::strEquCI(*Name, "max") ||
924 VStr::strEquCI(*Name, "clamp") || VStr::strEquCI(*Name, "abs"))
926 // determine if we want an integer one
927 if (HasFloatArg(ec)) {
928 VStr fname = VStr("f")+(*Name);
929 Name = VName(*fname, VName::AddLower);
930 } else {
931 Name = Name.GetLower();
933 M = ec.SelfClass->FindMethod(Name);
934 } else {
935 M = ec.SelfClass->FindDecorateStateAction(*Name);
939 if (M) {
940 if (M->Flags&FUNC_Iterator) {
941 ParseError(Loc, "Iterator methods can only be used in foreach statements (method '%s', class '%s')", *Name, *ec.SelfClass->GetFullName());
942 delete this;
943 return nullptr;
946 FixKnownShit(ec, *Name);
948 // rebuild min/max with more than two arguments into recursive expression (for now)
949 //FIXME: generate better VM code for this
950 if (NumArgs > 2 && (VStr::strEquCI(*Name, "fmin") || VStr::strEquCI(*Name, "fmax") ||
951 VStr::strEquCI(*Name, "min") || VStr::strEquCI(*Name, "max")))
953 VExpression *e = new VInvocation(nullptr, M, nullptr, false, false, Loc, 2, Args+NumArgs-2);
954 if (Func && CallerState) ((VInvocation *)e)->CallerState = CallerState;
955 int currlhs = NumArgs-3;
956 for (; currlhs >= 0; --currlhs) {
957 VExpression *newargs[2];
958 newargs[0] = Args[currlhs];
959 newargs[1] = e;
960 e = new VInvocation(nullptr, M, nullptr, false, false, Loc, 2, newargs);
961 if (Func && CallerState) ((VInvocation *)e)->CallerState = CallerState;
963 //GCon->Logf(NAME_Debug, "::: %s", *e->toString()); abort();
964 NumArgs = 0;
965 delete this;
966 return e->Resolve(ec);
969 VExpression *e = new VInvocation(nullptr, M, nullptr, false, false, Loc, NumArgs, Args);
970 if (Func && CallerState) ((VInvocation *)e)->CallerState = CallerState;
971 NumArgs = 0;
972 delete this;
973 return e->Resolve(ec);
977 if (ec.SelfClass) {
978 if (VStr::strEquCI(*Name, "A_WeaponOffset") || VStr::strEquCI(*Name, "A_Overlay")) {
979 ParseWarning(Loc, "Unknown decorate action `%s` in class `%s`", *Name, *ec.SelfClass->GetFullName());
980 VExpression *e = new VIntLiteral(0, Loc);
981 delete this;
982 return e->Resolve(ec);
984 ParseError(Loc, "Unknown decorate action `%s` in class `%s`", *Name, *ec.SelfClass->GetFullName());
985 } else {
986 ParseError(Loc, "Unknown decorate action `%s`", *Name);
989 delete this;
990 return nullptr;
995 //==========================================================================
997 // VDecorateInvocation::Emit
999 //==========================================================================
1000 void VDecorateInvocation::Emit (VEmitContext &) {
1001 ParseError(Loc, "Should not happen");
1005 //==========================================================================
1007 // VDecorateUserVar::VDecorateUserVar
1009 //==========================================================================
1010 VDecorateUserVar::VDecorateUserVar (VStr afldname, const TLocation &aloc)
1011 : VExpression(aloc)
1012 , fldname(afldname)
1013 , index(nullptr)
1018 //==========================================================================
1020 // VDecorateUserVar::VDecorateUserVar
1022 //==========================================================================
1023 VDecorateUserVar::VDecorateUserVar (VStr afldname, VExpression *aindex, const TLocation &aloc)
1024 : VExpression(aloc)
1025 , fldname(afldname)
1026 , index(aindex)
1031 //==========================================================================
1033 // VDecorateUserVar::~VDecorateUserVar
1035 //==========================================================================
1036 VDecorateUserVar::~VDecorateUserVar () {
1037 delete index; index = nullptr;
1038 //fldname.clear(); // just4fun
1042 //==========================================================================
1044 // VDecorateUserVar::HasSideEffects
1046 //==========================================================================
1047 bool VDecorateUserVar::HasSideEffects () {
1048 return (index && index->HasSideEffects());
1052 //==========================================================================
1054 // VDecorateUserVar::VisitChildren
1056 //==========================================================================
1057 void VDecorateUserVar::VisitChildren (VExprVisitor *v) {
1058 if (!v->stopIt && index) index->Visit(v);
1062 //==========================================================================
1064 // VDecorateUserVar::SyntaxCopy
1066 //==========================================================================
1067 VExpression *VDecorateUserVar::SyntaxCopy () {
1068 auto res = new VDecorateUserVar();
1069 DoSyntaxCopyTo(res);
1070 return res;
1074 //==========================================================================
1076 // VDecorateUserVar::DoSyntaxCopyTo
1078 //==========================================================================
1079 void VDecorateUserVar::DoSyntaxCopyTo (VExpression *e) {
1080 VExpression::DoSyntaxCopyTo(e);
1081 auto res = (VDecorateUserVar *)e;
1082 res->fldname = fldname;
1083 res->index = (index ? index->SyntaxCopy() : nullptr);
1087 //==========================================================================
1089 // VDecorateUserVar::DoResolve
1091 //==========================================================================
1092 VExpression *VDecorateUserVar::DoResolve (VEmitContext &ec) {
1093 if (!ec.SelfClass) Sys_Error("VDecorateUserVar::DoResolve: internal compiler error");
1094 //GLog.Logf(NAME_Debug, "%s: user field '%s'", *Loc.toString(), *fldname);
1095 VName fldnamelo = (fldname.isEmpty() ? NAME_None : VName(*fldname, VName::AddLower));
1096 VName fldn = ec.SelfClass->FindDecorateStateFieldTrans(fldnamelo);
1097 if (fldn == NAME_None) {
1098 ParseError(Loc, "field `%s` is not found in class `%s`", *fldname, *ec.SelfClass->GetFullName());
1099 delete this;
1100 return nullptr;
1102 // use checked field access
1103 VField *fld = ec.SelfClass->FindField(fldn);
1104 if (!fld) {
1105 ParseError(Loc, "field `%s` => `%s` is not found in class `%s`", *fldname, *fldn, *ec.SelfClass->GetFullName());
1106 delete this;
1107 return nullptr;
1109 VFieldType ftype = fld->Type;
1110 if (ftype.Type == TYPE_Array) ftype = ftype.GetArrayInnerType();
1111 VName mtname = (ftype.Type == TYPE_Int ? "_get_user_var_int" : "_get_user_var_float");
1112 VMethod *mt = ec.SelfClass->FindMethod(mtname);
1113 if (!mt) {
1114 ParseError(Loc, "internal method `%s` not found in class `%s`", *mtname, *ec.SelfClass->GetFullName());
1115 delete this;
1116 return nullptr;
1118 VExpression *args[2];
1119 args[0] = new VNameLiteral(fldn, Loc);
1120 args[1] = index;
1121 VExpression *e = new VInvocation(nullptr, mt, nullptr, false, false, Loc, 2, args);
1122 index = nullptr;
1123 delete this;
1124 return e->Resolve(ec);
1128 //==========================================================================
1130 // VDecorateUserVar::ResolveCompleteAssign
1132 // this will be called before actual assign resolving
1133 // return `nullptr` to indicate error, or consume `val` and set `resolved`
1134 // to `true` if resolved
1135 // if `nullptr` is returned, both `this` and `val` should be destroyed
1137 //==========================================================================
1138 VExpression *VDecorateUserVar::ResolveCompleteAssign (VEmitContext &ec, VExpression *val, bool &resolved) {
1139 if (!ec.SelfClass) Sys_Error("VDecorateUserVar::DoResolve: internal compiler error");
1140 if (!val) { delete this; return nullptr; }
1141 resolved = true; // anyway
1142 VName fldnamelo = (fldname.isEmpty() ? NAME_None : VName(*fldname, VName::AddLower));
1143 VName fldn = ec.SelfClass->FindDecorateStateFieldTrans(fldnamelo);
1144 if (fldn == NAME_None) {
1145 ParseError(Loc, "field `%s` is not found in class `%s`", *fldname, *ec.SelfClass->GetFullName());
1146 delete val;
1147 delete this;
1148 return nullptr;
1150 VField *fld = ec.SelfClass->FindField(fldn);
1151 if (!fld) {
1152 ParseError(Loc, "field `%s` => `%s` is not found in class `%s`", *fldname, *fldn, *ec.SelfClass->GetFullName());
1153 delete val;
1154 delete this;
1155 return nullptr;
1157 VFieldType ftype = fld->Type;
1158 if (ftype.Type == TYPE_Array) ftype = ftype.GetArrayInnerType();
1159 VName mtname = (ftype.Type == TYPE_Int ? "_set_user_var_int" : "_set_user_var_float");
1160 VMethod *mt = ec.SelfClass->FindMethod(mtname);
1161 if (!mt) {
1162 ParseError(Loc, "internal method `%s` not found in class `%s`", *mtname, *ec.SelfClass->GetFullName());
1163 delete val;
1164 delete this;
1165 return nullptr;
1167 VExpression *args[3];
1168 args[0] = new VNameLiteral(fldn, Loc);
1169 args[1] = val;
1170 args[2] = index;
1171 VExpression *e = new VInvocation(nullptr, mt, nullptr, false, false, Loc, 3, args);
1172 index = nullptr;
1173 //GLog.Logf(NAME_Debug, "**** %s: assign user field '%s' (%s : %s)", *Loc.toString(), *fldname, *fldnamelo, *fldn);
1174 delete this;
1175 return e->Resolve(ec);
1179 //==========================================================================
1181 // VDecorateUserVar::Emit
1183 //==========================================================================
1184 void VDecorateUserVar::Emit (VEmitContext &/*ec*/) {
1185 Sys_Error("VDecorateUserVar::Emit: the thing that should not be!");
1189 //==========================================================================
1191 // VDecorateUserVar::toString
1193 //==========================================================================
1194 VStr VDecorateUserVar::toString () const {
1195 VStr res = VStr(fldname);
1196 if (index) { res += "["; res += index->toString(); res += "]"; }
1197 return res;
1201 //==========================================================================
1203 // VDecorateUserVar::IsDecorateUserVar
1205 //==========================================================================
1206 bool VDecorateUserVar::IsDecorateUserVar () const {
1207 return true;
1211 //==========================================================================
1213 // VDecorateSingleName::VDecorateSingleName
1215 //==========================================================================
1216 VDecorateSingleName::VDecorateSingleName (VStr AName, const TLocation &ALoc)
1217 : VExpression(ALoc)
1218 , Name(AName)
1220 // check for local
1221 if (decoIsLocalName(AName)) {
1222 localAccess = true;
1223 //GCon->Logf(NAME_Debug, "%s: LOCAL: `%s`", *Loc.toStringNoCol(), *AName);
1224 } else {
1225 localAccess = false;
1226 //GCon->Logf(NAME_Debug, "%s: USER: `%s`", *Loc.toStringNoCol(), *AName);
1231 //==========================================================================
1233 // VDecorateSingleName::VDecorateSingleName
1235 //==========================================================================
1236 VDecorateSingleName::VDecorateSingleName () {
1240 //==========================================================================
1242 // VDecorateSingleName::HasSideEffects
1244 //==========================================================================
1245 bool VDecorateSingleName::HasSideEffects () {
1246 return false;
1250 //==========================================================================
1252 // VDecorateSingleName::VisitChildren
1254 //==========================================================================
1255 void VDecorateSingleName::VisitChildren (VExprVisitor *v) {
1259 //==========================================================================
1261 // VDecorateSingleName::toString
1263 //==========================================================================
1264 VStr VDecorateSingleName::toString () const {
1265 return VStr(Name);
1269 //==========================================================================
1271 // VDecorateSingleName::SyntaxCopy
1273 //==========================================================================
1274 VExpression *VDecorateSingleName::SyntaxCopy () {
1275 auto res = new VDecorateSingleName();
1276 DoSyntaxCopyTo(res);
1277 return res;
1281 //==========================================================================
1283 // VDecorateSingleName::DoSyntaxCopyTo
1285 //==========================================================================
1286 void VDecorateSingleName::DoSyntaxCopyTo (VExpression *e) {
1287 VExpression::DoSyntaxCopyTo(e);
1288 auto res = (VDecorateSingleName *)e;
1289 res->Name = Name;
1290 res->localAccess = localAccess;
1294 //==========================================================================
1296 // VDecorateSingleName::DoResolve
1298 //==========================================================================
1299 VExpression *VDecorateSingleName::DoResolve (VEmitContext &ec) {
1300 // local var?
1301 if (localAccess) {
1302 VExpression *e = new VSingleName(VName(*Name, VName::AddLower), Loc);
1303 delete this;
1304 return e->Resolve(ec);
1307 // route uservar
1308 if (VStr::startsWithCI(*Name, "user_")) {
1309 VExpression *e = new VDecorateUserVar(Name, Loc);
1310 delete this;
1311 return e->Resolve(ec);
1314 //GLog.Logf(NAME_Debug, "%s: field '%s'", *Loc.toString(), *Name);
1315 if (ec.SelfClass) {
1316 VName ExtName = va("decorate_%s", *Name.toLowerCase());
1318 // prefixed constant
1319 VConstant *Const = ec.SelfClass->FindConstant(ExtName);
1320 if (Const) {
1321 VExpression *e = new VConstantValue(Const, Loc);
1322 delete this;
1323 return e->Resolve(ec);
1326 // prefixed property
1327 VProperty *Prop = ec.SelfClass->FindDecorateProperty(Name);
1328 if (Prop) {
1329 if (!Prop->GetFunc) {
1330 ParseError(Loc, "Property `%s` cannot be read", *Name);
1331 delete this;
1332 return nullptr;
1334 VExpression *e = new VInvocation(nullptr, Prop->GetFunc, nullptr, false, false, Loc, 0, nullptr);
1335 delete this;
1336 return e->Resolve(ec);
1340 // non-prefixed constant
1341 // look only for constants defined in DECORATE scripts (and in the current class)
1343 VConstant *Const = nullptr;
1344 if (ec.SelfClass) {
1345 Const = ec.SelfClass->FindDecorateConstant(Name);
1346 } else {
1347 // try some other classes
1348 if (!Const && ActorClass) Const = ActorClass->FindDecorateConstant(Name);
1349 if (!Const && FakeInventoryClass) Const = FakeInventoryClass->FindDecorateConstant(Name);
1350 if (!Const && InventoryClass) Const = InventoryClass->FindDecorateConstant(Name);
1351 if (!Const && AmmoClass) Const = AmmoClass->FindDecorateConstant(Name);
1352 if (!Const && WeaponClass) Const = WeaponClass->FindDecorateConstant(Name);
1353 if (!Const && PlayerPawnClass) Const = PlayerPawnClass->FindDecorateConstant(Name);
1355 if (!Const) {
1356 VName CheckName = VName(*Name, VName::AddLower);
1357 Const = (ec.SelfClass ? ec.SelfClass->FindPackageConstant(ec.Package, CheckName) : nullptr);
1358 if (!Const) Const = ec.Package->FindConstant(CheckName);
1360 if (Const) {
1361 VExpression *e = new VConstantValue(Const, Loc);
1362 delete this;
1363 return e->Resolve(ec);
1367 // paren-less decorate method?
1368 if (ec.SelfClass) {
1369 VMethod *M = ec.SelfClass->FindDecorateStateAction(*Name);
1370 if (M) {
1371 VExpression *e = new VDecorateInvocation(M, *Name, Loc, 0, nullptr);
1372 delete this;
1373 return e->Resolve(ec);
1377 ParseError(Loc, "Illegal expression identifier `%s`", *Name);
1378 delete this;
1379 return nullptr;
1383 //==========================================================================
1385 // VDecorateSingleName::Emit
1387 //==========================================================================
1388 void VDecorateSingleName::Emit (VEmitContext &) {
1389 ParseError(Loc, "Should not happen");
1393 //==========================================================================
1395 // VDecorateSingleName::IsDecorateSingleName
1397 //==========================================================================
1398 bool VDecorateSingleName::IsDecorateSingleName () const {
1399 return true;
1404 //==========================================================================
1406 // VDecorateAJump::VDecorateAJump
1408 //==========================================================================
1409 VDecorateAJump::VDecorateAJump (const TLocation &aloc)
1410 : VExpression(aloc)
1411 //, xstc(nullptr)
1412 //, xass(nullptr)
1413 , crnd0(nullptr)
1414 , crnd1(nullptr)
1415 , prob(nullptr)
1416 , labels()
1417 , CallerState(nullptr)
1422 //==========================================================================
1424 // VDecorateAJump::~VDecorateAJump
1426 //==========================================================================
1427 VDecorateAJump::~VDecorateAJump () {
1428 //delete xstc; xstc = nullptr;
1429 //delete xass; xass = nullptr;
1430 delete crnd0; crnd0 = nullptr;
1431 delete crnd1; crnd1 = nullptr;
1432 delete prob; prob = nullptr;
1433 for (int f = labels.length()-1; f >= 0; --f) delete labels[f];
1434 labels.clear();
1435 CallerState = nullptr;
1439 //==========================================================================
1441 // VDecorateAJump::HasSideEffects
1443 //==========================================================================
1444 bool VDecorateAJump::HasSideEffects () {
1445 return true;
1449 //==========================================================================
1451 // VDecorateAJump::VisitChildren
1453 //==========================================================================
1454 void VDecorateAJump::VisitChildren (VExprVisitor *v) {
1455 if (!v->stopIt && crnd0) crnd0->Visit(v);
1456 if (!v->stopIt && crnd1) crnd1->Visit(v);
1457 if (!v->stopIt && prob) prob->Visit(v);
1458 for (int f = 0; !v->stopIt && f < labels.length(); f += 1) {
1459 labels[f]->Visit(v);
1464 //==========================================================================
1466 // VDecorateAJump::SyntaxCopy
1468 //==========================================================================
1469 VExpression *VDecorateAJump::SyntaxCopy () {
1470 auto res = new VDecorateAJump();
1471 DoSyntaxCopyTo(res);
1472 return res;
1476 //==========================================================================
1478 // VDecorateAJump::DoSyntaxCopyTo
1480 //==========================================================================
1481 void VDecorateAJump::DoSyntaxCopyTo (VExpression *e) {
1482 VExpression::DoSyntaxCopyTo(e);
1483 auto res = (VDecorateAJump *)e;
1484 res->prob = (prob ? prob->SyntaxCopy() : nullptr);
1485 res->labels.setLength(labels.length());
1486 for (int f = 0; f < labels.length(); ++f) {
1487 res->labels[f] = (labels[f] ? labels[f]->SyntaxCopy() : nullptr);
1489 res->CallerState = CallerState;
1493 //==========================================================================
1495 // VDecorateAJump::DoResolve
1497 //==========================================================================
1498 VExpression *VDecorateAJump::DoResolve (VEmitContext &ec) {
1499 if (!ec.SelfClass) Sys_Error("VDecorateAJump::DoResolve: internal compiler error");
1501 //AutoCopy probcopy(op);
1503 if (labels.length() > 255) {
1504 ParseError(Loc, "%d labels in `A_Jump` -- are you nuts?!", labels.length());
1505 delete this;
1506 return nullptr;
1509 if (!prob) { delete this; return nullptr; }
1511 prob = prob->Resolve(ec);
1512 if (!prob) { delete this; return nullptr; }
1514 if (prob->Type.Type != TYPE_Int && prob->Type.Type != TYPE_Byte) {
1515 ParseError(Loc, "`A_Jump` argument #1 should be integer, not `%s`", *prob->Type.GetName());
1516 delete this;
1517 return nullptr;
1520 for (int lbidx = 0; lbidx < labels.length(); ++lbidx) {
1521 VExpression *lbl = labels[lbidx];
1523 if (!lbl) {
1524 ParseError(Loc, "`A_Jump` cannot have default arguments");
1525 delete this;
1526 return nullptr;
1529 bool massaged = false;
1530 lbl = lbl->MassageDecorateArg(ec, nullptr, CallerState, "A_Jump", lbidx+2, VFieldType(TYPE_State), false/*not optional*/, nullptr, &massaged);
1531 if (!lbl) { delete this; return nullptr; } // some error
1533 lbl = new VCastOrInvocation("DoJump", Loc, 1, &lbl);
1534 lbl = new VDropResult(lbl);
1536 labels[lbidx] = lbl->Resolve(ec);
1537 if (!labels[lbidx]) { delete this; return nullptr; }
1540 /* generate this code:
1541 if (prob > 0) {
1542 if (prob > 255 || P_Random() < prob) {
1543 switch (P_Random()%label.length()) {
1544 case n: do_jump_to_label_n;
1548 // this is not needed, because `DoJump()` does it for us
1549 //if (XLevel.StateCall) XLevel.StateCall->Result = false;
1551 do it by allocate local array for labels, populate it, and generate code
1552 for checks and sets
1555 // create `XLevel.StateCall` access expression
1558 VExpression *xlvl = new VSingleName("XLevel", Loc);
1559 xstc = new VDotField(xlvl, "StateCall", Loc);
1561 // XLevel.StateCall->Result
1562 VExpression *xres = new VPointerField(xstc->SyntaxCopy(), "Result", Loc);
1563 // XLevel.StateCall->Result = false
1564 xass = new VAssignment(VAssignment::Assign, xres, new VIntLiteral(0, Loc), Loc);
1566 // call to `P_Random()`
1567 crnd0 = new VCastOrInvocation("P_Random", Loc, 0, nullptr);
1568 crnd1 = crnd0->SyntaxCopy();
1570 // now resolve all generated expressions
1571 //xstc = xstc->ResolveBoolean(ec);
1572 //if (!xstc) { delete this; return nullptr; }
1574 //xass = xass->Resolve(ec);
1575 //if (!xass) { delete this; return nullptr; }
1576 //vassert(xass->Type.Type == TYPE_Void);
1578 crnd0 = crnd0->Resolve(ec);
1579 if (!crnd0) { delete this; return nullptr; }
1581 crnd1 = crnd1->Resolve(ec);
1582 if (!crnd1) { delete this; return nullptr; }
1584 if (crnd0->Type.Type != TYPE_Int && crnd0->Type.Type != TYPE_Byte) {
1585 ParseError(Loc, "`P_Random()` should return integer, not `%s`", *crnd0->Type.GetName());
1586 delete this;
1587 return nullptr;
1590 if (labels.length() == 0) {
1591 ParseWarning(Loc, "this `A_Jump` is never taken");
1592 } else if (labels.length() == 1 && prob->IsIntConst() && prob->GetIntConst() > 255) {
1593 //k8: this is not a bug; usually, this is used to dynamically dispatch control
1594 // to a label that can be defined in subclass, for example.
1595 // note that `Goto` cannod do that.
1596 //ParseWarning(Loc, "this `A_Jump` is uncoditional; this is probably a bug (replace it with `Goto` if it isn't)");
1599 Type.Type = TYPE_Void;
1600 SetResolved();
1601 return this;
1605 //==========================================================================
1607 // VDecorateAJump::Emit
1609 //==========================================================================
1610 void VDecorateAJump::Emit (VEmitContext &ec) {
1611 /* generate this code:
1612 if (prob <= 0) goto end;
1613 if (prob > 255) goto doit;
1614 if (P_Random() >= prob) goto end;
1615 doit:
1616 switch (P_Random()%label.length()) {
1617 case n: do_jump_to_label_n;
1619 end:
1620 if (XLevel.StateCall) XLevel.StateCall->Result = false;
1622 do it by allocate local array for labels, populate it, and generate code
1623 for checks and sets
1626 EmitCheckResolved(ec);
1628 VLabel endTarget = ec.DefineLabel();
1629 VLabel doitTarget = ec.DefineLabel();
1631 bool doDrop; // do we need to drop `prob` at the end?
1633 if (prob->IsIntConst() && prob->GetIntConst() <= 0) {
1634 // jump is never taken
1635 doDrop = false;
1636 ec.AddStatement(OPC_Goto, endTarget, Loc);
1637 } else if (prob->IsIntConst() && prob->GetIntConst() > 255) {
1638 doDrop = false;
1639 // ...and check nothing, jump is always taken
1640 } else {
1641 doDrop = true;
1642 prob->Emit(ec); // prob
1644 if (!prob->IsIntConst()) {
1645 //if (prob <= 0) goto end;
1646 ec.AddStatement(OPC_DupPOD, Loc); // prob, prob
1647 ec.AddStatement(OPC_PushNumber, 0, Loc); // prob, prob, 0
1648 ec.AddStatement(OPC_LessEquals, Loc); // prob, flag
1649 ec.AddStatement(OPC_IfGoto, endTarget, Loc); // prob
1651 //if (prob > 255) goto doit;
1652 ec.AddStatement(OPC_DupPOD, Loc); // prob, prob
1653 ec.AddStatement(OPC_PushNumber, 255, Loc); // prob, prob, 255
1654 ec.AddStatement(OPC_GreaterEquals, Loc); // prob, flag
1655 ec.AddStatement(OPC_IfGoto, doitTarget, Loc); // prob
1657 ec.AddStatement(OPC_DupPOD, Loc); // prob, prob
1658 } else {
1659 // if we know prob value, we can omit most checks, and
1660 // we don't need to keep `prob` on the stack
1661 doDrop = false;
1664 //if (P_Random() >= prob) goto end;
1665 //equals to: if (prob < P_Random()) goto end;
1666 crnd0->Emit(ec); // prob, prob, prand
1667 ec.AddStatement(OPC_Less, Loc); // prob, flag
1668 ec.AddStatement(OPC_IfGoto, endTarget, Loc); // prob
1671 ec.MarkLabel(doitTarget); // prob
1673 if (labels.length() == 1) {
1674 labels[0]->Emit(ec); // prob
1675 } else if (labels.length() > 0) {
1676 // P_Random()%label.length()
1677 crnd1->Emit(ec); // prob, rnd
1678 ec.AddStatement(OPC_PushNumber, labels.length(), Loc); // prob, rnd, labels.length()
1679 ec.AddStatement(OPC_Modulus, Loc); // prob, lblidx
1680 // switch:
1681 TArray<VLabel> addrs;
1682 addrs.setLength(labels.length());
1683 for (int lidx = 0; lidx < labels.length(); ++lidx) addrs[lidx] = ec.DefineLabel();
1684 for (int lidx = 0; lidx < labels.length(); ++lidx) {
1685 if (lidx >= 0 && lidx < 256) {
1686 ec.AddStatement(OPC_CaseGotoB, lidx, addrs[lidx], Loc);
1687 }/* else if (lidx >= MIN_VINT16 && lidx < MAX_VINT16) {
1688 ec.AddStatement(OPC_CaseGotoS, lidx, addrs[lidx], Loc);
1689 } else*/ {
1690 ec.AddStatement(OPC_CaseGoto, lidx, addrs[lidx], Loc);
1693 // just in case (and for optimiser)
1694 ec.AddStatement(OPC_DropPOD, Loc); // prob (lidx dropped)
1695 ec.AddStatement(OPC_Goto, endTarget, Loc); // prob
1697 // now generate label jump code
1698 for (int lidx = 0; lidx < labels.length(); ++lidx) {
1699 ec.MarkLabel(addrs[lidx]); // prob
1700 labels[lidx]->Emit(ec); // prob
1701 if (lidx != labels.length()-1) ec.AddStatement(OPC_Goto, endTarget, Loc); // prob
1705 ec.MarkLabel(endTarget); // prob
1706 if (doDrop) ec.AddStatement(OPC_DropPOD, Loc);
1708 //if (XLevel.StateCall) XLevel.StateCall->Result = false;
1710 VLabel falseTarget = ec.DefineLabel();
1711 // expression
1712 xstc->EmitBranchable(ec, falseTarget, false);
1713 // true statement
1714 xass->Emit(ec);
1715 ec.MarkLabel(falseTarget);
1720 //==========================================================================
1722 // VDecorateAJump::toString
1724 //==========================================================================
1725 VStr VDecorateAJump::toString () const {
1726 VStr res = "A_Jump("+e2s(prob);
1727 for (int f = 0; f < labels.length(); ++f) {
1728 res += ", ";
1729 res += e2s(labels[f]);
1731 res += ")";
1732 return res;
1737 //==========================================================================
1739 // VDecorateRndPick::VDecorateRndPick
1741 //==========================================================================
1742 VDecorateRndPick::VDecorateRndPick (bool aAsFloat, const TLocation &aloc)
1743 : VExpression(aloc)
1744 , crnd0(nullptr)
1745 , numbers()
1746 , asFloat(aAsFloat)
1751 //==========================================================================
1753 // VDecorateRndPick::~VDecorateRndPick
1755 //==========================================================================
1756 VDecorateRndPick::~VDecorateRndPick () {
1757 delete crnd0; crnd0 = nullptr;
1758 for (int f = numbers.length()-1; f >= 0; --f) delete numbers[f];
1759 numbers.clear();
1763 //==========================================================================
1765 // VDecorateRndPick::HasSideEffects
1767 //==========================================================================
1768 bool VDecorateRndPick::HasSideEffects () {
1769 return true;
1773 //==========================================================================
1775 // VDecorateRndPick::VisitChildren
1777 //==========================================================================
1778 void VDecorateRndPick::VisitChildren (VExprVisitor *v) {
1779 if (!v->stopIt && crnd0) crnd0->Visit(v);
1780 for (int f = 0; !v->stopIt && f < numbers.length(); f += 1) {
1781 numbers[f]->Visit(v);
1786 //==========================================================================
1788 // VDecorateRndPick::SyntaxCopy
1790 //==========================================================================
1791 VExpression *VDecorateRndPick::SyntaxCopy () {
1792 auto res = new VDecorateRndPick();
1793 DoSyntaxCopyTo(res);
1794 return res;
1798 //==========================================================================
1800 // VDecorateRndPick::DoSyntaxCopyTo
1802 //==========================================================================
1803 void VDecorateRndPick::DoSyntaxCopyTo (VExpression *e) {
1804 VExpression::DoSyntaxCopyTo(e);
1805 auto res = (VDecorateRndPick *)e;
1806 res->crnd0 = (crnd0 ? crnd0->SyntaxCopy() : nullptr);
1807 res->numbers.setLength(numbers.length());
1808 for (int f = 0; f < numbers.length(); ++f) {
1809 res->numbers[f] = (numbers[f] ? numbers[f]->SyntaxCopy() : nullptr);
1811 res->asFloat = asFloat;
1815 //==========================================================================
1817 // VDecorateRndPick::DoResolve
1819 //==========================================================================
1820 VExpression *VDecorateRndPick::DoResolve (VEmitContext &ec) {
1821 if (numbers.length() == 0) {
1822 ParseError(Loc, "no choices in `%srandompick` -- are you nuts?!", (asFloat ? "f" : ""));
1823 delete this;
1824 return nullptr;
1827 if (numbers.length() > 255) {
1828 ParseError(Loc, "%d choices in `%srandompick` -- are you nuts?!", numbers.length(), (asFloat ? "f" : ""));
1829 delete this;
1830 return nullptr;
1833 // resolve numbers
1834 for (int lbidx = 0; lbidx < numbers.length(); ++lbidx) {
1835 VExpression *lbl = numbers[lbidx];
1837 if (!lbl) {
1838 ParseError(Loc, "`%srandompick` cannot have default arguments", (asFloat ? "f" : ""));
1839 delete this;
1840 return nullptr;
1843 bool massaged = false;
1844 lbl = lbl->MassageDecorateArg(ec, nullptr/*invokation*/, /*CallerState*/nullptr, (asFloat ? "frandompick" : "randompick"), lbidx+1, (asFloat ? VFieldType(TYPE_Float) : VFieldType(TYPE_Int)), false/*not optional*/, nullptr, &massaged);
1845 if (!lbl) { delete this; return nullptr; } // some error
1847 numbers[lbidx] = lbl->Resolve(ec);
1848 lbl = numbers[lbidx];
1849 if (!lbl) { delete this; return nullptr; }
1851 if (lbl->Type.Type == TYPE_Int || lbl->Type.Type == TYPE_Byte) {
1852 if (asFloat) {
1853 numbers[lbidx] = new VScalarToFloat(lbl, false);
1854 if (numbers[lbidx]) numbers[lbidx] = numbers[lbidx]->Resolve(ec);
1855 if (!numbers[lbidx]) { delete this; return nullptr; }
1857 } else if (lbl->Type.Type == TYPE_Float) {
1858 if (!asFloat) {
1859 ParseWarning(Loc, "`%srandompick()` argument #%d must be int", (asFloat ? "f" : ""), lbidx+1);
1860 numbers[lbidx] = new VScalarToInt(lbl, false);
1861 if (numbers[lbidx]) numbers[lbidx] = numbers[lbidx]->Resolve(ec);
1862 if (!numbers[lbidx]) { delete this; return nullptr; }
1864 } else {
1865 ParseError(Loc, "`%srandompick()` argument #%d has invalid type `%s`", (asFloat ? "f" : ""), lbidx+1, *lbl->Type.GetName());
1866 delete this;
1867 return nullptr;
1871 // one number means "just return it"
1872 if (numbers.length() > 1) {
1873 // call to `P_Random()`
1874 crnd0 = new VBinary(VBinary::Modulus,
1875 new VCastOrInvocation("P_Random", Loc, 0, nullptr),
1876 new VIntLiteral(numbers.length(), Loc), Loc);
1878 crnd0 = crnd0->Resolve(ec);
1879 if (!crnd0) { delete this; return nullptr; }
1881 if (crnd0->Type.Type != TYPE_Int && crnd0->Type.Type != TYPE_Byte) {
1882 ParseError(Loc, "`P_Random()` should return integer, not `%s`", *crnd0->Type.GetName());
1883 delete this;
1884 return nullptr;
1886 } else {
1887 crnd0 = nullptr; // just in case
1890 Type.Type = (asFloat ? TYPE_Float : TYPE_Int);
1891 SetResolved();
1892 return this;
1896 //==========================================================================
1898 // VDecorateRndPick::Emit
1900 //==========================================================================
1901 void VDecorateRndPick::Emit (VEmitContext &ec) {
1902 EmitCheckResolved(ec);
1904 vassert(numbers.length() > 0);
1905 if (numbers.length() == 1) {
1906 numbers[0]->Emit(ec); // number
1907 } else {
1908 VLabel endTarget = ec.DefineLabel();
1910 // index
1911 crnd0->Emit(ec);
1913 // switch:
1914 TArray<VLabel> addrs;
1915 addrs.setLength(numbers.length());
1916 for (int lidx = 0; lidx < numbers.length(); ++lidx) addrs[lidx] = ec.DefineLabel();
1917 for (int lidx = 0; lidx < numbers.length(); ++lidx) {
1918 if (lidx >= 0 && lidx < 256) {
1919 ec.AddStatement(OPC_CaseGotoB, lidx, addrs[lidx], Loc);
1920 }/* else if (lidx >= MIN_VINT16 && lidx < MAX_VINT16) {
1921 ec.AddStatement(OPC_CaseGotoS, lidx, addrs[lidx], Loc);
1922 }*/ else {
1923 ec.AddStatement(OPC_CaseGoto, lidx, addrs[lidx], Loc);
1926 // just in case (and for optimiser)
1927 ec.AddStatement(OPC_DropPOD, Loc); // (index dropped)
1928 ec.AddStatement(OPC_PushNumber, -1, Loc);
1929 ec.AddStatement(OPC_Goto, endTarget, Loc);
1931 // now generate label jump code
1932 for (int lidx = 0; lidx < numbers.length(); ++lidx) {
1933 ec.MarkLabel(addrs[lidx]);
1934 numbers[lidx]->Emit(ec); // number
1935 if (lidx != numbers.length()-1) ec.AddStatement(OPC_Goto, endTarget, Loc); // prob
1938 ec.MarkLabel(endTarget);
1943 //==========================================================================
1945 // VDecorateRndPick::toString
1947 //==========================================================================
1948 VStr VDecorateRndPick::toString () const {
1949 VStr res = va("%srandompick(", (asFloat ? "f" : ""));
1950 bool putComma = false;
1951 for (auto &&n : numbers) {
1952 if (putComma) res += ", "; else putComma = true;
1953 res += e2s(n);
1955 res += ")";
1956 return res;