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