OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4AulCompiler.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 
17 #include "C4Include.h"
18 #include "script/C4AulCompiler.h"
19 
20 #include "script/C4Aul.h"
21 #include "script/C4AulParse.h"
22 #include "script/C4AulScriptFunc.h"
23 #include "script/C4ScriptHost.h"
24 
25 #include <cinttypes>
26 
27 #define C4AUL_Inherited "inherited"
28 #define C4AUL_SafeInherited "_inherited"
29 #define C4AUL_DebugBreak "__debugbreak"
30 
31 static std::string FormatCodePosition(const C4ScriptHost *source_host, const char *pos, const C4ScriptHost *target_host = nullptr, const C4AulScriptFunc *func = nullptr)
32 {
33  std::string s;
34  if (func && func->GetFullName())
35  {
36  s += strprintf(" (in %s", func->GetFullName().getData());
37  if (source_host && pos)
38  s += ", ";
39  else
40  s += ")";
41  }
42  if (source_host && pos)
43  {
44  if (!func || !func->GetFullName())
45  s += " (";
46 
47  int line = SGetLine(source_host->GetScript(), pos);
48  int col = SLineGetCharacters(source_host->GetScript(), pos);
49 
50  s += strprintf("%s:%d:%d)",
51  source_host->GetFilePath(),
52  line, col
53  );
54  }
55  if (target_host && source_host != target_host)
56  {
57  s += strprintf(" (as #appendto/#include to %s)", target_host->ScriptName.getData());
58  }
59  return s;
60 }
61 
62 template<class... T>
63 static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const char *SPos, const C4AulScriptFunc *func, C4AulWarningId warning, T &&...args)
64 {
65  if (!host)
66  {
67  // Without a script host, just fall back to the default settings
68 #define DIAG(id, msg, enabled) if (warning == C4AulWarningId::id && !enabled) return;
69 #include "C4AulWarnings.h"
70 #undef DIAG
71  }
72  else if (!host->IsWarningEnabled(SPos, warning))
73  {
74  return;
75  }
76  const char *msg = C4AulWarningMessages[static_cast<size_t>(warning)];
77  std::string message = sizeof...(T) > 0 ? strprintf(msg, std::forward<T>(args)...) : msg;
78  message += FormatCodePosition(host, SPos, target_host, func);
79 
80  message += " [";
81  message += C4AulWarningIDs[static_cast<size_t>(warning)];
82  message += ']';
83 
84  ::ScriptEngine.GetErrorHandler()->OnWarning(message.c_str());
85 }
86 
87 template<class... T>
88 static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const ::aul::ast::Node *n, const C4AulScriptFunc *func, C4AulWarningId warning, T &&...args)
89 {
90  return Warn(target_host, host, n->loc, func, warning, std::forward<T>(args)...);
91 }
92 template<class... T>
93 static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const std::nullptr_t &, const C4AulScriptFunc *func, C4AulWarningId warning, T &&...args)
94 {
95  return Warn(target_host, host, static_cast<const char*>(nullptr), func, warning, std::forward<T>(args)...);
96 }
97 
98 template<class... T>
99 static C4AulParseError Error(const C4ScriptHost *target_host, const C4ScriptHost *host, const char *SPos, const C4AulScriptFunc *func, const char *msg, T &&...args)
100 {
101  std::string message = sizeof...(T) > 0 ? strprintf(msg, std::forward<T>(args)...) : msg;
102 
103  message += FormatCodePosition(host, SPos, target_host, func);
104  return C4AulParseError(static_cast<C4ScriptHost*>(nullptr), message.c_str());
105 }
106 
107 template<class... T>
108 static C4AulParseError Error(const C4ScriptHost *target_host, const C4ScriptHost *host, const ::aul::ast::Node *n, const C4AulScriptFunc *func, const char *msg, T &&...args)
109 {
110  return Error(target_host, host, n->loc, func, msg, std::forward<T>(args)...);
111 }
112 template<class... T>
113 static C4AulParseError Error(const C4ScriptHost *target_host, const C4ScriptHost *host, const std::nullptr_t &, const C4AulScriptFunc *func, const char *msg, T &&...args)
114 {
115  return Error(target_host, host, static_cast<const char*>(nullptr), func, msg, std::forward<T>(args)...);
116 }
117 
119 {
120  // target_host: The C4ScriptHost on which compilation is done
121  C4ScriptHost *target_host = nullptr;
122  // host: The C4ScriptHost where the script actually resides in
123  C4ScriptHost *host = nullptr;
124  // Fn: The C4AulScriptFunc that is currently getting parsed
125  C4AulScriptFunc *Fn = nullptr;
126 
127 public:
128  PreparseAstVisitor(C4ScriptHost *host, C4ScriptHost *source_host, C4AulScriptFunc *func = nullptr) : target_host(host), host(source_host), Fn(func) {}
129  explicit PreparseAstVisitor(C4AulScriptFunc *func) : Fn(func), target_host(func->pOrgScript), host(target_host) {}
130 
131  ~PreparseAstVisitor() override = default;
132 
133  using DefaultRecursiveVisitor::visit;
134  void visit(const ::aul::ast::RangeLoop *n) override;
135  void visit(const ::aul::ast::VarDecl *n) override;
136  void visit(const ::aul::ast::FunctionDecl *n) override;
137  void visit(const ::aul::ast::CallExpr *n) override;
138  void visit(const ::aul::ast::ParExpr *n) override;
139  void visit(const ::aul::ast::AppendtoPragma *n) override;
140  void visit(const ::aul::ast::IncludePragma *n) override;
141  void visit(const ::aul::ast::Script *n) override;
142 };
143 
145 {
146  C4AulScriptFunc *Fn = nullptr;
147  // target_host: The C4ScriptHost on which compilation is done
148  C4ScriptHost *target_host = nullptr;
149  // host: The C4ScriptHost where the script actually resides in
150  C4ScriptHost *host = nullptr;
151 
152  int32_t stack_height = 0;
153  bool at_jump_target = false;
154 
155  struct Loop
156  {
157  explicit Loop(int stack_height) : stack_height(stack_height) {}
158 
159  int stack_height = 0;
160  std::vector<int> continues;
161  std::vector<int> breaks;
162 
163  enum class Control
164  {
165  Continue,
166  Break
167  };
168  };
169 
170  std::stack<Loop> active_loops;
171 
172  // The type of the variable on top of the value stack. C4V_Any if unknown.
173  C4V_Type type_of_stack_top = C4V_Any;
174 
175  constexpr static bool IsJump(C4AulBCCType t)
176  {
177  return t == AB_JUMP || t == AB_JUMPAND || t == AB_JUMPOR || t == AB_JUMPNNIL || t == AB_CONDN || t == AB_COND;
178  }
179 
180  int AddJumpTarget();
181  void AddJumpTo(const char *loc, C4AulBCCType type, int target);
182  void UpdateJump(int jump, int target);
183  void PushLoop();
184  void PopLoop(int continue_target);
185  void AddLoopControl(const char *loc, Loop::Control c);
186 
187  int AddVarAccess(const char *TokenSPos, C4AulBCCType eType, intptr_t varnum);
188  int AddBCC(const char *TokenSPos, C4AulBCCType eType, intptr_t X = 0);
189  int AddBCC(const char *SPos, const C4AulBCC &bcc);
190 
191  template<class T>
192  void MaybePopValueOf(const std::unique_ptr<T> &n)
193  {
194  if (!n) return;
195  if (!n->has_value()) return;
196  AddBCC(n->loc, AB_STACK, -1);
197  }
198 
199  static int GetStackValue(C4AulBCCType eType, intptr_t X);
200  void RemoveLastBCC();
201  C4AulBCC MakeSetter(const char *SPos, bool fLeaveValue);
202 
203  void HandleError(const C4AulError &e)
204  {
205  if (Fn)
206  {
207  AddBCC(nullptr, AB_ERR, (intptr_t)::Strings.RegString(e.what()));
208  }
209  if (target_host) // target_host may be nullptr for DirectExec scripts
210  {
211  target_host->Engine->ErrorHandler->OnError(e.what());
212  }
213  }
214 
215  template<class T>
216  bool SafeVisit(const T &node)
217  {
218  // Swallows exceptions during evaluation of node. Use if you want to
219  // keep doing syntax checks for subsequent children. (Generated code
220  // will cause a runtime error if executed.)
221  try
222  {
223  node->accept(this);
224  return true;
225  }
226  catch (C4AulParseError &e)
227  {
228  HandleError(e);
229  return false;
230  }
231  }
232 
233  class StackGuard
234  {
235  // Ensures that the Aul value stack ends up at the expected height
236  CodegenAstVisitor *parent;
237  const int32_t target_stack_height;
238  public:
239  explicit StackGuard(CodegenAstVisitor *parent, int32_t offset = 0) : parent(parent), target_stack_height(parent->stack_height + offset)
240  {}
241  ~StackGuard()
242  {
243  assert(parent->stack_height == target_stack_height);
244  if (parent->stack_height != target_stack_height)
245  {
246  parent->HandleError(Error(parent->target_host, parent->host, nullptr, parent->Fn, "internal error: value stack left unbalanced"));
247  parent->AddBCC(nullptr, AB_STACK, target_stack_height - parent->stack_height);
248  }
249  }
250  };
251 
252 public:
253  CodegenAstVisitor(C4ScriptHost *host, C4ScriptHost *source_host) : target_host(host), host(source_host) {}
254  explicit CodegenAstVisitor(C4AulScriptFunc *func) : Fn(func), target_host(func->pOrgScript), host(target_host) {}
255 
256  ~CodegenAstVisitor() override = default;
257 
258  using DefaultRecursiveVisitor::visit;
259  void visit(const ::aul::ast::Noop *) override;
260  void visit(const ::aul::ast::StringLit *n) override;
261  void visit(const ::aul::ast::IntLit *n) override;
262  void visit(const ::aul::ast::BoolLit *n) override;
263  void visit(const ::aul::ast::ArrayLit *n) override;
264  void visit(const ::aul::ast::ProplistLit *n) override;
265  void visit(const ::aul::ast::NilLit *n) override;
266  void visit(const ::aul::ast::ThisLit *n) override;
267  void visit(const ::aul::ast::VarExpr *n) override;
268  void visit(const ::aul::ast::UnOpExpr *n) override;
269  void visit(const ::aul::ast::BinOpExpr *n) override;
270  void visit(const ::aul::ast::AssignmentExpr *n) override;
271  void visit(const ::aul::ast::SubscriptExpr *n) override;
272  void visit(const ::aul::ast::SliceExpr *n) override;
273  void visit(const ::aul::ast::CallExpr *n) override;
274  void visit(const ::aul::ast::ParExpr *n) override;
275  void visit(const ::aul::ast::Block *n) override;
276  void visit(const ::aul::ast::Return *n) override;
277  void visit(const ::aul::ast::ForLoop *n) override;
278  void visit(const ::aul::ast::RangeLoop *n) override;
279  void visit(const ::aul::ast::DoLoop *n) override;
280  void visit(const ::aul::ast::WhileLoop *n) override;
281  void visit(const ::aul::ast::Break *n) override;
282  void visit(const ::aul::ast::Continue *n) override;
283  void visit(const ::aul::ast::If *n) override;
284  void visit(const ::aul::ast::VarDecl *n) override;
285  void visit(const ::aul::ast::FunctionDecl *n) override;
286  void visit(const ::aul::ast::FunctionExpr *n) override;
287  void visit(const ::aul::ast::Script *n) override;
288 
289  template<class T>
290  void EmitFunctionCode(const T *n)
291  {
292  // This dynamic_cast resolves the problem where we have a Function*
293  // and want to emit code to it. All classes derived from Function
294  // are also ultimately derived from Node, so this call is fine
295  // without any additional checking.
296  EmitFunctionCode(n, dynamic_cast<const ::aul::ast::Node*>(n));
297  }
298 
299 private:
300  void EmitFunctionCode(const ::aul::ast::Function *f, const ::aul::ast::Node *n);
301 };
302 
304 {
305 public:
306  enum EvalFlag
307  {
308  // If this flag is set, ConstexprEvaluator will assume unset values
309  // are nil. If it is not set, evaluation of unset values will send an
310  // ExpressionNotConstant to the error handler.
311  IgnoreUnset = 1<<0,
312  // If this flag is set, ConstexprEvaluator will not send exceptions to
313  // the error handler (so it doesn't report them twice: once from the
314  // preparsing step, then again from the compile step).
316  };
317  typedef int EvalFlags;
318 
319  // Evaluates constant AST subtrees and returns the final C4Value.
320  // Flags ExpressionNotConstant if evaluation fails.
321  static C4Value eval(C4ScriptHost *host, const ::aul::ast::Expr *e, EvalFlags flags = 0);
322  static C4Value eval_static(C4ScriptHost *host, C4PropListStatic *parent, const std::string &parent_key, const ::aul::ast::Expr *e, EvalFlags flags = 0);
323 
324 private:
325  C4ScriptHost *host = nullptr;
326  C4Value v;
327  bool ignore_unset_values = false;
328  bool quiet = false;
329 
330  struct ProplistMagic
331  {
332  bool active = false;
333  C4PropListStatic *parent = nullptr;
334  std::string key;
335 
336  ProplistMagic() = default;
337  ProplistMagic(bool active, C4PropListStatic *parent, std::string key) : active(active), parent(parent), key(std::move(key)) {}
338  } proplist_magic;
339 
340  explicit ConstexprEvaluator(C4ScriptHost *host) : host(host) {}
341 
342  template<typename... T>
343  NORETURN void nonconst(const ::aul::ast::Node *n, const char *msg, T&&...args) const
344  {
345  throw ExpressionNotConstant(host, n, msg, std::forward<T>(args)...);
346  }
347 
348  void AssertValueType(const C4Value &v, C4V_Type Type1, const char *opname, const ::aul::ast::Node *n)
349  {
350  // Typecheck parameter
351  if (!v.CheckParConversion(Type1))
352  throw Error(host, host, n, nullptr, R"(operator "%s": got %s, but expected %s)", opname, v.GetTypeName(), GetC4VName(Type1));
353  }
354 public:
356  {
357  public:
358  template<typename... T>
359  ExpressionNotConstant(const C4ScriptHost *host, const ::aul::ast::Node *n, const char *reason, T&&...args) :
360  C4AulParseError(Error(host, host, n, nullptr, reason, std::forward<T>(args)...)) {}
361  };
362 
363  using AstVisitor::visit;
364  void visit(const ::aul::ast::StringLit *n) override;
365  void visit(const ::aul::ast::IntLit *n) override;
366  void visit(const ::aul::ast::BoolLit *n) override;
367  void visit(const ::aul::ast::ArrayLit *n) override;
368  void visit(const ::aul::ast::ProplistLit *n) override;
369  void visit(const ::aul::ast::NilLit *) override;
370  void visit(const ::aul::ast::ThisLit *n) override;
371  void visit(const ::aul::ast::VarExpr *n) override;
372  void visit(const ::aul::ast::UnOpExpr *n) override;
373  void visit(const ::aul::ast::BinOpExpr *n) override;
374  void visit(const ::aul::ast::AssignmentExpr *n) override;
375  void visit(const ::aul::ast::SubscriptExpr *n) override;
376  void visit(const ::aul::ast::SliceExpr *n) override;
377  void visit(const ::aul::ast::CallExpr *n) override;
378  void visit(const ::aul::ast::FunctionExpr *n) override;
379 };
380 
382 {
383  C4ScriptHost *host;
384  bool quiet = false;
385  explicit ConstantResolver(C4ScriptHost *host) : host(host) {}
386 
387 public:
388  static void resolve_quiet(C4ScriptHost *host, const ::aul::ast::Script *script)
389  {
390  // Does the same as resolve, but doesn't emit errors/warnings
391  // (because we'll emit them again later).
392  ConstantResolver r(host);
393  r.quiet = true;
394  r.visit(script);
395  }
396  static void resolve(C4ScriptHost *host, const ::aul::ast::Script *script)
397  {
398  // We resolve constants *twice*; this allows people to create circular
399  // references in proplists or arrays.
400  // Unfortunately it also results in unexpected behaviour in code like
401  // this:
402  // static const c1 = c2, c2 = c3, c3 = 1;
403  // which will set c1 to nil, and both c2 and c3 to 1.
404  // While this is unlikely to happen often, we should fix that so it
405  // resolves all three constants to 1.
406  ConstantResolver r(host);
407  r.visit(script);
408  }
409  ~ConstantResolver() override = default;
410 
411  using DefaultRecursiveVisitor::visit;
412  void visit(const ::aul::ast::Script *n) override;
413  void visit(const ::aul::ast::VarDecl *n) override;
414 };
415 
416 void C4AulCompiler::Preparse(C4ScriptHost *host, C4ScriptHost *source_host, const ::aul::ast::Script *script)
417 {
418  PreparseAstVisitor v(host, source_host);
419  v.visit(script);
420 
421  ConstantResolver::resolve_quiet(host, script);
422 }
423 
424 void C4AulCompiler::Compile(C4ScriptHost *host, C4ScriptHost *source_host, const ::aul::ast::Script *script)
425 {
426  ConstantResolver::resolve(host, script);
427 
428  CodegenAstVisitor v(host, source_host);
429  v.visit(script);
430 }
431 
432 void C4AulCompiler::Compile(C4AulScriptFunc *func, const ::aul::ast::Function *def)
433 {
434  {
435  // Don't visit the whole definition here; that would create a new function
436  // and we don't want that.
437  PreparseAstVisitor v(func);
438  def->body->accept(&v);
439  }
440  {
441  CodegenAstVisitor v(func);
442  v.EmitFunctionCode(def);
443  }
444 }
445 
446 #define ENSURE_COND(cond, failmsg) do { if (!(cond)) throw Error(target_host, host, n, Fn, failmsg); } while (0)
447 
448 void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::RangeLoop *n)
449 {
450  const char *cname = n->var.c_str();
451  if (n->scoped_var)
452  {
453  Fn->VarNamed.AddName(cname);
454  }
455  else
456  {
457  // Loop variable not explicitly declared here. Look it up in
458  // the function and warn if it hasn't been declared at all.
459  if (Fn->VarNamed.GetItemNr(cname) == -1)
460  {
461  Warn(target_host, host, n, Fn, C4AulWarningId::implicit_range_loop_var_decl, cname);
462  Fn->VarNamed.AddName(cname);
463  }
464  }
465  DefaultRecursiveVisitor::visit(n);
466 }
467 
468 void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::VarDecl *n)
469 {
470  if (n->constant && n->scope != ::aul::ast::VarDecl::Scope::Global)
471  {
472  Warn(target_host, host, n, Fn, C4AulWarningId::non_global_var_is_never_const);
473  }
474  for (const auto &var : n->decls)
475  {
476  const char *cname = var.name.c_str();
477  switch (n->scope)
478  {
479  case ::aul::ast::VarDecl::Scope::Func:
480  {
481  assert(Fn && "function-local var declaration outside of function");
482  if (!Fn)
483  throw Error(target_host, host, n, Fn, "internal error: function-local var declaration outside of function");
484 
485  if (target_host)
486  {
487  // if target_host is unset, we're parsing this func for direct execution,
488  // in which case we don't want to warn about variable hiding.
489  if (target_host->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || target_host->Engine->GlobalConstNames.GetItemNr(cname) >= 0)
490  Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, "local variable", cname, "global variable");
491  C4String *s = ::Strings.FindString(cname);
492  if (s && target_host->GetPropList()->HasProperty(s))
493  Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, "local variable", cname, "object-local variable");
494  if (Fn->ParNamed.GetItemNr(cname) != -1)
495  {
496  // The parameter order of this warning is correct:
497  // Aul looks up parameters before local variables, so
498  // the parameter actually shadows the local variable.
499  // This doesn't make a whole lot of sense and should
500  // probably be changed.
501  Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, "parameter", cname, "local variable");
502  }
503  }
504  Fn->VarNamed.AddName(cname);
505  break;
506  }
508  {
509  if (host->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || host->Engine->GlobalConstNames.GetItemNr(cname) >= 0)
510  Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, "object-local variable", cname, "global variable");
511  C4String *s = ::Strings.RegString(cname);
512  if (target_host->GetPropList()->HasProperty(s))
513  Warn(target_host, host, n, Fn, C4AulWarningId::redeclaration, "object-local variable", cname);
514  else
515  target_host->GetPropList()->SetPropertyByS(s, C4VNull);
516  break;
517  }
518  case ::aul::ast::VarDecl::Scope::Global:
519  assert(!Fn && "global var declaration inside function");
520  if (Fn)
521  throw Error(target_host, host, n, Fn, "internal error: global var declaration inside function");
522 
523  if (host->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || host->Engine->GlobalConstNames.GetItemNr(cname) >= 0)
524  Warn(target_host, host, n, Fn, C4AulWarningId::redeclaration, "global variable", cname);
525  if (n->constant)
526  host->Engine->GlobalConstNames.AddName(cname);
527  else
528  host->Engine->GlobalNamedNames.AddName(cname);
529  break;
530  }
531  }
532 
533  if (n->scope == ::aul::ast::VarDecl::Scope::Func)
534  {
535  // only func-scoped variables can potentially have initializers we care
536  // about in the pre-parsing stage: they may have calls that pass
537  // unnamed parameters
538  DefaultRecursiveVisitor::visit(n);
539  }
540 }
541 
542 void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::FunctionDecl *n)
543 {
544  // create script fn
545  C4PropListStatic *Parent = n->is_global ? target_host->Engine->GetPropList() : target_host->GetPropList();
546  const char *cname = n->name.c_str();
547 
548  assert(!Fn);
549 
550  // Look up the overloaded function before adding the overloading one
551  C4AulFunc *parent_func = Parent->GetFunc(cname);
552 
553  Fn = new C4AulScriptFunc(Parent, target_host, cname, n->loc);
554  host->ownedFunctions.push_back(C4VFunction(Fn));
555  for (const auto &param : n->params)
556  {
557  Fn->AddPar(param.name.c_str(), param.type);
558  }
559  if (n->has_unnamed_params)
560  Fn->ParCount = C4AUL_MAX_Par;
561 
562  // Add function to def/engine
563  Fn->SetOverloaded(parent_func);
564  Parent->SetPropertyByS(Fn->Name, C4VFunction(Fn));
565 
566  try
567  {
568  DefaultRecursiveVisitor::visit(n);
569  Fn = nullptr;
570  }
571  catch (...)
572  {
573  Fn = nullptr;
574  throw;
575  }
576 }
577 
578 void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::CallExpr *n)
579 {
580  if (n->append_unnamed_pars && Fn->ParCount != C4AUL_MAX_Par)
581  {
582  Fn->ParCount = C4AUL_MAX_Par;
583  }
584  DefaultRecursiveVisitor::visit(n);
585 }
586 
587 void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::ParExpr *n)
588 {
589  if (Fn->ParCount != C4AUL_MAX_Par)
590  {
591  Warn(target_host, host, n, Fn, C4AulWarningId::undeclared_varargs, "Par()");
592  Fn->ParCount = C4AUL_MAX_Par;
593  }
594  DefaultRecursiveVisitor::visit(n);
595 }
596 
597 void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::AppendtoPragma *n)
598 {
599  if (n->what.empty())
600  host->Appends.emplace_back("*");
601  else
602  host->Appends.emplace_back(n->what.c_str());
603 }
604 
605 void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::IncludePragma *n)
606 {
607  host->Includes.emplace_back(n->what.c_str());
608 }
609 
610 void C4AulCompiler::PreparseAstVisitor::visit(const::aul::ast::Script * n)
611 {
612  for (const auto &d : n->declarations)
613  {
614  try
615  {
616  d->accept(this);
617  }
618  catch (C4AulParseError &e)
619  {
620  target_host->Engine->GetErrorHandler()->OnError(e.what());
621  }
622  }
623 }
624 
625 int C4AulCompiler::CodegenAstVisitor::GetStackValue(C4AulBCCType eType, intptr_t X)
626 {
627  switch (eType)
628  {
629  case AB_INT:
630  case AB_BOOL:
631  case AB_STRING:
632  case AB_CPROPLIST:
633  case AB_CARRAY:
634  case AB_CFUNCTION:
635  case AB_NIL:
636  case AB_LOCALN:
637  case AB_GLOBALN:
638  case AB_DUP:
639  case AB_DUP_CONTEXT:
640  case AB_THIS:
641  return 1;
642 
643  case AB_Pow:
644  case AB_Div:
645  case AB_Mul:
646  case AB_Mod:
647  case AB_Sub:
648  case AB_Sum:
649  case AB_LeftShift:
650  case AB_RightShift:
651  case AB_LessThan:
652  case AB_LessThanEqual:
653  case AB_GreaterThan:
654  case AB_GreaterThanEqual:
655  case AB_Equal:
656  case AB_NotEqual:
657  case AB_BitAnd:
658  case AB_BitXOr:
659  case AB_BitOr:
660  case AB_PROP_SET:
661  case AB_ARRAYA:
662  case AB_CONDN:
663  case AB_COND:
664  case AB_POP_TO:
665  case AB_RETURN:
666  // JUMPAND/JUMPOR/JUMPNNIL are special: They either jump over instructions adding one to the stack
667  // or decrement the stack. Thus, for stack counting purposes, they decrement.
668  case AB_JUMPAND:
669  case AB_JUMPOR:
670  case AB_JUMPNNIL:
671  return -1;
672 
673  case AB_FUNC:
674  return -reinterpret_cast<C4AulFunc *>(X)->GetParCount() + 1;
675 
676  case AB_CALL:
677  case AB_CALLFS:
678  return -C4AUL_MAX_Par;
679 
680  case AB_STACK_SET:
681  case AB_LOCALN_SET:
682  case AB_PROP:
683  case AB_GLOBALN_SET:
684  case AB_Inc:
685  case AB_Dec:
686  case AB_BitNot:
687  case AB_Not:
688  case AB_Neg:
689  case AB_PAR:
690  case AB_FOREACH_NEXT:
691  case AB_ERR:
692  case AB_EOFN:
693  case AB_JUMP:
694  case AB_DEBUG:
695  return 0;
696 
697  case AB_STACK:
698  return X;
699 
700  case AB_NEW_ARRAY:
701  return -X + 1;
702 
703  case AB_NEW_PROPLIST:
704  return -X * 2 + 1;
705 
706  case AB_ARRAYA_SET:
707  case AB_ARRAY_SLICE:
708  return -2;
709 
710  case AB_ARRAY_SLICE_SET:
711  return -3;
712  }
713  assert(0 && "GetStackValue: unexpected bytecode not handled");
714  return 0;
715 }
716 
717 int C4AulCompiler::CodegenAstVisitor::AddVarAccess(const char *TokenSPos, C4AulBCCType eType, intptr_t varnum)
718 {
719  return AddBCC(TokenSPos, eType, 1 + varnum - (stack_height + Fn->VarNamed.iSize));
720 }
721 
722 int C4AulCompiler::CodegenAstVisitor::AddBCC(const char *TokenSPos, C4AulBCCType eType, intptr_t X)
723 {
724  // Track stack size
725  stack_height += GetStackValue(eType, X);
726 
727  // Use stack operation instead of 0-Any (enable optimization)
728  if (eType == AB_NIL)
729  {
730  eType = AB_STACK;
731  X = 1;
732  }
733 
734  assert(eType != AB_STACK || X != 0);
735 
736  // Join checks only if it's not a jump target
737  if (!at_jump_target && Fn->GetLastCode())
738  {
739  C4AulBCC *pCPos1 = Fn->GetLastCode();
740 
741  // Skip noop stack operation
742  if (eType == AB_STACK && X == 0)
743  {
744  return Fn->GetCodePos() - 1;
745  }
746 
747  // Join together stack operations
748  if (eType == AB_STACK && pCPos1->bccType == AB_STACK &&
749  (X <= 0 || pCPos1->Par.i >= 0))
750  {
751  pCPos1->Par.i += X;
752  // Empty? Remove it. This relies on the parser not issuing
753  // multiple negative stack operations consecutively, as
754  // that could result in removing a jump target bytecode.
755  if (!pCPos1->Par.i)
756  Fn->RemoveLastBCC();
757  return Fn->GetCodePos() - 1;
758  }
759 
760  // Prune unneeded Incs / Decs
761  if (eType == AB_STACK && X < 0 && (pCPos1->bccType == AB_Inc || pCPos1->bccType == AB_Dec))
762  {
763  if (!pCPos1->Par.X)
764  {
765  pCPos1->bccType = eType;
766  pCPos1->Par.i = X;
767  return Fn->GetCodePos() - 1;
768  }
769  else
770  {
771  // If it was a result modifier, we can safely remove it knowing that it was neither
772  // the first chunk nor a jump target. We can therefore apply additional optimizations.
773  Fn->RemoveLastBCC();
774  pCPos1--;
775  }
776  }
777 
778  // Join STACK_SET + STACK -1 to POP_TO (equivalent)
779  if (eType == AB_STACK && X == -1 && pCPos1->bccType == AB_STACK_SET)
780  {
781  pCPos1->bccType = AB_POP_TO;
782  return Fn->GetCodePos() - 1;
783  }
784 
785  // Join POP_TO + DUP to AB_STACK_SET if both target the same slot
786  if (eType == AB_DUP && pCPos1->bccType == AB_POP_TO && X == pCPos1->Par.i + 1)
787  {
788  pCPos1->bccType = AB_STACK_SET;
789  return Fn->GetCodePos() - 1;
790  }
791 
792  // Reduce some constructs like SUM + INT 1 to INC or DEC
793  if ((eType == AB_Sum || eType == AB_Sub) &&
794  pCPos1->bccType == AB_INT &&
795  (pCPos1->Par.i == 1 || pCPos1->Par.i == -1))
796  {
797  if ((pCPos1->Par.i > 0) == (eType == AB_Sum))
798  pCPos1->bccType = AB_Inc;
799  else
800  pCPos1->bccType = AB_Dec;
801  pCPos1->Par.i = X;
802  return Fn->GetCodePos() - 1;
803  }
804 
805  // Reduce Not + CONDN to COND, Not + COND to CONDN
806  if ((eType == AB_CONDN || eType == AB_COND) && pCPos1->bccType == AB_Not)
807  {
808  pCPos1->bccType = eType == AB_CONDN ? AB_COND : AB_CONDN;
809  pCPos1->Par.i = X + 1;
810  return Fn->GetCodePos() - 1;
811  }
812 
813  // Join AB_STRING + AB_ARRAYA to AB_PROP
814  if (eType == AB_ARRAYA && pCPos1->bccType == AB_STRING)
815  {
816  pCPos1->bccType = AB_PROP;
817  return Fn->GetCodePos() - 1;
818  }
819 
820  // Join AB_INT + AB_Neg to AB_INT
821  if (eType == AB_Neg && pCPos1->bccType == AB_INT)
822  {
823  pCPos1->Par.i *= -1;
824  return Fn->GetCodePos() - 1;
825  }
826  }
827 
828  // Add
829  Fn->AddBCC(eType, X, TokenSPos);
830 
831  // Reset jump flag
832  at_jump_target = false;
833 
834  return Fn->GetCodePos() - 1;
835 }
836 
837 void C4AulCompiler::CodegenAstVisitor::RemoveLastBCC()
838 {
839  // Security: This is unsafe on anything that might get optimized away
840  C4AulBCC *pBCC = Fn->GetLastCode();
841  assert(pBCC->bccType != AB_STACK && pBCC->bccType != AB_STACK_SET && pBCC->bccType != AB_POP_TO);
842  // Correct stack
843  stack_height -= GetStackValue(pBCC->bccType, pBCC->Par.X);
844  // Remove
845  Fn->RemoveLastBCC();
846 }
847 
848 int C4AulCompiler::CodegenAstVisitor::AddBCC(const char *SPos, const C4AulBCC &bcc)
849 {
850  return AddBCC(SPos, bcc.bccType, bcc.Par.X);
851 }
852 
853 C4AulBCC C4AulCompiler::CodegenAstVisitor::MakeSetter(const char *SPos, bool fLeaveValue)
854 {
855  assert(Fn);
856  C4AulBCC Value = *(Fn->GetLastCode()), Setter = Value;
857  // Check type
858  switch (Value.bccType)
859  {
860  case AB_ARRAYA: Setter.bccType = AB_ARRAYA_SET; break;
861  case AB_ARRAY_SLICE: Setter.bccType = AB_ARRAY_SLICE_SET; break;
862  case AB_DUP:
863  Setter.bccType = AB_STACK_SET;
864  // the setter additionally has the new value on the stack
865  --Setter.Par.i;
866  break;
867  case AB_STACK_SET: Setter.bccType = AB_STACK_SET; break;
868  case AB_LOCALN:
869  Setter.bccType = AB_LOCALN_SET;
870  break;
871  case AB_PROP:
872  Setter.bccType = AB_PROP_SET;
873  break;
874  case AB_GLOBALN: Setter.bccType = AB_GLOBALN_SET; break;
875  default:
876  throw Error(target_host, host, SPos, Fn, "assignment to a constant");
877  }
878  // If the new value is produced using the old one, the parameters to get the old one need to be duplicated.
879  // Otherwise, the setter can just use the parameters originally meant for the getter.
880  // All getters push one value, so the parameter count is one more than the values they pop from the stack.
881  int iParCount = 1 - GetStackValue(Value.bccType, Value.Par.X);
882  if (Value.bccType == AB_STACK_SET)
883  {
884  // STACK_SET has a side effect, so it can't be simply removed.
885  // Discard the unused value the usual way instead.
886  if (!fLeaveValue)
887  AddBCC(SPos, AB_STACK, -1);
888  // The original parameter isn't needed anymore, since in contrast to the other getters
889  // it does not indicate a position.
890  iParCount = 0;
891  }
892  else if (!fLeaveValue || iParCount)
893  {
894  RemoveLastBCC();
895  at_jump_target = true; // In case the original BCC was a jump target
896  }
897  if (fLeaveValue && iParCount)
898  {
899  for (int i = 0; i < iParCount; i++)
900  AddBCC(SPos, AB_DUP, 1 - iParCount);
901  // Finally re-add original BCC
902  AddBCC(SPos, Value.bccType, Value.Par.X);
903  }
904  // Done. The returned BCC should be added later once the value to be set was pushed on top.
905  assert(iParCount == -GetStackValue(Setter.bccType, Setter.Par.X));
906  return Setter;
907 }
908 
909 int C4AulCompiler::CodegenAstVisitor::AddJumpTarget()
910 {
911  assert(Fn && "Jump target outside of function");
912  if (!Fn)
913  throw C4AulParseError(host, "internal error: jump target outside of function");
914 
915  at_jump_target = true;
916  return Fn->GetCodePos();
917 }
918 
919 void C4AulCompiler::CodegenAstVisitor::UpdateJump(int jump, int target)
920 {
921  C4AulBCC *code = Fn->GetCodeByPos(jump);
922  assert(IsJump(code->bccType));
923  code->Par.i = target - jump;
924 }
925 
926 void C4AulCompiler::CodegenAstVisitor::AddJumpTo(const char *loc, C4AulBCCType type, int target)
927 {
928  AddBCC(loc, type, target - Fn->GetCodePos());
929 }
930 
931 void C4AulCompiler::CodegenAstVisitor::PushLoop()
932 {
933  active_loops.emplace(stack_height);
934 }
935 
936 void C4AulCompiler::CodegenAstVisitor::PopLoop(int continue_target)
937 {
938  assert(!active_loops.empty());
939  assert(stack_height == active_loops.top().stack_height);
940  // Update all loop control jumps
941  const auto &loop = active_loops.top();
942  for (auto &c : loop.continues)
943  UpdateJump(c, continue_target);
944  int loop_exit = AddJumpTarget();
945  for (auto &b : loop.breaks)
946  UpdateJump(b, loop_exit);
947  active_loops.pop();
948 }
949 
950 void C4AulCompiler::CodegenAstVisitor::AddLoopControl(const char *loc, Loop::Control c)
951 {
952  assert(!active_loops.empty());
953  if (active_loops.empty())
954  throw C4AulParseError(host, "internal error: loop control code emitted outside of loop");
955  // Clear stack
956  assert(active_loops.top().stack_height == stack_height);
957  if (active_loops.top().stack_height - stack_height > 0)
958  AddBCC(loc, AB_STACK, active_loops.top().stack_height - stack_height);
959  int jump = AddBCC(loc, AB_JUMP, 0);
960  switch (c)
961  {
962  case Loop::Control::Continue:
963  active_loops.top().continues.push_back(jump);
964  break;
965  case Loop::Control::Break:
966  active_loops.top().breaks.push_back(jump);
967  break;
968  }
969 }
970 
971 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Noop *) {}
972 
973 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::StringLit *n)
974 {
975  StackGuard g(this, 1);
976  AddBCC(n->loc, AB_STRING, (intptr_t)::Strings.RegString(n->value.c_str()));
977  type_of_stack_top = C4V_String;
978 }
979 
980 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::IntLit *n)
981 {
982  StackGuard g(this, 1);
983  AddBCC(n->loc, AB_INT, n->value);
984  type_of_stack_top = C4V_Int;
985 }
986 
987 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::BoolLit *n)
988 {
989  StackGuard g(this, 1);
990  AddBCC(n->loc, AB_BOOL, n->value);
991  type_of_stack_top = C4V_Bool;
992 }
993 
994 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ArrayLit *n)
995 {
996  StackGuard g(this, 1);
997  for (const auto &e : n->values)
998  {
999  SafeVisit(e);
1000  }
1001  AddBCC(n->loc, AB_NEW_ARRAY, n->values.size());
1002  type_of_stack_top = C4V_Array;
1003 }
1004 
1005 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ProplistLit *n)
1006 {
1007  StackGuard g(this, 1);
1008  for (const auto &e : n->values)
1009  {
1010  StackGuard g(this, 2);
1011  AddBCC(n->loc, AB_STRING, (intptr_t)::Strings.RegString(e.first.c_str()));
1012  SafeVisit(e.second);
1013  }
1014  AddBCC(n->loc, AB_NEW_PROPLIST, n->values.size());
1015  type_of_stack_top = C4V_PropList;
1016 }
1017 
1018 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::NilLit *n)
1019 {
1020  StackGuard g(this, 1);
1021  AddBCC(n->loc, AB_NIL);
1022  type_of_stack_top = C4V_Nil;
1023 }
1024 
1025 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ThisLit *n)
1026 {
1027  StackGuard g(this, 1);
1028  AddBCC(n->loc, AB_THIS);
1029  type_of_stack_top = C4V_PropList;
1030 }
1031 
1032 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::VarExpr *n)
1033 {
1034  StackGuard g(this, 1);
1035  assert(Fn);
1036  C4Value dummy;
1037  const char *cname = n->identifier.c_str();
1038  C4String *interned = ::Strings.FindString(cname);
1039 
1040  // Reset known type of top of value stack so we don't keep the old one around
1041  type_of_stack_top = C4V_Any;
1042 
1043  // Lookup order: Parameters > var > local > global > global const
1044  // Why parameters are considered before function-scoped variables
1045  // you ask? I've no idea, but that's how it was before I started
1046  // changing things.
1047  // NOTE: If you change this, remember to also change the warning
1048  // (variable_shadows_variable) in PreparseAstVisitor.
1049  if (Fn->ParNamed.GetItemNr(cname) != -1)
1050  {
1051  int pos = Fn->ParNamed.GetItemNr(cname);
1052  AddVarAccess(n->loc, AB_DUP, -Fn->GetParCount() + pos);
1053  type_of_stack_top = Fn->GetParType()[pos];
1054  }
1055  else if (Fn->VarNamed.GetItemNr(cname) != -1)
1056  {
1057  AddVarAccess(n->loc, AB_DUP, Fn->VarNamed.GetItemNr(cname));
1058  }
1059  // Can't use Fn->Parent->HasProperty here because that only returns true
1060  // for immediate properties, while we also want to interrogate prototypes
1061  else if (Fn->Parent && interned && Fn->Parent->GetPropertyByS(interned, &dummy))
1062  {
1063  AddBCC(n->loc, AB_LOCALN, (intptr_t)interned);
1064  }
1065  else if (ScriptEngine.GlobalNamedNames.GetItemNr(cname) != -1)
1066  {
1067  AddBCC(n->loc, AB_GLOBALN, ScriptEngine.GlobalNamedNames.GetItemNr(cname));
1068  }
1069  else if (ScriptEngine.GlobalConstNames.GetItemNr(cname) != -1)
1070  {
1071  C4Value v;
1072  ENSURE_COND(ScriptEngine.GetGlobalConstant(cname, &v), "internal error: global constant not retrievable");
1073  switch (v.GetType())
1074  {
1075  case C4V_Nil:
1076  AddBCC(n->loc, AB_NIL);
1077  break;
1078  case C4V_Int:
1079  AddBCC(n->loc, AB_INT, v._getInt());
1080  break;
1081  case C4V_Bool:
1082  AddBCC(n->loc, AB_BOOL, v._getBool());
1083  break;
1084  case C4V_PropList:
1085  AddBCC(n->loc, AB_CPROPLIST, reinterpret_cast<intptr_t>(v._getPropList()));
1086  break;
1087  case C4V_String:
1088  AddBCC(n->loc, AB_STRING, reinterpret_cast<intptr_t>(v._getStr()));
1089  break;
1090  case C4V_Array:
1091  AddBCC(n->loc, AB_CARRAY, reinterpret_cast<intptr_t>(v._getArray()));
1092  break;
1093  case C4V_Function:
1094  AddBCC(n->loc, AB_CFUNCTION, reinterpret_cast<intptr_t>(v._getFunction()));
1095  default:
1096  AddBCC(n->loc, AB_NIL);
1097  throw Error(target_host, host, n, Fn, "internal error: global constant of unexpected type: %s (of type %s)", cname, v.GetTypeName());
1098  }
1099  type_of_stack_top = v.GetType();
1100  }
1101  else
1102  {
1103  AddBCC(n->loc, AB_NIL);
1104  throw Error(target_host, host, n, Fn, "symbol not found in any symbol table: %s", cname);
1105  }
1106 }
1107 
1108 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::UnOpExpr *n)
1109 {
1110  StackGuard g(this, 1);
1111 
1112  n->operand->accept(this);
1113  const auto &op = C4ScriptOpMap[n->op];
1114  if (op.Changer)
1115  {
1116  C4AulBCC setter = MakeSetter(n->loc, true);
1117  AddBCC(n->loc, op.Code, 0);
1118  AddBCC(n->loc, setter);
1119  // On postfix inc/dec, regenerate the previous value
1120  if (op.Postfix && (op.Code == AB_Inc || op.Code == AB_Dec))
1121  {
1122  AddBCC(n->loc, op.Code == AB_Inc ? AB_Dec : AB_Inc, 1);
1123  }
1124  }
1125  else
1126  {
1127  AddBCC(n->loc, op.Code);
1128  }
1129  type_of_stack_top = op.RetType;
1130 }
1131 
1132 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::BinOpExpr *n)
1133 {
1134  StackGuard g(this, 1);
1135 
1136  SafeVisit(n->lhs);
1137 
1138  const auto &op = C4ScriptOpMap[n->op];
1139  if (op.Code == AB_JUMPAND || op.Code == AB_JUMPOR || op.Code == AB_JUMPNNIL)
1140  {
1141  // Short-circuiting operators. These are slightly more complex
1142  // because we don't want to evaluate their rhs operand when the
1143  // lhs one already decided the result
1144  int jump = AddBCC(n->loc, op.Code);
1145  SafeVisit(n->rhs);
1146  UpdateJump(jump, AddJumpTarget());
1147  }
1148  else if (op.Changer)
1149  {
1150  try
1151  {
1152  C4AulBCC setter = MakeSetter(n->loc, true);
1153  SafeVisit(n->rhs);
1154  AddBCC(n->loc, op.Code);
1155  AddBCC(n->loc, setter);
1156  }
1157  catch (C4AulParseError &e)
1158  {
1159  HandleError(e);
1160  }
1161  }
1162  else
1163  {
1164  SafeVisit(n->rhs);
1165  AddBCC(n->loc, op.Code, 0);
1166  }
1167 
1168  type_of_stack_top = op.RetType;
1169 }
1170 
1171 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::AssignmentExpr *n)
1172 {
1173  StackGuard g(this, 1);
1174  SafeVisit(n->lhs);
1175  try
1176  {
1177  C4AulBCC setter = MakeSetter(n->loc, false);
1178  SafeVisit(n->rhs);
1179  AddBCC(n->loc, setter);
1180  }
1181  catch (C4AulParseError &e)
1182  {
1183  HandleError(e);
1184  }
1185  // Assignment does not change the type of the variable
1186 }
1187 
1188 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::SubscriptExpr *n)
1189 {
1190  StackGuard g(this, 1);
1191  SafeVisit(n->object);
1192  SafeVisit(n->index);
1193  AddBCC(n->loc, AB_ARRAYA);
1194 
1195  // FIXME: Check if the subscripted object is a literal and if so, retrieve type
1196  type_of_stack_top = C4V_Any;
1197 }
1198 
1199 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::SliceExpr *n)
1200 {
1201  StackGuard g(this, 1);
1202  SafeVisit(n->object);
1203  SafeVisit(n->start);
1204  SafeVisit(n->end);
1205  AddBCC(n->loc, AB_ARRAY_SLICE);
1206 
1207  type_of_stack_top = C4V_Array;
1208 }
1209 
1210 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::CallExpr *n)
1211 {
1212  const char *cname = n->callee.c_str();
1213 
1214  if (n->callee == C4AUL_DebugBreak)
1215  {
1216  if (n->context)
1217  throw Error(target_host, host, n, Fn, R"("%s" can't be called in a different context)", cname);
1218  if (!n->args.empty())
1219  throw Error(target_host, host, n, Fn, R"("%s" must not have any arguments)", cname);
1220 
1221  AddBCC(n->loc, AB_DEBUG);
1222  // Add a pseudo-nil to keep the stack balanced
1223  AddBCC(n->loc, AB_NIL);
1224  type_of_stack_top = C4V_Nil;
1225  return;
1226  }
1227 
1228  if (n->callee == C4AUL_Inherited || n->callee == C4AUL_SafeInherited)
1229  {
1230  // inherited can only be called within the same context
1231  if (n->context)
1232  {
1233  throw Error(target_host, host, n, Fn, R"("%s" can't be called in a different context)", cname);
1234  }
1235  }
1236 
1237  if (n->callee == C4AUL_Inherited && !Fn->OwnerOverloaded)
1238  {
1239  throw Error(target_host, host, n, Fn, "inherited function not found (use " C4AUL_SafeInherited " to disable this message)");
1240  }
1241 
1242  const auto pre_call_stack = stack_height;
1243 
1244  if (n->context)
1245  SafeVisit(n->context);
1246 
1247  std::vector<C4V_Type> known_par_types;
1248  known_par_types.reserve(n->args.size());
1249 
1250  for (const auto &arg : n->args)
1251  {
1252  SafeVisit(arg);
1253  known_par_types.push_back(type_of_stack_top);
1254  }
1255 
1256  C4AulFunc *callee = nullptr;
1257 
1258  // Special handling for the overload chain
1259  if (n->callee == C4AUL_Inherited || n->callee == C4AUL_SafeInherited)
1260  {
1261  callee = Fn->OwnerOverloaded;
1262  }
1263 
1264  size_t fn_argc = C4AUL_MAX_Par;
1265  if (!n->context)
1266  {
1267  // if this is a function without explicit context, we resolve it
1268  if (!callee)
1269  callee = Fn->Parent->GetFunc(cname);
1270  if (!callee && target_host)
1271  callee = target_host->Engine->GetFunc(cname);
1272 
1273  if (callee)
1274  {
1275  fn_argc = callee->GetParCount();
1276  }
1277  else
1278  {
1279  // pop all args off the stack
1280  if (!n->args.empty())
1281  AddBCC(n->loc, AB_STACK, -(intptr_t)n->args.size());
1282  // and "return" nil
1283  AddBCC(n->loc, AB_NIL);
1284  type_of_stack_top = C4V_Nil;
1285 
1286  if (n->callee != C4AUL_SafeInherited)
1287  {
1288  HandleError(Error(target_host, host, n, Fn, "called function not found: %s", cname));
1289  }
1290  return;
1291  }
1292  }
1293 
1294  if (n->args.size() > fn_argc)
1295  {
1296  // Pop off any args that are over the limit
1297  Warn(target_host, host, n->args[fn_argc].get(), Fn, C4AulWarningId::arg_count_mismatch,
1298  cname, (unsigned)n->args.size(), fn_argc);
1299  AddBCC(n->loc, AB_STACK, fn_argc - n->args.size());
1300  }
1301  else if (n->args.size() < fn_argc)
1302  {
1303  if (n->append_unnamed_pars)
1304  {
1305  assert(Fn->GetParCount() == C4AUL_MAX_Par);
1306  int missing_par_count = fn_argc - n->args.size();
1307  int available_par_count = Fn->GetParCount() - Fn->ParNamed.iSize;
1308  for (int i = 0; i < std::min(missing_par_count, available_par_count); ++i)
1309  {
1310  AddVarAccess(n->loc, AB_DUP, -Fn->GetParCount() + Fn->ParNamed.iSize + i);
1311  }
1312  // Fill up remaining, unsettable parameters with nil
1313  if (available_par_count < missing_par_count)
1314  AddBCC(n->loc, AB_STACK, missing_par_count - available_par_count);
1315  }
1316  else if (fn_argc > n->args.size())
1317  {
1318  // Add nil for each missing parameter
1319  AddBCC(n->loc, AB_STACK, fn_argc - n->args.size());
1320  }
1321  }
1322 
1323  // Check passed parameters for this call (as far as possible)
1324  std::vector<C4V_Type> expected_par_types;
1325  if (n->context)
1326  {
1327  AddBCC(n->loc, n->safe_call ? AB_CALLFS : AB_CALL, (intptr_t)::Strings.RegString(cname));
1328  // Since we don't know the context in which this call will happen at
1329  // runtime, we'll check whether all available functions with the same
1330  // name agree on their parameters.
1331  const C4AulFunc *candidate = target_host ? target_host->Engine->GetFirstFunc(cname) : nullptr;
1332  if (candidate)
1333  {
1334  expected_par_types.assign(candidate->GetParType(), candidate->GetParType() + candidate->GetParCount());
1335  while ((candidate = target_host->Engine->GetNextSNFunc(candidate)) != nullptr)
1336  {
1337  if (candidate->GetParCount() > expected_par_types.size())
1338  {
1339  expected_par_types.resize(candidate->GetParCount(), C4V_Any);
1340  }
1341  for (size_t i = 0; i < expected_par_types.size(); ++i)
1342  {
1343  C4V_Type a = expected_par_types[i];
1344  C4V_Type b = candidate->GetParType()[i];
1345  // If we can convert one of the types into the other
1346  // without a warning, use the wider one
1347  bool implicit_a_to_b = !C4Value::WarnAboutConversion(a, b);
1348  bool implicit_b_to_a = !C4Value::WarnAboutConversion(b, a);
1349  if (implicit_a_to_b && !implicit_b_to_a)
1350  expected_par_types[i] = b;
1351  else if (implicit_b_to_a && !implicit_a_to_b)
1352  expected_par_types[i] = a;
1353  // but if we can convert neither of the types into the
1354  // other, give up and assume the user will do the right
1355  // thing
1356  else if (!implicit_a_to_b && !implicit_b_to_a)
1357  expected_par_types[i] = C4V_Any;
1358  }
1359  }
1360  }
1361  type_of_stack_top = C4V_Any;
1362  }
1363  else
1364  {
1365  assert(callee);
1366  AddBCC(n->loc, AB_FUNC, (intptr_t)callee);
1367  expected_par_types.assign(callee->GetParType(), callee->GetParType() + callee->GetParCount());
1368  type_of_stack_top = callee->GetRetType();
1369  }
1370 
1371  // Check parameters
1372  for (size_t i = 0; i < std::min(known_par_types.size(), expected_par_types.size()); ++i)
1373  {
1374  C4V_Type from = known_par_types[i];
1375  C4V_Type to = expected_par_types[i];
1376  if (C4Value::WarnAboutConversion(from, to))
1377  {
1378  Warn(target_host, host, n->args[i].get(), Fn, C4AulWarningId::arg_type_mismatch, (unsigned)i, cname, GetC4VName(from), GetC4VName(to));
1379  }
1380  }
1381 
1382  // We leave one value (the return value) on the stack
1383  assert(pre_call_stack + 1 == stack_height);
1384 }
1385 
1386 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ParExpr *n)
1387 {
1388  StackGuard g(this, 1);
1389 
1390  SafeVisit(n->arg);
1391  AddBCC(n->loc, AB_PAR);
1392  type_of_stack_top = C4V_Any;
1393 }
1394 
1395 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Block *n)
1396 {
1397  for (const auto &s : n->children)
1398  {
1399  StackGuard g(this, 0);
1400  if (SafeVisit(s))
1401  {
1402  // If the statement has left a stack value, pop it off
1403  MaybePopValueOf(s);
1404  }
1405  }
1406 }
1407 
1408 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Return *n)
1409 {
1410  StackGuard g(this, 0);
1411 
1412  SafeVisit(n->value);
1413  AddBCC(n->loc, AB_RETURN);
1414 }
1415 
1416 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ForLoop *n)
1417 {
1418  // Bytecode arranged like this:
1419  // initializer
1420  // cond: condition
1421  // CONDN exit
1422  // body: body
1423  // incr: incrementor
1424  // JUMP cond
1425  // exit:
1426  //
1427  // continue jumps to incr
1428  // break jumps to exit
1429 
1430  if (n->init)
1431  {
1432  if (SafeVisit(n->init))
1433  MaybePopValueOf(n->init);
1434  }
1435  int cond = -1, condition_jump = -1;
1436  PushLoop();
1437  if (n->cond)
1438  {
1439  cond = AddJumpTarget();
1440  SafeVisit(n->cond);
1441  active_loops.top().breaks.push_back(AddBCC(n->cond->loc, AB_CONDN));
1442  }
1443 
1444  int body = AddJumpTarget();
1445  if (!n->cond)
1446  cond = body;
1447  if (SafeVisit(n->body))
1448  MaybePopValueOf(n->body);
1449 
1450  int incr = -1;
1451  if (n->incr)
1452  {
1453  incr = AddJumpTarget();
1454  if (SafeVisit(n->incr))
1455  MaybePopValueOf(n->incr);
1456  }
1457  else
1458  {
1459  // If no incrementor exists, just jump straight to the condition
1460  incr = cond;
1461  }
1462  // start the next iteration of the loop
1463  AddJumpTo(n->loc, AB_JUMP, cond);
1464  PopLoop(incr);
1465 }
1466 
1467 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::RangeLoop *n)
1468 {
1469  // Bytecode arranged like this:
1470  // condition (aka iterated array)
1471  // INT 0 (the loop index variable)
1472  // cond: FOREACH_NEXT
1473  // JUMP exit
1474  // body: body
1475  // JUMP cond
1476  // exit: STACK -2 (to clean the iteration variables)
1477  //
1478  // continue jumps to cond
1479  // break jumps to exit
1480 
1481  const char *cname = n->var.c_str();
1482  int var_id = Fn->VarNamed.GetItemNr(cname);
1483  assert(var_id != -1 && "CodegenAstVisitor: unable to find variable in foreach");
1484  if (var_id == -1)
1485  throw Error(target_host, host, n, Fn, "internal error: unable to find variable in foreach: %s", cname);
1486  // Emit code for array
1487  SafeVisit(n->cond);
1488  // Emit code for iteration
1489  AddBCC(n->loc, AB_INT, 0);
1490  int cond = AddJumpTarget();
1491  PushLoop();
1492  AddVarAccess(n->loc, AB_FOREACH_NEXT, var_id);
1493  AddLoopControl(n->loc, Loop::Control::Break); // Will be skipped by AB_FOREACH_NEXT as long as more entries exist
1494 
1495  // Emit body
1496  if (SafeVisit(n->body))
1497  MaybePopValueOf(n->body);
1498  // continue starts the next iteration of the loop
1499  AddLoopControl(n->loc, Loop::Control::Continue);
1500  PopLoop(cond);
1501  // Pop off iterator and array
1502  AddBCC(n->loc, AB_STACK, -2);
1503 }
1504 
1505 void C4AulCompiler::CodegenAstVisitor::EmitFunctionCode(const ::aul::ast::Function *f, const ::aul::ast::Node *n)
1506 {
1507  assert(Fn != nullptr);
1508 
1509  Fn->ClearCode();
1510 
1511  // Reserve var stack space
1512  if (Fn->VarNamed.iSize > 0)
1513  AddBCC(n->loc, AB_STACK, Fn->VarNamed.iSize);
1514  stack_height = 0;
1515 
1516  try
1517  {
1518  f->body->accept(this);
1519  }
1520  catch (C4AulParseError &e)
1521  {
1522  AddBCC(nullptr, AB_ERR, (intptr_t)::Strings.RegString(e.what()));
1523  throw;
1524  }
1525 
1526  if (f->body->children.empty() || !dynamic_cast<::aul::ast::Return*>(f->body->children.rbegin()->get()))
1527  {
1528  // If the last statement isn't a return, add one to the byte
1529  // code. We're not doing CFA because the worst thing that might
1530  // happen is we insert two instructions that never get executed.
1531  AddBCC(n->loc, AB_NIL);
1532  AddBCC(n->loc, AB_RETURN);
1533  }
1534  Fn->DumpByteCode();
1535  // This instruction should never be reached but we'll add it just in
1536  // case.
1537  AddBCC(n->loc, AB_EOFN);
1538  assert(stack_height == 0);
1539 }
1540 
1541 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::DoLoop *n)
1542 {
1543  int body = AddJumpTarget();
1544  PushLoop();
1545  if (SafeVisit(n->body))
1546  MaybePopValueOf(n->body);
1547  int cond = AddJumpTarget();
1548  SafeVisit(n->cond);
1549  AddJumpTo(n->loc, AB_COND, body);
1550  PopLoop(cond);
1551 }
1552 
1553 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::WhileLoop *n)
1554 {
1555  int cond = AddJumpTarget();
1556  PushLoop();
1557  SafeVisit(n->cond);
1558  active_loops.top().breaks.push_back(AddBCC(n->cond->loc, AB_CONDN));
1559  if (SafeVisit(n->body))
1560  MaybePopValueOf(n->body);
1561  // continue starts the next iteration of the loop
1562  AddLoopControl(n->loc, Loop::Control::Continue);
1563  PopLoop(cond);
1564 }
1565 
1566 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Break *n)
1567 {
1568  ENSURE_COND(!active_loops.empty(), "'break' outside loop");
1569  AddLoopControl(n->loc, Loop::Control::Break);
1570 }
1571 
1572 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Continue *n)
1573 {
1574  ENSURE_COND(!active_loops.empty(), "'continue' outside loop");
1575  AddLoopControl(n->loc, Loop::Control::Continue);
1576 }
1577 
1578 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::If *n)
1579 {
1580  SafeVisit(n->cond);
1581  int jump = AddBCC(n->loc, AB_CONDN);
1582  // Warn if we're controlling a no-op ("if (...);")
1583  if (dynamic_cast<::aul::ast::Noop*>(n->iftrue.get()))
1584  {
1585  Warn(target_host, host, n->iftrue->loc, Fn, C4AulWarningId::empty_if);
1586  }
1587  if (SafeVisit(n->iftrue))
1588  MaybePopValueOf(n->iftrue);
1589 
1590  if (dynamic_cast<::aul::ast::Noop*>(n->iffalse.get()))
1591  {
1592  Warn(target_host, host, n->iffalse->loc, Fn, C4AulWarningId::empty_if);
1593  }
1594 
1595  if (n->iffalse)
1596  {
1597  int jumpout = AddBCC(n->loc, AB_JUMP);
1598  UpdateJump(jump, AddJumpTarget());
1599  jump = jumpout;
1600  if (SafeVisit(n->iffalse))
1601  MaybePopValueOf(n->iffalse);
1602  }
1603  UpdateJump(jump, AddJumpTarget());
1604 }
1605 
1606 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::VarDecl *n)
1607 {
1608  for (const auto &dec : n->decls)
1609  {
1610  const char *cname = dec.name.c_str();
1611  switch (n->scope)
1612  {
1613  case ::aul::ast::VarDecl::Scope::Func:
1614  if (dec.init)
1615  {
1616  // Emit code for the initializer
1617  SafeVisit(dec.init);
1618  int var_idx = Fn->VarNamed.GetItemNr(cname);
1619  assert(var_idx >= 0 && "CodegenAstVisitor: var not found in variable table");
1620  if (var_idx < 0)
1621  {
1622  AddBCC(n->loc, AB_STACK, -1);
1623  throw Error(target_host, host, n, Fn, "internal error: var not found in variable table: %s", cname);
1624  }
1625  AddVarAccess(n->loc, AB_POP_TO, var_idx);
1626  }
1627  break;
1629  case ::aul::ast::VarDecl::Scope::Global:
1630  // Object-local and global constants are handled by ConstantResolver.
1631  break;
1632  }
1633  }
1634 }
1635 
1636 void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::FunctionDecl *n)
1637 {
1638  assert(!Fn && "CodegenAstVisitor: function declaration encountered within active function");
1639  if (Fn)
1640  throw Error(target_host, host, n, Fn, "internal error: function declaration for '%s' encountered within active function", n->name.c_str());
1641 
1642  C4PropListStatic *Parent = n->is_global ? target_host->Engine->GetPropList() : target_host->GetPropList();
1643 
1644  C4String *name = ::Strings.FindString(n->name.c_str());
1645  C4AulFunc *f = Parent->GetFunc(name);
1646  while (f)
1647  {
1648  if (f->SFunc() && f->SFunc()->pOrgScript == host && f->Parent == Parent)
1649  {
1650  if (Fn)
1651  Warn(target_host, host, n, Fn, C4AulWarningId::redeclaration, "function", f->GetName());
1652  Fn = f->SFunc();
1653  }
1654  f = f->SFunc() ? f->SFunc()->OwnerOverloaded : nullptr;
1655  }
1656 
1657  if (!Fn && Parent->HasProperty(name))
1658  {
1659  throw Error(target_host, host, n, Fn, "declaration of '%s': cannot override local variable via 'func %s'", n->name.c_str(), n->name.c_str());
1660  }
1661 
1662  assert(Fn && "CodegenAstVisitor: unable to find function definition");
1663  if (!Fn)
1664  throw Error(target_host, host, n, Fn, "internal error: unable to find function definition for %s", n->name.c_str());
1665 
1666  // If this isn't a global function, but there is a global one with
1667  // the same name, and this function isn't overloading a different
1668  // one, add the global function to the overload chain
1669  if (!n->is_global && !Fn->OwnerOverloaded)
1670  {
1671  C4AulFunc *global_parent = target_host->Engine->GetFunc(Fn->GetName());
1672  if (global_parent)
1673  Fn->SetOverloaded(global_parent);
1674  }
1675 
1676  try
1677  {
1678  EmitFunctionCode(n);
1679  Fn = nullptr;
1680  }
1681  catch (...)
1682  {
1683  Fn = nullptr;
1684  throw;
1685  }
1686 }
1687 
1688 void C4AulCompiler::CodegenAstVisitor::visit(const::aul::ast::FunctionExpr * n)
1689 {
1690  AddBCC(n->loc, AB_NIL);
1691  throw Error(target_host, host, n, Fn, "can't define a function in a function-scoped proplist");
1692 }
1693 
1694 void C4AulCompiler::CodegenAstVisitor::visit(const::aul::ast::Script * n)
1695 {
1696  for (const auto &d : n->declarations)
1697  {
1698  SafeVisit(d);
1699  }
1700 }
1701 
1702 #undef ENSURE_COND
1703 #define ENSURE_COND(cond, failmsg) do { if (!(cond)) throw Error(host, host, n, nullptr, failmsg); } while (0)
1704 
1705 // Evaluates constant AST subtrees and returns the final C4Value.
1706 // Throws ExpressionNotConstant if evaluation fails.
1707 
1708 C4Value C4AulCompiler::ConstexprEvaluator::eval(C4ScriptHost *host, const ::aul::ast::Expr *e, EvalFlags flags)
1709 {
1710  ConstexprEvaluator ce(host);
1711  ce.ignore_unset_values = (flags & IgnoreUnset) == IgnoreUnset;
1712  try
1713  {
1714  e->accept(&ce);
1715  return ce.v;
1716  }
1717  catch (C4AulParseError &e)
1718  {
1719  if ((flags & SuppressErrors) == 0)
1720  host->Engine->ErrorHandler->OnError(e.what());
1721  return C4VNull;
1722  }
1723 }
1724 
1725 C4Value C4AulCompiler::ConstexprEvaluator::eval_static(C4ScriptHost *host, C4PropListStatic *parent, const std::string &parent_key, const ::aul::ast::Expr *e, EvalFlags flags)
1726 {
1727  ConstexprEvaluator ce(host);
1728  ce.proplist_magic = ConstexprEvaluator::ProplistMagic{ true, parent, parent_key };
1729  ce.ignore_unset_values = (flags & IgnoreUnset) == IgnoreUnset;
1730  try
1731  {
1732  e->accept(&ce);
1733  return ce.v;
1734  }
1735  catch (C4AulParseError &e)
1736  {
1737  if ((flags & SuppressErrors) == 0)
1738  host->Engine->ErrorHandler->OnError(e.what());
1739  return C4VNull;
1740  }
1741 }
1742 
1743 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::StringLit *n) { v = C4VString(n->value.c_str()); }
1744 
1745 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::IntLit *n) { v = C4VInt(n->value); }
1746 
1747 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::BoolLit *n) { v = C4VBool(n->value); }
1748 
1749 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::ArrayLit *n)
1750 {
1751  auto a = std::make_unique<C4ValueArray>(n->values.size());
1752  for (size_t i = 0; i < n->values.size(); ++i)
1753  {
1754  n->values[i]->accept(this);
1755  a->SetItem(i, v);
1756  }
1757  v = C4VArray(a.release());
1758 }
1759 
1760 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::ProplistLit *n)
1761 {
1762  std::unique_ptr<C4PropList> new_proplist;
1763  C4PropList *p = nullptr;
1764 
1765  bool first_pass = true;
1766 
1767  if (proplist_magic.active)
1768  {
1769  // Check if there's already a proplist available
1770  C4String *key = ::Strings.RegString(proplist_magic.key.c_str());
1771  C4Value old;
1772  if (proplist_magic.parent)
1773  {
1774  proplist_magic.parent->GetPropertyByS(key, &old);
1775  }
1776  else
1777  {
1778  // If proplist_magic.parent is nullptr, we're handling a global constant.
1779  host->Engine->GetGlobalConstant(key->GetCStr(), &old);
1780  }
1781  if (old.getPropList())
1782  {
1783  p = old.getPropList();
1784  first_pass = false;
1785  }
1786  else
1787  {
1788  p = C4PropList::NewStatic(nullptr, proplist_magic.parent, key);
1789  new_proplist.reset(p);
1790  }
1791  }
1792  else
1793  {
1794  p = C4PropList::New();
1795  new_proplist.reset(p);
1796  }
1797 
1798  // Since the values may be functions that refer to other values in the
1799  // proplist, pre-populate the new proplist with dummy values until the
1800  // real ones are set
1801  if (first_pass)
1802  {
1803  for (const auto &kv : n->values)
1804  {
1805  p->SetPropertyByS(::Strings.RegString(kv.first.c_str()), C4VNull);
1806  }
1807  }
1808 
1809  auto saved_magic = std::move(proplist_magic);
1810  for (const auto &kv : n->values)
1811  {
1812  proplist_magic = ProplistMagic { saved_magic.active, p->IsStatic(), kv.first };
1813  kv.second->accept(this);
1814  p->SetPropertyByS(::Strings.RegString(kv.first.c_str()), v);
1815  }
1816  proplist_magic = std::move(saved_magic);
1817  v = C4VPropList(p);
1818  new_proplist.release();
1819 }
1820 
1821 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::NilLit *) { v = C4VNull; }
1822 
1823 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::ThisLit *n) { nonconst(n, "\"this\" is not a global constant"); }
1824 
1825 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::VarExpr *n)
1826 {
1827  const char *cname = n->identifier.c_str();
1828  C4String *interned = ::Strings.FindString(cname);
1829  if (interned && host->GetPropList()->GetPropertyByS(interned, &v))
1830  return;
1831  if (host->Engine->GetGlobalConstant(cname, &v))
1832  return;
1833 
1834  if (ignore_unset_values)
1835  {
1836  v = C4VNull;
1837  return;
1838  }
1839 
1840  nonconst(n, "the variable \"%s\" is not a global constant", cname);
1841 }
1842 
1843 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::UnOpExpr *n)
1844 {
1845  n->operand->accept(this);
1846  assert(n->op > 0);
1847  const auto &op = C4ScriptOpMap[n->op];
1848  if (op.Changer)
1849  nonconst(n, "unary operator %s is applied in a non-const fashion", op.Identifier);
1850  AssertValueType(v, op.Type1, op.Identifier, n);
1851  switch (op.Code)
1852  {
1853  case AB_BitNot:
1854  v.SetInt(~v._getInt());
1855  break;
1856  case AB_Not:
1857  v.SetBool(!v.getBool());
1858  break;
1859  case AB_Neg:
1860  v.SetInt(-v._getInt());
1861  break;
1862  default:
1863  assert(!"ConstexprEvaluator: Unexpected unary operator");
1864  throw Error(host, host, n, nullptr, "internal error: unary operator not found in operator table");
1865  }
1866 }
1867 
1868 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::BinOpExpr *n)
1869 {
1870  assert(n->op > 0);
1871  const auto &op = C4ScriptOpMap[n->op];
1872  if (op.Changer)
1873  nonconst(n, "binary operator %s is applied in a non-const fashion", op.Identifier);
1874 
1875  n->lhs->accept(this);
1876  C4Value lhs = v;
1877  // Evaluate the short-circuiting operators here
1878  if ((op.Code == AB_JUMPAND && !lhs) || (op.Code == AB_JUMPOR && lhs) || (op.Code == AB_JUMPNNIL && lhs.GetType() != C4V_Nil))
1879  {
1880  v = lhs;
1881  return;
1882  }
1883  n->rhs->accept(this);
1884  C4Value &rhs = v;
1885 
1886  AssertValueType(lhs, op.Type1, op.Identifier, n);
1887  AssertValueType(rhs, op.Type2, op.Identifier, n);
1888 
1889  switch (op.Code)
1890  {
1891  case AB_Pow:
1892  v.SetInt(Pow(lhs._getInt(), rhs._getInt()));
1893  break;
1894  case AB_Div:
1895  ENSURE_COND(rhs._getInt() != 0, "division by zero");
1896  ENSURE_COND(lhs._getInt() != INT32_MIN || rhs._getInt() != -1, "division overflow");
1897  v.SetInt(lhs._getInt() / rhs._getInt());
1898  break;
1899  case AB_Mul:
1900  v.SetInt(lhs._getInt() * rhs._getInt());
1901  break;
1902  case AB_Mod:
1903  ENSURE_COND(rhs._getInt() != 0, "division by zero");
1904  ENSURE_COND(lhs._getInt() != INT32_MIN || rhs._getInt() != -1, "division overflow");
1905  v.SetInt(lhs._getInt() / rhs._getInt());
1906  break;
1907 #define INT_BINOP(code, op) case code: v.SetInt(lhs._getInt() op rhs._getInt()); break
1908  INT_BINOP(AB_Sum, +);
1909  INT_BINOP(AB_Sub, -);
1910  INT_BINOP(AB_LeftShift, << );
1911  INT_BINOP(AB_RightShift, >> );
1912  INT_BINOP(AB_BitAnd, &);
1913  INT_BINOP(AB_BitXOr, ^);
1914  INT_BINOP(AB_BitOr, | );
1915 #undef INT_BINOP
1916 #define BOOL_BINOP(code, op) case code: v.SetBool(lhs._getInt() op rhs._getInt()); break
1917  BOOL_BINOP(AB_LessThan, <);
1921 #undef BOOL_BINOP
1922  case AB_Equal:
1923  v.SetBool(lhs.IsIdenticalTo(rhs));
1924  break;
1925  case AB_NotEqual:
1926  v.SetBool(!lhs.IsIdenticalTo(rhs));
1927  break;
1928  case AB_JUMPAND:
1929  case AB_JUMPOR:
1930  case AB_JUMPNNIL:
1931  // If we hit this, then the short-circuit above failed
1932  v = rhs;
1933  break;
1934  default:
1935  assert(!"ConstexprEvaluator: Unexpected binary operator");
1936  throw Error(host, host, n, nullptr, "internal error: binary operator not found in operator table");
1937  break;
1938  }
1939 }
1940 
1941 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::AssignmentExpr *n)
1942 {
1943  nonconst(n, "updating assignment used in a non-const fashion");
1944 }
1945 
1946 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::SubscriptExpr *n)
1947 {
1948  n->object->accept(this);
1949  C4Value obj = v;
1950  n->index->accept(this);
1951  C4Value &index = v;
1952 
1953  if (obj.CheckConversion(C4V_Array))
1954  {
1955  ENSURE_COND(index.CheckConversion(C4V_Int), FormatString("array access: index of type %s, but expected int", index.GetTypeName()).getData());
1956  v = obj.getArray()->GetItem(index.getInt());
1957  }
1958  else if (obj.CheckConversion(C4V_PropList))
1959  {
1960  ENSURE_COND(index.CheckConversion(C4V_String), FormatString("proplist access: index of type %s, but expected string", index.GetTypeName()).getData());
1961  if (!obj.getPropList()->GetPropertyByS(index.getStr(), &v))
1962  v.Set0();
1963  }
1964  else
1965  {
1966  ENSURE_COND(false, FormatString("can't access %s as array or proplist", obj.GetTypeName()).getData());
1967  }
1968 }
1969 
1970 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::SliceExpr *n)
1971 {
1972  n->object->accept(this);
1973  C4Value obj = v;
1974  n->start->accept(this);
1975  C4Value start = v;
1976  n->end->accept(this);
1977  C4Value &end = v;
1978 
1979  ENSURE_COND(obj.CheckConversion(C4V_Array), FormatString("array slice: can't access %s as an array", obj.GetTypeName()).getData());
1980  ENSURE_COND(start.CheckConversion(C4V_Int), FormatString("array slice: start index of type %s, int expected", start.GetTypeName()).getData());
1981  ENSURE_COND(end.CheckConversion(C4V_Int), FormatString("array slice: end index of type %s, int expected", end.GetTypeName()).getData());
1982 
1983  v.SetArray(obj.getArray()->GetSlice(start.getInt(), end.getInt()));
1984 }
1985 
1986 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::CallExpr *n)
1987 {
1988  // TODO: allow side-effect-free calls here
1989  nonconst(n, "call to function (%s) not supported in constant expressions", n->callee.c_str());
1990 }
1991 
1992 void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::FunctionExpr *n)
1993 {
1994  // Function expressions can only occur inside static proplists.
1995  ENSURE_COND(proplist_magic.active, "internal error: function expression outside of static proplist");
1996 
1997  C4AulScriptFunc *sfunc = nullptr;
1998  bool first_pass = true;
1999 
2000  if (auto func = proplist_magic.parent->GetFunc(proplist_magic.key.c_str()))
2001  {
2002  sfunc = func->SFunc();
2003  first_pass = false;
2004  }
2005  else
2006  {
2007  sfunc = new C4AulScriptFunc(proplist_magic.parent, host, proplist_magic.key.c_str(), n->loc);
2008  }
2009 
2010  ENSURE_COND(sfunc != nullptr, "internal error: function expression target resolved to non-function value");
2011 
2012  if (first_pass)
2013  {
2014  for (const auto &param : n->params)
2015  {
2016  sfunc->AddPar(param.name.c_str());
2017  }
2018  if (n->has_unnamed_params)
2019  sfunc->ParCount = C4AUL_MAX_Par;
2020 
2021  PreparseAstVisitor preparser(host, host, sfunc);
2022  preparser.visit(n->body.get());
2023  }
2024  else
2025  {
2026  CodegenAstVisitor cg(sfunc);
2027  cg.EmitFunctionCode(n);
2028  }
2029 
2030  v.SetFunction(sfunc);
2031 }
2032 
2033 void C4AulCompiler::ConstantResolver::visit(const::aul::ast::Script *n)
2034 {
2035  for (const auto &d : n->declarations)
2036  {
2037  try
2038  {
2039  d->accept(this);
2040  }
2041  catch (C4AulParseError &e)
2042  {
2043  host->Engine->GetErrorHandler()->OnError(e.what());
2044  }
2045  }
2046 }
2047 
2048 void C4AulCompiler::ConstantResolver::visit(const ::aul::ast::VarDecl *n)
2049 {
2050  const int quiet_flag = quiet ? ConstexprEvaluator::SuppressErrors : 0;
2051  for (const auto &dec : n->decls)
2052  {
2053  const char *cname = dec.name.c_str();
2055  switch (n->scope)
2056  {
2057  case ::aul::ast::VarDecl::Scope::Func:
2058  // Function-scoped declarations and their initializers are handled by CodegenAstVisitor.
2059  break;
2061  if (!host->GetPropList()->HasProperty(name))
2062  host->GetPropList()->SetPropertyByS(name, C4VNull);
2063  if (dec.init)
2064  {
2065  assert(host->GetPropList()->IsStatic());
2066  try
2067  {
2068  C4Value v = ConstexprEvaluator::eval_static(host, host->GetPropList()->IsStatic(), dec.name, dec.init.get(), ConstexprEvaluator::IgnoreUnset | quiet_flag);
2069  host->GetPropList()->SetPropertyByS(name, v);
2070  }
2071  catch (C4AulParseError &e)
2072  {
2073  if (!quiet)
2074  host->Engine->ErrorHandler->OnError(e.what());
2075  }
2076  }
2077  break;
2078  case ::aul::ast::VarDecl::Scope::Global:
2079  if ((dec.init != nullptr) != n->constant)
2080  {
2081  if (!quiet)
2082  host->Engine->ErrorHandler->OnError(Error(host, host, n->loc, nullptr, "global variable must be either constant or uninitialized: %s", cname).what());
2083  }
2084  else if (dec.init)
2085  {
2086  try
2087  {
2088  assert(n->constant && "CodegenAstVisitor: initialized global variable isn't const");
2089  C4Value *v = host->Engine->GlobalConsts.GetItem(cname);
2090  assert(v && "CodegenAstVisitor: global constant not found in variable table");
2091  if (!v)
2092  throw Error(host, host, n->loc, nullptr, "internal error: global constant not found in variable table: %s", cname);
2093  *v = ConstexprEvaluator::eval_static(host, nullptr, dec.name, dec.init.get(), ConstexprEvaluator::IgnoreUnset | quiet_flag);
2094  }
2095  catch (C4AulParseError &e)
2096  {
2097  if (!quiet)
2098  host->Engine->ErrorHandler->OnError(e.what());
2099  }
2100  }
2101  break;
2102  }
2103  }
2104 }
C4Value * GetItem(const char *strName)
Definition: C4ValueMap.cpp:235
void visit(const ::aul::ast::Script *n) override
C4ValueMapNames GlobalNamedNames
Definition: C4Aul.h:134
C4V_Type
Definition: C4Value.h:23
~CodegenAstVisitor() override=default
C4AulErrorHandler * GetErrorHandler() const
Definition: C4Aul.h:173
const C4ScriptOpDef C4ScriptOpMap[]
Definition: C4AulParse.cpp:261
PreparseAstVisitor(C4AulScriptFunc *func)
~ConstantResolver() override=default
C4PropListStatic * GetPropList()
Definition: C4Aul.h:151
C4String * getStr() const
Definition: C4Value.h:117
C4AulWarningId
Definition: C4Aul.h:29
C4PropListStatic * IsStatic() override
Definition: C4PropList.h:266
#define C4AUL_Inherited
C4AulScriptEngine ScriptEngine
Definition: C4Globals.cpp:43
union C4AulBCC::@81 Par
#define b
bool _getBool() const
Definition: C4Value.h:123
C4String * FindString(const char *strString) const
int SGetLine(const char *szText, const char *cpPosition)
Definition: Standard.cpp:473
std::list< StdCopyStrBuf > Appends
Definition: C4ScriptHost.h:80
const char * GetName() const
Definition: C4AulFunc.h:56
C4AulFunc * GetFunc(C4PropertyName k) const
Definition: C4PropList.h:105
static void Preparse(C4ScriptHost *out, C4ScriptHost *source, const ::aul::ast::Script *s)
#define BOOL_BINOP(code, op)
const char * GetCStr() const
Definition: C4StringTable.h:49
C4String * RegString(StdStrBuf String)
C4Value C4VInt(int32_t i)
Definition: C4Value.h:242
~PreparseAstVisitor() override=default
C4AulBCCType
bool GetGlobalConstant(const char *szName, C4Value *pTargetValue)
C4String * _getStr() const
Definition: C4Value.h:126
void Set0()
Definition: C4Value.h:336
bool IsWarningEnabled(const char *pos, C4AulWarningId warning) const
void SetBool(bool b)
Definition: C4Value.h:137
C4ScriptHost * pOrgScript
virtual class C4PropListStatic * IsStatic()
Definition: C4PropList.h:85
#define ENSURE_COND(cond, failmsg)
bool getBool() const
Definition: C4Value.h:113
CodegenAstVisitor(C4ScriptHost *host, C4ScriptHost *source_host)
C4AulScriptEngine * Engine
Definition: C4ScriptHost.h:76
virtual C4PropListStatic * GetPropList()
Definition: C4ScriptHost.h:50
#define a
C4AulFunc * OwnerOverloaded
C4ValueMapNames GlobalConstNames
Definition: C4Aul.h:141
int SLineGetCharacters(const char *szText, const char *cpPosition)
Definition: Standard.cpp:485
const C4Value & GetItem(int32_t iElem) const
Definition: C4ValueArray.h:38
ALWAYS_INLINE bool CheckConversion(C4V_Type vtToType) const
Definition: C4Value.h:189
C4AulFunc * GetFirstFunc(const char *Name)
Definition: C4Aul.h:117
C4ValueArray * _getArray() const
Definition: C4Value.h:127
int32_t AddName(const char *pnName)
Definition: C4ValueMap.cpp:429
#define NORETURN
#define C4AUL_MAX_Par
Definition: C4AulFunc.h:26
C4AulFunc * GetNextSNFunc(const C4AulFunc *After)
Definition: C4Aul.h:119
ExpressionNotConstant(const C4ScriptHost *host, const ::aul::ast::Node *n, const char *reason, T &&...args)
C4V_Type GetType() const
Definition: C4Value.h:161
C4StringTable Strings
Definition: C4Globals.cpp:42
const char * GetTypeName() const
Definition: C4Value.h:164
virtual bool GetPropertyByS(const C4String *k, C4Value *pResult) const
Definition: C4PropList.cpp:757
C4Value C4VPropList(C4PropList *p)
Definition: C4Value.h:245
static void resolve(C4ScriptHost *host, const ::aul::ast::Script *script)
C4GameControl Control
C4ValueMapNames VarNamed
C4AulScriptFunc * SFunc() override
static bool WarnAboutConversion(C4V_Type Type, C4V_Type vtToType)
Definition: C4Value.cpp:111
const char * what() const noexceptoverride
int32_t _getInt() const
Definition: C4Value.h:122
std::list< StdCopyStrBuf > Includes
Definition: C4ScriptHost.h:79
ALWAYS_INLINE bool CheckParConversion(C4V_Type vtToType) const
Definition: C4Value.h:171
void SetArray(C4ValueArray *Array)
Definition: C4Value.h:139
C4ValueMapData GlobalConsts
Definition: C4Aul.h:142
static void Compile(C4AulScriptFunc *out, const ::aul::ast::Function *f)
static C4Value eval_static(C4ScriptHost *host, C4PropListStatic *parent, const std::string &parent_key, const ::aul::ast::Expr *e, EvalFlags flags=0)
C4ValueArray * getArray() const
Definition: C4Value.h:118
C4Value C4VArray(C4ValueArray *pArray)
Definition: C4Value.h:249
const char * C4AulWarningMessages[]
Definition: C4Aul.cpp:28
static C4PropListStatic * NewStatic(C4PropList *prototype, const C4PropListStatic *parent, C4String *key)
Definition: C4PropList.cpp:70
const char * GetC4VName(const C4V_Type Type)
Definition: C4Value.cpp:32
void SetInt(int32_t i)
Definition: C4Value.h:136
static void resolve_quiet(C4ScriptHost *host, const ::aul::ast::Script *script)
#define C4AUL_DebugBreak
void AddPar(const char *Idtf, C4V_Type type=C4V_Any)
virtual void OnError(const char *msg)=0
int32_t getInt() const
Definition: C4Value.h:112
C4Value C4VBool(bool b)
Definition: C4Value.h:243
int32_t GetItemNr(const char *strName) const
Definition: C4ValueMap.cpp:459
bool HasProperty(C4String *k) const
Definition: C4PropList.h:118
void SetFunction(C4AulFunc *Fn)
Definition: C4Value.h:140
C4Object * Object(C4PropList *_this)
Definition: C4AulDefFunc.h:34
PreparseAstVisitor(C4ScriptHost *host, C4ScriptHost *source_host, C4AulScriptFunc *func=nullptr)
const C4Value C4VNull
Definition: C4Value.cpp:30
virtual void OnWarning(const char *msg)=0
#define C4AUL_SafeInherited
C4AulFunc * _getFunction() const
Definition: C4Value.h:128
static C4Value eval(C4ScriptHost *host, const ::aul::ast::Expr *e, EvalFlags flags=0)
CodegenAstVisitor(C4AulScriptFunc *func)
C4Value C4VString(C4String *pStr)
Definition: C4Value.h:246
int Pow(int base, int exponent)
Definition: Standard.cpp:69
void visit(const ::aul::ast::RangeLoop *n) override
C4AulBCCType bccType
void visit(const ::aul::ast::StringLit *n) override
C4Value C4VFunction(C4AulFunc *pFn)
Definition: C4Value.h:250
virtual void SetPropertyByS(C4String *k, const C4Value &to)
Definition: C4PropList.cpp:952
C4PropListStatic * Parent
Definition: C4AulFunc.h:55
#define X(sdl, oc)
bool IsIdenticalTo(const C4Value &cmp) const
Definition: C4Value.h:149
const char * GetScript() const
Definition: C4ScriptHost.h:52
std::string strprintf(const char *format,...)
Definition: Standard.cpp:832
#define INT_BINOP(code, op)
C4ValueArray * GetSlice(int32_t startIndex, int32_t endIndex)
C4AulErrorHandler * ErrorHandler
Definition: C4Aul.h:128
C4PropList * _getPropList() const
Definition: C4Value.h:129
const char * C4AulWarningIDs[]
Definition: C4Aul.cpp:34
virtual C4AulScriptFunc * SFunc()
Definition: C4AulFunc.h:65
std::vector< C4Value > ownedFunctions
Definition: C4ScriptHost.h:96
const char * GetFilePath() const
#define s
static C4PropList * New(C4PropList *prototype=nullptr)
Definition: C4PropList.cpp:64
void visit(const ::aul::ast::Noop *) override
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
virtual int GetParCount() const
Definition: C4AulFunc.h:69
C4PropList * getPropList() const
Definition: C4Value.h:116