1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// parses scripts
19
20#include <C4Include.h>
21#include <C4Aul.h>
22
23#include <C4Def.h>
24#include <C4Game.h>
25#include <C4Wrappers.h>
26
27#include <cinttypes>
28
29#define DEBUG_BYTECODE_DUMP 0
30
31#define C4AUL_Include "#include"
32#define C4AUL_Strict "#strict"
33#define C4AUL_Append "#appendto"
34#define C4AUL_NoWarn "nowarn"
35
36#define C4AUL_Func "func"
37
38#define C4AUL_Private "private"
39#define C4AUL_Protected "protected"
40#define C4AUL_Public "public"
41#define C4AUL_Global "global"
42#define C4AUL_Const "const"
43
44#define C4AUL_If "if"
45#define C4AUL_Else "else"
46#define C4AUL_While "while"
47#define C4AUL_For "for"
48#define C4AUL_In "in"
49#define C4AUL_Return "return"
50#define C4AUL_Var "Var"
51#define C4AUL_Par "Par"
52#define C4AUL_Goto "goto"
53#define C4AUL_Break "break"
54#define C4AUL_Continue "continue"
55#define C4AUL_Inherited "inherited"
56#define C4AUL_SafeInherited "_inherited"
57#define C4AUL_this "this"
58
59#define C4AUL_Image "Image"
60#define C4AUL_Contents "Contents"
61#define C4AUL_Condition "Condition"
62#define C4AUL_Method "Method"
63#define C4AUL_Desc "Desc"
64
65#define C4AUL_MethodAll "All"
66#define C4AUL_MethodNone "None"
67#define C4AUL_MethodClassic "Classic"
68#define C4AUL_MethodJumpAndRun "JumpAndRun"
69
70#define C4AUL_GlobalNamed "static"
71#define C4AUL_LocalNamed "local"
72#define C4AUL_VarNamed "var"
73
74#define C4AUL_TypeInt "int"
75#define C4AUL_TypeBool "bool"
76#define C4AUL_TypeC4ID "id"
77#define C4AUL_TypeC4Object "object"
78#define C4AUL_TypeString "string"
79#define C4AUL_TypeArray "array"
80#define C4AUL_TypeMap "map"
81#define C4AUL_TypeAny "any"
82
83#define C4AUL_Nil "nil"
84
85#define C4AUL_True "true"
86#define C4AUL_False "false"
87
88#define C4AUL_CodeBufSize 16
89
90// script token type
91enum C4AulTokenType
92{
93 ATT_INVALID, // invalid token
94 ATT_DIR, // directive
95 ATT_IDTF, // identifier
96 ATT_INT, // integer constant
97 ATT_BOOL, // boolean constant
98 ATT_NIL, // nil
99 ATT_STRING, // string constant
100 ATT_C4ID, // C4ID constant
101 ATT_COMMA, // ","
102 ATT_COLON, // ":"
103 ATT_DCOLON, // "::"
104 ATT_SCOLON, // ";"
105 ATT_BOPEN, // "("
106 ATT_BCLOSE, // ")"
107 ATT_BOPEN2, // "["
108 ATT_BCLOSE2, // "]"
109 ATT_BLOPEN, // "{"
110 ATT_BLCLOSE, // "}"
111 ATT_SEP, // "|"
112 ATT_CALL, // "->"
113 ATT_GLOBALCALL, // "global->"
114 ATT_STAR, // "*"
115 ATT_AMP, // "&"
116 ATT_TILDE, // '~'
117 ATT_LDOTS, // '...'
118 ATT_DOT, // '.'
119 ATT_QMARK, // '?'
120 ATT_OPERATOR, // operator
121 ATT_EOF // end of file
122};
123
124class C4AulParseState
125{
126public:
127 typedef enum { PARSER, PREPARSER } TypeType;
128 C4AulParseState(C4AulScriptFunc *Fn, C4AulScript *a, TypeType Type) :
129 Fn(Fn), a(a), SPos(Fn ? Fn->Script : a->Script.getData()),
130 Done(false),
131 Type(Type),
132 fJump(false),
133 iStack(0),
134 pLoopStack(nullptr) {}
135
136 ~C4AulParseState()
137 {
138 while (pLoopStack) PopLoop();
139 }
140
141 C4AulScriptFunc *Fn; C4AulScript *a;
142 const char *SPos; // current position in the script
143 char Idtf[C4AUL_MAX_Identifier]; // current identifier
144 C4AulTokenType TokenType; // current token type
145 std::intptr_t cInt; // current int constant (std::intptr_t for compatibility with x86_64)
146 bool Done; // done parsing?
147 TypeType Type; // emitting bytecode?
148 void Parse_Script();
149 void Parse_FuncHead();
150 void Parse_Desc();
151 void Parse_Function();
152 void Parse_Statement();
153 void Parse_Block();
154 int Parse_Params(int iMaxCnt, const char *sWarn, C4AulFunc *pFunc = nullptr);
155 void Parse_Array();
156 void Parse_Map();
157 void Parse_While();
158 void Parse_If();
159 void Parse_For();
160 void Parse_ForEach();
161 void Parse_Expression(int iParentPrio = -1); // includes Parse_Expression2
162 void Parse_Expression2(int iParentPrio = -1); // parses operators + Parse_Expression3
163 bool Parse_Expression3(); // navigation: ->, [], . and ?
164 void Parse_Var();
165 void Parse_Local();
166 void Parse_Static();
167 void Parse_Const();
168
169 bool IsMapLiteral();
170
171 template<C4AulTokenType closingAtt>
172 void SkipBlock();
173
174 bool AdvanceSpaces(); // skip whitespaces; return whether script ended
175 int GetOperator(const char *pScript);
176 enum HoldStringsPolicy { Discard, Hold, Ref };
177 C4AulTokenType GetNextToken(char *pToken, std::intptr_t *pInt, HoldStringsPolicy HoldStrings, bool bOperator); // get next token of SPos
178
179 void Shift(HoldStringsPolicy HoldStrings = Hold, bool bOperator = true);
180 void Match(C4AulTokenType TokenType, const char *Message = nullptr);
181 void UnexpectedToken(const char *Expected);
182 const char *GetTokenName(C4AulTokenType TokenType);
183
184 void Warn(std::string_view msg, const char *pIdtf = nullptr);
185 void StrictError(std::string_view message, C4AulScriptStrict errorSince, const char *identifier = nullptr);
186 void Strict2Error(std::string_view message, const char *identifier = nullptr);
187
188 void SetNoRef(); // Switches the bytecode to generate a value instead of a reference
189
190private:
191 bool fJump;
192 std::intptr_t iStack;
193
194 void AddBCC(C4AulBCCType eType, std::intptr_t X = 0);
195
196 size_t JumpHere(); // Get position for a later jump to next instruction added
197 void SetJumpHere(size_t iJumpOp); // Use the next inserted instruction as jump target for the given jump operation
198 void SetJump(size_t iJumpOp, size_t iWhere);
199 void AddJump(C4AulBCCType eType, size_t iWhere);
200
201 // Keep track of loops and break/continue usages
202 struct Loop
203 {
204 struct Control
205 {
206 bool Break;
207 size_t Pos;
208 Control *Next;
209 };
210 Control *Controls;
211 size_t StackSize;
212 Loop *Next;
213 };
214 Loop *pLoopStack;
215
216 void PushLoop();
217 void PopLoop();
218 void AddLoopControl(bool fBreak);
219};
220
221void C4AulScript::Warn(const std::string_view msg, const char *pIdtf)
222{
223 C4AulParseError{this, msg, pIdtf, true}.show();
224 ++Game.ScriptEngine.warnCnt;
225}
226
227void C4AulParseState::Warn(const std::string_view msg, const char *pIdtf)
228{
229 // do not show errors for System.c4g scripts that appear to be pure #appendto scripts
230 if (Fn && !Fn->Owner->Def && !Fn->Owner->Appends.empty()) return;
231
232 C4AulParseError{this, msg, pIdtf, true}.show();
233 if (Fn && Fn->pOrgScript != a)
234 {
235 DebugLog(fmt: " (as #appendto/#include to {})", args&: Fn->Owner->ScriptName);
236 }
237 ++Game.ScriptEngine.warnCnt;
238}
239
240void C4AulParseState::StrictError(const std::string_view message, C4AulScriptStrict errorSince, const char *identifier)
241{
242 const auto strictness = Fn ? Fn->pOrgScript->Strict : a->Strict;
243 if (strictness < errorSince)
244 Warn(msg: message, pIdtf: identifier);
245 else
246 throw C4AulParseError(this, message, identifier);
247}
248
249void C4AulParseState::Strict2Error(const std::string_view message, const char *identifier)
250{
251 return StrictError(message, errorSince: C4AulScriptStrict::STRICT2, identifier);
252}
253
254C4AulParseError::C4AulParseError(const std::string_view message, const char *identifier, bool warn)
255{
256 if (identifier)
257 {
258 this->message = std::format(fmt: "{}{}", args: message, args&: identifier);
259 }
260 else
261 {
262 this->message = message;
263 }
264
265 isWarning = warn;
266}
267
268C4AulParseError::C4AulParseError(C4AulParseState *state, const std::string_view msg, const char *pIdtf, bool Warn)
269 : C4AulParseError{msg, pIdtf, Warn}
270{
271 if (state->Fn && *(state->Fn->Name))
272 {
273 // Show function name
274 message += " (in ";
275 message += state->Fn->Name;
276
277 // Exact position
278 if (state->Fn->pOrgScript && state->SPos)
279 message += std::format(fmt: ", {}:{}:{}",
280 args&: state->Fn->pOrgScript->ScriptName,
281 args: SGetLine(szText: state->Fn->pOrgScript->Script.getData(), cpPosition: state->SPos),
282 args: SLineGetCharacters(szText: state->Fn->pOrgScript->Script.getData(), cpPosition: state->SPos));
283 else
284 message += ')';
285 }
286 else if (state->a)
287 {
288 // Script name
289 message += std::format(fmt: " ({}:{}:{})",
290 args&: state->a->ScriptName,
291 args: SGetLine(szText: state->a->Script.getData(), cpPosition: state->SPos),
292 args: SLineGetCharacters(szText: state->a->Script.getData(), cpPosition: state->SPos));
293 }
294}
295
296C4AulParseError::C4AulParseError(C4AulScript *pScript, const std::string_view msg, const char *pIdtf, bool Warn)
297 : C4AulParseError{msg, pIdtf, Warn}
298{
299 if (pScript)
300 {
301 // Script name
302 message += std::format(fmt: " ({})", args&: pScript->ScriptName);
303 }
304}
305
306void C4AulScriptFunc::ParseDesc()
307{
308 // do nothing if no desc is given
309 if (!Desc.getLength()) return;
310 const char *DPos = Desc.getData();
311 // parse desc
312 while (*DPos)
313 {
314 const char *DPos0 = DPos; // beginning of segment
315 const char *DPos2 = nullptr; // pos of equal sign, if found
316 // parse until end of segment
317 while (*DPos && (*DPos != '|'))
318 {
319 // store break pos if found
320 if (*DPos == '=') if (!DPos2) DPos2 = DPos;
321 DPos++;
322 }
323
324 // if this was an assignment segment, get value to assign
325 if (DPos2)
326 {
327 char Val[C4AUL_MAX_String + 1];
328 SCopyUntil(szSource: DPos2 + 1, sTarget: Val, cUntil: *DPos, C4AUL_MAX_String);
329 // Image
330 if (SEqual2(szStr1: DPos0, C4AUL_Image))
331 {
332 // image: special contents-image?
333 if (SEqual(szStr1: Val, C4AUL_Contents))
334 idImage = C4ID_Contents;
335 else
336 {
337 // Find phase separator (:)
338 char *colon;
339 for (colon = Val; *colon != ':' && *colon != '\0'; ++colon);
340 if (*colon == ':') *colon = '\0';
341 else colon = nullptr;
342 // get image id
343 idImage = C4Id(str: Val);
344 // get image phase
345 if (colon)
346 iImagePhase = atoi(nptr: colon + 1);
347 }
348 }
349 // Condition
350 else if (SEqual2(szStr1: DPos0, C4AUL_Condition))
351 // condition? get condition func
352 Condition = Owner->GetFuncRecursive(pIdtf: Val);
353 // Method
354 else if (SEqual2(szStr1: DPos0, C4AUL_Method))
355 {
356 if (SEqual2(szStr1: Val, C4AUL_MethodAll))
357 ControlMethod = C4AUL_ControlMethod_All;
358 else if (SEqual2(szStr1: Val, C4AUL_MethodNone))
359 ControlMethod = C4AUL_ControlMethod_None;
360 else if (SEqual2(szStr1: Val, C4AUL_MethodClassic))
361 ControlMethod = C4AUL_ControlMethod_Classic;
362 else if (SEqual2(szStr1: Val, C4AUL_MethodJumpAndRun))
363 ControlMethod = C4AUL_ControlMethod_JumpAndRun;
364 else
365 // unrecognized: Default to all
366 ControlMethod = C4AUL_ControlMethod_All;
367 }
368 // Long Description
369 else if (SEqual2(szStr1: DPos0, C4AUL_Desc))
370 {
371 DescLong.CopyUntil(szString: Val, cUntil: '|');
372 }
373 // unrecognized? never mind
374 }
375
376 if (*DPos) DPos++;
377 }
378 assert(!Condition || !Condition->Owner->Def || Condition->Owner->Def == Owner->Def);
379 // copy desc text
380 DescText.CopyUntil(szString: Desc.getData(), cUntil: '|');
381}
382
383bool C4AulParseState::AdvanceSpaces()
384{
385 char C, C2{0};
386 // defaultly, not in comment
387 int InComment = 0; // 0/1/2 = no comment/line comment/multi line comment
388 // don't go past end
389 while (C = *SPos)
390 {
391 // loop until out of comment and non-whitespace is found
392 switch (InComment)
393 {
394 case 0:
395 if (C == '/')
396 {
397 SPos++;
398 switch (*SPos)
399 {
400 case '/': InComment = 1; break;
401 case '*': InComment = 2; break;
402 default: SPos--; return true;
403 }
404 }
405 else if (static_cast<uint8_t>(C) > 32) return true;
406 break;
407 case 1:
408 if ((static_cast<uint8_t>(C) == 13) || (static_cast<uint8_t>(C) == 10)) InComment = 0;
409 break;
410 case 2:
411 if ((C == '/') && (C2 == '*')) InComment = 0;
412 break;
413 }
414 // next char; store prev
415 SPos++; C2 = C;
416 }
417 // end of script reached; return false
418 return false;
419}
420
421// C4Script Operator Map
422
423C4ScriptOpDef C4ScriptOpMap[] =
424{
425 // priority postfix RetType
426 // | identifier | right-associative
427 // | | Bytecode | | no second id ParType1 ParType2
428 // prefix
429 { .Priority: 16, .Identifier: "++", .Code: AB_Inc1, .Postfix: 0, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_pC4Value, .Type2: C4V_Any },
430 { .Priority: 16, .Identifier: "--", .Code: AB_Dec1, .Postfix: 0, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_pC4Value, .Type2: C4V_Any },
431 { .Priority: 16, .Identifier: "~", .Code: AB_BitNot, .Postfix: 0, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Any },
432 { .Priority: 16, .Identifier: "!", .Code: AB_Not, .Postfix: 0, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_Bool, .Type2: C4V_Any },
433 { .Priority: 16, .Identifier: "+", .Code: AB_ERR, .Postfix: 0, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Any },
434 { .Priority: 16, .Identifier: "-", .Code: AB_Neg, .Postfix: 0, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Any },
435
436 // postfix (whithout second statement)
437 { .Priority: 17, .Identifier: "++", .Code: AB_Inc1_Postfix, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 1, .RetType: C4V_Int, .Type1: C4V_pC4Value, .Type2: C4V_Any },
438 { .Priority: 17, .Identifier: "--", .Code: AB_Dec1_Postfix, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 1, .RetType: C4V_Int, .Type1: C4V_pC4Value, .Type2: C4V_Any },
439
440 // postfix
441 { .Priority: 15, .Identifier: "**", .Code: AB_Pow, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
442 { .Priority: 14, .Identifier: "/", .Code: AB_Div, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
443 { .Priority: 14, .Identifier: "*", .Code: AB_Mul, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
444 { .Priority: 14, .Identifier: "%", .Code: AB_Mod, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
445 { .Priority: 13, .Identifier: "-", .Code: AB_Sub, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
446 { .Priority: 13, .Identifier: "+", .Code: AB_Sum, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
447 { .Priority: 12, .Identifier: "<<", .Code: AB_LeftShift, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
448 { .Priority: 12, .Identifier: ">>", .Code: AB_RightShift, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
449 { .Priority: 11, .Identifier: "<", .Code: AB_LessThan, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_Int, .Type2: C4V_Int },
450 { .Priority: 11, .Identifier: "<=", .Code: AB_LessThanEqual, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_Int, .Type2: C4V_Int },
451 { .Priority: 11, .Identifier: ">", .Code: AB_GreaterThan, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_Int, .Type2: C4V_Int },
452 { .Priority: 11, .Identifier: ">=", .Code: AB_GreaterThanEqual, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_Int, .Type2: C4V_Int },
453 { .Priority: 10, .Identifier: "..", .Code: AB_Concat, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_String, .Type1: C4V_Any, .Type2: C4V_Any},
454 { .Priority: 9, .Identifier: "==", .Code: AB_Equal, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_Any, .Type2: C4V_Any },
455 { .Priority: 9, .Identifier: "!=", .Code: AB_NotEqual, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_Any, .Type2: C4V_Any },
456 { .Priority: 9, .Identifier: "S=", .Code: AB_SEqual, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_String, .Type2: C4V_String },
457 { .Priority: 9, .Identifier: "eq", .Code: AB_SEqual, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_String, .Type2: C4V_String },
458 { .Priority: 9, .Identifier: "ne", .Code: AB_SNEqual, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_String, .Type2: C4V_String },
459 { .Priority: 8, .Identifier: "&", .Code: AB_BitAnd, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
460 { .Priority: 6, .Identifier: "^", .Code: AB_BitXOr, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
461 { .Priority: 6, .Identifier: "|", .Code: AB_BitOr, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Int, .Type1: C4V_Int, .Type2: C4V_Int },
462 { .Priority: 5, .Identifier: "&&", .Code: AB_And, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_Bool, .Type2: C4V_Bool },
463 { .Priority: 4, .Identifier: "||", .Code: AB_Or, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Bool, .Type1: C4V_Bool, .Type2: C4V_Bool },
464 { .Priority: 3, .Identifier: "??", .Code: AB_NilCoalescing, .Postfix: 1, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_Any, .Type2: C4V_Any },
465 { .Priority: 2, .Identifier: "**=", .Code: AB_PowIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
466 { .Priority: 2, .Identifier: "*=", .Code: AB_MulIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
467 { .Priority: 2, .Identifier: "/=", .Code: AB_DivIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
468 { .Priority: 2, .Identifier: "%=", .Code: AB_ModIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
469 { .Priority: 2, .Identifier: "+=", .Code: AB_Inc, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
470 { .Priority: 2, .Identifier: "-=", .Code: AB_Dec, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
471 { .Priority: 2, .Identifier: "<<=", .Code: AB_LeftShiftIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
472 { .Priority: 2, .Identifier: ">>=", .Code: AB_RightShiftIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
473 { .Priority: 2, .Identifier: "..=", .Code: AB_ConcatIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Any },
474 { .Priority: 2, .Identifier: "&=", .Code: AB_AndIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
475 { .Priority: 2, .Identifier: "|=", .Code: AB_OrIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
476 { .Priority: 2, .Identifier: "^=", .Code: AB_XOrIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Int },
477 { .Priority: 2, .Identifier: "??=", .Code: AB_NilCoalescingIt, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Any },
478 { .Priority: 2, .Identifier: "=", .Code: AB_Set, .Postfix: 1, .RightAssociative: 1, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_pC4Value, .Type2: C4V_Any },
479
480 { .Priority: 0, .Identifier: nullptr, .Code: AB_ERR, .Postfix: 0, .RightAssociative: 0, .NoSecondStatement: 0, .RetType: C4V_Any, .Type1: C4V_Any, .Type2: C4V_Any }
481};
482
483int C4AulParseState::GetOperator(const char *pScript)
484{
485 // return value:
486 // >= 0: operator found. could be found in C4ScriptOfDef
487 // -1: isn't an operator
488
489 unsigned int i;
490
491 if (!*pScript) return 0;
492 // text with > 2 characters or text and #strict 2?
493 // then break (may be misinterpreted as operator
494 if ((*pScript >= 'a' && *pScript <= 'z') ||
495 (*pScript >= 'A' && *pScript <= 'Z'))
496 {
497 if (Fn ? (Fn->pOrgScript->Strict >= C4AulScriptStrict::STRICT2) : (a->Strict >= C4AulScriptStrict::STRICT2))
498 return -1;
499 if ((*(pScript + 1) >= 'a' && *(pScript + 1) <= 'z') ||
500 (*(pScript + 1) >= 'A' && *(pScript + 1) <= 'Z'))
501 if ((*(pScript + 2) >= 'a' && *(pScript + 2) <= 'z') ||
502 (*(pScript + 2) >= 'A' && *(pScript + 2) <= 'Z') ||
503 (*(pScript + 2) >= '0' && *(pScript + 2) <= '9') ||
504 *(pScript + 2) == '_')
505 return -1;
506 }
507
508 // check from longest to shortest
509 for (size_t len = 3; len > 0; --len)
510 for (i = 0; C4ScriptOpMap[i].Identifier; i++)
511 if (SLen(sptr: C4ScriptOpMap[i].Identifier) == len)
512 if (SEqual2(szStr1: pScript, szStr2: C4ScriptOpMap[i].Identifier))
513 return i;
514
515 return -1;
516}
517
518C4AulTokenType C4AulParseState::GetNextToken(char *pToken, std::intptr_t *pInt, HoldStringsPolicy HoldStrings, bool bOperator)
519{
520 // move to start of token
521 if (!AdvanceSpaces()) return ATT_EOF;
522 // store offset
523 const char *SPos0 = SPos;
524 int Len = 0;
525 // token get state
526 enum TokenGetState
527 {
528 TGS_None, // just started
529 TGS_Ident, // getting identifier
530 TGS_Int, // getting integer
531 TGS_IntHex, // getting hexadecimal integer
532 TGS_C4ID, // getting C4ID
533 TGS_String, // getting string
534 TGS_Dir // getting directive
535 };
536 TokenGetState State = TGS_None;
537
538 static char StrBuff[C4AUL_MAX_String + 1];
539 char *pStrPos = StrBuff;
540
541 const auto strictLevel = Fn ? Fn->pOrgScript->Strict : a->Strict;
542
543 // loop until finished
544 while (true)
545 {
546 // get char
547 char C = *SPos;
548
549 switch (State)
550 {
551 case TGS_None:
552 {
553 // get token type by first char (+/- are operators)
554 if (((C >= '0') && (C <= '9'))) // integer by 0-9
555 State = TGS_Int;
556 else if (C == '"') State = TGS_String; // string by "
557 else if (C == '#') State = TGS_Dir; // directive by "#"
558 else if (C == ',') { SPos++; return ATT_COMMA; } // ","
559 else if (C == ';') { SPos++; return ATT_SCOLON; } // ";"
560 else if (C == '(') { SPos++; return ATT_BOPEN; } // "("
561 else if (C == ')') { SPos++; return ATT_BCLOSE; } // ")"
562 else if (C == '[') { SPos++; return ATT_BOPEN2; } // "["
563 else if (C == ']') { SPos++; return ATT_BCLOSE2; } // "]"
564 else if (C == '{') { SPos++; return ATT_BLOPEN; } // "{"
565 else if (C == '}') { SPos++; return ATT_BLCLOSE; } // "}"
566 else if (C == ':') // ":"
567 {
568 SPos++;
569 // double-colon?
570 if (*SPos == ':') // "::"
571 {
572 SPos++;
573 return ATT_DCOLON;
574 }
575 else // ":"
576 return ATT_COLON;
577 }
578 else if (C == '-' && *(SPos + 1) == '>') // "->"
579 {
580 SPos += 2; return ATT_CALL;
581 }
582 else if (C == '.' && *(SPos + 1) == '.' && *(SPos + 2) == '.') // "..."
583 {
584 SPos += 3; return ATT_LDOTS;
585 }
586 else
587 {
588 if (bOperator)
589 {
590 // may it be an operator?
591 int iOpID;
592 if ((iOpID = GetOperator(pScript: SPos)) != -1)
593 {
594 // store operator ID in pInt
595 *pInt = iOpID;
596 SPos += SLen(sptr: C4ScriptOpMap[iOpID].Identifier);
597 return ATT_OPERATOR;
598 }
599 }
600 else if (C == '*') { SPos++; return ATT_STAR; } // "*"
601 else if (C == '&') { SPos++; return ATT_AMP; } // "&"
602 else if (C == '~') { SPos++; return ATT_TILDE; } // "~"
603
604 if (C == '.')
605 {
606 ++SPos;
607 return ATT_DOT; // "."
608 }
609
610 if (C == '?')
611 {
612 ++SPos;
613 return ATT_QMARK; // "?"
614 }
615
616 // identifier by all non-special chars
617 if (C >= '@')
618 {
619 // Old Scripts could have wacky identifier
620 if (strictLevel < C4AulScriptStrict::STRICT2)
621 {
622 State = TGS_Ident;
623 break;
624 }
625 // But now only the alphabet and '_' is allowed
626 else if ((C >= 'A' && C <= 'Z') || (C >= 'a' && C <= 'z') || (C == '_'))
627 {
628 State = TGS_Ident;
629 break;
630 }
631 // unrecognized char
632 // make sure to skip the invalid char so the error won't be output forever
633 ++SPos;
634 }
635 else
636 {
637 // examine next char
638 ++SPos; ++Len;
639 // no operator expected and '-' or '+' found?
640 // this could be an int const; parse it directly
641 if (!bOperator && (C == '-' || C == '+'))
642 {
643 // skip spaces between sign and int constant
644 if (AdvanceSpaces())
645 {
646 // continue parsing int, if a numeral follows
647 C = *SPos;
648 if (((C >= '0') && (C <= '9')))
649 {
650 State = TGS_Int;
651 break;
652 }
653 }
654 }
655 // special char and/or error getting it as a signed int
656 }
657 // unrecognized char
658 // show appropriate error message
659 if (C >= '!' && C <= '~')
660 throw C4AulParseError(this, std::format(fmt: "unexpected character '{}' found", args&: C));
661 else
662 throw C4AulParseError(this, std::format(fmt: "unexpected character {:#x} found", args&: C));
663 }
664 break;
665 }
666 case TGS_Ident: // ident and directive: parse until non ident-char is found
667 case TGS_Dir:
668 if (!Inside(ival: C, lbound: '0', rbound: '9')
669 && !Inside(ival: C, lbound: 'a', rbound: 'z')
670 && !Inside(ival: C, lbound: 'A', rbound: 'Z')
671 && C != '_')
672 {
673 // return ident/directive
674 Len = (std::min)(a: Len, C4AUL_MAX_Identifier);
675 SCopy(szSource: SPos0, sTarget: pToken, iMaxL: Len);
676 // check if it's a C4ID (and NOT a label)
677 bool fllid = LooksLikeID(str: pToken);
678 if ((C != '(') && (C != ':' || *(SPos + 1) == ':') && fllid)
679 {
680 // will be parsed next time
681 State = TGS_C4ID; SPos--; Len--;
682 }
683 else
684 {
685 // warn if using C4ID as func label
686 if (fllid) Strict2Error(message: "stupid func label: ", identifier: pToken);
687 // directive?
688 if (State == TGS_Dir) return ATT_DIR;
689 // check reserved names
690 if (SEqual(szStr1: pToken, C4AUL_False)) { *pInt = false; return ATT_BOOL; }
691 if (SEqual(szStr1: pToken, C4AUL_True)) { *pInt = true; return ATT_BOOL; }
692 if (SEqual(szStr1: pToken, C4AUL_Nil) && strictLevel >= C4AulScriptStrict::STRICT3) { return ATT_NIL; }
693 if (SEqual(szStr1: pToken, C4AUL_Global) && strictLevel >= C4AulScriptStrict::STRICT3 && C == '-' && *(SPos + 1) == '>') // "global->"
694 {
695 SPos += 2;
696 return ATT_GLOBALCALL;
697 }
698 // everything else is an identifier
699 return ATT_IDTF;
700 }
701 }
702 break;
703
704 case TGS_Int: // integer: parse until non-number is found
705 case TGS_IntHex:
706 if ((C < '0') || (C > '9'))
707 {
708 const char *szScanCode;
709 if (State == TGS_Int)
710 {
711 // turn to hex mode?
712 if (*SPos0 == '0' && C == 'x' && Len == 1)
713 {
714 State = TGS_IntHex;
715 break;
716 }
717 // some strange C4ID?
718 if (((C >= 'A') && (C <= 'Z')) || (C == '_'))
719 {
720 State = TGS_C4ID;
721 break;
722 }
723 // parse as decimal int
724 szScanCode = "%" SCNdPTR;
725 }
726 else
727 {
728 // parse as hexadecimal int: Also allow 'a' to 'f' and 'A' to 'F'
729 if ((C >= 'A' && C <= 'F') || (C >= 'a' && C <= 'f')) break;
730 szScanCode = "%" SCNxPTR;
731 }
732 // return integer
733 Len = (std::min)(a: Len, C4AUL_MAX_Identifier);
734 SCopy(szSource: SPos0, sTarget: pToken, iMaxL: Len);
735 // or is it a func label?
736 if ((C == '(') || (C == ':'))
737 {
738 Strict2Error(message: "stupid func label: ", identifier: pToken);
739 return ATT_IDTF;
740 }
741 // it's not, so return the int
742 sscanf(s: pToken, format: szScanCode, pInt);
743 return ATT_INT;
744 }
745 break;
746
747 case TGS_C4ID: // c4id: parse until non-ident char is found
748 if (!Inside(ival: C, lbound: '0', rbound: '9')
749 && !Inside(ival: C, lbound: 'a', rbound: 'z')
750 && !Inside(ival: C, lbound: 'A', rbound: 'Z'))
751 {
752 // return C4ID string
753 Len = (std::min)(a: Len, C4AUL_MAX_Identifier);
754 SCopy(szSource: SPos0, sTarget: pToken, iMaxL: Len);
755 // another stupid label identifier?
756 if ((C == '(') || (C == ':' && *(SPos + 1) != ':'))
757 {
758 Strict2Error(message: "stupid func label: ", identifier: pToken);
759 return ATT_IDTF;
760 }
761 // check if valid
762 if (!LooksLikeID(str: pToken)) throw C4AulParseError(this, "erroneous Ident: ", pToken);
763 // get id of it
764 *pInt = static_cast<intptr_t>(C4Id(str: pToken));
765 return ATT_C4ID;
766 }
767 break;
768
769 case TGS_String: // string: parse until '"'; check for eof!
770 // string end
771 if (C == '"')
772 {
773 *pStrPos = 0;
774 SPos++;
775 // no string expected?
776 if (HoldStrings == Discard) return ATT_STRING;
777 // reg string (if not already done so)
778 C4String *pString;
779 if (!(pString = a->Engine->Strings.FindString(strString: StrBuff)))
780 pString = a->Engine->Strings.RegString(strString: StrBuff);
781 if (HoldStrings == Hold) pString->Hold = 1;
782 // return pointer on string object
783 *pInt = reinterpret_cast<std::intptr_t>(pString);
784 return ATT_STRING;
785 }
786 // check: enough buffer space?
787 if (pStrPos - StrBuff >= C4AUL_MAX_String)
788 StrictError(message: "string too long", errorSince: C4AulScriptStrict::STRICT3);
789 else
790 {
791 if (C == '\\') // escape
792 switch (*(SPos + 1))
793 {
794 case '"': SPos++; *(pStrPos++) = '"'; break;
795 case '\\': SPos++; *(pStrPos++) = '\\'; break;
796 default:
797 {
798 // just insert "\"
799 *(pStrPos++) = '\\';
800 // show warning
801 char strEscape[2] = { *(SPos + 1), 0 };
802 Warn(msg: "unknown escape: ", pIdtf: strEscape);
803 }
804 }
805 else if (C == 0 || C == 10 || C == 13) // line break / feed
806 throw C4AulParseError(this, "string not closed");
807 else
808 // copy character
809 *(pStrPos++) = C;
810 }
811 break;
812 }
813 // next char
814 SPos++; Len++;
815 }
816}
817
818static const char *GetTTName(C4AulBCCType e)
819{
820 switch (e)
821 {
822 case AB_DEREF: return "AB_DEREF"; // deref current value
823 case AB_MAPA_R: return "AB_MAPA_R"; // map access via .
824 case AB_MAPA_V: return "AB_MAPA_V"; // not creating a reference
825 case AB_ARRAYA_R: return "AB_ARRAYA_R"; // array access
826 case AB_ARRAYA_V: return "AB_ARRAYA_V"; // not creating a reference
827 case AB_ARRAY_APPEND: return "AB_ARRAY_APPEND"; // not creating a reference
828 case AB_VARN_R: return "AB_VARN_R"; // a named var
829 case AB_VARN_V: return "AB_VARN_V";
830 case AB_PARN_R: return "AB_PARN_R"; // a named parameter
831 case AB_PARN_V: return "AB_PARN_V";
832 case AB_LOCALN_R: return "AB_LOCALN_R"; // a named local
833 case AB_LOCALN_V: return "AB_LOCALN_V";
834 case AB_GLOBALN_R: return "AB_GLOBALN_R"; // a named global
835 case AB_GLOBALN_V: return "AB_GLOBALN_V";
836 case AB_VAR_R: return "AB_VAR_R"; // Var statement
837 case AB_VAR_V: return "AB_VAR_V";
838 case AB_PAR_R: return "AB_PAR_R"; // Par statement
839 case AB_PAR_V: return "AB_PAR_V";
840 case AB_FUNC: return "AB_FUNC"; // function
841
842 // prefix
843 case AB_Inc1: return "AB_Inc1"; // ++
844 case AB_Dec1: return "AB_Dec1"; // --
845 case AB_BitNot: return "AB_BitNot"; // ~
846 case AB_Not: return "AB_Not"; // !
847 case AB_Neg: return "AB_Neg"; // -
848
849 // postfix (whithout second statement)
850 case AB_Inc1_Postfix: return "AB_Inc1_Postfix"; // ++
851 case AB_Dec1_Postfix: return "AB_Dec1_Postfix"; // --
852
853 // postfix
854 case AB_Pow: return "AB_Pow"; // **
855 case AB_Div: return "AB_Div"; // /
856 case AB_Mul: return "AB_Mul"; // *
857 case AB_Mod: return "AB_Mod"; // %
858 case AB_Sub: return "AB_Sub"; // -
859 case AB_Sum: return "AB_Sum"; // +
860 case AB_LeftShift: return "AB_LeftShift"; // <<
861 case AB_RightShift: return "AB_RightShift"; // >>
862 case AB_LessThan: return "AB_LessThan"; // <
863 case AB_LessThanEqual: return "AB_LessThanEqual"; // <=
864 case AB_GreaterThan: return "AB_GreaterThan"; // >
865 case AB_GreaterThanEqual: return "AB_GreaterThanEqual"; // >=
866 case AB_Concat: return "AB_Concat"; // ..
867 case AB_EqualIdent: return "AB_EqualIdent"; // old ==
868 case AB_Equal: return "AB_Equal"; // new ==
869 case AB_NotEqualIdent: return "AB_NotEqualIdent"; // old !=
870 case AB_NotEqual: return "AB_NotEqual"; // new !=
871 case AB_SEqual: return "AB_SEqual"; // S=, eq
872 case AB_SNEqual: return "AB_SNEqual"; // ne
873 case AB_BitAnd: return "AB_BitAnd"; // &
874 case AB_BitXOr: return "AB_BitXOr"; // ^
875 case AB_BitOr: return "AB_BitOr"; // |
876 case AB_And: return "AB_And"; // &&
877 case AB_Or: return "AB_Or"; // ||
878 case AB_NilCoalescing: return "AB_NilCoalescing"; // ??
879 case AB_PowIt: return "AB_PowIt"; // **=
880 case AB_MulIt: return "AB_MulIt"; // *=
881 case AB_DivIt: return "AB_DivIt"; // /=
882 case AB_ModIt: return "AB_ModIt"; // %=
883 case AB_Inc: return "AB_Inc"; // +=
884 case AB_Dec: return "AB_Dec"; // -=
885 case AB_LeftShiftIt: return "AB_LeftShiftIt"; // <<=
886 case AB_RightShiftIt: return "AB_RightShiftIt"; // >>=
887 case AB_ConcatIt: return "AB_ConcatIt"; // ..=
888 case AB_AndIt: return "AB_AndIt"; // &=
889 case AB_OrIt: return "AB_OrIt"; // |=
890 case AB_XOrIt: return "AB_XOrIt"; // ^=
891 case AB_NilCoalescingIt: return "AB_NilCoalescingIt"; // ??=
892 case AB_Set: return "AB_Set"; // =
893
894 case AB_CALL: return "AB_CALL"; // direct object call
895 case AB_CALLGLOBAL: return "AB_CALLGLOBAL"; // global context call
896 case AB_CALLFS: return "AB_CALLFS"; // failsafe direct call
897 case AB_CALLNS: return "AB_CALLNS"; // direct object call: namespace operator
898 case AB_STACK: return "AB_STACK"; // push nulls / pop
899 case AB_NIL: return "AB_NIL"; // constant: nil
900 case AB_INT: return "AB_INT"; // constant: int
901 case AB_BOOL: return "AB_BOOL"; // constant: bool
902 case AB_STRING: return "AB_STRING"; // constant: string
903 case AB_C4ID: return "AB_C4ID"; // constant: C4ID
904 case AB_ARRAY: return "AB_ARRAY"; // semi-constant: array
905 case AB_MAP: return "AB_MAP"; // semi-constant: map
906 case AB_IVARN: return "AB_IVARN"; // initialization of named var
907 case AB_JUMP: return "AB_JUMP"; // jump
908 case AB_JUMPAND: return "AB_JUMPAND";
909 case AB_JUMPOR: return "AB_JUMPOR";
910 case AB_JUMPNIL: return "AB_JUMPNIL";
911 case AB_JUMPNOTNIL: return "AB_JUMPNOTNIL";
912 case AB_CONDN: return "AB_CONDN"; // conditional jump (negated, pops stack)
913 case AB_FOREACH_NEXT: return "AB_FOREACH_NEXT"; // foreach: next element
914 case AB_FOREACH_MAP_NEXT: return "AB_FOREACH_MAP_NEXT"; // foreach: next element
915 case AB_RETURN: return "AB_RETURN"; // return statement
916 case AB_ERR: return "AB_ERR"; // parse error at this position
917 case AB_EOFN: return "AB_EOFN"; // end of function
918 case AB_EOF: return "AB_EOF";
919 }
920 return "?";
921}
922
923void C4AulScript::AddBCC(C4AulBCCType eType, std::intptr_t X, const char *SPos)
924{
925 // range check
926 if (CodeSize >= CodeBufSize)
927 {
928 // create new buffer
929 CodeBufSize = CodeBufSize ? 2 * CodeBufSize : C4AUL_CodeBufSize;
930 C4AulBCC *nCode = new C4AulBCC[CodeBufSize];
931 // copy data
932 memcpy(dest: nCode, src: Code, n: sizeof(*Code) * CodeSize);
933 // replace buffer
934 delete[] Code;
935 Code = nCode;
936 // adjust pointer
937 CPos = Code + CodeSize;
938 }
939 // store chunk
940 CPos->bccType = eType;
941 CPos->bccX = X;
942 CPos->SPos = SPos;
943 CPos++; CodeSize++;
944}
945
946bool C4AulScript::Preparse()
947{
948 // handle easiest case first
949 if (State < ASS_NONE) return false;
950 if (!Script) { State = ASS_PREPARSED; return true; }
951
952 // clear stuff
953 Includes.clear(); Appends.clear();
954 CPos = Code;
955 while (Func0)
956 {
957 // belongs to this script?
958 if (Func0->SFunc())
959 if (Func0->SFunc()->pOrgScript == this)
960 // then desroy linked funcs, too
961 Func0->DestroyLinked();
962 // destroy func
963 delete Func0;
964 }
965
966 C4AulParseState state(nullptr, this, C4AulParseState::PREPARSER);
967 state.Parse_Script();
968
969 // no #strict? we don't like that :(
970 if (Strict == C4AulScriptStrict::NONSTRICT)
971 {
972 Engine->nonStrictCnt++;
973 }
974
975 // done, reset state var
976 Preparsing = false;
977
978 // #include will have to be resolved now...
979 IncludesResolved = false;
980
981 // return success
982 C4AulScript::State = ASS_PREPARSED;
983 return true;
984}
985
986void C4AulParseState::AddBCC(C4AulBCCType eType, std::intptr_t X)
987{
988 if (Type != PARSER) return;
989 // Track stack size
990 switch (eType)
991 {
992 case AB_NIL:
993 case AB_INT:
994 case AB_BOOL:
995 case AB_STRING:
996 case AB_C4ID:
997 case AB_VARN_R:
998 case AB_VARN_V:
999 case AB_PARN_R:
1000 case AB_PARN_V:
1001 case AB_LOCALN_R:
1002 case AB_LOCALN_V:
1003 case AB_GLOBALN_R:
1004 case AB_GLOBALN_V:
1005 iStack++;
1006 break;
1007
1008 case AB_Pow:
1009 case AB_Div:
1010 case AB_Mul:
1011 case AB_Mod:
1012 case AB_Sub:
1013 case AB_Sum:
1014 case AB_LeftShift:
1015 case AB_RightShift:
1016 case AB_LessThan:
1017 case AB_LessThanEqual:
1018 case AB_GreaterThan:
1019 case AB_GreaterThanEqual:
1020 case AB_Concat:
1021 case AB_Equal:
1022 case AB_EqualIdent:
1023 case AB_NotEqual:
1024 case AB_NotEqualIdent:
1025 case AB_SEqual:
1026 case AB_SNEqual:
1027 case AB_BitAnd:
1028 case AB_BitXOr:
1029 case AB_BitOr:
1030 case AB_And:
1031 case AB_Or:
1032 case AB_PowIt:
1033 case AB_MulIt:
1034 case AB_DivIt:
1035 case AB_ModIt:
1036 case AB_Inc:
1037 case AB_Dec:
1038 case AB_LeftShiftIt:
1039 case AB_RightShiftIt:
1040 case AB_ConcatIt:
1041 case AB_AndIt:
1042 case AB_OrIt:
1043 case AB_XOrIt:
1044 case AB_Set:
1045 case AB_ARRAYA_R:
1046 case AB_ARRAYA_V:
1047 case AB_CONDN:
1048 case AB_IVARN:
1049 case AB_RETURN:
1050 // JUMPAND/JUMPOR/JUMPNOTNIL are special: They either jump over instructions adding one to the stack
1051 // or decrement the stack. Thus, for stack counting purposes, they decrement.
1052 case AB_JUMPAND:
1053 case AB_JUMPOR:
1054 case AB_JUMPNOTNIL:
1055 iStack--;
1056 break;
1057
1058 case AB_FUNC:
1059 iStack -= reinterpret_cast<C4AulFunc *>(X)->GetParCount() - 1;
1060 break;
1061
1062 case AB_CALL:
1063 case AB_CALLFS:
1064 case AB_CALLGLOBAL:
1065 iStack -= C4AUL_MAX_Par;
1066 break;
1067
1068 case AB_DEREF:
1069 case AB_JUMPNIL:
1070 case AB_MAPA_R:
1071 case AB_MAPA_V:
1072 case AB_ARRAY_APPEND:
1073 case AB_Inc1:
1074 case AB_Dec1:
1075 case AB_BitNot:
1076 case AB_Not:
1077 case AB_Neg:
1078 case AB_Inc1_Postfix:
1079 case AB_Dec1_Postfix:
1080 case AB_VAR_R:
1081 case AB_VAR_V:
1082 case AB_PAR_R:
1083 case AB_PAR_V:
1084 case AB_FOREACH_NEXT:
1085 case AB_FOREACH_MAP_NEXT:
1086 case AB_ERR:
1087 case AB_EOFN:
1088 case AB_EOF:
1089 case AB_JUMP:
1090 case AB_CALLNS:
1091 case AB_NilCoalescingIt: // does not pop, the following AB_Set does
1092 break;
1093
1094 case AB_STACK:
1095 iStack += X;
1096 break;
1097
1098 case AB_MAP:
1099 iStack -= 2 * X - 1;
1100 break;
1101
1102 case AB_ARRAY:
1103 iStack -= X - 1;
1104 break;
1105
1106 default:
1107 case AB_NilCoalescing:
1108 assert(false);
1109 }
1110
1111 // Use stack operation instead of 0-Int (enable optimization)
1112 if ((eType == AB_INT || eType == AB_BOOL) && !X && (Fn ? (Fn->pOrgScript->Strict < C4AulScriptStrict::STRICT3) : (a->Strict < C4AulScriptStrict::STRICT3)))
1113 {
1114 eType = AB_STACK;
1115 X = 1;
1116 }
1117
1118 // Join checks only if it's not a jump target
1119 if (!fJump)
1120 {
1121 // Join together stack operations
1122 if (eType == AB_STACK &&
1123 a->CPos > a->Code &&
1124 (a->CPos - 1)->bccType == AB_STACK
1125 && (X <= 0 || (a->CPos - 1)->bccX >= 0))
1126 {
1127 (a->CPos - 1)->bccX += X;
1128 // Empty? Remove it.
1129 if (!(a->CPos - 1)->bccX)
1130 {
1131 a->CPos--;
1132 a->CodeSize--;
1133 }
1134 return;
1135 }
1136 }
1137
1138 // Add
1139 a->AddBCC(eType, X, SPos);
1140
1141 // Reset jump flag
1142 fJump = false;
1143}
1144
1145namespace
1146{
1147 void SkipExpressions(intptr_t n, C4AulBCC *&CPos, C4AulBCC *const Code)
1148 {
1149 while (n > 0 && CPos > Code)
1150 {
1151 switch (CPos->bccType)
1152 {
1153 case AB_STACK:
1154 if (CPos->bccX > 0) n -= CPos--->bccX;
1155 break;
1156
1157 case AB_INT: case AB_BOOL: case AB_STRING: case AB_C4ID:
1158 case AB_PARN_R: case AB_PARN_V: case AB_VARN_R: case AB_VARN_V:
1159 case AB_LOCALN_R: case AB_LOCALN_V:
1160 case AB_GLOBALN_R: case AB_GLOBALN_V:
1161 --n;
1162 --CPos;
1163 break;
1164
1165 case AB_MAPA_R: case AB_MAPA_V: case AB_ARRAY_APPEND:
1166 --CPos;
1167 SkipExpressions(n: 1, CPos, Code);
1168 --n;
1169 break;
1170
1171 case AB_ARRAYA_R: case AB_ARRAYA_V:
1172 --CPos;
1173 SkipExpressions(n: 2, CPos, Code);
1174 --n;
1175 break;
1176
1177 case AB_ARRAY:
1178 {
1179 const auto size = CPos->bccX;
1180 --CPos;
1181 SkipExpressions(n: size, CPos, Code);
1182 --n;
1183 break;
1184 }
1185
1186 case AB_MAP:
1187 {
1188 const auto size = 2 * CPos->bccX;
1189 --CPos;
1190 SkipExpressions(n: size, CPos, Code);
1191 --n;
1192 break;
1193
1194 }
1195
1196 case AB_PAR_R: case AB_PAR_V: case AB_VAR_R: case AB_VAR_V:
1197 --CPos;
1198 SkipExpressions(n: 1, CPos, Code);
1199 --n;
1200 break;
1201
1202 case AB_FUNC:
1203 {
1204 const auto pars = reinterpret_cast<C4AulFunc *>(CPos->bccX)->GetParCount();
1205 --CPos;
1206 SkipExpressions(n: pars, CPos, Code);
1207 --n;
1208 break;
1209 }
1210
1211 case AB_CALLNS:
1212 --CPos;
1213 break;
1214
1215 case AB_CALL: case AB_CALLFS: case AB_CALLGLOBAL:
1216 --CPos;
1217 SkipExpressions(C4AUL_MAX_Par + (CPos->bccType != AB_CALLGLOBAL ? 1 : 0), CPos, Code);
1218 --n;
1219 break;
1220
1221 default:
1222 // operator?
1223 if (Inside(ival: CPos->bccType, lbound: AB_Inc1, rbound: AB_Set) && CPos > Code)
1224 {
1225 const auto &op = C4ScriptOpMap[CPos->bccX];
1226 --CPos;
1227 SkipExpressions(n: op.NoSecondStatement || !op.Postfix ? 1 : 2, CPos, Code);
1228 --n;
1229 }
1230 else
1231 return;
1232 }
1233 }
1234 }
1235}
1236
1237void C4AulParseState::SetNoRef()
1238{
1239 if (Type != PARSER) return;
1240 for(C4AulBCC *CPos = a->CPos - 1; CPos >= a->Code; )
1241 {
1242 switch (CPos->bccType)
1243 {
1244 case AB_MAPA_R:
1245 CPos->bccType = AB_MAPA_V;
1246 --CPos;
1247 // propagate back to the accessed map
1248 break;
1249 case AB_ARRAYA_R:
1250 CPos->bccType = AB_ARRAYA_V;
1251 --CPos;
1252 // propagate back to the accessed array
1253 SkipExpressions(n: 1, CPos, Code: a->Code);
1254 break;
1255 case AB_PAR_R: CPos->bccType = AB_PAR_V; return;
1256 case AB_VAR_R: CPos->bccType = AB_VAR_V; return;
1257 case AB_PARN_R: CPos->bccType = AB_PARN_V; return;
1258 case AB_VARN_R: CPos->bccType = AB_VARN_V; return;
1259 case AB_LOCALN_R: CPos->bccType = AB_LOCALN_V; return;
1260 case AB_GLOBALN_R: CPos->bccType = AB_GLOBALN_V; return;
1261 default: return;
1262 }
1263 }
1264}
1265
1266size_t C4AulParseState::JumpHere()
1267{
1268 // Set flag so the next generated code chunk won't get joined
1269 fJump = true;
1270 return a->GetCodePos();
1271}
1272
1273namespace
1274{
1275 bool IsJumpType(C4AulBCCType type) noexcept
1276 {
1277 return type == AB_JUMP || type == AB_JUMPAND || type == AB_JUMPOR || type == AB_CONDN || type == AB_JUMPNIL || type == AB_JUMPNOTNIL || type == AB_NilCoalescingIt;
1278 }
1279}
1280
1281void C4AulParseState::SetJumpHere(size_t iJumpOp)
1282{
1283 if (Type != PARSER) return;
1284 // Set target
1285 C4AulBCC *pBCC = a->GetCodeByPos(iPos: iJumpOp);
1286 assert(IsJumpType(pBCC->bccType));
1287 pBCC->bccX = a->GetCodePos() - iJumpOp;
1288 // Set flag so the next generated code chunk won't get joined
1289 fJump = true;
1290}
1291
1292void C4AulParseState::SetJump(size_t iJumpOp, size_t iWhere)
1293{
1294 if (Type != PARSER) return;
1295 // Set target
1296 C4AulBCC *pBCC = a->GetCodeByPos(iPos: iJumpOp);
1297 assert(IsJumpType(pBCC->bccType));
1298 pBCC->bccX = iWhere - iJumpOp;
1299}
1300
1301void C4AulParseState::AddJump(C4AulBCCType eType, size_t iWhere)
1302{
1303 AddBCC(eType, X: iWhere - a->GetCodePos());
1304}
1305
1306void C4AulParseState::PushLoop()
1307{
1308 if (Type != PARSER) return;
1309 Loop *pNew = new Loop();
1310 pNew->StackSize = iStack;
1311 pNew->Controls = nullptr;
1312 pNew->Next = pLoopStack;
1313 pLoopStack = pNew;
1314}
1315
1316void C4AulParseState::PopLoop()
1317{
1318 if (Type != PARSER) return;
1319 // Delete loop controls
1320 Loop *pLoop = pLoopStack;
1321 while (pLoop->Controls)
1322 {
1323 // Unlink
1324 Loop::Control *pCtrl = pLoop->Controls;
1325 pLoop->Controls = pCtrl->Next;
1326 // Delete
1327 delete pCtrl;
1328 }
1329 // Unlink & delete
1330 pLoopStack = pLoop->Next;
1331 delete pLoop;
1332}
1333
1334void C4AulParseState::AddLoopControl(bool fBreak)
1335{
1336 if (Type != PARSER) return;
1337 Loop::Control *pNew = new Loop::Control();
1338 pNew->Break = fBreak;
1339 pNew->Pos = a->GetCodePos();
1340 pNew->Next = pLoopStack->Controls;
1341 pLoopStack->Controls = pNew;
1342}
1343
1344const char *C4AulParseState::GetTokenName(C4AulTokenType TokenType)
1345{
1346 switch (TokenType)
1347 {
1348 case ATT_INVALID: return "invalid token";
1349 case ATT_DIR: return "directive";
1350 case ATT_IDTF: return "identifier";
1351 case ATT_INT: return "integer constant";
1352 case ATT_BOOL: return "boolean constant";
1353 case ATT_NIL: return "nil";
1354 case ATT_STRING: return "string constant";
1355 case ATT_C4ID: return "id constant";
1356 case ATT_COMMA: return "','";
1357 case ATT_COLON: return "':'";
1358 case ATT_DCOLON: return "'::'";
1359 case ATT_SCOLON: return "';'";
1360 case ATT_BOPEN: return "'('";
1361 case ATT_BCLOSE: return "')'";
1362 case ATT_BOPEN2: return "'['";
1363 case ATT_BCLOSE2: return "']'";
1364 case ATT_BLOPEN: return "'{'";
1365 case ATT_BLCLOSE: return "'}'";
1366 case ATT_SEP: return "'|'";
1367 case ATT_CALL: return "'->'";
1368 case ATT_GLOBALCALL: return "'global->'";
1369 case ATT_STAR: return "'*'";
1370 case ATT_AMP: return "'&'";
1371 case ATT_TILDE: return "'~'";
1372 case ATT_LDOTS: return "'...'";
1373 case ATT_DOT: return "'.'";
1374 case ATT_QMARK: return "'?'";
1375 case ATT_OPERATOR: return "operator";
1376 case ATT_EOF: return "end of file";
1377 }
1378 return "unrecognized token";
1379}
1380
1381void C4AulParseState::Shift(HoldStringsPolicy HoldStrings, bool bOperator)
1382{
1383 TokenType = GetNextToken(pToken: Idtf, pInt: &cInt, HoldStrings, bOperator);
1384}
1385
1386void C4AulParseState::Match(C4AulTokenType RefTokenType, const char *Message)
1387{
1388 if (TokenType != RefTokenType)
1389 // error
1390 throw C4AulParseError(this, Message ? Message :
1391 std::format(fmt: "{} expected, but found {}", args: GetTokenName(TokenType: RefTokenType), args: GetTokenName(TokenType)));
1392 Shift();
1393}
1394
1395void C4AulParseState::UnexpectedToken(const char *Expected)
1396{
1397 throw C4AulParseError(this, std::format(fmt: "{} expected, but found {}", args&: Expected, args: GetTokenName(TokenType)));
1398}
1399
1400void C4AulScript::ParseFn(C4AulScriptFunc *Fn, bool fExprOnly)
1401{
1402 // check if fn overloads other fn (all func tables are built now)
1403 // *MUST* check Fn->Owner-list, because it may be the engine (due to linked globals)
1404 if (Fn->OwnerOverloaded = Fn->Owner->GetOverloadedFunc(ByFunc: Fn))
1405 if (Fn->Owner == Fn->OwnerOverloaded->Owner)
1406 Fn->OwnerOverloaded->OverloadedBy = Fn;
1407 // reset pointer to next same-named func (will be set in AfterLink)
1408 Fn->NextSNFunc = nullptr;
1409 // store byte code pos
1410 // (relative position to code start; code pointer may change while
1411 // parsing)
1412 Fn->Code = reinterpret_cast<C4AulBCC *>(CPos - Code);
1413 // parse
1414 C4AulParseState state(Fn, this, C4AulParseState::PARSER);
1415 // get first token
1416 state.Shift();
1417 if (!fExprOnly)
1418 state.Parse_Function();
1419 else
1420 {
1421 state.Parse_Expression();
1422 state.SetNoRef();
1423 AddBCC(eType: AB_RETURN, X: 0, SPos: state.SPos);
1424 }
1425 // done
1426 return;
1427}
1428
1429void C4AulParseState::Parse_Script()
1430{
1431 bool fDone = false;
1432 const char *SPos0 = SPos;
1433 bool all_ok = true;
1434 while (!fDone) try
1435 {
1436 // Go to the next token if the current token could not be processed or no token has yet been parsed
1437 if (SPos == SPos0)
1438 {
1439 Shift();
1440 }
1441 SPos0 = SPos;
1442 switch (TokenType)
1443 {
1444 case ATT_DIR:
1445 {
1446 // check for include statement
1447 if (SEqual(szStr1: Idtf, C4AUL_Include))
1448 {
1449 Shift();
1450 // get id of script to include
1451 if (TokenType != ATT_C4ID)
1452 UnexpectedToken(Expected: "id constant");
1453 C4ID Id = static_cast<C4ID>(cInt);
1454 Shift();
1455 // add to include list
1456 a->Includes.push_front(x: Id);
1457 }
1458 else if (SEqual(szStr1: Idtf, C4AUL_Append))
1459 {
1460 // for #appendto * '*' needs to be ATT_STAR, not an operator.
1461 Shift(HoldStrings: Hold, bOperator: false);
1462 // get id of script to include/append
1463 bool nowarn = false;
1464 C4ID Id;
1465 switch (TokenType)
1466 {
1467 case ATT_C4ID:
1468 Id = static_cast<C4ID>(cInt);
1469 Shift();
1470 if (TokenType == ATT_IDTF && SEqual(szStr1: Idtf, C4AUL_NoWarn))
1471 {
1472 nowarn = true;
1473 Shift();
1474 }
1475 break;
1476 case ATT_STAR: // "*"
1477 Id = ~0;
1478 Shift();
1479 break;
1480 default:
1481 // -> ID expected
1482 UnexpectedToken(Expected: "id constant");
1483 }
1484 // add to append list
1485 a->Appends.push_back(x: {.id: Id, .nowarn: nowarn});
1486 }
1487 else if (SEqual(szStr1: Idtf, C4AUL_Strict))
1488 {
1489 // declare it as strict
1490 a->Strict = C4AulScriptStrict::STRICT1;
1491 Shift();
1492 if (TokenType == ATT_INT)
1493 {
1494 if (cInt == 2)
1495 a->Strict = C4AulScriptStrict::STRICT2;
1496 else if (cInt == 3)
1497 a->Strict = C4AulScriptStrict::STRICT3;
1498 else
1499 throw C4AulParseError(this, "unknown strict level");
1500 Shift();
1501 }
1502 }
1503 else
1504 // -> unknown directive
1505 throw C4AulParseError(this, "unknown directive: ", Idtf);
1506 break;
1507 }
1508 case ATT_IDTF:
1509 {
1510 if (SEqual(szStr1: Idtf, C4AUL_For))
1511 {
1512 throw C4AulParseError(this, "unexpected for outside function");
1513 }
1514 // check for variable definition (var)
1515 else if (SEqual(szStr1: Idtf, C4AUL_VarNamed))
1516 {
1517 throw C4AulParseError(this, "unexpected variable definition outside function");
1518 }
1519 // check for object-local variable definition (local)
1520 else if (SEqual(szStr1: Idtf, C4AUL_LocalNamed))
1521 {
1522 Shift();
1523 Parse_Local();
1524 Match(RefTokenType: ATT_SCOLON);
1525 break;
1526 }
1527 // check for variable definition (static)
1528 else if (SEqual(szStr1: Idtf, C4AUL_GlobalNamed))
1529 {
1530 Shift();
1531 // constant?
1532 if (TokenType == ATT_IDTF && SEqual(szStr1: Idtf, C4AUL_Const))
1533 {
1534 Shift();
1535 Parse_Const();
1536 }
1537 else
1538 Parse_Static();
1539 Match(RefTokenType: ATT_SCOLON);
1540 break;
1541 }
1542 else
1543 Parse_FuncHead();
1544 break;
1545 }
1546 case ATT_EOF:
1547 fDone = true;
1548 break;
1549 default:
1550 UnexpectedToken(Expected: "declaration");
1551 }
1552 all_ok = true;
1553 }
1554 catch (const C4AulError &err)
1555 {
1556 // damn! something went wrong, print it out
1557 // but only one error per function
1558 if (all_ok)
1559 err.show();
1560 all_ok = false;
1561 }
1562}
1563
1564void C4AulParseState::Parse_FuncHead()
1565{
1566 C4AulAccess Acc = AA_PUBLIC;
1567 // Access?
1568 if (SEqual(szStr1: Idtf, C4AUL_Private)) { Acc = AA_PRIVATE; Shift(); }
1569 else if (SEqual(szStr1: Idtf, C4AUL_Protected)) { Acc = AA_PROTECTED; Shift(); }
1570 else if (SEqual(szStr1: Idtf, C4AUL_Public)) { Acc = AA_PUBLIC; Shift(); }
1571 else if (SEqual(szStr1: Idtf, C4AUL_Global)) { Acc = AA_GLOBAL; Shift(); }
1572 // check for func declaration
1573 if (SEqual(szStr1: Idtf, C4AUL_Func))
1574 {
1575 Shift(HoldStrings: Discard, bOperator: false);
1576 bool bReturnRef = false;
1577 // get next token, must be func name or "&"
1578 if (TokenType == ATT_AMP)
1579 {
1580 bReturnRef = true;
1581 Shift(HoldStrings: Discard, bOperator: false);
1582 }
1583 if (TokenType != ATT_IDTF)
1584 UnexpectedToken(Expected: "function name");
1585 // check: symbol already in use?
1586 switch (Acc)
1587 {
1588 case AA_PRIVATE:
1589 case AA_PROTECTED:
1590 case AA_PUBLIC:
1591 if (a->LocalNamed.GetItemNr(strName: Idtf) != -1)
1592 throw C4AulParseError(this, "function definition: name already in use (local variable)");
1593 if (a->Def)
1594 break;
1595 // func in global context: fallthru
1596 case AA_GLOBAL:
1597 if (a->Engine->GlobalNamedNames.GetItemNr(strName: Idtf) != -1)
1598 throw C4AulParseError(this, "function definition: name already in use (global variable)");
1599 if (a->Engine->GlobalConstNames.GetItemNr(strName: Idtf) != -1)
1600 Strict2Error(message: "function definition: name already in use (global variable)");
1601 }
1602 // create script fn
1603 if (Acc == AA_GLOBAL)
1604 {
1605 // global func
1606 Fn = new C4AulScriptFunc(a->Engine, Idtf);
1607 C4AulFunc *FnLink = new C4AulFunc(a, nullptr);
1608 FnLink->LinkedTo = Fn; Fn->LinkedTo = FnLink;
1609 Acc = AA_PUBLIC;
1610 }
1611 else
1612 {
1613 // normal, local func
1614 Fn = new C4AulScriptFunc(a, Idtf);
1615 }
1616 // set up func (in the case we got an error)
1617 Fn->Script = SPos; // temporary
1618 Fn->Access = Acc; Fn->pOrgScript = a;
1619 Fn->bNewFormat = true; Fn->bReturnRef = bReturnRef;
1620 Shift(HoldStrings: Discard, bOperator: false);
1621 // expect an opening bracket now
1622 if (TokenType != ATT_BOPEN)
1623 UnexpectedToken(Expected: "'('");
1624 Shift(HoldStrings: Discard, bOperator: false);
1625 // get pars
1626 Fn->ParNamed.Reset(); // safety :)
1627 int cpar = 0;
1628 while (1)
1629 {
1630 // closing bracket?
1631 if (TokenType == ATT_BCLOSE)
1632 {
1633 Fn->Script = SPos;
1634 Shift();
1635 // end of params
1636 break;
1637 }
1638 // too many parameters?
1639 if (cpar >= C4AUL_MAX_Par)
1640 throw C4AulParseError(this, "'func' parameter list: too many parameters (max 10)");
1641
1642 if (TokenType == ATT_LDOTS)
1643 {
1644 Fn->Script = SPos;
1645 Shift();
1646 Match(RefTokenType: ATT_BCLOSE);
1647 break;
1648 }
1649 // must be a name or type now
1650 if (TokenType != ATT_IDTF && TokenType != ATT_AMP)
1651 {
1652 UnexpectedToken(Expected: "parameter or closing bracket");
1653 }
1654 char TypeIdtf[C4AUL_MAX_Identifier] = ""; // current identifier
1655 if (TokenType == ATT_IDTF)
1656 {
1657 SCopy(szSource: Idtf, sTarget: TypeIdtf);
1658 }
1659 // type identifier?
1660 if (SEqual(szStr1: Idtf, C4AUL_TypeInt)) { Fn->ParType[cpar] = C4V_Int; Shift(HoldStrings: Discard, bOperator: false); }
1661 else if (SEqual(szStr1: Idtf, C4AUL_TypeBool)) { Fn->ParType[cpar] = C4V_Bool; Shift(HoldStrings: Discard, bOperator: false); }
1662 else if (SEqual(szStr1: Idtf, C4AUL_TypeC4ID)) { Fn->ParType[cpar] = C4V_C4ID; Shift(HoldStrings: Discard, bOperator: false); }
1663 else if (SEqual(szStr1: Idtf, C4AUL_TypeC4Object)) { Fn->ParType[cpar] = C4V_C4Object; Shift(HoldStrings: Discard, bOperator: false); }
1664 else if (SEqual(szStr1: Idtf, C4AUL_TypeString)) { Fn->ParType[cpar] = C4V_String; Shift(HoldStrings: Discard, bOperator: false); }
1665 else if (SEqual(szStr1: Idtf, C4AUL_TypeArray)) { Fn->ParType[cpar] = C4V_Array; Shift(HoldStrings: Discard, bOperator: false); }
1666 else if (SEqual(szStr1: Idtf, C4AUL_TypeMap)) { Fn->ParType[cpar] = C4V_Map; Shift(HoldStrings: Discard, bOperator: false); }
1667 else if (SEqual(szStr1: Idtf, C4AUL_TypeAny)) { Fn->ParType[cpar] = C4V_Any; Shift(HoldStrings: Discard, bOperator: false); }
1668 // ampersand?
1669 if (TokenType == ATT_AMP) { Fn->ParType[cpar] = C4V_pC4Value; Shift(HoldStrings: Discard, bOperator: false); }
1670 if (TokenType != ATT_IDTF)
1671 {
1672 if (SEqual(szStr1: TypeIdtf, szStr2: ""))
1673 UnexpectedToken(Expected: "parameter or closing bracket");
1674 // A parameter with the same name as a type
1675 Strict2Error(message: "parameter has the same name as type ", identifier: TypeIdtf);
1676 Fn->ParType[cpar] = C4V_Any;
1677 Fn->ParNamed.AddName(pnName: TypeIdtf);
1678 }
1679 else
1680 {
1681 Fn->ParNamed.AddName(pnName: Idtf);
1682 Shift();
1683 }
1684 // end of params?
1685 if (TokenType == ATT_BCLOSE)
1686 {
1687 Fn->Script = SPos;
1688 Shift();
1689 break;
1690 }
1691 // must be a comma now
1692 if (TokenType != ATT_COMMA)
1693 UnexpectedToken(Expected: "comma or closing bracket");
1694 Shift(HoldStrings: Discard, bOperator: false);
1695 cpar++;
1696 }
1697 // ok, expect opening block
1698 if (TokenType != ATT_BLOPEN)
1699 {
1700 // warn
1701 Strict2Error(message: "'func': expecting opening block ('{') after func declaration");
1702 // not really new syntax (a sort of legacy mode)
1703 Fn->bNewFormat = false;
1704 }
1705 else
1706 {
1707 Fn->Script = SPos;
1708 Shift();
1709 }
1710 Parse_Desc();
1711 Parse_Function();
1712 Match(RefTokenType: ATT_BLCLOSE);
1713 return;
1714 }
1715 // Must be old-style function declaration now
1716 if (a->Strict >= C4AulScriptStrict::STRICT2)
1717 throw C4AulParseError(this, "Declaration expected, but found identifier ", Idtf);
1718 // check: symbol already in use?
1719 switch (Acc)
1720 {
1721 case AA_PRIVATE:
1722 case AA_PROTECTED:
1723 case AA_PUBLIC:
1724 if (a->LocalNamed.GetItemNr(strName: Idtf) != -1)
1725 throw C4AulParseError(this, "function definition: name already in use (local variable)");
1726 if (a->Def)
1727 break;
1728 // func in global context: fallthru
1729 case AA_GLOBAL:
1730 if (a->Engine->GlobalNamedNames.GetItemNr(strName: Idtf) != -1)
1731 throw C4AulParseError(this, "function definition: name already in use (global variable)");
1732 }
1733 char FuncIdtf[C4AUL_MAX_Identifier] = ""; // current identifier
1734 SCopy(szSource: Idtf, sTarget: FuncIdtf);
1735 Shift();
1736 if (TokenType != ATT_COLON)
1737 throw C4AulParseError(this, std::format(fmt: "declaration expected, but found identifier '{}'", args: +FuncIdtf));
1738 // create script fn
1739 if (Acc == AA_GLOBAL)
1740 {
1741 // global func
1742 Fn = new C4AulScriptFunc(a->Engine, FuncIdtf);
1743 C4AulFunc *FnLink = new C4AulFunc(a, nullptr);
1744 FnLink->LinkedTo = Fn; Fn->LinkedTo = FnLink;
1745 Acc = AA_PUBLIC;
1746 }
1747 else
1748 {
1749 // normal, local func
1750 Fn = new C4AulScriptFunc(a, FuncIdtf);
1751 }
1752 Fn->Script = SPos;
1753 Fn->Access = Acc;
1754 Fn->pOrgScript = a;
1755 Fn->bNewFormat = false;
1756 Fn->bReturnRef = false;
1757 Shift();
1758 Parse_Desc();
1759 const char *SPos0 = SPos;
1760 while (1) switch (TokenType)
1761 {
1762 // end of function
1763 case ATT_EOF: case ATT_DIR: return;
1764 case ATT_IDTF:
1765 {
1766 // check for func declaration
1767 if (SEqual(szStr1: Idtf, C4AUL_Private)) return;
1768 else if (SEqual(szStr1: Idtf, C4AUL_Protected)) return;
1769 else if (SEqual(szStr1: Idtf, C4AUL_Public)) return;
1770 else if (SEqual(szStr1: Idtf, C4AUL_Global)) return;
1771 else if (SEqual(szStr1: Idtf, C4AUL_Func)) return;
1772 // check for variable definition (var)
1773 else if (SEqual(szStr1: Idtf, C4AUL_VarNamed))
1774 {
1775 Shift();
1776 Parse_Var();
1777 }
1778 // check for variable definition (local)
1779 else if (SEqual(szStr1: Idtf, C4AUL_LocalNamed))
1780 {
1781 if (a->Def)
1782 {
1783 Shift();
1784 Parse_Local();
1785 }
1786 else
1787 throw C4AulParseError(this, "'local' variable declaration in global script");
1788 }
1789 // check for variable definition (static)
1790 else if (SEqual(szStr1: Idtf, C4AUL_GlobalNamed))
1791 {
1792 Shift();
1793 Parse_Static();
1794 }
1795 // might be old style declaration
1796 else
1797 {
1798 const char *SPos0_ = SPos;
1799 Shift();
1800 if (TokenType == ATT_COLON)
1801 {
1802 // Reset source position to the point before the label
1803 SPos = SPos0;
1804 Shift();
1805 return;
1806 }
1807 else
1808 {
1809 // The current token might be a label
1810 // In that case the next round of the loop will need to reset the position to what's in SPos0_ now
1811 SPos0 = SPos0_;
1812 }
1813 }
1814 break;
1815 }
1816 default:
1817 {
1818 SPos0 = SPos;
1819 Shift();
1820 break;
1821 }
1822 }
1823}
1824
1825void C4AulParseState::Parse_Desc()
1826{
1827 // check for function desc
1828 if (TokenType == ATT_BOPEN2)
1829 {
1830 // parse for end of desc
1831 const char *SPos0 = SPos;
1832 int Len = 0;
1833 int iBracketsOpen = 1;
1834 while (true)
1835 {
1836 // another bracket open
1837 if (*SPos == '[') iBracketsOpen++;
1838 // a bracket closed
1839 if (*SPos == ']') iBracketsOpen--;
1840 // last bracket closed: at end of desc block
1841 if (iBracketsOpen == 0) break;
1842 // check for eof
1843 if (!*SPos)
1844 // -> function desc not closed
1845 throw C4AulParseError(this, "function desc not closed");
1846 // next char
1847 SPos++; Len++;
1848 }
1849 SPos++;
1850 // extract desc
1851 Fn->Desc.Copy(pnData: SPos0, iChars: Len);
1852 Fn->Script = SPos;
1853 Shift();
1854 }
1855 else
1856 Fn->Desc.Clear();
1857}
1858
1859void C4AulParseState::Parse_Function()
1860{
1861 iStack = 0;
1862 Done = false;
1863 while (!Done) switch (TokenType)
1864 {
1865 // a block end?
1866 case ATT_BLCLOSE:
1867 {
1868 // new-form func?
1869 if (Fn->bNewFormat)
1870 {
1871 // all ok, insert a return
1872 C4AulBCC *CPos = a->GetCodeByPos(iPos: std::max<size_t>(a: a->GetCodePos(), b: 1) - 1);
1873 if (!CPos || CPos->bccType != AB_RETURN || fJump)
1874 {
1875 AddBCC(eType: AB_NIL);
1876 AddBCC(eType: AB_RETURN);
1877 }
1878 // and break
1879 Done = true;
1880 // Do not blame this function for script errors between functions
1881 Fn = nullptr;
1882 return;
1883 }
1884 throw C4AulParseError(this, "no '{' found for '}'");
1885 }
1886 case ATT_EOF:
1887 {
1888 Done = true;
1889 return;
1890 }
1891 default:
1892 {
1893 Parse_Statement();
1894 assert(!iStack);
1895 }
1896 }
1897}
1898
1899void C4AulParseState::Parse_Block()
1900{
1901 Match(RefTokenType: ATT_BLOPEN);
1902 // insert block in byte code
1903 while (1) switch (TokenType)
1904 {
1905 case ATT_BLCLOSE:
1906 Shift();
1907 return;
1908 default:
1909 {
1910 Parse_Statement();
1911 break;
1912 }
1913 }
1914}
1915
1916void C4AulParseState::Parse_Statement()
1917{
1918 switch (TokenType)
1919 {
1920 // do we have a block start?
1921 case ATT_BLOPEN:
1922 {
1923 const auto isMap = IsMapLiteral();
1924 TokenType = ATT_BLOPEN;
1925 if (!isMap)
1926 {
1927 Parse_Block();
1928 return;
1929 }
1930
1931 // fall through
1932 }
1933 case ATT_BOPEN:
1934 case ATT_BOPEN2:
1935 case ATT_OPERATOR:
1936 case ATT_NIL:
1937 case ATT_INT: // constant in cInt
1938 case ATT_BOOL: // constant in cInt
1939 case ATT_STRING: // reference in cInt
1940 case ATT_C4ID: // converted ID in cInt
1941 case ATT_GLOBALCALL:
1942 {
1943 Parse_Expression();
1944 SetNoRef();
1945 AddBCC(eType: AB_STACK, X: -1);
1946 Match(RefTokenType: ATT_SCOLON);
1947 return;
1948 }
1949 // additional function separator
1950 case ATT_SCOLON:
1951 {
1952 Shift();
1953 break;
1954 }
1955 case ATT_IDTF:
1956 {
1957 // check for variable definition (var)
1958 if (SEqual(szStr1: Idtf, C4AUL_VarNamed))
1959 {
1960 Shift();
1961 Parse_Var();
1962 }
1963 // check for variable definition (local)
1964 else if (SEqual(szStr1: Idtf, C4AUL_LocalNamed))
1965 {
1966 Shift();
1967 Parse_Local();
1968 }
1969 // check for variable definition (static)
1970 else if (SEqual(szStr1: Idtf, C4AUL_GlobalNamed))
1971 {
1972 Shift();
1973 Parse_Static();
1974 }
1975 // check for parameter
1976 else if (Fn->ParNamed.GetItemNr(strName: Idtf) != -1)
1977 {
1978 Parse_Expression();
1979 SetNoRef();
1980 AddBCC(eType: AB_STACK, X: -1);
1981 }
1982 // check for variable (var)
1983 else if (Fn->VarNamed.GetItemNr(strName: Idtf) != -1)
1984 {
1985 Parse_Expression();
1986 SetNoRef();
1987 AddBCC(eType: AB_STACK, X: -1);
1988 }
1989 // check for objectlocal variable (local)
1990 else if (a->LocalNamed.GetItemNr(strName: Idtf) != -1)
1991 {
1992 // global func?
1993 if (Fn->Owner == &Game.ScriptEngine)
1994 throw C4AulParseError(this, "using local variable in global function!");
1995 // insert variable by id
1996 Parse_Expression();
1997 AddBCC(eType: AB_STACK, X: -1);
1998 }
1999 // check for global variable (static)
2000 else if (a->Engine->GlobalNamedNames.GetItemNr(strName: Idtf) != -1)
2001 {
2002 Parse_Expression();
2003 AddBCC(eType: AB_STACK, X: -1);
2004 }
2005 // check new-form func begin
2006 else if (SEqual(szStr1: Idtf, C4AUL_Func))
2007 {
2008 // break parsing: start of next func
2009 Done = true;
2010 break;
2011 }
2012 // get function by identifier: first check special functions
2013 else if (SEqual(szStr1: Idtf, C4AUL_If)) // if
2014 {
2015 Shift();
2016 Parse_If();
2017 break;
2018 }
2019 else if (SEqual(szStr1: Idtf, C4AUL_Else)) // else
2020 {
2021 throw C4AulParseError(this, "misplaced 'else'");
2022 }
2023 else if (SEqual(szStr1: Idtf, C4AUL_While)) // while
2024 {
2025 Shift();
2026 Parse_While();
2027 break;
2028 }
2029 else if (SEqual(szStr1: Idtf, C4AUL_For)) // for
2030 {
2031 Shift();
2032 // Look if it's the "for ([var] foo in array) or for ([var] k, v in map)"-form
2033 const char *SPos0 = SPos;
2034 // must be followed by a bracket
2035 Match(RefTokenType: ATT_BOPEN);
2036 // optional var
2037 if (TokenType == ATT_IDTF && SEqual(szStr1: Idtf, C4AUL_VarNamed))
2038 Shift();
2039
2040 bool isForEach = false;
2041 // variable and "in"
2042 if (TokenType == ATT_IDTF
2043 && GetNextToken(pToken: Idtf, pInt: &cInt, HoldStrings: Discard, bOperator: true) == ATT_IDTF
2044 && SEqual(szStr1: Idtf, C4AUL_In)) isForEach = true;
2045
2046 SPos = SPos0;
2047 Shift();
2048 if (!isForEach)
2049 {
2050 if (TokenType == ATT_IDTF && SEqual(szStr1: Idtf, C4AUL_VarNamed))
2051 Shift();
2052 if (TokenType == ATT_IDTF
2053 && GetNextToken(pToken: Idtf, pInt: &cInt, HoldStrings: Discard, bOperator: true) == ATT_COMMA
2054 && GetNextToken(pToken: Idtf, pInt: &cInt, HoldStrings: Discard, bOperator: true) == ATT_IDTF
2055 && GetNextToken(pToken: Idtf, pInt: &cInt, HoldStrings: Discard, bOperator: true) == ATT_IDTF
2056 && SEqual(szStr1: Idtf, C4AUL_In)) isForEach = true;
2057
2058 SPos = SPos0;
2059 Shift();
2060 }
2061
2062 if (isForEach) Parse_ForEach();
2063 else Parse_For();
2064 break;
2065 }
2066 else if (SEqual(szStr1: Idtf, C4AUL_Return)) // return
2067 {
2068 bool multi_params_hack = false;
2069 Shift();
2070 if (TokenType == ATT_BOPEN && Fn->pOrgScript->Strict < C4AulScriptStrict::STRICT2)
2071 {
2072 // parse return(retvals) - return(retval, unused, parameters, ...) allowed for backwards compatibility
2073 if (Parse_Params(iMaxCnt: 1, sWarn: nullptr) == 1)
2074 {
2075 // return (1 + 1) * 3 returns 6, not 2
2076 Parse_Expression2();
2077 }
2078 else
2079 multi_params_hack = true;
2080 }
2081 else if (TokenType == ATT_SCOLON)
2082 {
2083 // allow return; without return value (implies nil)
2084 AddBCC(eType: AB_NIL);
2085 }
2086 else
2087 {
2088 Parse_Expression();
2089 }
2090 if (!Fn->bReturnRef)
2091 SetNoRef();
2092 AddBCC(eType: AB_RETURN);
2093 if (multi_params_hack && TokenType != ATT_SCOLON)
2094 {
2095 // return (1, 1) * 3 returns 1
2096 // but warn about it, the * 3 won't get executed and a stray ',' could lead to unexpected results
2097 Warn(msg: "';' expected, but found ", pIdtf: GetTokenName(TokenType));
2098 AddBCC(eType: AB_STACK, X: +1);
2099 Parse_Expression2();
2100 AddBCC(eType: AB_STACK, X: -1);
2101 }
2102 }
2103 else if (SEqual(szStr1: Idtf, C4AUL_this)) // this on top level
2104 {
2105 Parse_Expression();
2106 SetNoRef();
2107 AddBCC(eType: AB_STACK, X: -1);
2108 }
2109 else if (SEqual(szStr1: Idtf, C4AUL_Break)) // break
2110 {
2111 Shift();
2112 if (Type == PARSER)
2113 {
2114 // Must be inside a loop
2115 if (!pLoopStack)
2116 {
2117 Strict2Error(message: "'break' is only allowed inside loops");
2118 }
2119 else
2120 {
2121 // Insert code
2122 if (pLoopStack->StackSize != iStack)
2123 AddBCC(eType: AB_STACK, X: pLoopStack->StackSize - iStack);
2124 AddLoopControl(fBreak: true);
2125 AddBCC(eType: AB_JUMP);
2126 }
2127 }
2128 }
2129 else if (SEqual(szStr1: Idtf, C4AUL_Continue)) // continue
2130 {
2131 Shift();
2132 if (Type == PARSER)
2133 {
2134 // Must be inside a loop
2135 if (!pLoopStack)
2136 {
2137 Strict2Error(message: "'continue' is only allowed inside loops");
2138 }
2139 else
2140 {
2141 // Insert code
2142 if (pLoopStack->StackSize != iStack)
2143 AddBCC(eType: AB_STACK, X: pLoopStack->StackSize - iStack);
2144 AddLoopControl(fBreak: false);
2145 AddBCC(eType: AB_JUMP);
2146 }
2147 }
2148 }
2149 else if (SEqual(szStr1: Idtf, C4AUL_Var)) // Var
2150 {
2151 Parse_Expression();
2152 SetNoRef();
2153 AddBCC(eType: AB_STACK, X: -1);
2154 }
2155 else if (SEqual(szStr1: Idtf, C4AUL_Par)) // Par
2156 {
2157 Parse_Expression();
2158 SetNoRef();
2159 AddBCC(eType: AB_STACK, X: -1);
2160 }
2161 else if (SEqual(szStr1: Idtf, C4AUL_Inherited) || SEqual(szStr1: Idtf, C4AUL_SafeInherited))
2162 {
2163 Parse_Expression();
2164 SetNoRef();
2165 AddBCC(eType: AB_STACK, X: -1);
2166 }
2167 // now this may be the end of the function: first of all check for access directives
2168 else if (SEqual(szStr1: Idtf, C4AUL_Private) ||
2169 SEqual(szStr1: Idtf, C4AUL_Protected) ||
2170 SEqual(szStr1: Idtf, C4AUL_Public) ||
2171 SEqual(szStr1: Idtf, C4AUL_Global))
2172 {
2173 Shift();
2174 // check if it's followed by a function declaration
2175 // 'func' idtf?
2176 if (TokenType == ATT_IDTF && SEqual(szStr1: Idtf, C4AUL_Func))
2177 {
2178 // ok, break here
2179 Done = true;
2180 }
2181 else
2182 {
2183 // expect function name and colon
2184 Match(RefTokenType: ATT_IDTF);
2185 Match(RefTokenType: ATT_COLON);
2186 // break here
2187 Done = true;
2188 }
2189 break;
2190 }
2191 else
2192 {
2193 bool gotohack = false;
2194 // none of these? then it's a function
2195 // if it's a label, it will be missinterpreted here, which will be corrected later
2196 // it may be the first goto() found? (old syntax only!)
2197 if (SEqual(szStr1: Idtf, C4AUL_Goto) && Fn->pOrgScript->Strict == C4AulScriptStrict::NONSTRICT)
2198 // add AB_RETURN later on
2199 gotohack = true;
2200 C4AulFunc *FoundFn;
2201 // old syntax: do not allow recursive calls in overloaded functions
2202 if (Fn->pOrgScript->Strict == C4AulScriptStrict::NONSTRICT && Fn->OwnerOverloaded && SEqual(szStr1: Idtf, szStr2: Fn->Name))
2203 FoundFn = Fn->OwnerOverloaded;
2204 else
2205 // get regular function
2206 if (Fn->Owner == &Game.ScriptEngine)
2207 FoundFn = a->Owner->GetFuncRecursive(pIdtf: Idtf);
2208 else
2209 FoundFn = a->GetFuncRecursive(pIdtf: Idtf);
2210
2211 if (FoundFn && FoundFn->SFunc() && FoundFn->SFunc()->Access < Fn->pOrgScript->GetAllowedAccess(func: FoundFn, caller: Fn->pOrgScript))
2212 {
2213 throw C4AulParseError(this, "insufficient access level", Idtf);
2214 }
2215
2216 // ignore func-not-found errors in the preparser, because the function tables are not built yet
2217 if (!FoundFn && Type == PARSER)
2218 {
2219 // the function could not be found
2220 // this *could* be because it's a label to the next function, which, however, is not visible in the current
2221 // context (e.g., global functions) [Soeren]
2222 // parsing would have to be aborted anyway, so have a short look at the next token
2223 if (GetNextToken(pToken: Idtf, pInt: &cInt, HoldStrings: Discard, bOperator: true) == ATT_COLON)
2224 {
2225 // OK, next function found. abort
2226 Done = true;
2227 break;
2228 }
2229 // -> func not found
2230 throw C4AulParseError(this, "unknown identifier: ", Idtf);
2231 }
2232 Shift();
2233 // check if it's a label - labels like OCF_Living are OK (ugh...)
2234 if (TokenType == ATT_COLON)
2235 {
2236 // break here
2237 Done = true;
2238 return;
2239 }
2240 // The preparser assumes the syntax is correct
2241 if (TokenType == ATT_BOPEN || Type == PARSER)
2242 Parse_Params(iMaxCnt: FoundFn ? FoundFn->GetParCount() : 10, sWarn: FoundFn ? FoundFn->Name : Idtf, pFunc: FoundFn);
2243 AddBCC(eType: AB_FUNC, X: reinterpret_cast<std::intptr_t>(FoundFn));
2244 if (gotohack)
2245 {
2246 AddBCC(eType: AB_RETURN);
2247 AddBCC(eType: AB_STACK, X: +1);
2248 }
2249 Parse_Expression2();
2250 SetNoRef();
2251 AddBCC(eType: AB_STACK, X: -1);
2252 }
2253 Match(RefTokenType: ATT_SCOLON);
2254 break;
2255 }
2256 default:
2257 {
2258 // -> unexpected token
2259 UnexpectedToken(Expected: "statement");
2260 }
2261 }
2262}
2263
2264int C4AulParseState::Parse_Params(int iMaxCnt, const char *sWarn, C4AulFunc *pFunc)
2265{
2266 int size = 0;
2267 // so it's a regular function; force "("
2268 Match(RefTokenType: ATT_BOPEN);
2269 bool fDone = false;
2270 do
2271 switch (TokenType)
2272 {
2273 case ATT_BCLOSE:
2274 {
2275 Shift();
2276 // () -> size 0, (*,) -> size 2, (*,*,) -> size 3
2277 if (size > 0)
2278 {
2279 AddBCC(eType: AB_NIL);
2280 ++size;
2281 }
2282 fDone = true;
2283 break;
2284 }
2285 case ATT_COMMA:
2286 {
2287 // got no parameter before a ","? then push a 0-constant
2288 AddBCC(eType: AB_NIL);
2289 Shift();
2290 ++size;
2291 break;
2292 }
2293 case ATT_LDOTS:
2294 {
2295 Shift();
2296 // Push all unnamed parameters of the current function as parameters
2297 int i = Fn->ParNamed.iSize;
2298 while (size < iMaxCnt && i < C4AUL_MAX_Par)
2299 {
2300 AddBCC(eType: AB_PARN_R, X: i);
2301 ++i;
2302 ++size;
2303 }
2304 // Do not allow more parameters even if there is place left
2305 fDone = true;
2306 Match(RefTokenType: ATT_BCLOSE);
2307 break;
2308 }
2309 default:
2310 {
2311 // get a parameter
2312 Parse_Expression();
2313 if (pFunc)
2314 {
2315 bool anyfunctakesref = pFunc->GetParCount() > size && (pFunc->GetParType()[size] == C4V_pC4Value);
2316 // pFunc either was the return value from a GetFuncFast-Call or
2317 // pFunc is the only function that could be called, so this loop is superflous
2318 C4AulFunc *pFunc2 = pFunc;
2319 while (pFunc2 = a->Engine->GetNextSNFunc(After: pFunc2))
2320 if (pFunc2->GetParCount() > size && pFunc2->GetParType()[size] == C4V_pC4Value) anyfunctakesref = true;
2321 // Change the bytecode to the equivalent that does not produce a reference.
2322 if (!anyfunctakesref)
2323 SetNoRef();
2324 }
2325 ++size;
2326 // end of parameter list?
2327 if (TokenType == ATT_COMMA)
2328 Shift();
2329 else if (TokenType == ATT_BCLOSE)
2330 {
2331 Shift();
2332 fDone = true;
2333 }
2334 else UnexpectedToken(Expected: "',' or ')'");
2335 break;
2336 }
2337 }
2338 while (!fDone);
2339 // too many parameters?
2340 if (sWarn && size > iMaxCnt && Type == PARSER)
2341 Warn(msg: std::format(fmt: "{}: passing {} parameters, but only {} are used", args&: sWarn, args&: size, args&: iMaxCnt), pIdtf: nullptr);
2342 // Balance stack
2343 if (size != iMaxCnt)
2344 AddBCC(eType: AB_STACK, X: iMaxCnt - size);
2345 return size;
2346}
2347
2348void C4AulParseState::Parse_Array()
2349{
2350 // force "["
2351 Match(RefTokenType: ATT_BOPEN2);
2352 // Create an array
2353 int size = 0;
2354 bool fDone = false;
2355 do
2356 switch (TokenType)
2357 {
2358 case ATT_BCLOSE2:
2359 {
2360 Shift();
2361 // [] -> size 0, [*,] -> size 2, [*,*,] -> size 3
2362 if (size > 0)
2363 {
2364 AddBCC(eType: AB_NIL);
2365 ++size;
2366 }
2367 fDone = true;
2368 break;
2369 }
2370 case ATT_COMMA:
2371 {
2372 // got no parameter before a ","? then push a 0-constant
2373 AddBCC(eType: AB_NIL);
2374 Shift();
2375 ++size;
2376 break;
2377 }
2378 default:
2379 {
2380 Parse_Expression();
2381 SetNoRef();
2382 ++size;
2383 if (TokenType == ATT_COMMA)
2384 Shift();
2385 else if (TokenType == ATT_BCLOSE2)
2386 {
2387 Shift();
2388 fDone = true;
2389 break;
2390 }
2391 else
2392 UnexpectedToken(Expected: "',' or ']'");
2393 }
2394 }
2395 while (!fDone);
2396 // add terminator
2397 AddBCC(eType: AB_ARRAY, X: size);
2398}
2399
2400void C4AulParseState::Parse_Map()
2401{
2402 // force "{"
2403 Match(RefTokenType: ATT_BLOPEN);
2404 // Create a map
2405 int size = 0;
2406 bool fDone = false;
2407 do
2408 switch (TokenType)
2409 {
2410 case ATT_BLCLOSE:
2411 {
2412 Shift();
2413 fDone = true;
2414 break;
2415 }
2416 case ATT_COMMA:
2417 {
2418 // got no parameter before a ","? this is an error in a map
2419 throw C4AulParseError(this, "',' found in a map without preceding key-value assignment ");
2420 break;
2421 }
2422 default:
2423 {
2424 switch (TokenType)
2425 {
2426 case ATT_IDTF:
2427 {
2428 C4String *string;
2429 if (!(string = a->Engine->Strings.FindString(strString: Idtf)))
2430 string = a->Engine->Strings.RegString(strString: Idtf);
2431 if (Type == PARSER) string->Hold = true;
2432 AddBCC(eType: AB_STRING, X: reinterpret_cast<std::intptr_t>(string));
2433 Shift();
2434 break;
2435 }
2436 case ATT_STRING:
2437 AddBCC(eType: AB_STRING, X: cInt);
2438 Shift();
2439 break;
2440 case ATT_BOPEN2:
2441 Shift();
2442 Parse_Expression();
2443 SetNoRef();
2444 Match(RefTokenType: ATT_BCLOSE2);
2445 break;
2446 default:
2447 UnexpectedToken(Expected: "string or identifier");
2448 }
2449
2450 if (TokenType != ATT_OPERATOR || !SEqual(szStr1: C4ScriptOpMap[cInt].Identifier, szStr2: "="))
2451 {
2452 UnexpectedToken(Expected: "'='");
2453 }
2454 Shift();
2455
2456 Parse_Expression();
2457 SetNoRef();
2458 ++size;
2459 if (TokenType == ATT_COMMA)
2460 Shift();
2461 else if (TokenType == ATT_BLCLOSE)
2462 {
2463 Shift();
2464 fDone = true;
2465 break;
2466 }
2467 else
2468 UnexpectedToken(Expected: "',' or '}'");
2469 }
2470 }
2471 while (!fDone);
2472 // add terminator
2473 AddBCC(eType: AB_MAP, X: size);
2474}
2475
2476void C4AulParseState::Parse_While()
2477{
2478 // Save position for later jump back
2479 const auto iStart = JumpHere();
2480 // Execute condition
2481 if (Fn->pOrgScript->Strict >= C4AulScriptStrict::STRICT2)
2482 {
2483 Match(RefTokenType: ATT_BOPEN);
2484 Parse_Expression();
2485 Match(RefTokenType: ATT_BCLOSE);
2486 }
2487 else
2488 Parse_Params(iMaxCnt: 1, C4AUL_While);
2489 SetNoRef();
2490 // Check condition
2491 const auto iCond = a->GetCodePos();
2492 AddBCC(eType: AB_CONDN);
2493 // We got a loop
2494 PushLoop();
2495 // Execute body
2496 Parse_Statement();
2497 if (Type != PARSER) return;
2498 // Jump back
2499 AddJump(eType: AB_JUMP, iWhere: iStart);
2500 // Set target for conditional jump
2501 SetJumpHere(iCond);
2502 // Set targets for break/continue
2503 for (Loop::Control *pCtrl = pLoopStack->Controls; pCtrl; pCtrl = pCtrl->Next)
2504 if (pCtrl->Break)
2505 SetJumpHere(pCtrl->Pos);
2506 else
2507 SetJump(iJumpOp: pCtrl->Pos, iWhere: iStart);
2508 PopLoop();
2509}
2510
2511void C4AulParseState::Parse_If()
2512{
2513 if (Fn->pOrgScript->Strict >= C4AulScriptStrict::STRICT2)
2514 {
2515 Match(RefTokenType: ATT_BOPEN);
2516 Parse_Expression();
2517 Match(RefTokenType: ATT_BCLOSE);
2518 }
2519 else
2520 Parse_Params(iMaxCnt: 1, C4AUL_If);
2521 SetNoRef();
2522 // create bytecode, remember position
2523 const auto iCond = a->GetCodePos();
2524 AddBCC(eType: AB_CONDN);
2525 // parse controlled statement
2526 Parse_Statement();
2527 if (TokenType == ATT_IDTF && SEqual(szStr1: Idtf, C4AUL_Else))
2528 {
2529 // add jump
2530 const auto iJump = a->GetCodePos();
2531 AddBCC(eType: AB_JUMP);
2532 // set condition jump target
2533 SetJumpHere(iCond);
2534 Shift();
2535 // expect a command now
2536 Parse_Statement();
2537 // set jump target
2538 SetJumpHere(iJump);
2539 }
2540 else
2541 // set condition jump target
2542 SetJumpHere(iCond);
2543}
2544
2545void C4AulParseState::Parse_For()
2546{
2547 // Initialization
2548 if (TokenType == ATT_IDTF && SEqual(szStr1: Idtf, C4AUL_VarNamed))
2549 {
2550 Shift();
2551 Parse_Var();
2552 }
2553 else if (TokenType != ATT_SCOLON)
2554 {
2555 Parse_Expression();
2556 SetNoRef();
2557 AddBCC(eType: AB_STACK, X: -1);
2558 }
2559 // Consume first semicolon
2560 Match(RefTokenType: ATT_SCOLON);
2561 // Condition
2562 size_t iCondition = SizeMax, iJumpBody = SizeMax, iJumpOut = SizeMax;
2563 if (TokenType != ATT_SCOLON)
2564 {
2565 // Add condition code
2566 iCondition = JumpHere();
2567 Parse_Expression();
2568 SetNoRef();
2569 // Jump out
2570 iJumpOut = a->GetCodePos();
2571 AddBCC(eType: AB_CONDN);
2572 }
2573 // Consume second semicolon
2574 Match(RefTokenType: ATT_SCOLON);
2575 // Incrementor
2576 size_t iIncrementor = SizeMax;
2577 if (TokenType != ATT_BCLOSE)
2578 {
2579 // Must jump over incrementor
2580 iJumpBody = a->GetCodePos();
2581 AddBCC(eType: AB_JUMP);
2582 // Add incrementor code
2583 iIncrementor = JumpHere();
2584 Parse_Expression();
2585 SetNoRef();
2586 AddBCC(eType: AB_STACK, X: -1);
2587 // Jump to condition
2588 if (iCondition != SizeMax)
2589 AddJump(eType: AB_JUMP, iWhere: iCondition);
2590 }
2591 // Consume closing bracket
2592 Match(RefTokenType: ATT_BCLOSE);
2593 // Allow break/continue from now on
2594 PushLoop();
2595 // Body
2596 const auto iBody = JumpHere();
2597 if (iJumpBody != SizeMax)
2598 SetJumpHere(iJumpBody);
2599 Parse_Statement();
2600 if (Type != PARSER) return;
2601 // Where to jump back?
2602 size_t iJumpBack;
2603 if (iIncrementor != SizeMax)
2604 iJumpBack = iIncrementor;
2605 else if (iCondition != SizeMax)
2606 iJumpBack = iCondition;
2607 else
2608 iJumpBack = iBody;
2609 AddJump(eType: AB_JUMP, iWhere: iJumpBack);
2610 // Set target for condition
2611 if (iJumpOut != SizeMax)
2612 SetJumpHere(iJumpOut);
2613 // Set targets for break/continue
2614 for (Loop::Control *pCtrl = pLoopStack->Controls; pCtrl; pCtrl = pCtrl->Next)
2615 if (pCtrl->Break)
2616 SetJumpHere(pCtrl->Pos);
2617 else
2618 SetJump(iJumpOp: pCtrl->Pos, iWhere: iJumpBack);
2619 PopLoop();
2620}
2621
2622void C4AulParseState::Parse_ForEach()
2623{
2624 bool forMap = false;
2625
2626 if (TokenType == ATT_IDTF && SEqual(szStr1: Idtf, C4AUL_VarNamed))
2627 {
2628 Shift();
2629 }
2630 // get variable name
2631 if (TokenType != ATT_IDTF)
2632 UnexpectedToken(Expected: "variable name");
2633 if (Type == PREPARSER)
2634 {
2635 // insert variable
2636 Fn->VarNamed.AddName(pnName: Idtf);
2637 }
2638 // search variable (fail if not found)
2639 int iVarID = Fn->VarNamed.GetItemNr(strName: Idtf);
2640 if (iVarID < 0)
2641 throw C4AulParseError(this, "internal error: var definition: var not found in variable table");
2642 Shift();
2643
2644 int iVarIDForMapValue = 0;
2645 if (TokenType == ATT_COMMA)
2646 {
2647 Shift();
2648 if (TokenType == ATT_IDTF && !SEqual(szStr1: Idtf, C4AUL_In))
2649 {
2650 forMap = true;
2651
2652 if (Type == PREPARSER)
2653 {
2654 // insert variable
2655 Fn->VarNamed.AddName(pnName: Idtf);
2656 }
2657 // search variable (fail if not found)
2658 iVarIDForMapValue = Fn->VarNamed.GetItemNr(strName: Idtf);
2659 if (iVarIDForMapValue < 0)
2660 throw C4AulParseError(this, "internal error: var definition: var not found in variable table");
2661 Shift();
2662 }
2663 }
2664
2665 if (TokenType != ATT_IDTF || !SEqual(szStr1: Idtf, C4AUL_In))
2666 UnexpectedToken(Expected: "'in'");
2667 Shift();
2668 // get expression for array or map
2669 Parse_Expression();
2670 Match(RefTokenType: ATT_BCLOSE);
2671 // push second var id for the map iteration
2672 if (forMap) AddBCC(eType: AB_INT, X: iVarIDForMapValue);
2673 // push initial position (0)
2674 AddBCC(eType: AB_INT);
2675 // get array element
2676 const auto iStart = a->GetCodePos();
2677 AddBCC(eType: forMap ? AB_FOREACH_MAP_NEXT : AB_FOREACH_NEXT, X: iVarID);
2678 // jump out (FOREACH[_MAP]_NEXT will jump over this if
2679 // we're not at the end of the array yet)
2680 const auto iCond = a->GetCodePos();
2681 AddBCC(eType: AB_JUMP);
2682 // got a loop...
2683 PushLoop();
2684 // loop body
2685 Parse_Statement();
2686 if (Type != PARSER) return;
2687 // jump back
2688 AddJump(eType: AB_JUMP, iWhere: iStart);
2689 // set condition jump target
2690 SetJumpHere(iCond);
2691 // set jump targets for break/continue
2692 for (Loop::Control *pCtrl = pLoopStack->Controls; pCtrl; pCtrl = pCtrl->Next)
2693 if (pCtrl->Break)
2694 SetJumpHere(pCtrl->Pos);
2695 else
2696 SetJump(iJumpOp: pCtrl->Pos, iWhere: iStart);
2697 PopLoop();
2698 // remove array/map and counter/iterator from stack
2699 AddBCC(eType: AB_STACK, X: forMap ? -3 : -2);
2700}
2701
2702void C4AulParseState::Parse_Expression(int iParentPrio)
2703{
2704 switch (TokenType)
2705 {
2706 case ATT_IDTF:
2707 {
2708 // check for parameter (par)
2709 if (Fn->ParNamed.GetItemNr(strName: Idtf) != -1)
2710 {
2711 // insert variable by id
2712 AddBCC(eType: AB_PARN_R, X: Fn->ParNamed.GetItemNr(strName: Idtf));
2713 Shift();
2714 }
2715 // check for variable (var)
2716 else if (Fn->VarNamed.GetItemNr(strName: Idtf) != -1)
2717 {
2718 // insert variable by id
2719 AddBCC(eType: AB_VARN_R, X: Fn->VarNamed.GetItemNr(strName: Idtf));
2720 Shift();
2721 }
2722 // check for variable (local)
2723 else if (a->LocalNamed.GetItemNr(strName: Idtf) != -1)
2724 {
2725 // global func?
2726 if (Fn->Owner == &Game.ScriptEngine)
2727 throw C4AulParseError(this, "using local variable in global function!");
2728 // insert variable by id
2729 AddBCC(eType: AB_LOCALN_R, X: a->LocalNamed.GetItemNr(strName: Idtf));
2730 Shift();
2731 }
2732 // check for global variable (static)
2733 else if (a->Engine->GlobalNamedNames.GetItemNr(strName: Idtf) != -1)
2734 {
2735 // insert variable by id
2736 AddBCC(eType: AB_GLOBALN_R, X: a->Engine->GlobalNamedNames.GetItemNr(strName: Idtf));
2737 Shift();
2738 }
2739 // function identifier: check special functions
2740 else if (SEqual(szStr1: Idtf, C4AUL_If))
2741 // -> if is not a valid parameter
2742 throw C4AulParseError(this, "'if' may not be used as a parameter");
2743 else if (SEqual(szStr1: Idtf, C4AUL_While))
2744 // -> while is not a valid parameter
2745 throw C4AulParseError(this, "'while' may not be used as a parameter");
2746 else if (SEqual(szStr1: Idtf, C4AUL_Else))
2747 // -> else is not a valid parameter
2748 throw C4AulParseError(this, "misplaced 'else'");
2749 else if (SEqual(szStr1: Idtf, C4AUL_For))
2750 // -> for is not a valid parameter
2751 throw C4AulParseError(this, "'for' may not be used as a parameter");
2752 else if (SEqual(szStr1: Idtf, C4AUL_Return))
2753 {
2754 // return: treat as regular function with special byte code
2755 Strict2Error(message: "return used as a parameter");
2756 Shift();
2757 Parse_Params(iMaxCnt: 1, sWarn: nullptr);
2758 AddBCC(eType: AB_RETURN);
2759 AddBCC(eType: AB_STACK, X: +1);
2760 }
2761 else if (SEqual(szStr1: Idtf, C4AUL_Par))
2762 {
2763 // and for Par
2764 Shift();
2765 Parse_Params(iMaxCnt: 1, C4AUL_Par);
2766 AddBCC(eType: AB_PAR_R);
2767 }
2768 else if (SEqual(szStr1: Idtf, C4AUL_Var))
2769 {
2770 // same for Var
2771 Shift();
2772 Parse_Params(iMaxCnt: 1, C4AUL_Var);
2773 AddBCC(eType: AB_VAR_R);
2774 }
2775 else if (SEqual(szStr1: Idtf, C4AUL_Inherited) || SEqual(szStr1: Idtf, C4AUL_SafeInherited))
2776 {
2777 Shift();
2778 // inherited keyword: check strict syntax
2779 if (Fn->pOrgScript->Strict == C4AulScriptStrict::NONSTRICT) throw C4AulParseError(this, "inherited disabled; use #strict syntax!");
2780 // get function
2781 if (Fn->OwnerOverloaded)
2782 {
2783 // add direct call to byte code
2784 Parse_Params(iMaxCnt: Fn->OwnerOverloaded->GetParCount(), sWarn: nullptr, pFunc: Fn->OwnerOverloaded);
2785 AddBCC(eType: AB_FUNC, X: reinterpret_cast<std::intptr_t>(Fn->OwnerOverloaded));
2786 }
2787 else
2788 // not found? raise an error, if it's not a safe call
2789 if (SEqual(szStr1: Idtf, C4AUL_Inherited) && Type == PARSER)
2790 throw C4AulParseError(this, "inherited function not found, use _inherited to call failsafe");
2791 else
2792 {
2793 // otherwise, parse parameters, but discard them
2794 Parse_Params(iMaxCnt: 0, sWarn: nullptr);
2795 // Push a null as return value
2796 AddBCC(eType: AB_STACK, X: 1);
2797 }
2798 }
2799 else if (Fn->pOrgScript->Strict == C4AulScriptStrict::NONSTRICT && Fn->OwnerOverloaded && SEqual(szStr1: Idtf, szStr2: Fn->Name))
2800 {
2801 // old syntax: do not allow recursive calls in overloaded functions
2802 Shift();
2803 Parse_Params(iMaxCnt: Fn->OwnerOverloaded->GetParCount(), sWarn: Fn->Name, pFunc: Fn);
2804 AddBCC(eType: AB_FUNC, X: reinterpret_cast<std::intptr_t>(Fn->OwnerOverloaded));
2805 }
2806 else
2807 {
2808 C4AulFunc *FoundFn;
2809 // get regular function
2810 if (Fn->Owner == &Game.ScriptEngine)
2811 FoundFn = Fn->Owner->GetFuncRecursive(pIdtf: Idtf);
2812 else
2813 FoundFn = a->GetFuncRecursive(pIdtf: Idtf);
2814 if (Type == PREPARSER)
2815 {
2816 Shift();
2817 // The preparser just assumes that the syntax is correct: if no '(' follows, it must be a constant
2818 if (TokenType == ATT_BOPEN)
2819 Parse_Params(iMaxCnt: FoundFn ? FoundFn->GetParCount() : C4AUL_MAX_Par, sWarn: Idtf, pFunc: FoundFn);
2820 }
2821 else if (FoundFn)
2822 {
2823 Shift();
2824 // Function parameters for all functions except "this", which can be used without
2825 if (!SEqual(szStr1: FoundFn->Name, C4AUL_this) || TokenType == ATT_BOPEN)
2826 Parse_Params(iMaxCnt: FoundFn->GetParCount(), sWarn: FoundFn->Name, pFunc: FoundFn);
2827 else
2828 AddBCC(eType: AB_STACK, X: FoundFn->GetParCount());
2829 AddBCC(eType: AB_FUNC, X: reinterpret_cast<std::intptr_t>(FoundFn));
2830 }
2831 else
2832 {
2833 // -> func not found
2834 // check for global constant (static const)
2835 // global constants have lowest priority for backwards compatibility
2836 // it is now allowed to have functional overloads of these constants
2837 C4Value val;
2838 if (a->Engine->GetGlobalConstant(szName: Idtf, pTargetValue: &val))
2839 {
2840 // store as direct constant
2841 switch (val.GetType())
2842 {
2843 case C4V_Int: AddBCC(eType: AB_INT, X: val._getInt()); break;
2844 case C4V_Bool: AddBCC(eType: AB_BOOL, X: val._getBool()); break;
2845 case C4V_String: AddBCC(eType: AB_STRING, X: reinterpret_cast<std::intptr_t>(val.GetData().Str)); break;
2846 case C4V_C4ID: AddBCC(eType: AB_C4ID, X: val._getC4ID()); break;
2847 case C4V_Any: AddBCC(eType: AB_NIL); break;
2848 default:
2849 {
2850 throw C4AulParseError(this, std::format(fmt: "internal error: constant {} has undefined type {}", args: +Idtf, args: std::to_underlying(value: val.GetType())));
2851 }
2852 }
2853 Shift();
2854 // now let's check whether it used old- or new-style
2855 if (TokenType == ATT_BOPEN && Fn->pOrgScript->Strict < C4AulScriptStrict::STRICT2)
2856 {
2857 // old-style usage: ignore function call
2858 // must not use parameters here (although generating the byte code for that would be possible)
2859 Shift();
2860 Match(RefTokenType: ATT_BCLOSE, Message: "parameters not allowed in functional usage of constants");
2861 }
2862 }
2863 else
2864 {
2865 // identifier could not be resolved
2866 throw C4AulParseError(this, "unknown identifier: ", Idtf);
2867 }
2868 }
2869 }
2870 break;
2871 }
2872 case ATT_NIL:
2873 {
2874 AddBCC(eType: AB_NIL);
2875 Shift();
2876 break;
2877 }
2878 case ATT_INT: // constant in cInt
2879 {
2880 AddBCC(eType: AB_INT, X: cInt);
2881 Shift();
2882 break;
2883 }
2884 case ATT_BOOL: // constant in cInt
2885 {
2886 AddBCC(eType: AB_BOOL, X: cInt);
2887 Shift();
2888 break;
2889 }
2890 case ATT_STRING: // reference in cInt
2891 {
2892 AddBCC(eType: AB_STRING, X: cInt);
2893 Shift();
2894 break;
2895 }
2896 case ATT_C4ID: // converted ID in cInt
2897 {
2898 AddBCC(eType: AB_C4ID, X: cInt);
2899 Shift();
2900 break;
2901 }
2902 case ATT_OPERATOR:
2903 {
2904 // -> must be a prefix operator
2905 // get operator ID
2906 const auto OpID = cInt;
2907 // postfix?
2908 if (C4ScriptOpMap[OpID].Postfix)
2909 // oops. that's wrong
2910 throw C4AulParseError(this, "postfix operator without first expression");
2911 Shift();
2912 // generate code for the following expression
2913 Parse_Expression(iParentPrio: C4ScriptOpMap[OpID].Priority);
2914 // ignore?
2915 if (SEqual(szStr1: C4ScriptOpMap[OpID].Identifier, szStr2: "+"))
2916 break;
2917 // negate constant?
2918 if (Type == PARSER && SEqual(szStr1: C4ScriptOpMap[OpID].Identifier, szStr2: "-"))
2919 if ((a->CPos - 1)->bccType == AB_INT)
2920 {
2921 (a->CPos - 1)->bccX = -(a->CPos - 1)->bccX;
2922 break;
2923 }
2924
2925 if (C4ScriptOpMap[OpID].Type1 != C4V_pC4Value)
2926 {
2927 SetNoRef();
2928 }
2929 // write byte code
2930 AddBCC(eType: C4ScriptOpMap[OpID].Code, X: OpID);
2931 break;
2932 }
2933 case ATT_BOPEN:
2934 {
2935 // parse it like a function...
2936 Shift();
2937 Parse_Expression();
2938 Match(RefTokenType: ATT_BCLOSE);
2939 break;
2940 }
2941 case ATT_BOPEN2:
2942 {
2943 // Arrays are not tested in non-strict mode at all
2944 if (Fn->pOrgScript->Strict == C4AulScriptStrict::NONSTRICT)
2945 throw C4AulParseError(this, "unexpected '['");
2946 Parse_Array();
2947 break;
2948 }
2949 case ATT_BLOPEN:
2950 {
2951 // Maps are not tested below strict 3 mode at all
2952 if (Fn->pOrgScript->Strict < C4AulScriptStrict::STRICT3)
2953 throw C4AulParseError(this, "unexpected '{'");
2954 Parse_Map();
2955 break;
2956 }
2957 case ATT_GLOBALCALL:
2958 break;
2959 default:
2960 {
2961 // -> unexpected token
2962 UnexpectedToken(Expected: "expression");
2963 }
2964 }
2965 Parse_Expression2(iParentPrio);
2966}
2967
2968void C4AulParseState::Parse_Expression2(int iParentPrio)
2969{
2970 while (1) switch (TokenType)
2971 {
2972 case ATT_OPERATOR:
2973 {
2974 // expect postfix operator
2975 auto OpID = cInt;
2976 if (!C4ScriptOpMap[OpID].Postfix)
2977 {
2978 // does an operator with the same name exist?
2979 // when it's a postfix-operator, it can be used instead.
2980 auto nOpID = OpID + 1;
2981 for (; C4ScriptOpMap[nOpID].Identifier; nOpID++)
2982 if (SEqual(szStr1: C4ScriptOpMap[OpID].Identifier, szStr2: C4ScriptOpMap[nOpID].Identifier))
2983 if (C4ScriptOpMap[nOpID].Postfix)
2984 break;
2985 // not found?
2986 if (!C4ScriptOpMap[nOpID].Identifier)
2987 {
2988 throw C4AulParseError(this, "unexpected prefix operator: ", C4ScriptOpMap[OpID].Identifier);
2989 }
2990 // otherwise use the new-found correct postfix operator
2991 OpID = nOpID;
2992 }
2993 // lower priority?
2994 if (C4ScriptOpMap[OpID].RightAssociative ?
2995 C4ScriptOpMap[OpID].Priority < iParentPrio :
2996 C4ScriptOpMap[OpID].Priority <= iParentPrio)
2997 return;
2998 // If the operator does not modify the first argument, no reference is necessary
2999 if (C4ScriptOpMap[OpID].Type1 != C4V_pC4Value)
3000 SetNoRef();
3001 Shift();
3002
3003 if (((C4ScriptOpMap[OpID].Code == AB_And || C4ScriptOpMap[OpID].Code == AB_Or) && Fn->pOrgScript->Strict >= C4AulScriptStrict::STRICT2) || C4ScriptOpMap[OpID].Code == AB_NilCoalescing || C4ScriptOpMap[OpID].Code == AB_NilCoalescingIt)
3004 {
3005 // create bytecode, remember position
3006 const auto iCond = a->GetCodePos();
3007
3008 if (C4ScriptOpMap[OpID].Code == AB_NilCoalescingIt)
3009 {
3010 AddBCC(eType: AB_NilCoalescingIt);
3011 }
3012 else
3013 {
3014 // Jump or discard first parameter
3015 AddBCC(eType: C4ScriptOpMap[OpID].Code == AB_And ? AB_JUMPAND : C4ScriptOpMap[OpID].Code == AB_Or ? AB_JUMPOR : AB_JUMPNOTNIL);
3016 }
3017
3018 // parse second expression
3019 Parse_Expression(iParentPrio: C4ScriptOpMap[OpID].Priority);
3020
3021 if (C4ScriptOpMap[OpID].Code == AB_NilCoalescingIt)
3022 {
3023 AddBCC(eType: AB_Set, X: OpID);
3024 }
3025
3026 // set condition jump target
3027 SetJumpHere(iCond);
3028 break;
3029 }
3030 else
3031 {
3032 // expect second parameter for operator
3033 if (!C4ScriptOpMap[OpID].NoSecondStatement)
3034 {
3035 switch (TokenType)
3036 {
3037 case ATT_IDTF: case ATT_NIL: case ATT_INT: case ATT_BOOL: case ATT_STRING: case ATT_C4ID:
3038 case ATT_OPERATOR: case ATT_BOPEN: case ATT_BOPEN2: case ATT_BLOPEN: case ATT_GLOBALCALL:
3039 Parse_Expression(iParentPrio: C4ScriptOpMap[OpID].Priority);
3040 // If the operator does not modify the second argument, no reference is necessary
3041 if (C4ScriptOpMap[OpID].Type2 != C4V_pC4Value)
3042 SetNoRef();
3043 break;
3044 default:
3045 // Stuff like foo(42+,1) used to silently work
3046 Strict2Error(message: std::format(fmt: "Operator {}: Second expression expected, but {} found",
3047 args&: C4ScriptOpMap[OpID].Identifier, args: GetTokenName(TokenType)));
3048 AddBCC(eType: AB_INT, X: 0);
3049 break;
3050 }
3051 }
3052 // write byte code, with a few backward compat changes
3053 if (C4ScriptOpMap[OpID].Code == AB_Equal && Fn->pOrgScript->Strict < C4AulScriptStrict::STRICT2)
3054 AddBCC(eType: AB_EqualIdent, X: OpID);
3055 else if (C4ScriptOpMap[OpID].Code == AB_NotEqual && Fn->pOrgScript->Strict < C4AulScriptStrict::STRICT2)
3056 AddBCC(eType: AB_NotEqualIdent, X: OpID);
3057 else
3058 AddBCC(eType: C4ScriptOpMap[OpID].Code, X: OpID);
3059 }
3060 break;
3061 }
3062 default:
3063 {
3064 if (!Parse_Expression3()) return;
3065 }
3066 }
3067}
3068
3069bool C4AulParseState::Parse_Expression3()
3070{
3071 switch (TokenType)
3072 {
3073 case ATT_BOPEN2:
3074 {
3075 // Arrays are not tested in non-strict mode at all
3076 if (Fn->pOrgScript->Strict == C4AulScriptStrict::NONSTRICT)
3077 throw C4AulParseError(this, "unexpected '['");
3078 // Access the array
3079 const char *SPos0 = SPos;
3080 Shift();
3081 if (TokenType == ATT_BCLOSE2)
3082 {
3083 Shift();
3084 AddBCC(eType: AB_ARRAY_APPEND);
3085 break;
3086 }
3087
3088 // optimize map["foo"] to map.foo (because map.foo execution is less complicated)
3089 if (TokenType == ATT_STRING && GetNextToken(pToken: Idtf, pInt: &cInt, HoldStrings: Discard, bOperator: true) == ATT_BCLOSE2)
3090 {
3091 AddBCC(eType: AB_MAPA_R, X: cInt);
3092 Shift();
3093 break;
3094 }
3095
3096 SPos = SPos0;
3097 Shift();
3098
3099 Parse_Expression();
3100 SetNoRef();
3101 Match(RefTokenType: ATT_BCLOSE2);
3102 AddBCC(eType: AB_ARRAYA_R);
3103 break;
3104 }
3105 case ATT_QMARK:
3106 {
3107 if (Fn->pOrgScript->Strict < C4AulScriptStrict::STRICT3)
3108 throw C4AulParseError(this, "unexpected '?'");
3109
3110 SetNoRef();
3111
3112 const auto here = a->GetCodePos();
3113 AddBCC(eType: AB_JUMPNIL);
3114
3115 Shift();
3116 if (TokenType == ATT_QMARK)
3117 UnexpectedToken(Expected: "navigation operator (->, [], .)");
3118
3119 if (!Parse_Expression3())
3120 UnexpectedToken(Expected: "navigation operator (->, [], .)");
3121 SetNoRef();
3122
3123 while (Parse_Expression3()) SetNoRef();
3124
3125 // safe navigation mustn't return a reference because it doesn't make any sense if it is expected to return non-ref nil occasionally
3126 AddBCC(eType: AB_DEREF);
3127 SetJumpHere(here);
3128 break;
3129 }
3130 case ATT_DOT:
3131 {
3132 if (Fn->pOrgScript->Strict < C4AulScriptStrict::STRICT3)
3133 throw C4AulParseError(this, "unexpected '.'");
3134 Shift();
3135 if (TokenType == ATT_IDTF)
3136 {
3137 C4String *string;
3138 if (!(string = a->Engine->Strings.FindString(strString: Idtf)))
3139 string = a->Engine->Strings.RegString(strString: Idtf);
3140 if (Type == PARSER) string->Hold = true;
3141 AddBCC(eType: AB_MAPA_R, X: reinterpret_cast<std::intptr_t>(string));
3142 Shift();
3143 break;
3144 }
3145 else
3146 {
3147 UnexpectedToken(Expected: "identifier");
3148 }
3149 break;
3150 }
3151 case ATT_GLOBALCALL:
3152 case ATT_CALL:
3153 {
3154 C4AulBCCType eCallType = (TokenType == ATT_GLOBALCALL ? AB_CALLGLOBAL : AB_CALL);
3155 // Here, a '~' is not an operator, but a token
3156 Shift(HoldStrings: Discard, bOperator: false);
3157 // C4ID -> namespace given
3158 C4AulFunc *pFunc = nullptr;
3159 C4ID idNS = 0;
3160 if (TokenType == ATT_C4ID && eCallType != AB_CALLGLOBAL)
3161 {
3162 // from now on, stupid func names must stay outside ;P
3163 idNS = static_cast<C4ID>(cInt);
3164 Shift();
3165 // expect namespace-operator now
3166 Match(RefTokenType: ATT_DCOLON);
3167 // next, we need a function name
3168 if (TokenType != ATT_IDTF) UnexpectedToken(Expected: "function name");
3169 if (Type == PARSER)
3170 {
3171 // get def from id
3172 C4Def *pDef = C4Id2Def(id: idNS);
3173 if (!pDef)
3174 {
3175 throw C4AulParseError(this, "direct object call: def not found: ", C4IdText(id: idNS));
3176 }
3177 // search func
3178 if (!(pFunc = pDef->Script.GetSFunc(pIdtf: Idtf)))
3179 {
3180 throw C4AulParseError(this, std::format(fmt: "direct object call: function {}::{} not found", args: C4IdText(id: idNS), args: +Idtf));
3181 }
3182
3183 if (pFunc->SFunc() && pFunc->SFunc()->Access < pDef->Script.GetAllowedAccess(func: pFunc, caller: Fn->pOrgScript))
3184 {
3185 throw C4AulParseError(this, "insufficient access level", Idtf);
3186 }
3187
3188 // write namespace chunk to byte code
3189 AddBCC(eType: AB_CALLNS, X: static_cast<std::intptr_t>(idNS));
3190 }
3191 }
3192 else
3193 {
3194 auto failSafe = false;
3195 // may it be a failsafe call?
3196 if (TokenType == ATT_TILDE)
3197 {
3198 // store this and get the next token
3199 if (eCallType != AB_CALLGLOBAL)
3200 eCallType = AB_CALLFS;
3201 Shift();
3202 failSafe = true;
3203 }
3204 // expect identifier of called function now
3205 if (TokenType != ATT_IDTF) throw C4AulParseError(this, "expecting func name after '->'");
3206 // search a function with the given name
3207 if (eCallType == AB_CALLGLOBAL)
3208 {
3209 pFunc = a->Engine->GetFunc(Name: Idtf, Owner: a->Engine, After: nullptr);
3210 // allocate space for return value, otherwise the call-target-variable is used, which is not present here
3211 AddBCC(eType: AB_STACK, X: +1);
3212 }
3213 else
3214 {
3215 pFunc = a->Engine->GetFirstFunc(Name: Idtf);
3216 }
3217 if (!pFunc)
3218 {
3219 // not failsafe?
3220 if (!failSafe && Type == PARSER)
3221 {
3222 throw C4AulParseError(this, std::format(fmt: "direct object call: function {} not found", args: +Idtf));
3223 }
3224 // otherwise: nothing to call - just execute parameters and discard them
3225 Shift();
3226 Parse_Params(iMaxCnt: 0, sWarn: nullptr);
3227 // remove target from stack, push a zero value as result
3228 AddBCC(eType: AB_STACK, X: -1);
3229 AddBCC(eType: AB_STACK, X: +1);
3230 // done
3231 break;
3232 }
3233 else if (pFunc->SFunc() && pFunc->SFunc()->Access < Fn->pOrgScript->GetAllowedAccess(func: pFunc, caller: Fn->pOrgScript))
3234 {
3235 throw C4AulParseError(this, "insufficient access level", Idtf);
3236 }
3237 }
3238 // add call chunk
3239 Shift();
3240 Parse_Params(C4AUL_MAX_Par, sWarn: pFunc ? pFunc->Name : nullptr, pFunc);
3241 if (idNS != 0)
3242 AddBCC(eType: AB_CALLNS, X: static_cast<std::intptr_t>(idNS));
3243 AddBCC(eType: eCallType, X: reinterpret_cast<std::intptr_t>(pFunc));
3244 break;
3245 }
3246 default:
3247 return false;
3248 }
3249 return true;
3250}
3251
3252void C4AulParseState::Parse_Var()
3253{
3254 while (1)
3255 {
3256 // get desired variable name
3257 if (TokenType != ATT_IDTF)
3258 UnexpectedToken(Expected: "variable name");
3259 if (Type == PREPARSER)
3260 {
3261 // insert variable
3262 Fn->VarNamed.AddName(pnName: Idtf);
3263 }
3264 // search variable (fail if not found)
3265 int iVarID = Fn->VarNamed.GetItemNr(strName: Idtf);
3266 if (iVarID < 0)
3267 throw C4AulParseError(this, "internal error: var definition: var not found in variable table");
3268 Shift();
3269 if (TokenType == ATT_OPERATOR)
3270 {
3271 // only accept "="
3272 const auto iOpID = cInt;
3273 if (SEqual(szStr1: C4ScriptOpMap[iOpID].Identifier, szStr2: "="))
3274 {
3275 // insert initialization in byte code
3276 Shift();
3277 Parse_Expression();
3278 SetNoRef();
3279 AddBCC(eType: AB_IVARN, X: iVarID);
3280 }
3281 else
3282 throw C4AulParseError(this, "unexpected operator");
3283 }
3284 switch (TokenType)
3285 {
3286 case ATT_COMMA:
3287 {
3288 Shift();
3289 break;
3290 }
3291 case ATT_SCOLON:
3292 {
3293 return;
3294 }
3295 default:
3296 {
3297 UnexpectedToken(Expected: "',' or ';'");
3298 }
3299 }
3300 }
3301}
3302
3303void C4AulParseState::Parse_Local()
3304{
3305 while (1)
3306 {
3307 if (Type == PREPARSER)
3308 {
3309 // get desired variable name
3310 if (TokenType != ATT_IDTF)
3311 UnexpectedToken(Expected: "variable name");
3312 // check: symbol already in use?
3313 if (a->GetFunc(pIdtf: Idtf))
3314 throw C4AulParseError(this, "variable definition: name already in use");
3315 // insert variable
3316 a->LocalNamed.AddName(pnName: Idtf);
3317 }
3318 Match(RefTokenType: ATT_IDTF);
3319 switch (TokenType)
3320 {
3321 case ATT_COMMA:
3322 {
3323 Shift();
3324 break;
3325 }
3326 case ATT_SCOLON:
3327 {
3328 return;
3329 }
3330 default:
3331 {
3332 UnexpectedToken(Expected: "',' or ';'");
3333 }
3334 }
3335 }
3336}
3337
3338void C4AulParseState::Parse_Static()
3339{
3340 while (1)
3341 {
3342 if (Type == PREPARSER)
3343 {
3344 // get desired variable name
3345 if (TokenType != ATT_IDTF)
3346 UnexpectedToken(Expected: "variable name");
3347 // global variable definition
3348 // check: symbol already in use?
3349 if (a->Engine->GetFuncRecursive(pIdtf: Idtf)) throw C4AulParseError(this, "variable definition: name already in use");
3350 if (a->Engine->GetGlobalConstant(szName: Idtf, pTargetValue: nullptr)) Strict2Error(message: "constant and variable with name ", identifier: Idtf);
3351 // insert variable if not defined already
3352 if (a->Engine->GlobalNamedNames.GetItemNr(strName: Idtf) == -1)
3353 a->Engine->GlobalNamedNames.AddName(pnName: Idtf);
3354 }
3355 Match(RefTokenType: ATT_IDTF);
3356 switch (TokenType)
3357 {
3358 case ATT_COMMA:
3359 {
3360 Shift();
3361 break;
3362 }
3363 case ATT_SCOLON:
3364 {
3365 return;
3366 }
3367 default:
3368 {
3369 UnexpectedToken(Expected: "',' or ';'");
3370 }
3371 }
3372 }
3373}
3374
3375void C4AulParseState::Parse_Const()
3376{
3377 // get global constant definition(s)
3378 while (1)
3379 {
3380 char Name[C4AUL_MAX_Identifier] = ""; // current identifier
3381 // get desired variable name
3382 if (TokenType != ATT_IDTF)
3383 UnexpectedToken(Expected: "constant name");
3384 SCopy(szSource: Idtf, sTarget: Name);
3385 // check func lists - functions of same name are allowed for backwards compatibility
3386 // (e.g., for overloading constants such as OCF_Living() in chaos scenarios)
3387 // it is not encouraged though, so better warn
3388 if (a->Engine->GetFuncRecursive(pIdtf: Idtf))
3389 Strict2Error(message: "definition of constant hidden by function ", identifier: Idtf);
3390 if (a->Engine->GlobalNamedNames.GetItemNr(strName: Idtf) != -1)
3391 Strict2Error(message: "constant and variable with name ", identifier: Idtf);
3392 Match(RefTokenType: ATT_IDTF);
3393 // expect '='
3394 if (TokenType != ATT_OPERATOR || !SEqual(szStr1: C4ScriptOpMap[cInt].Identifier, szStr2: "="))
3395 UnexpectedToken(Expected: "'='");
3396 // expect value. Theoretically, something like C4AulScript::ExecOperator could be used here
3397 // this would allow for definitions like "static const OCF_Edible = 1 << 23"
3398 // However, such stuff should better be generalized, so the preparser (and parser)
3399 // can evaluate any constant expression, including functions with constant retval (e.g. Sqrt)
3400 // So allow only direct constants for now.
3401 // Do not set a string constant to "Hold" (which would delete it in the next UnLink())
3402 Shift(HoldStrings: Ref, bOperator: false);
3403 if (Type == PREPARSER)
3404 {
3405 C4Value vGlobalValue;
3406 switch (TokenType)
3407 {
3408 case ATT_NIL: vGlobalValue.Set0(); break;
3409 case ATT_INT: vGlobalValue.SetInt(static_cast<C4ValueInt>(cInt)); break;
3410 case ATT_BOOL: vGlobalValue.SetBool(!!cInt); break;
3411 case ATT_STRING: vGlobalValue.SetString(reinterpret_cast<C4String *>(cInt)); break; // increases ref count of C4String in cInt to 1
3412 case ATT_C4ID: vGlobalValue.SetC4ID(static_cast<C4ID>(cInt)); break;
3413 case ATT_IDTF:
3414 // identifier is only OK if it's another constant
3415 if (!a->Engine->GetGlobalConstant(szName: Idtf, pTargetValue: &vGlobalValue))
3416 UnexpectedToken(Expected: "constant value");
3417 break;
3418 default:
3419 UnexpectedToken(Expected: "constant value");
3420 }
3421 // register as constant
3422 a->Engine->RegisterGlobalConstant(szName: Name, rValue: vGlobalValue);
3423 }
3424 // expect ',' (next global) or ';' (end of definition) now
3425 Shift();
3426 switch (TokenType)
3427 {
3428 case ATT_COMMA:
3429 {
3430 Shift();
3431 break;
3432 }
3433 case ATT_SCOLON:
3434 {
3435 return;
3436 }
3437 default:
3438 {
3439 UnexpectedToken(Expected: "',' or ';'");
3440 }
3441 }
3442 }
3443}
3444
3445template<C4AulTokenType closingAtt>
3446void C4AulParseState::SkipBlock()
3447{
3448 for (;;)
3449 {
3450 switch (GetNextToken(pToken: Idtf, pInt: &cInt, HoldStrings: Discard, bOperator: true))
3451 {
3452 case closingAtt: case ATT_EOF:
3453 return;
3454 case ATT_BOPEN:
3455 SkipBlock<ATT_BCLOSE>();
3456 break;
3457 case ATT_BOPEN2:
3458 SkipBlock<ATT_BCLOSE2>();
3459 break;
3460 case ATT_BLOPEN:
3461 SkipBlock<ATT_BLCLOSE>();
3462 break;
3463 default:
3464 continue;
3465 }
3466 }
3467}
3468
3469bool C4AulParseState::IsMapLiteral()
3470{
3471 bool result = false;
3472 const char *SPos0 = SPos;
3473 for (;;)
3474 {
3475 switch (GetNextToken(pToken: Idtf, pInt: &cInt, HoldStrings: Discard, bOperator: true))
3476 {
3477 case ATT_BOPEN:
3478 SkipBlock<ATT_BCLOSE>();
3479 break;
3480 case ATT_BOPEN2:
3481 SkipBlock<ATT_BCLOSE2>();
3482 break;
3483 case ATT_BLOPEN:
3484 SkipBlock<ATT_BLCLOSE>();
3485 break;
3486
3487 case ATT_OPERATOR:
3488 if (SEqual(szStr1: C4ScriptOpMap[cInt].Identifier, szStr2: "=")) result = true;
3489 break;
3490
3491 // in uncertain cases, {} is considered as an empty block
3492 case ATT_BLCLOSE:
3493 goto loopEnd;
3494 case ATT_SCOLON:
3495 result = false;
3496 goto loopEnd;
3497 case ATT_EOF:
3498 goto loopEnd;
3499 default:
3500 // just continue
3501 continue;
3502 }
3503 }
3504
3505loopEnd:
3506 SPos = SPos0;
3507 return result;
3508}
3509
3510bool C4AulScript::Parse()
3511{
3512#if DEBUG_BYTECODE_DUMP
3513 const auto logger = Application.LogSystem.GetOrCreate("C4AulScript");
3514
3515 C4ScriptHost *scripthost{nullptr};
3516 if (Def) scripthost = &Def->Script;
3517 if (scripthost) logger->info("parsing {}...", scripthost->GetFilePath());
3518 else logger->info("parsing unknown..");
3519#endif
3520
3521 // parse children
3522 C4AulScript *s = Child0;
3523 while (s) { s->Parse(); s = s->Next; }
3524 // check state
3525 if (State != ASS_LINKED) return false;
3526 // don't parse global funcs again, as they're parsed already through links
3527 if (this == Engine) return false;
3528 // delete existing code
3529 delete[] Code;
3530 CodeSize = CodeBufSize = 0;
3531 // reset code and script pos
3532 CPos = Code;
3533
3534 // parse script funcs
3535 C4AulFunc *f;
3536 for (f = Func0; f; f = f->Next)
3537 {
3538 // check whether it's a script func, or linked to one
3539 C4AulScriptFunc *Fn;
3540 if (!(Fn = f->SFunc()))
3541 {
3542 if (f->LinkedTo) Fn = f->LinkedTo->SFunc();
3543 // do only parse global funcs, because otherwise, the #append-links get parsed (->code overflow)
3544 if (Fn) if (Fn->Owner != Engine) Fn = nullptr;
3545 }
3546 if (Fn)
3547 {
3548 // parse function
3549 try
3550 {
3551 ParseFn(Fn);
3552 }
3553 catch (const C4AulError &err)
3554 {
3555 // do not show errors for System.c4g scripts that appear to be pure #appendto scripts
3556 if (Fn->Owner->Def || Fn->Owner->Appends.empty())
3557 {
3558 // show
3559 err.show();
3560 // show a warning if the error is in a remote script
3561 if (Fn->pOrgScript != this)
3562 DebugLog(fmt: " (as #appendto/#include to {})", args&: Fn->Owner->ScriptName);
3563 // and count (visible only ;) )
3564 ++Game.ScriptEngine.errCnt;
3565 }
3566 // make all jumps that don't have their destination yet jump here
3567 // std::intptr_t to make it work on 64bit
3568 for (std::intptr_t i = reinterpret_cast<std::intptr_t>(Fn->Code); i < CPos - Code; i++)
3569 {
3570 C4AulBCC *pBCC = Code + i;
3571 if (IsJumpType(type: pBCC->bccType))
3572 if (!pBCC->bccX)
3573 pBCC->bccX = CPos - Code - i;
3574 }
3575 // add an error chunk
3576 AddBCC(eType: AB_ERR);
3577 }
3578
3579 // add separator
3580 AddBCC(eType: AB_EOFN);
3581 }
3582 }
3583
3584 // add eof chunk
3585 AddBCC(eType: AB_EOF);
3586
3587 // calc absolute code addresses for script funcs
3588 for (f = Func0; f; f = f->Next)
3589 {
3590 C4AulScriptFunc *Fn;
3591 if (!(Fn = f->SFunc()))
3592 {
3593 if (f->LinkedTo) Fn = f->LinkedTo->SFunc();
3594 if (Fn) if (Fn->Owner != Engine) Fn = nullptr;
3595 }
3596 if (Fn)
3597 Fn->Code = Code + reinterpret_cast<std::intptr_t>(Fn->Code);
3598 }
3599
3600 // save line count
3601 Engine->lineCnt += SGetLine(szText: Script.getData(), cpPosition: Script.getPtr(i: Script.getLength()));
3602
3603 // dump bytecode
3604#if DEBUG_BYTECODE_DUMP
3605 for (f = Func0; f; f = f->Next)
3606 {
3607 C4AulScriptFunc *Fn;
3608 if (!(Fn = f->SFunc()))
3609 {
3610 if (f->LinkedTo) Fn = f->LinkedTo->SFunc();
3611 if (Fn) if (Fn->Owner != Engine) Fn = nullptr;
3612 }
3613 if (Fn)
3614 {
3615 logger->info("{}:", Fn->Name);
3616 for (C4AulBCC *pBCC = Fn->Code;; pBCC++)
3617 {
3618 C4AulBCCType eType = pBCC->bccType;
3619 const auto X = pBCC->bccX;
3620 switch (eType)
3621 {
3622 case AB_FUNC: case AB_CALL: case AB_CALLFS: case AB_CALLGLOBAL:
3623 logger->info("{}\t'{}'", GetTTName(eType), X ? (reinterpret_cast<C4AulFunc *>(X))->Name : ""); break;
3624 case AB_STRING:
3625 logger->info("{}\t'{}'", GetTTName(eType), X ? (reinterpret_cast<C4String *>(X))->Data.getData() : ""); break;
3626 default:
3627 logger->info("{}\t{}", GetTTName(eType), X); break;
3628 }
3629 if (eType == AB_EOFN) break;
3630 }
3631 }
3632 }
3633#endif
3634
3635 // finished
3636 State = ASS_PARSED;
3637
3638 return true;
3639}
3640
3641void C4AulScript::ParseDescs()
3642{
3643 // parse children
3644 C4AulScript *s = Child0;
3645 while (s) { s->ParseDescs(); s = s->Next; }
3646 // check state
3647 if (State < ASS_LINKED) return;
3648 // parse descs of all script funcs
3649 for (C4AulFunc *f = Func0; f; f = f->Next)
3650 if (C4AulScriptFunc *Fn = f->SFunc()) Fn->ParseDesc();
3651}
3652
3653C4AulScript *C4AulScript::FindFirstNonStrictScript()
3654{
3655 // self is not #strict?
3656 if (Script && Strict == C4AulScriptStrict::NONSTRICT) return this;
3657 // search children
3658 C4AulScript *pNonStrScr;
3659 for (C4AulScript *pScr = Child0; pScr; pScr = pScr->Next)
3660 if (pNonStrScr = pScr->FindFirstNonStrictScript())
3661 return pNonStrScr;
3662 // nothing found
3663 return nullptr;
3664}
3665
3666#undef DEBUG_BYTECODE_DUMP
3667