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