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