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