forked from Mirrors/openclonk
2213 lines
64 KiB
C++
2213 lines
64 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
* Copyright (c) 2009-2018, The OpenClonk Team and contributors
|
|
*
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
* "COPYING" for details.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
* See accompanying file "TRADEMARK" for details.
|
|
*
|
|
* To redistribute this file separately, substitute the full license texts
|
|
* for the above references.
|
|
*/
|
|
|
|
#include "C4Include.h"
|
|
#include "script/C4AulCompiler.h"
|
|
|
|
#include "script/C4Aul.h"
|
|
#include "script/C4AulParse.h"
|
|
#include "script/C4AulScriptFunc.h"
|
|
#include "script/C4ScriptHost.h"
|
|
|
|
#include <cinttypes>
|
|
#include <deque>
|
|
|
|
#define C4AUL_Inherited "inherited"
|
|
#define C4AUL_SafeInherited "_inherited"
|
|
#define C4AUL_DebugBreak "__debugbreak"
|
|
|
|
namespace
|
|
{
|
|
enum class ScriptLinkType
|
|
{
|
|
Include,
|
|
Same,
|
|
Appendto,
|
|
};
|
|
}
|
|
static ScriptLinkType GetScriptLinkType(const C4ScriptHost *source_host, const C4ScriptHost *target_host)
|
|
{
|
|
if (source_host == target_host)
|
|
return ScriptLinkType::Same;
|
|
|
|
const auto &sources = target_host->SourceScripts;
|
|
const auto source_script_index = std::find(begin(sources), end(sources), source_host);
|
|
const auto target_script_index = std::find(begin(sources), end(sources), target_host);
|
|
assert(source_script_index != target_script_index);
|
|
if (source_script_index < target_script_index)
|
|
return ScriptLinkType::Include;
|
|
else if (source_script_index > target_script_index)
|
|
return ScriptLinkType::Appendto;
|
|
return ScriptLinkType::Same;
|
|
}
|
|
|
|
static std::string FormatCodePosition(const C4ScriptHost *source_host, const char *pos, const C4ScriptHost *target_host = nullptr, const C4AulScriptFunc *func = nullptr)
|
|
{
|
|
std::string s;
|
|
if (func && func->GetFullName())
|
|
{
|
|
s += strprintf(" (in %s", func->GetFullName().getData());
|
|
if (source_host && pos)
|
|
s += ", ";
|
|
else
|
|
s += ")";
|
|
}
|
|
if (source_host && pos)
|
|
{
|
|
if (!func || !func->GetFullName())
|
|
s += " (";
|
|
|
|
int line = SGetLine(source_host->GetScript(), pos);
|
|
int col = SLineGetCharacters(source_host->GetScript(), pos);
|
|
|
|
s += strprintf("%s:%d:%d)", source_host->GetFilePath(), line, col);
|
|
}
|
|
if (target_host && source_host != target_host)
|
|
{
|
|
if (GetScriptLinkType(source_host, target_host) == ScriptLinkType::Include)
|
|
s += strprintf(" (included by %s)", target_host->ScriptName.getData());
|
|
else
|
|
s += strprintf(" (appended to %s)", target_host->ScriptName.getData());
|
|
}
|
|
return s;
|
|
}
|
|
|
|
#pragma GCC diagnostic push
|
|
// GCC does not properly handle that warning for strprintf in templated code, see #1992.
|
|
#pragma GCC diagnostic ignored "-Wformat-security"
|
|
|
|
template<class... T>
|
|
static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const char *SPos, const C4AulScriptFunc *func, C4AulWarningId warning, T &&...args)
|
|
{
|
|
if (!host)
|
|
{
|
|
// Without a script host, just fall back to the default settings
|
|
#define DIAG(id, msg, enabled) if (warning == C4AulWarningId::id && !enabled) return;
|
|
#include "C4AulWarnings.h"
|
|
#undef DIAG
|
|
}
|
|
else if (target_host && GetScriptLinkType(host, target_host) == ScriptLinkType::Include)
|
|
{
|
|
// Don't re-emit warnings for an #include'd script, they've already
|
|
// been shown when the original script was compiled
|
|
return;
|
|
}
|
|
else if (!host->IsWarningEnabled(SPos, warning))
|
|
{
|
|
return;
|
|
}
|
|
const char *msg = C4AulWarningMessages[static_cast<size_t>(warning)];
|
|
std::string message = sizeof...(T) > 0 ? strprintf(msg, std::forward<T>(args)...) : msg;
|
|
message += FormatCodePosition(host, SPos, target_host, func);
|
|
|
|
message += " [";
|
|
message += C4AulWarningIDs[static_cast<size_t>(warning)];
|
|
message += ']';
|
|
|
|
::ScriptEngine.GetErrorHandler()->OnWarning(message.c_str());
|
|
}
|
|
|
|
template<class... T>
|
|
static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const ::aul::ast::Node *n, const C4AulScriptFunc *func, C4AulWarningId warning, T &&...args)
|
|
{
|
|
return Warn(target_host, host, n->loc, func, warning, std::forward<T>(args)...);
|
|
}
|
|
template<class... T>
|
|
static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const std::nullptr_t &, const C4AulScriptFunc *func, C4AulWarningId warning, T &&...args)
|
|
{
|
|
return Warn(target_host, host, static_cast<const char*>(nullptr), func, warning, std::forward<T>(args)...);
|
|
}
|
|
|
|
template<class... T>
|
|
static C4AulParseError Error(const C4ScriptHost *target_host, const C4ScriptHost *host, const char *SPos, const C4AulScriptFunc *func, const char *msg, T &&...args)
|
|
{
|
|
std::string message = sizeof...(T) > 0 ? strprintf(msg, std::forward<T>(args)...) : msg;
|
|
|
|
message += FormatCodePosition(host, SPos, target_host, func);
|
|
return C4AulParseError(static_cast<C4ScriptHost*>(nullptr), message.c_str());
|
|
}
|
|
|
|
template<class... T>
|
|
static C4AulParseError Error(const C4ScriptHost *target_host, const C4ScriptHost *host, const ::aul::ast::Node *n, const C4AulScriptFunc *func, const char *msg, T &&...args)
|
|
{
|
|
return Error(target_host, host, n->loc, func, msg, std::forward<T>(args)...);
|
|
}
|
|
template<class... T>
|
|
static C4AulParseError Error(const C4ScriptHost *target_host, const C4ScriptHost *host, const std::nullptr_t &, const C4AulScriptFunc *func, const char *msg, T &&...args)
|
|
{
|
|
return Error(target_host, host, static_cast<const char*>(nullptr), func, msg, std::forward<T>(args)...);
|
|
}
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
class C4AulCompiler::PreparseAstVisitor : public ::aul::DefaultRecursiveVisitor
|
|
{
|
|
// target_host: The C4ScriptHost on which compilation is done
|
|
C4ScriptHost *target_host = nullptr;
|
|
// host: The C4ScriptHost where the script actually resides in
|
|
C4ScriptHost *host = nullptr;
|
|
// Fn: The C4AulScriptFunc that is currently getting parsed
|
|
C4AulScriptFunc *Fn = nullptr;
|
|
|
|
public:
|
|
PreparseAstVisitor(C4ScriptHost *host, C4ScriptHost *source_host, C4AulScriptFunc *func = nullptr) : target_host(host), host(source_host), Fn(func) {}
|
|
explicit PreparseAstVisitor(C4AulScriptFunc *func) : Fn(func), target_host(func->pOrgScript), host(target_host) {}
|
|
|
|
~PreparseAstVisitor() override = default;
|
|
|
|
using DefaultRecursiveVisitor::visit;
|
|
void visit(const ::aul::ast::RangeLoop *n) override;
|
|
void visit(const ::aul::ast::VarDecl *n) override;
|
|
void visit(const ::aul::ast::FunctionDecl *n) override;
|
|
void visit(const ::aul::ast::CallExpr *n) override;
|
|
void visit(const ::aul::ast::ParExpr *n) override;
|
|
void visit(const ::aul::ast::AppendtoPragma *n) override;
|
|
void visit(const ::aul::ast::IncludePragma *n) override;
|
|
void visit(const ::aul::ast::Script *n) override;
|
|
};
|
|
|
|
class C4AulCompiler::CodegenAstVisitor : public ::aul::DefaultRecursiveVisitor
|
|
{
|
|
C4AulScriptFunc *Fn = nullptr;
|
|
// target_host: The C4ScriptHost on which compilation is done
|
|
C4ScriptHost *target_host = nullptr;
|
|
// host: The C4ScriptHost where the script actually resides in
|
|
C4ScriptHost *host = nullptr;
|
|
|
|
int32_t stack_height = 0;
|
|
bool at_jump_target = false;
|
|
|
|
struct Loop
|
|
{
|
|
explicit Loop(int stack_height) : stack_height(stack_height) {}
|
|
|
|
int stack_height = 0;
|
|
std::vector<int> continues;
|
|
std::vector<int> breaks;
|
|
|
|
enum class Control
|
|
{
|
|
Continue,
|
|
Break
|
|
};
|
|
};
|
|
|
|
std::stack<Loop> active_loops;
|
|
|
|
struct Scope
|
|
{
|
|
std::set<std::string> variables;
|
|
};
|
|
std::deque<Scope> scopes;
|
|
|
|
// The type of the variable on top of the value stack. C4V_Any if unknown.
|
|
C4V_Type type_of_stack_top = C4V_Any;
|
|
|
|
constexpr static bool IsJump(C4AulBCCType t)
|
|
{
|
|
return t == AB_JUMP || t == AB_JUMPAND || t == AB_JUMPOR || t == AB_JUMPNNIL || t == AB_CONDN || t == AB_COND;
|
|
}
|
|
|
|
int AddJumpTarget();
|
|
void AddJumpTo(const char *loc, C4AulBCCType type, int target);
|
|
void UpdateJump(int jump, int target);
|
|
void PushLoop();
|
|
void PopLoop(int continue_target);
|
|
void AddLoopControl(const char *loc, Loop::Control c);
|
|
|
|
int AddVarAccess(const char *TokenSPos, C4AulBCCType eType, intptr_t varnum);
|
|
int AddBCC(const char *TokenSPos, C4AulBCCType eType, intptr_t X = 0);
|
|
int AddBCC(const char *SPos, const C4AulBCC &bcc);
|
|
|
|
template<class T>
|
|
void MaybePopValueOf(const std::unique_ptr<T> &n)
|
|
{
|
|
if (!n) return;
|
|
if (!n->has_value()) return;
|
|
AddBCC(n->loc, AB_STACK, -1);
|
|
}
|
|
|
|
static int GetStackValue(C4AulBCCType eType, intptr_t X);
|
|
void RemoveLastBCC();
|
|
C4AulBCC MakeSetter(const char *SPos, bool fLeaveValue);
|
|
|
|
void HandleError(const C4AulError &e)
|
|
{
|
|
if (Fn)
|
|
{
|
|
AddBCC(nullptr, AB_ERR, (intptr_t)::Strings.RegString(e.what()));
|
|
}
|
|
if (target_host) // target_host may be nullptr for DirectExec scripts
|
|
{
|
|
target_host->Engine->ErrorHandler->OnError(e.what());
|
|
}
|
|
}
|
|
|
|
template<class T>
|
|
bool SafeVisit(const T &node)
|
|
{
|
|
// Swallows exceptions during evaluation of node. Use if you want to
|
|
// keep doing syntax checks for subsequent children. (Generated code
|
|
// will cause a runtime error if executed.)
|
|
try
|
|
{
|
|
node->accept(this);
|
|
return true;
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
HandleError(e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class StackGuard
|
|
{
|
|
// Ensures that the Aul value stack ends up at the expected height
|
|
CodegenAstVisitor *parent;
|
|
const int32_t target_stack_height;
|
|
public:
|
|
explicit StackGuard(CodegenAstVisitor *parent, int32_t offset = 0) : parent(parent), target_stack_height(parent->stack_height + offset)
|
|
{}
|
|
~StackGuard()
|
|
{
|
|
assert(parent->stack_height == target_stack_height);
|
|
if (parent->stack_height != target_stack_height)
|
|
{
|
|
parent->HandleError(Error(parent->target_host, parent->host, nullptr, parent->Fn, "internal error: value stack left unbalanced"));
|
|
parent->AddBCC(nullptr, AB_STACK, target_stack_height - parent->stack_height);
|
|
}
|
|
}
|
|
};
|
|
|
|
class ScopeGuard
|
|
{
|
|
// Ensures that the scope stack is properly updated
|
|
CodegenAstVisitor * const parent;
|
|
public:
|
|
explicit ScopeGuard(CodegenAstVisitor *parent) : parent(parent)
|
|
{
|
|
parent->scopes.emplace_front();
|
|
}
|
|
~ScopeGuard()
|
|
{
|
|
parent->scopes.pop_front();
|
|
}
|
|
|
|
// moveable, not copyable
|
|
ScopeGuard(ScopeGuard &&rhs) = default;
|
|
ScopeGuard &operator=(ScopeGuard &&) = default;
|
|
|
|
ScopeGuard(const ScopeGuard &) = delete;
|
|
ScopeGuard &operator=(const ScopeGuard &) = delete;
|
|
};
|
|
ScopeGuard enterScope() { return ScopeGuard(this); }
|
|
|
|
void WarnOnAssignment(const ::aul::ast::ExprPtr &n) const
|
|
{
|
|
if (dynamic_cast<const ::aul::ast::AssignmentExpr*>(n.get()) != nullptr)
|
|
{
|
|
Warn(target_host, host, n.get(), Fn, C4AulWarningId::suspicious_assignment);
|
|
}
|
|
}
|
|
|
|
public:
|
|
CodegenAstVisitor(C4ScriptHost *host, C4ScriptHost *source_host) : target_host(host), host(source_host) {}
|
|
explicit CodegenAstVisitor(C4AulScriptFunc *func) : Fn(func), target_host(func->pOrgScript), host(target_host) {}
|
|
|
|
~CodegenAstVisitor() override = default;
|
|
|
|
using DefaultRecursiveVisitor::visit;
|
|
void visit(const ::aul::ast::Noop *) override;
|
|
void visit(const ::aul::ast::StringLit *n) override;
|
|
void visit(const ::aul::ast::IntLit *n) override;
|
|
void visit(const ::aul::ast::BoolLit *n) override;
|
|
void visit(const ::aul::ast::ArrayLit *n) override;
|
|
void visit(const ::aul::ast::ProplistLit *n) override;
|
|
void visit(const ::aul::ast::NilLit *n) override;
|
|
void visit(const ::aul::ast::ThisLit *n) override;
|
|
void visit(const ::aul::ast::VarExpr *n) override;
|
|
void visit(const ::aul::ast::UnOpExpr *n) override;
|
|
void visit(const ::aul::ast::BinOpExpr *n) override;
|
|
void visit(const ::aul::ast::AssignmentExpr *n) override;
|
|
void visit(const ::aul::ast::SubscriptExpr *n) override;
|
|
void visit(const ::aul::ast::SliceExpr *n) override;
|
|
void visit(const ::aul::ast::CallExpr *n) override;
|
|
void visit(const ::aul::ast::ParExpr *n) override;
|
|
void visit(const ::aul::ast::Block *n) override;
|
|
void visit(const ::aul::ast::Return *n) override;
|
|
void visit(const ::aul::ast::ForLoop *n) override;
|
|
void visit(const ::aul::ast::RangeLoop *n) override;
|
|
void visit(const ::aul::ast::DoLoop *n) override;
|
|
void visit(const ::aul::ast::WhileLoop *n) override;
|
|
void visit(const ::aul::ast::Break *n) override;
|
|
void visit(const ::aul::ast::Continue *n) override;
|
|
void visit(const ::aul::ast::If *n) override;
|
|
void visit(const ::aul::ast::VarDecl *n) override;
|
|
void visit(const ::aul::ast::FunctionDecl *n) override;
|
|
void visit(const ::aul::ast::FunctionExpr *n) override;
|
|
void visit(const ::aul::ast::Script *n) override;
|
|
|
|
template<class T>
|
|
void EmitFunctionCode(const T *n)
|
|
{
|
|
// This dynamic_cast resolves the problem where we have a Function*
|
|
// and want to emit code to it. All classes derived from Function
|
|
// are also ultimately derived from Node, so this call is fine
|
|
// without any additional checking.
|
|
EmitFunctionCode(n, dynamic_cast<const ::aul::ast::Node*>(n));
|
|
}
|
|
|
|
private:
|
|
void EmitFunctionCode(const ::aul::ast::Function *f, const ::aul::ast::Node *n);
|
|
};
|
|
|
|
class C4AulCompiler::ConstexprEvaluator : public ::aul::AstVisitor
|
|
{
|
|
public:
|
|
enum EvalFlag
|
|
{
|
|
// If this flag is set, ConstexprEvaluator will assume unset values
|
|
// are nil. If it is not set, evaluation of unset values will send an
|
|
// ExpressionNotConstant to the error handler.
|
|
IgnoreUnset = 1<<0,
|
|
// If this flag is set, ConstexprEvaluator will not send exceptions to
|
|
// the error handler (so it doesn't report them twice: once from the
|
|
// preparsing step, then again from the compile step).
|
|
SuppressErrors = 1<<1
|
|
};
|
|
typedef int EvalFlags;
|
|
|
|
// Evaluates constant AST subtrees and returns the final C4Value.
|
|
// Flags ExpressionNotConstant if evaluation fails.
|
|
static C4Value eval(C4ScriptHost *host, const ::aul::ast::Expr *e, EvalFlags flags = 0);
|
|
static C4Value eval_static(C4ScriptHost *host, C4PropListStatic *parent, const std::string &parent_key, const ::aul::ast::Expr *e, EvalFlags flags = 0);
|
|
|
|
private:
|
|
C4ScriptHost *host = nullptr;
|
|
C4Value v;
|
|
bool ignore_unset_values = false;
|
|
bool quiet = false;
|
|
|
|
struct ProplistMagic
|
|
{
|
|
bool active = false;
|
|
C4PropListStatic *parent = nullptr;
|
|
std::string key;
|
|
|
|
ProplistMagic() = default;
|
|
ProplistMagic(bool active, C4PropListStatic *parent, std::string key) : active(active), parent(parent), key(std::move(key)) {}
|
|
} proplist_magic;
|
|
|
|
explicit ConstexprEvaluator(C4ScriptHost *host) : host(host) {}
|
|
|
|
template<typename... T>
|
|
NORETURN void nonconst(const ::aul::ast::Node *n, const char *msg, T&&...args) const
|
|
{
|
|
throw ExpressionNotConstant(host, n, msg, std::forward<T>(args)...);
|
|
}
|
|
|
|
void AssertValueType(const C4Value &v, C4V_Type Type1, const char *opname, const ::aul::ast::Node *n)
|
|
{
|
|
// Typecheck parameter
|
|
if (!v.CheckParConversion(Type1))
|
|
throw Error(host, host, n, nullptr, R"(operator "%s": got %s, but expected %s)", opname, v.GetTypeName(), GetC4VName(Type1));
|
|
}
|
|
public:
|
|
class ExpressionNotConstant : public C4AulParseError
|
|
{
|
|
public:
|
|
template<typename... T>
|
|
ExpressionNotConstant(const C4ScriptHost *host, const ::aul::ast::Node *n, const char *reason, T&&...args) :
|
|
C4AulParseError(Error(host, host, n, nullptr, reason, std::forward<T>(args)...)) {}
|
|
};
|
|
|
|
using AstVisitor::visit;
|
|
void visit(const ::aul::ast::StringLit *n) override;
|
|
void visit(const ::aul::ast::IntLit *n) override;
|
|
void visit(const ::aul::ast::BoolLit *n) override;
|
|
void visit(const ::aul::ast::ArrayLit *n) override;
|
|
void visit(const ::aul::ast::ProplistLit *n) override;
|
|
void visit(const ::aul::ast::NilLit *) override;
|
|
void visit(const ::aul::ast::ThisLit *n) override;
|
|
void visit(const ::aul::ast::VarExpr *n) override;
|
|
void visit(const ::aul::ast::UnOpExpr *n) override;
|
|
void visit(const ::aul::ast::BinOpExpr *n) override;
|
|
void visit(const ::aul::ast::AssignmentExpr *n) override;
|
|
void visit(const ::aul::ast::SubscriptExpr *n) override;
|
|
void visit(const ::aul::ast::SliceExpr *n) override;
|
|
void visit(const ::aul::ast::CallExpr *n) override;
|
|
void visit(const ::aul::ast::FunctionExpr *n) override;
|
|
};
|
|
|
|
class C4AulCompiler::ConstantResolver : public ::aul::DefaultRecursiveVisitor
|
|
{
|
|
C4ScriptHost *host;
|
|
bool quiet = false;
|
|
explicit ConstantResolver(C4ScriptHost *host) : host(host) {}
|
|
|
|
public:
|
|
static void resolve_quiet(C4ScriptHost *host, const ::aul::ast::Script *script)
|
|
{
|
|
// Does the same as resolve, but doesn't emit errors/warnings
|
|
// (because we'll emit them again later).
|
|
ConstantResolver r(host);
|
|
r.quiet = true;
|
|
r.visit(script);
|
|
}
|
|
static void resolve(C4ScriptHost *host, const ::aul::ast::Script *script)
|
|
{
|
|
// We resolve constants *twice*; this allows people to create circular
|
|
// references in proplists or arrays.
|
|
// Unfortunately it also results in unexpected behaviour in code like
|
|
// this:
|
|
// static const c1 = c2, c2 = c3, c3 = 1;
|
|
// which will set c1 to nil, and both c2 and c3 to 1.
|
|
// While this is unlikely to happen often, we should fix that so it
|
|
// resolves all three constants to 1.
|
|
ConstantResolver r(host);
|
|
r.visit(script);
|
|
}
|
|
~ConstantResolver() override = default;
|
|
|
|
using DefaultRecursiveVisitor::visit;
|
|
void visit(const ::aul::ast::Script *n) override;
|
|
void visit(const ::aul::ast::VarDecl *n) override;
|
|
};
|
|
|
|
void C4AulCompiler::Preparse(C4ScriptHost *host, C4ScriptHost *source_host, const ::aul::ast::Script *script)
|
|
{
|
|
PreparseAstVisitor v(host, source_host);
|
|
v.visit(script);
|
|
|
|
ConstantResolver::resolve_quiet(host, script);
|
|
}
|
|
|
|
void C4AulCompiler::Compile(C4ScriptHost *host, C4ScriptHost *source_host, const ::aul::ast::Script *script)
|
|
{
|
|
ConstantResolver::resolve(host, script);
|
|
|
|
CodegenAstVisitor v(host, source_host);
|
|
v.visit(script);
|
|
}
|
|
|
|
void C4AulCompiler::Compile(C4AulScriptFunc *func, const ::aul::ast::Function *def)
|
|
{
|
|
{
|
|
// Don't visit the whole definition here; that would create a new function
|
|
// and we don't want that.
|
|
PreparseAstVisitor v(func);
|
|
def->body->accept(&v);
|
|
}
|
|
{
|
|
CodegenAstVisitor v(func);
|
|
v.EmitFunctionCode(def);
|
|
}
|
|
}
|
|
|
|
#define ENSURE_COND(cond, failmsg) do { if (!(cond)) throw Error(target_host, host, n, Fn, failmsg); } while (0)
|
|
|
|
void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::RangeLoop *n)
|
|
{
|
|
const char *cname = n->var.c_str();
|
|
if (n->scoped_var)
|
|
{
|
|
Fn->VarNamed.AddName(cname);
|
|
}
|
|
else
|
|
{
|
|
// Loop variable not explicitly declared here. Look it up in
|
|
// the function and warn if it hasn't been declared at all.
|
|
if (Fn->VarNamed.GetItemNr(cname) == -1)
|
|
{
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::implicit_range_loop_var_decl, cname);
|
|
Fn->VarNamed.AddName(cname);
|
|
}
|
|
}
|
|
DefaultRecursiveVisitor::visit(n);
|
|
}
|
|
|
|
void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::VarDecl *n)
|
|
{
|
|
if (n->constant && n->scope != ::aul::ast::VarDecl::Scope::Global)
|
|
{
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::non_global_var_is_never_const);
|
|
}
|
|
for (const auto &var : n->decls)
|
|
{
|
|
const char *cname = var.name.c_str();
|
|
switch (n->scope)
|
|
{
|
|
case ::aul::ast::VarDecl::Scope::Func:
|
|
{
|
|
assert(Fn && "function-local var declaration outside of function");
|
|
if (!Fn)
|
|
throw Error(target_host, host, n, Fn, "internal error: function-local var declaration outside of function");
|
|
|
|
if (target_host)
|
|
{
|
|
// if target_host is unset, we're parsing this func for direct execution,
|
|
// in which case we don't want to warn about variable hiding.
|
|
if (target_host->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || target_host->Engine->GlobalConstNames.GetItemNr(cname) >= 0)
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, "local variable", cname, "global variable");
|
|
C4String *s = ::Strings.FindString(cname);
|
|
if (s && target_host->GetPropList()->HasProperty(s))
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, "local variable", cname, "object-local variable");
|
|
if (Fn->ParNamed.GetItemNr(cname) != -1)
|
|
{
|
|
// The parameter order of this warning is correct:
|
|
// Aul looks up parameters before local variables, so
|
|
// the parameter actually shadows the local variable.
|
|
// This doesn't make a whole lot of sense and should
|
|
// probably be changed.
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, "parameter", cname, "local variable");
|
|
}
|
|
}
|
|
Fn->VarNamed.AddName(cname);
|
|
break;
|
|
}
|
|
case ::aul::ast::VarDecl::Scope::Object:
|
|
{
|
|
if (host->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || host->Engine->GlobalConstNames.GetItemNr(cname) >= 0)
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, "object-local variable", cname, "global variable");
|
|
C4String *s = ::Strings.RegString(cname);
|
|
if (target_host->GetPropList()->HasProperty(s))
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::redeclaration, "object-local variable", cname);
|
|
else
|
|
target_host->GetPropList()->SetPropertyByS(s, C4VNull);
|
|
break;
|
|
}
|
|
case ::aul::ast::VarDecl::Scope::Global:
|
|
assert(!Fn && "global var declaration inside function");
|
|
if (Fn)
|
|
throw Error(target_host, host, n, Fn, "internal error: global var declaration inside function");
|
|
|
|
if (host->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || host->Engine->GlobalConstNames.GetItemNr(cname) >= 0)
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::redeclaration, "global variable", cname);
|
|
if (n->constant)
|
|
host->Engine->GlobalConstNames.AddName(cname);
|
|
else
|
|
host->Engine->GlobalNamedNames.AddName(cname);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (n->scope == ::aul::ast::VarDecl::Scope::Func)
|
|
{
|
|
// only func-scoped variables can potentially have initializers we care
|
|
// about in the pre-parsing stage: they may have calls that pass
|
|
// unnamed parameters
|
|
DefaultRecursiveVisitor::visit(n);
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::FunctionDecl *n)
|
|
{
|
|
// create script fn
|
|
C4PropListStatic *Parent = n->is_global ? target_host->Engine->GetPropList() : target_host->GetPropList();
|
|
const char *cname = n->name.c_str();
|
|
|
|
assert(!Fn);
|
|
|
|
// Look up the overloaded function before adding the overloading one
|
|
C4AulFunc *parent_func = Parent->GetFunc(cname);
|
|
|
|
Fn = new C4AulScriptFunc(Parent, target_host, cname, n->loc);
|
|
host->ownedFunctions.push_back(C4VFunction(Fn));
|
|
for (const auto ¶m : n->params)
|
|
{
|
|
Fn->AddPar(param.name.c_str(), param.type);
|
|
}
|
|
if (n->has_unnamed_params)
|
|
Fn->ParCount = C4AUL_MAX_Par;
|
|
|
|
// Add function to def/engine
|
|
Fn->SetOverloaded(parent_func);
|
|
Parent->SetPropertyByS(Fn->Name, C4VFunction(Fn));
|
|
|
|
try
|
|
{
|
|
DefaultRecursiveVisitor::visit(n);
|
|
Fn = nullptr;
|
|
}
|
|
catch (...)
|
|
{
|
|
Fn = nullptr;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::CallExpr *n)
|
|
{
|
|
if (n->append_unnamed_pars && Fn->ParCount != C4AUL_MAX_Par)
|
|
{
|
|
Fn->ParCount = C4AUL_MAX_Par;
|
|
}
|
|
DefaultRecursiveVisitor::visit(n);
|
|
}
|
|
|
|
void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::ParExpr *n)
|
|
{
|
|
if (Fn->ParCount != C4AUL_MAX_Par)
|
|
{
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::undeclared_varargs, "Par()");
|
|
Fn->ParCount = C4AUL_MAX_Par;
|
|
}
|
|
DefaultRecursiveVisitor::visit(n);
|
|
}
|
|
|
|
void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::AppendtoPragma *n)
|
|
{
|
|
if (n->what.empty())
|
|
host->Appends.emplace_back("*");
|
|
else
|
|
host->Appends.emplace_back(n->what.c_str());
|
|
}
|
|
|
|
void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::IncludePragma *n)
|
|
{
|
|
host->Includes.emplace_back(n->what.c_str());
|
|
}
|
|
|
|
void C4AulCompiler::PreparseAstVisitor::visit(const::aul::ast::Script * n)
|
|
{
|
|
for (const auto &d : n->declarations)
|
|
{
|
|
try
|
|
{
|
|
d->accept(this);
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
target_host->Engine->GetErrorHandler()->OnError(e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
int C4AulCompiler::CodegenAstVisitor::GetStackValue(C4AulBCCType eType, intptr_t X)
|
|
{
|
|
switch (eType)
|
|
{
|
|
case AB_INT:
|
|
case AB_BOOL:
|
|
case AB_STRING:
|
|
case AB_CPROPLIST:
|
|
case AB_CARRAY:
|
|
case AB_CFUNCTION:
|
|
case AB_NIL:
|
|
case AB_LOCALN:
|
|
case AB_GLOBALN:
|
|
case AB_DUP:
|
|
case AB_DUP_CONTEXT:
|
|
case AB_THIS:
|
|
return 1;
|
|
|
|
case AB_Pow:
|
|
case AB_Div:
|
|
case AB_Mul:
|
|
case AB_Mod:
|
|
case AB_Sub:
|
|
case AB_Sum:
|
|
case AB_LeftShift:
|
|
case AB_RightShift:
|
|
case AB_LessThan:
|
|
case AB_LessThanEqual:
|
|
case AB_GreaterThan:
|
|
case AB_GreaterThanEqual:
|
|
case AB_Equal:
|
|
case AB_NotEqual:
|
|
case AB_BitAnd:
|
|
case AB_BitXOr:
|
|
case AB_BitOr:
|
|
case AB_PROP_SET:
|
|
case AB_ARRAYA:
|
|
case AB_CONDN:
|
|
case AB_COND:
|
|
case AB_POP_TO:
|
|
case AB_RETURN:
|
|
// JUMPAND/JUMPOR/JUMPNNIL are special: They either jump over instructions adding one to the stack
|
|
// or decrement the stack. Thus, for stack counting purposes, they decrement.
|
|
case AB_JUMPAND:
|
|
case AB_JUMPOR:
|
|
case AB_JUMPNNIL:
|
|
return -1;
|
|
|
|
case AB_FUNC:
|
|
return -reinterpret_cast<C4AulFunc *>(X)->GetParCount() + 1;
|
|
|
|
case AB_CALL:
|
|
case AB_CALLFS:
|
|
return -C4AUL_MAX_Par;
|
|
|
|
case AB_STACK_SET:
|
|
case AB_LOCALN_SET:
|
|
case AB_PROP:
|
|
case AB_GLOBALN_SET:
|
|
case AB_Inc:
|
|
case AB_Dec:
|
|
case AB_BitNot:
|
|
case AB_Not:
|
|
case AB_Neg:
|
|
case AB_PAR:
|
|
case AB_FOREACH_NEXT:
|
|
case AB_ERR:
|
|
case AB_EOFN:
|
|
case AB_JUMP:
|
|
case AB_DEBUG:
|
|
return 0;
|
|
|
|
case AB_STACK:
|
|
return X;
|
|
|
|
case AB_NEW_ARRAY:
|
|
return -X + 1;
|
|
|
|
case AB_NEW_PROPLIST:
|
|
return -X * 2 + 1;
|
|
|
|
case AB_ARRAYA_SET:
|
|
case AB_ARRAY_SLICE:
|
|
return -2;
|
|
|
|
case AB_ARRAY_SLICE_SET:
|
|
return -3;
|
|
}
|
|
assert(0 && "GetStackValue: unexpected bytecode not handled");
|
|
return 0;
|
|
}
|
|
|
|
int C4AulCompiler::CodegenAstVisitor::AddVarAccess(const char *TokenSPos, C4AulBCCType eType, intptr_t varnum)
|
|
{
|
|
return AddBCC(TokenSPos, eType, 1 + varnum - (stack_height + Fn->VarNamed.iSize));
|
|
}
|
|
|
|
int C4AulCompiler::CodegenAstVisitor::AddBCC(const char *TokenSPos, C4AulBCCType eType, intptr_t X)
|
|
{
|
|
// Track stack size
|
|
stack_height += GetStackValue(eType, X);
|
|
|
|
// Use stack operation instead of 0-Any (enable optimization)
|
|
if (eType == AB_NIL)
|
|
{
|
|
eType = AB_STACK;
|
|
X = 1;
|
|
}
|
|
|
|
assert(eType != AB_STACK || X != 0);
|
|
|
|
// Join checks only if it's not a jump target
|
|
if (!at_jump_target && Fn->GetLastCode())
|
|
{
|
|
C4AulBCC *pCPos1 = Fn->GetLastCode();
|
|
|
|
// Skip noop stack operation
|
|
if (eType == AB_STACK && X == 0)
|
|
{
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
|
|
// Join together stack operations
|
|
if (eType == AB_STACK && pCPos1->bccType == AB_STACK &&
|
|
(X <= 0 || pCPos1->Par.i >= 0))
|
|
{
|
|
pCPos1->Par.i += X;
|
|
// Empty? Remove it. This relies on the parser not issuing
|
|
// multiple negative stack operations consecutively, as
|
|
// that could result in removing a jump target bytecode.
|
|
if (!pCPos1->Par.i)
|
|
Fn->RemoveLastBCC();
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
|
|
// Prune unneeded Incs / Decs
|
|
if (eType == AB_STACK && X < 0 && (pCPos1->bccType == AB_Inc || pCPos1->bccType == AB_Dec))
|
|
{
|
|
if (!pCPos1->Par.X)
|
|
{
|
|
pCPos1->bccType = eType;
|
|
pCPos1->Par.i = X;
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
else
|
|
{
|
|
// If it was a result modifier, we can safely remove it knowing that it was neither
|
|
// the first chunk nor a jump target. We can therefore apply additional optimizations.
|
|
Fn->RemoveLastBCC();
|
|
pCPos1--;
|
|
}
|
|
}
|
|
|
|
// Join STACK_SET + STACK -1 to POP_TO (equivalent)
|
|
if (eType == AB_STACK && X == -1 && pCPos1->bccType == AB_STACK_SET)
|
|
{
|
|
pCPos1->bccType = AB_POP_TO;
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
|
|
// Join POP_TO + DUP to AB_STACK_SET if both target the same slot
|
|
if (eType == AB_DUP && pCPos1->bccType == AB_POP_TO && X == pCPos1->Par.i + 1)
|
|
{
|
|
pCPos1->bccType = AB_STACK_SET;
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
|
|
// Reduce some constructs like SUM + INT 1 to INC or DEC
|
|
if ((eType == AB_Sum || eType == AB_Sub) &&
|
|
pCPos1->bccType == AB_INT &&
|
|
(pCPos1->Par.i == 1 || pCPos1->Par.i == -1))
|
|
{
|
|
if ((pCPos1->Par.i > 0) == (eType == AB_Sum))
|
|
pCPos1->bccType = AB_Inc;
|
|
else
|
|
pCPos1->bccType = AB_Dec;
|
|
pCPos1->Par.i = X;
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
|
|
// Reduce Not + CONDN to COND, Not + COND to CONDN
|
|
if ((eType == AB_CONDN || eType == AB_COND) && pCPos1->bccType == AB_Not)
|
|
{
|
|
pCPos1->bccType = eType == AB_CONDN ? AB_COND : AB_CONDN;
|
|
pCPos1->Par.i = X + 1;
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
|
|
// Join AB_STRING + AB_ARRAYA to AB_PROP
|
|
if (eType == AB_ARRAYA && pCPos1->bccType == AB_STRING)
|
|
{
|
|
pCPos1->bccType = AB_PROP;
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
|
|
// Join AB_INT + AB_Neg to AB_INT
|
|
if (eType == AB_Neg && pCPos1->bccType == AB_INT)
|
|
{
|
|
pCPos1->Par.i *= -1;
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
}
|
|
|
|
// Add
|
|
Fn->AddBCC(eType, X, TokenSPos);
|
|
|
|
// Reset jump flag
|
|
at_jump_target = false;
|
|
|
|
return Fn->GetCodePos() - 1;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::RemoveLastBCC()
|
|
{
|
|
// Security: This is unsafe on anything that might get optimized away
|
|
C4AulBCC *pBCC = Fn->GetLastCode();
|
|
assert(pBCC->bccType != AB_STACK && pBCC->bccType != AB_STACK_SET && pBCC->bccType != AB_POP_TO);
|
|
// Correct stack
|
|
stack_height -= GetStackValue(pBCC->bccType, pBCC->Par.X);
|
|
// Remove
|
|
Fn->RemoveLastBCC();
|
|
}
|
|
|
|
int C4AulCompiler::CodegenAstVisitor::AddBCC(const char *SPos, const C4AulBCC &bcc)
|
|
{
|
|
return AddBCC(SPos, bcc.bccType, bcc.Par.X);
|
|
}
|
|
|
|
C4AulBCC C4AulCompiler::CodegenAstVisitor::MakeSetter(const char *SPos, bool fLeaveValue)
|
|
{
|
|
assert(Fn);
|
|
C4AulBCC Value = *(Fn->GetLastCode()), Setter = Value;
|
|
// Check type
|
|
switch (Value.bccType)
|
|
{
|
|
case AB_ARRAYA: Setter.bccType = AB_ARRAYA_SET; break;
|
|
case AB_ARRAY_SLICE: Setter.bccType = AB_ARRAY_SLICE_SET; break;
|
|
case AB_DUP:
|
|
Setter.bccType = AB_STACK_SET;
|
|
// the setter additionally has the new value on the stack
|
|
--Setter.Par.i;
|
|
break;
|
|
case AB_STACK_SET: Setter.bccType = AB_STACK_SET; break;
|
|
case AB_LOCALN:
|
|
Setter.bccType = AB_LOCALN_SET;
|
|
break;
|
|
case AB_PROP:
|
|
Setter.bccType = AB_PROP_SET;
|
|
break;
|
|
case AB_GLOBALN: Setter.bccType = AB_GLOBALN_SET; break;
|
|
default:
|
|
throw Error(target_host, host, SPos, Fn, "assignment to a constant");
|
|
}
|
|
// If the new value is produced using the old one, the parameters to get the old one need to be duplicated.
|
|
// Otherwise, the setter can just use the parameters originally meant for the getter.
|
|
// All getters push one value, so the parameter count is one more than the values they pop from the stack.
|
|
int iParCount = 1 - GetStackValue(Value.bccType, Value.Par.X);
|
|
if (Value.bccType == AB_STACK_SET)
|
|
{
|
|
// STACK_SET has a side effect, so it can't be simply removed.
|
|
// Discard the unused value the usual way instead.
|
|
if (!fLeaveValue)
|
|
AddBCC(SPos, AB_STACK, -1);
|
|
// The original parameter isn't needed anymore, since in contrast to the other getters
|
|
// it does not indicate a position.
|
|
iParCount = 0;
|
|
}
|
|
else if (!fLeaveValue || iParCount)
|
|
{
|
|
RemoveLastBCC();
|
|
at_jump_target = true; // In case the original BCC was a jump target
|
|
}
|
|
if (fLeaveValue && iParCount)
|
|
{
|
|
for (int i = 0; i < iParCount; i++)
|
|
AddBCC(SPos, AB_DUP, 1 - iParCount);
|
|
// Finally re-add original BCC
|
|
AddBCC(SPos, Value.bccType, Value.Par.X);
|
|
}
|
|
// Done. The returned BCC should be added later once the value to be set was pushed on top.
|
|
assert(iParCount == -GetStackValue(Setter.bccType, Setter.Par.X));
|
|
return Setter;
|
|
}
|
|
|
|
int C4AulCompiler::CodegenAstVisitor::AddJumpTarget()
|
|
{
|
|
assert(Fn && "Jump target outside of function");
|
|
if (!Fn)
|
|
throw C4AulParseError(host, "internal error: jump target outside of function");
|
|
|
|
at_jump_target = true;
|
|
return Fn->GetCodePos();
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::UpdateJump(int jump, int target)
|
|
{
|
|
C4AulBCC *code = Fn->GetCodeByPos(jump);
|
|
assert(IsJump(code->bccType));
|
|
code->Par.i = target - jump;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::AddJumpTo(const char *loc, C4AulBCCType type, int target)
|
|
{
|
|
AddBCC(loc, type, target - Fn->GetCodePos());
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::PushLoop()
|
|
{
|
|
active_loops.emplace(stack_height);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::PopLoop(int continue_target)
|
|
{
|
|
assert(!active_loops.empty());
|
|
assert(stack_height == active_loops.top().stack_height);
|
|
// Update all loop control jumps
|
|
const auto &loop = active_loops.top();
|
|
for (auto &c : loop.continues)
|
|
UpdateJump(c, continue_target);
|
|
int loop_exit = AddJumpTarget();
|
|
for (auto &b : loop.breaks)
|
|
UpdateJump(b, loop_exit);
|
|
active_loops.pop();
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::AddLoopControl(const char *loc, Loop::Control c)
|
|
{
|
|
assert(!active_loops.empty());
|
|
if (active_loops.empty())
|
|
throw C4AulParseError(host, "internal error: loop control code emitted outside of loop");
|
|
// Clear stack
|
|
assert(active_loops.top().stack_height == stack_height);
|
|
if (active_loops.top().stack_height - stack_height > 0)
|
|
AddBCC(loc, AB_STACK, active_loops.top().stack_height - stack_height);
|
|
int jump = AddBCC(loc, AB_JUMP, 0);
|
|
switch (c)
|
|
{
|
|
case Loop::Control::Continue:
|
|
active_loops.top().continues.push_back(jump);
|
|
break;
|
|
case Loop::Control::Break:
|
|
active_loops.top().breaks.push_back(jump);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Noop *) {}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::StringLit *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
AddBCC(n->loc, AB_STRING, (intptr_t)::Strings.RegString(n->value.c_str()));
|
|
type_of_stack_top = C4V_String;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::IntLit *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
AddBCC(n->loc, AB_INT, n->value);
|
|
type_of_stack_top = C4V_Int;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::BoolLit *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
AddBCC(n->loc, AB_BOOL, n->value);
|
|
type_of_stack_top = C4V_Bool;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ArrayLit *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
for (const auto &e : n->values)
|
|
{
|
|
SafeVisit(e);
|
|
}
|
|
AddBCC(n->loc, AB_NEW_ARRAY, n->values.size());
|
|
type_of_stack_top = C4V_Array;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ProplistLit *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
for (const auto &e : n->values)
|
|
{
|
|
StackGuard g(this, 2);
|
|
AddBCC(n->loc, AB_STRING, (intptr_t)::Strings.RegString(e.first.c_str()));
|
|
SafeVisit(e.second);
|
|
}
|
|
AddBCC(n->loc, AB_NEW_PROPLIST, n->values.size());
|
|
type_of_stack_top = C4V_PropList;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::NilLit *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
AddBCC(n->loc, AB_NIL);
|
|
type_of_stack_top = C4V_Nil;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ThisLit *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
AddBCC(n->loc, AB_THIS);
|
|
type_of_stack_top = C4V_PropList;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::VarExpr *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
assert(Fn);
|
|
assert(!scopes.empty());
|
|
C4Value dummy;
|
|
const char *cname = n->identifier.c_str();
|
|
C4String *interned = ::Strings.FindString(cname);
|
|
|
|
// Reset known type of top of value stack so we don't keep the old one around
|
|
type_of_stack_top = C4V_Any;
|
|
|
|
// Lookup order: Parameters > var > local > global > global const
|
|
// Why parameters are considered before function-scoped variables
|
|
// you ask? I've no idea, but that's how it was before I started
|
|
// changing things.
|
|
// NOTE: If you change this, remember to also change the warning
|
|
// (variable_shadows_variable) in PreparseAstVisitor.
|
|
if (Fn->ParNamed.GetItemNr(cname) != -1)
|
|
{
|
|
int pos = Fn->ParNamed.GetItemNr(cname);
|
|
AddVarAccess(n->loc, AB_DUP, -Fn->GetParCount() + pos);
|
|
type_of_stack_top = Fn->GetParType()[pos];
|
|
}
|
|
else if (Fn->VarNamed.GetItemNr(cname) != -1)
|
|
{
|
|
const bool in_scope = end(scopes) != std::find_if(begin(scopes), end(scopes), [n](const Scope &scope) {
|
|
return scope.variables.find(n->identifier) != scope.variables.end();
|
|
});
|
|
if (!in_scope)
|
|
{
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::variable_out_of_scope, cname);
|
|
}
|
|
AddVarAccess(n->loc, AB_DUP, Fn->VarNamed.GetItemNr(cname));
|
|
}
|
|
// Can't use Fn->Parent->HasProperty here because that only returns true
|
|
// for immediate properties, while we also want to interrogate prototypes
|
|
else if (Fn->Parent && interned && Fn->Parent->GetPropertyByS(interned, &dummy))
|
|
{
|
|
AddBCC(n->loc, AB_LOCALN, (intptr_t)interned);
|
|
}
|
|
else if (ScriptEngine.GlobalNamedNames.GetItemNr(cname) != -1)
|
|
{
|
|
AddBCC(n->loc, AB_GLOBALN, ScriptEngine.GlobalNamedNames.GetItemNr(cname));
|
|
}
|
|
else if (ScriptEngine.GlobalConstNames.GetItemNr(cname) != -1)
|
|
{
|
|
C4Value v;
|
|
ENSURE_COND(ScriptEngine.GetGlobalConstant(cname, &v), "internal error: global constant not retrievable");
|
|
switch (v.GetType())
|
|
{
|
|
case C4V_Nil:
|
|
AddBCC(n->loc, AB_NIL);
|
|
break;
|
|
case C4V_Int:
|
|
AddBCC(n->loc, AB_INT, v._getInt());
|
|
break;
|
|
case C4V_Bool:
|
|
AddBCC(n->loc, AB_BOOL, v._getBool());
|
|
break;
|
|
case C4V_PropList:
|
|
AddBCC(n->loc, AB_CPROPLIST, reinterpret_cast<intptr_t>(v._getPropList()));
|
|
break;
|
|
case C4V_String:
|
|
AddBCC(n->loc, AB_STRING, reinterpret_cast<intptr_t>(v._getStr()));
|
|
break;
|
|
case C4V_Array:
|
|
AddBCC(n->loc, AB_CARRAY, reinterpret_cast<intptr_t>(v._getArray()));
|
|
break;
|
|
case C4V_Function:
|
|
AddBCC(n->loc, AB_CFUNCTION, reinterpret_cast<intptr_t>(v._getFunction()));
|
|
default:
|
|
AddBCC(n->loc, AB_NIL);
|
|
throw Error(target_host, host, n, Fn, "internal error: global constant of unexpected type: %s (of type %s)", cname, v.GetTypeName());
|
|
}
|
|
type_of_stack_top = v.GetType();
|
|
}
|
|
else
|
|
{
|
|
AddBCC(n->loc, AB_NIL);
|
|
throw Error(target_host, host, n, Fn, "symbol not found in any symbol table: %s", cname);
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::UnOpExpr *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
|
|
n->operand->accept(this);
|
|
const auto &op = C4ScriptOpMap[n->op];
|
|
if (op.Changer)
|
|
{
|
|
C4AulBCC setter = MakeSetter(n->loc, true);
|
|
AddBCC(n->loc, op.Code, 0);
|
|
AddBCC(n->loc, setter);
|
|
// On postfix inc/dec, regenerate the previous value
|
|
if (op.Postfix && (op.Code == AB_Inc || op.Code == AB_Dec))
|
|
{
|
|
AddBCC(n->loc, op.Code == AB_Inc ? AB_Dec : AB_Inc, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddBCC(n->loc, op.Code);
|
|
}
|
|
type_of_stack_top = op.RetType;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::BinOpExpr *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
|
|
SafeVisit(n->lhs);
|
|
|
|
const auto &op = C4ScriptOpMap[n->op];
|
|
if (op.Code == AB_JUMPAND || op.Code == AB_JUMPOR || op.Code == AB_JUMPNNIL)
|
|
{
|
|
// Short-circuiting operators. These are slightly more complex
|
|
// because we don't want to evaluate their rhs operand when the
|
|
// lhs one already decided the result
|
|
int jump = AddBCC(n->loc, op.Code);
|
|
SafeVisit(n->rhs);
|
|
UpdateJump(jump, AddJumpTarget());
|
|
}
|
|
else if (op.Changer)
|
|
{
|
|
try
|
|
{
|
|
C4AulBCC setter = MakeSetter(n->loc, true);
|
|
SafeVisit(n->rhs);
|
|
AddBCC(n->loc, op.Code);
|
|
AddBCC(n->loc, setter);
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
HandleError(e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SafeVisit(n->rhs);
|
|
AddBCC(n->loc, op.Code, 0);
|
|
}
|
|
|
|
type_of_stack_top = op.RetType;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::AssignmentExpr *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
SafeVisit(n->lhs);
|
|
try
|
|
{
|
|
C4AulBCC setter = MakeSetter(n->loc, false);
|
|
SafeVisit(n->rhs);
|
|
AddBCC(n->loc, setter);
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
HandleError(e);
|
|
}
|
|
// Assignment does not change the type of the variable
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::SubscriptExpr *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
SafeVisit(n->object);
|
|
SafeVisit(n->index);
|
|
AddBCC(n->loc, AB_ARRAYA);
|
|
|
|
// FIXME: Check if the subscripted object is a literal and if so, retrieve type
|
|
type_of_stack_top = C4V_Any;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::SliceExpr *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
SafeVisit(n->object);
|
|
SafeVisit(n->start);
|
|
SafeVisit(n->end);
|
|
AddBCC(n->loc, AB_ARRAY_SLICE);
|
|
|
|
type_of_stack_top = C4V_Array;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::CallExpr *n)
|
|
{
|
|
const char *cname = n->callee.c_str();
|
|
|
|
if (n->callee == C4AUL_DebugBreak)
|
|
{
|
|
if (n->context)
|
|
throw Error(target_host, host, n, Fn, R"("%s" can't be called in a different context)", cname);
|
|
if (!n->args.empty())
|
|
throw Error(target_host, host, n, Fn, R"("%s" must not have any arguments)", cname);
|
|
|
|
AddBCC(n->loc, AB_DEBUG);
|
|
// Add a pseudo-nil to keep the stack balanced
|
|
AddBCC(n->loc, AB_NIL);
|
|
type_of_stack_top = C4V_Nil;
|
|
return;
|
|
}
|
|
|
|
if (n->callee == C4AUL_Inherited || n->callee == C4AUL_SafeInherited)
|
|
{
|
|
// inherited can only be called within the same context
|
|
if (n->context)
|
|
{
|
|
throw Error(target_host, host, n, Fn, R"("%s" can't be called in a different context)", cname);
|
|
}
|
|
}
|
|
|
|
if (n->callee == C4AUL_Inherited && !Fn->OwnerOverloaded)
|
|
{
|
|
throw Error(target_host, host, n, Fn, "inherited function not found (use " C4AUL_SafeInherited " to disable this message)");
|
|
}
|
|
|
|
const auto pre_call_stack = stack_height;
|
|
|
|
if (n->context)
|
|
SafeVisit(n->context);
|
|
|
|
std::vector<C4V_Type> known_par_types;
|
|
known_par_types.reserve(n->args.size());
|
|
|
|
for (const auto &arg : n->args)
|
|
{
|
|
SafeVisit(arg);
|
|
known_par_types.push_back(type_of_stack_top);
|
|
}
|
|
|
|
C4AulFunc *callee = nullptr;
|
|
|
|
// Special handling for the overload chain
|
|
if (n->callee == C4AUL_Inherited || n->callee == C4AUL_SafeInherited)
|
|
{
|
|
callee = Fn->OwnerOverloaded;
|
|
}
|
|
|
|
size_t fn_argc = C4AUL_MAX_Par;
|
|
if (!n->context)
|
|
{
|
|
// if this is a function without explicit context, we resolve it
|
|
if (!callee)
|
|
callee = Fn->Parent->GetFunc(cname);
|
|
if (!callee && target_host)
|
|
callee = target_host->Engine->GetFunc(cname);
|
|
|
|
if (callee)
|
|
{
|
|
fn_argc = callee->GetParCount();
|
|
}
|
|
else
|
|
{
|
|
// pop all args off the stack
|
|
if (!n->args.empty())
|
|
AddBCC(n->loc, AB_STACK, -(intptr_t)n->args.size());
|
|
// and "return" nil
|
|
AddBCC(n->loc, AB_NIL);
|
|
type_of_stack_top = C4V_Nil;
|
|
|
|
if (n->callee != C4AUL_SafeInherited)
|
|
{
|
|
HandleError(Error(target_host, host, n, Fn, "called function not found: %s", cname));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (n->args.size() > fn_argc)
|
|
{
|
|
// Pop off any args that are over the limit
|
|
Warn(target_host, host, n->args[fn_argc].get(), Fn, C4AulWarningId::arg_count_mismatch,
|
|
cname, (unsigned)n->args.size(), fn_argc);
|
|
AddBCC(n->loc, AB_STACK, fn_argc - n->args.size());
|
|
}
|
|
else if (n->args.size() < fn_argc)
|
|
{
|
|
if (n->append_unnamed_pars)
|
|
{
|
|
assert(Fn->GetParCount() == C4AUL_MAX_Par);
|
|
int missing_par_count = fn_argc - n->args.size();
|
|
int available_par_count = Fn->GetParCount() - Fn->ParNamed.iSize;
|
|
for (int i = 0; i < std::min(missing_par_count, available_par_count); ++i)
|
|
{
|
|
AddVarAccess(n->loc, AB_DUP, -Fn->GetParCount() + Fn->ParNamed.iSize + i);
|
|
}
|
|
// Fill up remaining, unsettable parameters with nil
|
|
if (available_par_count < missing_par_count)
|
|
AddBCC(n->loc, AB_STACK, missing_par_count - available_par_count);
|
|
}
|
|
else if (fn_argc > n->args.size())
|
|
{
|
|
// Add nil for each missing parameter
|
|
AddBCC(n->loc, AB_STACK, fn_argc - n->args.size());
|
|
}
|
|
}
|
|
|
|
// Check passed parameters for this call (as far as possible)
|
|
std::vector<C4V_Type> expected_par_types;
|
|
if (n->context)
|
|
{
|
|
AddBCC(n->loc, n->safe_call ? AB_CALLFS : AB_CALL, (intptr_t)::Strings.RegString(cname));
|
|
// Since we don't know the context in which this call will happen at
|
|
// runtime, we'll check whether all available functions with the same
|
|
// name agree on their parameters.
|
|
const C4AulFunc *candidate = target_host ? target_host->Engine->GetFirstFunc(cname) : nullptr;
|
|
if (candidate)
|
|
{
|
|
expected_par_types.assign(candidate->GetParType(), candidate->GetParType() + candidate->GetParCount());
|
|
while ((candidate = target_host->Engine->GetNextSNFunc(candidate)) != nullptr)
|
|
{
|
|
if (candidate->GetParCount() > expected_par_types.size())
|
|
{
|
|
expected_par_types.resize(candidate->GetParCount(), C4V_Any);
|
|
}
|
|
for (size_t i = 0; i < expected_par_types.size(); ++i)
|
|
{
|
|
C4V_Type a = expected_par_types[i];
|
|
C4V_Type b = candidate->GetParType()[i];
|
|
// If we can convert one of the types into the other
|
|
// without a warning, use the wider one
|
|
bool implicit_a_to_b = !C4Value::WarnAboutConversion(a, b);
|
|
bool implicit_b_to_a = !C4Value::WarnAboutConversion(b, a);
|
|
if (implicit_a_to_b && !implicit_b_to_a)
|
|
expected_par_types[i] = b;
|
|
else if (implicit_b_to_a && !implicit_a_to_b)
|
|
expected_par_types[i] = a;
|
|
// but if we can convert neither of the types into the
|
|
// other, give up and assume the user will do the right
|
|
// thing
|
|
else if (!implicit_a_to_b && !implicit_b_to_a)
|
|
expected_par_types[i] = C4V_Any;
|
|
}
|
|
}
|
|
}
|
|
type_of_stack_top = C4V_Any;
|
|
}
|
|
else
|
|
{
|
|
assert(callee);
|
|
AddBCC(n->loc, AB_FUNC, (intptr_t)callee);
|
|
expected_par_types.assign(callee->GetParType(), callee->GetParType() + callee->GetParCount());
|
|
type_of_stack_top = callee->GetRetType();
|
|
}
|
|
|
|
// Check parameters
|
|
for (size_t i = 0; i < std::min(known_par_types.size(), expected_par_types.size()); ++i)
|
|
{
|
|
C4V_Type from = known_par_types[i];
|
|
C4V_Type to = expected_par_types[i];
|
|
if (C4Value::WarnAboutConversion(from, to))
|
|
{
|
|
Warn(target_host, host, n->args[i].get(), Fn, C4AulWarningId::arg_type_mismatch, (unsigned)i, cname, GetC4VName(from), GetC4VName(to));
|
|
}
|
|
}
|
|
|
|
// We leave one value (the return value) on the stack
|
|
assert(pre_call_stack + 1 == stack_height);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ParExpr *n)
|
|
{
|
|
StackGuard g(this, 1);
|
|
|
|
SafeVisit(n->arg);
|
|
AddBCC(n->loc, AB_PAR);
|
|
type_of_stack_top = C4V_Any;
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Block *n)
|
|
{
|
|
auto scope = enterScope();
|
|
for (const auto &s : n->children)
|
|
{
|
|
StackGuard g(this, 0);
|
|
if (SafeVisit(s))
|
|
{
|
|
// If the statement has left a stack value, pop it off
|
|
MaybePopValueOf(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Return *n)
|
|
{
|
|
StackGuard g(this, 0);
|
|
|
|
WarnOnAssignment(n->value);
|
|
SafeVisit(n->value);
|
|
AddBCC(n->loc, AB_RETURN);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ForLoop *n)
|
|
{
|
|
// Bytecode arranged like this:
|
|
// initializer
|
|
// cond: condition
|
|
// CONDN exit
|
|
// body: body
|
|
// incr: incrementor
|
|
// JUMP cond
|
|
// exit:
|
|
//
|
|
// continue jumps to incr
|
|
// break jumps to exit
|
|
|
|
auto scope = enterScope();
|
|
if (n->init)
|
|
{
|
|
if (SafeVisit(n->init))
|
|
MaybePopValueOf(n->init);
|
|
}
|
|
int cond = -1, condition_jump = -1;
|
|
PushLoop();
|
|
if (n->cond)
|
|
{
|
|
// XXX:
|
|
// Assignments in the condition here should warn as well (like they do in
|
|
// if conditions) but a ton of code uses those assignments at the moment
|
|
// and people are divided about allowing it
|
|
cond = AddJumpTarget();
|
|
SafeVisit(n->cond);
|
|
active_loops.top().breaks.push_back(AddBCC(n->cond->loc, AB_CONDN));
|
|
}
|
|
|
|
int body = AddJumpTarget();
|
|
if (!n->cond)
|
|
cond = body;
|
|
if (SafeVisit(n->body))
|
|
MaybePopValueOf(n->body);
|
|
|
|
int incr = -1;
|
|
if (n->incr)
|
|
{
|
|
incr = AddJumpTarget();
|
|
if (SafeVisit(n->incr))
|
|
MaybePopValueOf(n->incr);
|
|
}
|
|
else
|
|
{
|
|
// If no incrementor exists, just jump straight to the condition
|
|
incr = cond;
|
|
}
|
|
// start the next iteration of the loop
|
|
AddJumpTo(n->loc, AB_JUMP, cond);
|
|
PopLoop(incr);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::RangeLoop *n)
|
|
{
|
|
// Bytecode arranged like this:
|
|
// condition (aka iterated array)
|
|
// INT 0 (the loop index variable)
|
|
// cond: FOREACH_NEXT
|
|
// JUMP exit
|
|
// body: body
|
|
// JUMP cond
|
|
// exit: STACK -2 (to clean the iteration variables)
|
|
//
|
|
// continue jumps to cond
|
|
// break jumps to exit
|
|
|
|
auto scope = enterScope();
|
|
scopes.front().variables.insert(n->var);
|
|
|
|
const char *cname = n->var.c_str();
|
|
int var_id = Fn->VarNamed.GetItemNr(cname);
|
|
assert(var_id != -1 && "CodegenAstVisitor: unable to find variable in foreach");
|
|
if (var_id == -1)
|
|
throw Error(target_host, host, n, Fn, "internal error: unable to find variable in foreach: %s", cname);
|
|
// Emit code for array
|
|
SafeVisit(n->cond);
|
|
// Emit code for iteration
|
|
AddBCC(n->loc, AB_INT, 0);
|
|
int cond = AddJumpTarget();
|
|
PushLoop();
|
|
AddVarAccess(n->loc, AB_FOREACH_NEXT, var_id);
|
|
AddLoopControl(n->loc, Loop::Control::Break); // Will be skipped by AB_FOREACH_NEXT as long as more entries exist
|
|
|
|
// Emit body
|
|
if (SafeVisit(n->body))
|
|
MaybePopValueOf(n->body);
|
|
// continue starts the next iteration of the loop
|
|
AddLoopControl(n->loc, Loop::Control::Continue);
|
|
PopLoop(cond);
|
|
// Pop off iterator and array
|
|
AddBCC(n->loc, AB_STACK, -2);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::EmitFunctionCode(const ::aul::ast::Function *f, const ::aul::ast::Node *n)
|
|
{
|
|
assert(Fn != nullptr);
|
|
assert(scopes.empty());
|
|
|
|
Fn->ClearCode();
|
|
|
|
// Reserve var stack space
|
|
if (Fn->VarNamed.iSize > 0)
|
|
AddBCC(n->loc, AB_STACK, Fn->VarNamed.iSize);
|
|
stack_height = 0;
|
|
|
|
auto scope = enterScope();
|
|
try
|
|
{
|
|
f->body->accept(this);
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
AddBCC(nullptr, AB_ERR, (intptr_t)::Strings.RegString(e.what()));
|
|
throw;
|
|
}
|
|
|
|
if (f->body->children.empty() || !dynamic_cast<::aul::ast::Return*>(f->body->children.rbegin()->get()))
|
|
{
|
|
// If the last statement isn't a return, add one to the byte
|
|
// code. We're not doing CFA because the worst thing that might
|
|
// happen is we insert two instructions that never get executed.
|
|
AddBCC(n->loc, AB_NIL);
|
|
AddBCC(n->loc, AB_RETURN);
|
|
}
|
|
Fn->DumpByteCode();
|
|
// This instruction should never be reached but we'll add it just in
|
|
// case.
|
|
AddBCC(n->loc, AB_EOFN);
|
|
assert(stack_height == 0);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::DoLoop *n)
|
|
{
|
|
auto scope = enterScope();
|
|
int body = AddJumpTarget();
|
|
PushLoop();
|
|
if (SafeVisit(n->body))
|
|
MaybePopValueOf(n->body);
|
|
int cond = AddJumpTarget();
|
|
// XXX:
|
|
// Assignments in the condition here should warn as well (like they do in
|
|
// if conditions) but a ton of code uses those assignments at the moment
|
|
// and people are divided about allowing it
|
|
SafeVisit(n->cond);
|
|
AddJumpTo(n->loc, AB_COND, body);
|
|
PopLoop(cond);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::WhileLoop *n)
|
|
{
|
|
auto scope = enterScope();
|
|
int cond = AddJumpTarget();
|
|
PushLoop();
|
|
// XXX:
|
|
// Assignments in the condition here should warn as well (like they do in
|
|
// if conditions) but a ton of code uses those assignments at the moment
|
|
// and people are divided about allowing it
|
|
SafeVisit(n->cond);
|
|
active_loops.top().breaks.push_back(AddBCC(n->cond->loc, AB_CONDN));
|
|
if (SafeVisit(n->body))
|
|
MaybePopValueOf(n->body);
|
|
// continue starts the next iteration of the loop
|
|
AddLoopControl(n->loc, Loop::Control::Continue);
|
|
PopLoop(cond);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Break *n)
|
|
{
|
|
ENSURE_COND(!active_loops.empty(), "'break' outside loop");
|
|
AddLoopControl(n->loc, Loop::Control::Break);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Continue *n)
|
|
{
|
|
ENSURE_COND(!active_loops.empty(), "'continue' outside loop");
|
|
AddLoopControl(n->loc, Loop::Control::Continue);
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::If *n)
|
|
{
|
|
auto scope = enterScope();
|
|
WarnOnAssignment(n->cond);
|
|
SafeVisit(n->cond);
|
|
int jump = AddBCC(n->loc, AB_CONDN);
|
|
// Warn if we're controlling a no-op ("if (...);")
|
|
if (dynamic_cast<::aul::ast::Noop*>(n->iftrue.get()))
|
|
{
|
|
Warn(target_host, host, n->iftrue->loc, Fn, C4AulWarningId::empty_if);
|
|
}
|
|
if (SafeVisit(n->iftrue))
|
|
MaybePopValueOf(n->iftrue);
|
|
|
|
if (dynamic_cast<::aul::ast::Noop*>(n->iffalse.get()))
|
|
{
|
|
Warn(target_host, host, n->iffalse->loc, Fn, C4AulWarningId::empty_if);
|
|
}
|
|
|
|
if (n->iffalse)
|
|
{
|
|
int jumpout = AddBCC(n->loc, AB_JUMP);
|
|
UpdateJump(jump, AddJumpTarget());
|
|
jump = jumpout;
|
|
if (SafeVisit(n->iffalse))
|
|
MaybePopValueOf(n->iffalse);
|
|
}
|
|
UpdateJump(jump, AddJumpTarget());
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::VarDecl *n)
|
|
{
|
|
for (const auto &dec : n->decls)
|
|
{
|
|
const char *cname = dec.name.c_str();
|
|
switch (n->scope)
|
|
{
|
|
case ::aul::ast::VarDecl::Scope::Func:
|
|
scopes.front().variables.insert(dec.name);
|
|
if (dec.init)
|
|
{
|
|
// Emit code for the initializer
|
|
SafeVisit(dec.init);
|
|
int var_idx = Fn->VarNamed.GetItemNr(cname);
|
|
assert(var_idx >= 0 && "CodegenAstVisitor: var not found in variable table");
|
|
if (var_idx < 0)
|
|
{
|
|
AddBCC(n->loc, AB_STACK, -1);
|
|
throw Error(target_host, host, n, Fn, "internal error: var not found in variable table: %s", cname);
|
|
}
|
|
AddVarAccess(n->loc, AB_POP_TO, var_idx);
|
|
}
|
|
break;
|
|
case ::aul::ast::VarDecl::Scope::Object:
|
|
case ::aul::ast::VarDecl::Scope::Global:
|
|
// Object-local and global constants are handled by ConstantResolver.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::FunctionDecl *n)
|
|
{
|
|
assert(!Fn && "CodegenAstVisitor: function declaration encountered within active function");
|
|
if (Fn)
|
|
throw Error(target_host, host, n, Fn, "internal error: function declaration for '%s' encountered within active function", n->name.c_str());
|
|
|
|
C4PropListStatic *Parent = n->is_global ? target_host->Engine->GetPropList() : target_host->GetPropList();
|
|
|
|
C4String *name = ::Strings.FindString(n->name.c_str());
|
|
C4AulFunc *f = Parent->GetFunc(name);
|
|
while (f)
|
|
{
|
|
if (f->SFunc() && f->SFunc()->pOrgScript == host && f->Parent == Parent)
|
|
{
|
|
if (Fn)
|
|
Warn(target_host, host, n, Fn, C4AulWarningId::redeclaration, "function", f->GetName());
|
|
Fn = f->SFunc();
|
|
}
|
|
f = f->SFunc() ? f->SFunc()->OwnerOverloaded : nullptr;
|
|
}
|
|
|
|
if (!Fn && Parent->HasProperty(name))
|
|
{
|
|
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());
|
|
}
|
|
|
|
assert(Fn && "CodegenAstVisitor: unable to find function definition");
|
|
if (!Fn)
|
|
throw Error(target_host, host, n, Fn, "internal error: unable to find function definition for %s", n->name.c_str());
|
|
|
|
// If this isn't a global function, but there is a global one with
|
|
// the same name, and this function isn't overloading a different
|
|
// one, add the global function to the overload chain
|
|
if (!n->is_global && !Fn->OwnerOverloaded)
|
|
{
|
|
C4AulFunc *global_parent = target_host->Engine->GetFunc(Fn->GetName());
|
|
if (global_parent)
|
|
Fn->SetOverloaded(global_parent);
|
|
}
|
|
|
|
try
|
|
{
|
|
EmitFunctionCode(n);
|
|
Fn = nullptr;
|
|
}
|
|
catch (...)
|
|
{
|
|
Fn = nullptr;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const::aul::ast::FunctionExpr * n)
|
|
{
|
|
AddBCC(n->loc, AB_NIL);
|
|
throw Error(target_host, host, n, Fn, "can't define a function in a function-scoped proplist");
|
|
}
|
|
|
|
void C4AulCompiler::CodegenAstVisitor::visit(const::aul::ast::Script * n)
|
|
{
|
|
for (const auto &d : n->declarations)
|
|
{
|
|
SafeVisit(d);
|
|
}
|
|
}
|
|
|
|
#undef ENSURE_COND
|
|
#define ENSURE_COND(cond, failmsg) do { if (!(cond)) throw Error(host, host, n, nullptr, failmsg); } while (0)
|
|
|
|
// Evaluates constant AST subtrees and returns the final C4Value.
|
|
// Throws ExpressionNotConstant if evaluation fails.
|
|
|
|
C4Value C4AulCompiler::ConstexprEvaluator::eval(C4ScriptHost *host, const ::aul::ast::Expr *e, EvalFlags flags)
|
|
{
|
|
ConstexprEvaluator ce(host);
|
|
ce.ignore_unset_values = (flags & IgnoreUnset) == IgnoreUnset;
|
|
try
|
|
{
|
|
e->accept(&ce);
|
|
return ce.v;
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
if ((flags & SuppressErrors) == 0)
|
|
host->Engine->ErrorHandler->OnError(e.what());
|
|
return C4VNull;
|
|
}
|
|
}
|
|
|
|
C4Value C4AulCompiler::ConstexprEvaluator::eval_static(C4ScriptHost *host, C4PropListStatic *parent, const std::string &parent_key, const ::aul::ast::Expr *e, EvalFlags flags)
|
|
{
|
|
ConstexprEvaluator ce(host);
|
|
ce.proplist_magic = ConstexprEvaluator::ProplistMagic{ true, parent, parent_key };
|
|
ce.ignore_unset_values = (flags & IgnoreUnset) == IgnoreUnset;
|
|
try
|
|
{
|
|
e->accept(&ce);
|
|
return ce.v;
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
if ((flags & SuppressErrors) == 0)
|
|
host->Engine->ErrorHandler->OnError(e.what());
|
|
return C4VNull;
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::StringLit *n) { v = C4VString(n->value.c_str()); }
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::IntLit *n) { v = C4VInt(n->value); }
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::BoolLit *n) { v = C4VBool(n->value); }
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::ArrayLit *n)
|
|
{
|
|
auto a = std::make_unique<C4ValueArray>(n->values.size());
|
|
for (size_t i = 0; i < n->values.size(); ++i)
|
|
{
|
|
n->values[i]->accept(this);
|
|
a->SetItem(i, v);
|
|
}
|
|
v = C4VArray(a.release());
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::ProplistLit *n)
|
|
{
|
|
std::unique_ptr<C4PropList> new_proplist;
|
|
C4PropList *p = nullptr;
|
|
|
|
bool first_pass = true;
|
|
|
|
if (proplist_magic.active)
|
|
{
|
|
// Check if there's already a proplist available
|
|
C4String *key = ::Strings.RegString(proplist_magic.key.c_str());
|
|
C4Value old;
|
|
if (proplist_magic.parent)
|
|
{
|
|
proplist_magic.parent->GetPropertyByS(key, &old);
|
|
}
|
|
else
|
|
{
|
|
// If proplist_magic.parent is nullptr, we're handling a global constant.
|
|
host->Engine->GetGlobalConstant(key->GetCStr(), &old);
|
|
}
|
|
if (old.getPropList())
|
|
{
|
|
p = old.getPropList();
|
|
first_pass = false;
|
|
}
|
|
else
|
|
{
|
|
p = C4PropList::NewStatic(nullptr, proplist_magic.parent, key);
|
|
new_proplist.reset(p);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p = C4PropList::New();
|
|
new_proplist.reset(p);
|
|
}
|
|
|
|
// Since the values may be functions that refer to other values in the
|
|
// proplist, pre-populate the new proplist with dummy values until the
|
|
// real ones are set
|
|
if (first_pass)
|
|
{
|
|
for (const auto &kv : n->values)
|
|
{
|
|
p->SetPropertyByS(::Strings.RegString(kv.first.c_str()), C4VNull);
|
|
}
|
|
}
|
|
|
|
auto saved_magic = std::move(proplist_magic);
|
|
for (const auto &kv : n->values)
|
|
{
|
|
proplist_magic = ProplistMagic { saved_magic.active, p->IsStatic(), kv.first };
|
|
kv.second->accept(this);
|
|
p->SetPropertyByS(::Strings.RegString(kv.first.c_str()), v);
|
|
}
|
|
proplist_magic = std::move(saved_magic);
|
|
v = C4VPropList(p);
|
|
new_proplist.release();
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::NilLit *) { v = C4VNull; }
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::ThisLit *n) { nonconst(n, "\"this\" is not a global constant"); }
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::VarExpr *n)
|
|
{
|
|
const char *cname = n->identifier.c_str();
|
|
C4String *interned = ::Strings.FindString(cname);
|
|
if (interned && host->GetPropList()->GetPropertyByS(interned, &v))
|
|
return;
|
|
if (host->Engine->GetGlobalConstant(cname, &v))
|
|
return;
|
|
|
|
if (ignore_unset_values)
|
|
{
|
|
v = C4VNull;
|
|
return;
|
|
}
|
|
|
|
nonconst(n, "the variable \"%s\" is not a global constant", cname);
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::UnOpExpr *n)
|
|
{
|
|
n->operand->accept(this);
|
|
assert(n->op > 0);
|
|
const auto &op = C4ScriptOpMap[n->op];
|
|
if (op.Changer)
|
|
nonconst(n, "unary operator %s is applied in a non-const fashion", op.Identifier);
|
|
AssertValueType(v, op.Type1, op.Identifier, n);
|
|
switch (op.Code)
|
|
{
|
|
case AB_BitNot:
|
|
v.SetInt(~v._getInt());
|
|
break;
|
|
case AB_Not:
|
|
v.SetBool(!v.getBool());
|
|
break;
|
|
case AB_Neg:
|
|
v.SetInt(-v._getInt());
|
|
break;
|
|
default:
|
|
assert(!"ConstexprEvaluator: Unexpected unary operator");
|
|
throw Error(host, host, n, nullptr, "internal error: unary operator not found in operator table");
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::BinOpExpr *n)
|
|
{
|
|
assert(n->op > 0);
|
|
const auto &op = C4ScriptOpMap[n->op];
|
|
if (op.Changer)
|
|
nonconst(n, "binary operator %s is applied in a non-const fashion", op.Identifier);
|
|
|
|
n->lhs->accept(this);
|
|
C4Value lhs = v;
|
|
// Evaluate the short-circuiting operators here
|
|
if ((op.Code == AB_JUMPAND && !lhs) || (op.Code == AB_JUMPOR && lhs) || (op.Code == AB_JUMPNNIL && lhs.GetType() != C4V_Nil))
|
|
{
|
|
v = lhs;
|
|
return;
|
|
}
|
|
n->rhs->accept(this);
|
|
C4Value &rhs = v;
|
|
|
|
AssertValueType(lhs, op.Type1, op.Identifier, n);
|
|
AssertValueType(rhs, op.Type2, op.Identifier, n);
|
|
|
|
switch (op.Code)
|
|
{
|
|
case AB_Pow:
|
|
v.SetInt(Pow(lhs._getInt(), rhs._getInt()));
|
|
break;
|
|
case AB_Div:
|
|
ENSURE_COND(rhs._getInt() != 0, "division by zero");
|
|
ENSURE_COND(lhs._getInt() != INT32_MIN || rhs._getInt() != -1, "division overflow");
|
|
v.SetInt(lhs._getInt() / rhs._getInt());
|
|
break;
|
|
case AB_Mul:
|
|
v.SetInt(lhs._getInt() * rhs._getInt());
|
|
break;
|
|
case AB_Mod:
|
|
ENSURE_COND(rhs._getInt() != 0, "division by zero");
|
|
ENSURE_COND(lhs._getInt() != INT32_MIN || rhs._getInt() != -1, "division overflow");
|
|
v.SetInt(lhs._getInt() / rhs._getInt());
|
|
break;
|
|
#define INT_BINOP(code, op) case code: v.SetInt(lhs._getInt() op rhs._getInt()); break
|
|
INT_BINOP(AB_Sum, +);
|
|
INT_BINOP(AB_Sub, -);
|
|
INT_BINOP(AB_LeftShift, << );
|
|
INT_BINOP(AB_RightShift, >> );
|
|
INT_BINOP(AB_BitAnd, &);
|
|
INT_BINOP(AB_BitXOr, ^);
|
|
INT_BINOP(AB_BitOr, | );
|
|
#undef INT_BINOP
|
|
#define BOOL_BINOP(code, op) case code: v.SetBool(lhs._getInt() op rhs._getInt()); break
|
|
BOOL_BINOP(AB_LessThan, <);
|
|
BOOL_BINOP(AB_LessThanEqual, <= );
|
|
BOOL_BINOP(AB_GreaterThan, >);
|
|
BOOL_BINOP(AB_GreaterThanEqual, >= );
|
|
#undef BOOL_BINOP
|
|
case AB_Equal:
|
|
v.SetBool(lhs.IsIdenticalTo(rhs));
|
|
break;
|
|
case AB_NotEqual:
|
|
v.SetBool(!lhs.IsIdenticalTo(rhs));
|
|
break;
|
|
case AB_JUMPAND:
|
|
case AB_JUMPOR:
|
|
case AB_JUMPNNIL:
|
|
// If we hit this, then the short-circuit above failed
|
|
v = rhs;
|
|
break;
|
|
default:
|
|
assert(!"ConstexprEvaluator: Unexpected binary operator");
|
|
throw Error(host, host, n, nullptr, "internal error: binary operator not found in operator table");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::AssignmentExpr *n)
|
|
{
|
|
nonconst(n, "updating assignment used in a non-const fashion");
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::SubscriptExpr *n)
|
|
{
|
|
n->object->accept(this);
|
|
C4Value obj = v;
|
|
n->index->accept(this);
|
|
C4Value &index = v;
|
|
|
|
if (obj.CheckConversion(C4V_Array))
|
|
{
|
|
ENSURE_COND(index.CheckConversion(C4V_Int), FormatString("array access: index of type %s, but expected int", index.GetTypeName()).getData());
|
|
v = obj.getArray()->GetItem(index.getInt());
|
|
}
|
|
else if (obj.CheckConversion(C4V_PropList))
|
|
{
|
|
ENSURE_COND(index.CheckConversion(C4V_String), FormatString("proplist access: index of type %s, but expected string", index.GetTypeName()).getData());
|
|
if (!obj.getPropList()->GetPropertyByS(index.getStr(), &v))
|
|
v.Set0();
|
|
}
|
|
else
|
|
{
|
|
ENSURE_COND(false, FormatString("can't access %s as array or proplist", obj.GetTypeName()).getData());
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::SliceExpr *n)
|
|
{
|
|
n->object->accept(this);
|
|
C4Value obj = v;
|
|
n->start->accept(this);
|
|
C4Value start = v;
|
|
n->end->accept(this);
|
|
C4Value &end = v;
|
|
|
|
ENSURE_COND(obj.CheckConversion(C4V_Array), FormatString("array slice: can't access %s as an array", obj.GetTypeName()).getData());
|
|
ENSURE_COND(start.CheckConversion(C4V_Int), FormatString("array slice: start index of type %s, int expected", start.GetTypeName()).getData());
|
|
ENSURE_COND(end.CheckConversion(C4V_Int), FormatString("array slice: end index of type %s, int expected", end.GetTypeName()).getData());
|
|
|
|
v.SetArray(obj.getArray()->GetSlice(start.getInt(), end.getInt()));
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::CallExpr *n)
|
|
{
|
|
// TODO: allow side-effect-free calls here
|
|
nonconst(n, "call to function (%s) not supported in constant expressions", n->callee.c_str());
|
|
}
|
|
|
|
void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::FunctionExpr *n)
|
|
{
|
|
// Function expressions can only occur inside static proplists.
|
|
ENSURE_COND(proplist_magic.active, "internal error: function expression outside of static proplist");
|
|
|
|
C4AulScriptFunc *sfunc = nullptr;
|
|
bool first_pass = true;
|
|
|
|
if (auto func = proplist_magic.parent->GetFunc(proplist_magic.key.c_str()))
|
|
{
|
|
sfunc = func->SFunc();
|
|
first_pass = false;
|
|
}
|
|
else
|
|
{
|
|
sfunc = new C4AulScriptFunc(proplist_magic.parent, host, proplist_magic.key.c_str(), n->loc);
|
|
}
|
|
|
|
ENSURE_COND(sfunc != nullptr, "internal error: function expression target resolved to non-function value");
|
|
|
|
if (first_pass)
|
|
{
|
|
for (const auto ¶m : n->params)
|
|
{
|
|
sfunc->AddPar(param.name.c_str());
|
|
}
|
|
if (n->has_unnamed_params)
|
|
sfunc->ParCount = C4AUL_MAX_Par;
|
|
|
|
PreparseAstVisitor preparser(host, host, sfunc);
|
|
preparser.visit(n->body.get());
|
|
}
|
|
else
|
|
{
|
|
CodegenAstVisitor cg(sfunc);
|
|
cg.EmitFunctionCode(n);
|
|
}
|
|
|
|
v.SetFunction(sfunc);
|
|
}
|
|
|
|
void C4AulCompiler::ConstantResolver::visit(const::aul::ast::Script *n)
|
|
{
|
|
for (const auto &d : n->declarations)
|
|
{
|
|
try
|
|
{
|
|
d->accept(this);
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
host->Engine->GetErrorHandler()->OnError(e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4AulCompiler::ConstantResolver::visit(const ::aul::ast::VarDecl *n)
|
|
{
|
|
const int quiet_flag = quiet ? ConstexprEvaluator::SuppressErrors : 0;
|
|
for (const auto &dec : n->decls)
|
|
{
|
|
const char *cname = dec.name.c_str();
|
|
C4RefCntPointer<C4String> name = ::Strings.RegString(cname);
|
|
switch (n->scope)
|
|
{
|
|
case ::aul::ast::VarDecl::Scope::Func:
|
|
// Function-scoped declarations and their initializers are handled by CodegenAstVisitor.
|
|
break;
|
|
case ::aul::ast::VarDecl::Scope::Object:
|
|
if (!host->GetPropList()->HasProperty(name))
|
|
host->GetPropList()->SetPropertyByS(name, C4VNull);
|
|
if (dec.init)
|
|
{
|
|
assert(host->GetPropList()->IsStatic());
|
|
try
|
|
{
|
|
C4Value v = ConstexprEvaluator::eval_static(host, host->GetPropList()->IsStatic(), dec.name, dec.init.get(), ConstexprEvaluator::IgnoreUnset | quiet_flag);
|
|
host->GetPropList()->SetPropertyByS(name, v);
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
if (!quiet)
|
|
host->Engine->ErrorHandler->OnError(e.what());
|
|
}
|
|
}
|
|
break;
|
|
case ::aul::ast::VarDecl::Scope::Global:
|
|
if ((dec.init != nullptr) != n->constant)
|
|
{
|
|
if (!quiet)
|
|
host->Engine->ErrorHandler->OnError(Error(host, host, n->loc, nullptr, "global variable must be either constant or uninitialized: %s", cname).what());
|
|
}
|
|
else if (dec.init)
|
|
{
|
|
try
|
|
{
|
|
assert(n->constant && "CodegenAstVisitor: initialized global variable isn't const");
|
|
C4Value *v = host->Engine->GlobalConsts.GetItem(cname);
|
|
assert(v && "CodegenAstVisitor: global constant not found in variable table");
|
|
if (!v)
|
|
throw Error(host, host, n->loc, nullptr, "internal error: global constant not found in variable table: %s", cname);
|
|
*v = ConstexprEvaluator::eval_static(host, nullptr, dec.name, dec.init.get(), ConstexprEvaluator::IgnoreUnset | quiet_flag);
|
|
}
|
|
catch (C4AulParseError &e)
|
|
{
|
|
if (!quiet)
|
|
host->Engine->ErrorHandler->OnError(e.what());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|