From 092a23c2f7a39bbb45c8ad321699f2425c3fe438 Mon Sep 17 00:00:00 2001 From: Nicolas Hake Date: Thu, 12 May 2016 19:43:48 +0200 Subject: [PATCH] Aul: Parse scripts into an AST, then generate bytecode from that This commit contains a fairly substantial rewrite of the C4Script code generator. Instead of generating bytecode while parsing the script, we're now parsing the script into a syntax tree, and have any further processing happen on that instead of the raw source. At this time, the code generator emits the same bytecode as the old parser; there are several optimization opportunities that arise from the new possibility to emit code out of order from its specification by the author. Compared to the old compiler, this one is still rather deficient when dealing with incorrect code; it's also not emitting several warnings that used to be diagnosed. --- CMakeLists.txt | 1 + src/script/C4Aul.h | 1 - src/script/C4AulAST.h | 544 ++++++++++++ src/script/C4AulCompiler.cpp | 1606 +++++++++++++++++++++++++++++++--- src/script/C4AulCompiler.h | 49 +- src/script/C4AulParse.cpp | 1470 ++++++++----------------------- src/script/C4AulParse.h | 68 +- src/script/C4ScriptHost.cpp | 5 + src/script/C4ScriptHost.h | 6 + tests/aul/AulTest.cpp | 11 + 10 files changed, 2438 insertions(+), 1323 deletions(-) create mode 100644 src/script/C4AulAST.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fad3bb4b3..268409cc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1066,6 +1066,7 @@ src/script/C4AulExec.h src/script/C4AulFunc.cpp src/script/C4AulFunc.h src/script/C4Aul.h +src/script/C4AulAST.h src/script/C4AulLink.cpp src/script/C4AulParse.cpp src/script/C4AulParse.h diff --git a/src/script/C4Aul.h b/src/script/C4Aul.h index 8d1f0f74b..ccf28d10e 100644 --- a/src/script/C4Aul.h +++ b/src/script/C4Aul.h @@ -49,7 +49,6 @@ public: C4AulParseError(C4ScriptHost *pScript, const char *pMsg); // constructor C4AulParseError(class C4AulParse * state, const char *pMsg); // constructor C4AulParseError(C4AulScriptFunc * Fn, const char *SPos, const char *pMsg); - static C4AulParseError FromSPos(const C4ScriptHost *host, const char *SPos, C4AulScriptFunc *Fn, const char *msg, const char *Idtf = nullptr, bool Warn = false); }; // execution error diff --git a/src/script/C4AulAST.h b/src/script/C4AulAST.h new file mode 100644 index 000000000..78c09c084 --- /dev/null +++ b/src/script/C4AulAST.h @@ -0,0 +1,544 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2016, 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. + */ + +// C4Aul abstract syntax tree nodes + +#ifndef INC_C4AulAST +#define INC_C4AulAST + +#include + +#include +#include + +#include "script/C4Value.h" + +namespace aul { namespace ast { + class Noop; + class StringLit; + class IntLit; + class BoolLit; + class ArrayLit; + class ProplistLit; + class NilLit; + class ThisLit; + class VarExpr; + class UnOpExpr; + class BinOpExpr; + class SubscriptExpr; + class SliceExpr; + class CallExpr; + class ParExpr; + class FunctionExpr; + class Block; + class Return; + class ForLoop; + class RangeLoop; + class DoLoop; + class WhileLoop; + class Break; + class Continue; + class If; + class VarDecl; + class FunctionDecl; + class IncludePragma; + class AppendtoPragma; + class Script; +}} + +namespace aul { +class AstVisitor +{ +public: + virtual ~AstVisitor() {} + + virtual void visit(const ::aul::ast::Noop *) {} + virtual void visit(const ::aul::ast::StringLit *) {} + virtual void visit(const ::aul::ast::IntLit *) {} + virtual void visit(const ::aul::ast::BoolLit *) {} + virtual void visit(const ::aul::ast::ArrayLit *) {} + virtual void visit(const ::aul::ast::ProplistLit *) {} + virtual void visit(const ::aul::ast::NilLit *) {} + virtual void visit(const ::aul::ast::ThisLit *) {} + virtual void visit(const ::aul::ast::VarExpr *n) {} + virtual void visit(const ::aul::ast::UnOpExpr *) {} + virtual void visit(const ::aul::ast::BinOpExpr *) {} + virtual void visit(const ::aul::ast::SubscriptExpr *) {} + virtual void visit(const ::aul::ast::SliceExpr *) {} + virtual void visit(const ::aul::ast::CallExpr *) {} + virtual void visit(const ::aul::ast::ParExpr *) {} + virtual void visit(const ::aul::ast::Block *) {} + virtual void visit(const ::aul::ast::Return *) {} + virtual void visit(const ::aul::ast::ForLoop *) {} + virtual void visit(const ::aul::ast::RangeLoop *) {} + virtual void visit(const ::aul::ast::DoLoop *) {} + virtual void visit(const ::aul::ast::WhileLoop *) {} + virtual void visit(const ::aul::ast::Break *) {} + virtual void visit(const ::aul::ast::Continue *) {} + virtual void visit(const ::aul::ast::If *) {} + virtual void visit(const ::aul::ast::VarDecl *) {} + virtual void visit(const ::aul::ast::FunctionDecl *) {} + virtual void visit(const ::aul::ast::FunctionExpr *) {} + virtual void visit(const ::aul::ast::IncludePragma *) {} + virtual void visit(const ::aul::ast::AppendtoPragma *) {} + virtual void visit(const ::aul::ast::Script *) {} + + // This template will catch any type missing from the list above + // to ensure that the nodes don't accidentally get visited via a + // base class instead + template + void visit(const T *) = delete; +}; +} + +namespace aul { namespace ast { + +#define AST_NODE(cls) \ + public: \ + virtual void accept(::aul::AstVisitor *v) const override { v->visit(this); } \ + template static std::unique_ptr New(const char *loc, T &&...t) { auto n = std::make_unique(std::forward(t)...); n->loc = loc; return n; } \ + private: + +class Node +{ +public: + virtual ~Node() {} + + struct Location + { + std::string file; + size_t line = 0; + size_t column = 0; + }; + + const char *loc; + + virtual void accept(::aul::AstVisitor *) const = 0; +}; + +class Stmt : public Node +{ +public: + // Does executing this statement generate a return value? + virtual bool has_value() const { return false; } +}; + +typedef std::unique_ptr StmtPtr; + +class Noop : public Stmt +{ + AST_NODE(Noop); +}; + +class Expr : public Stmt +{ +public: + virtual bool has_value() const override { return true; } +}; +typedef std::unique_ptr ExprPtr; + +class Literal : public Expr +{}; + +class StringLit : public Literal +{ + AST_NODE(StringLit); +public: + explicit StringLit(const std::string &value) : value(value) {} + std::string value; +}; + +class IntLit : public Literal +{ + AST_NODE(IntLit); +public: + explicit IntLit(int32_t value) : value(value) {} + uint32_t value; +}; + +class BoolLit : public Literal +{ + AST_NODE(BoolLit); +public: + explicit BoolLit(bool value) : value(value) {} + bool value; +}; + +class ArrayLit : public Literal +{ + AST_NODE(ArrayLit); +public: + std::vector values; +}; + +class ProplistLit : public Literal +{ + AST_NODE(ProplistLit); +public: + std::vector> values; +}; + +class NilLit : public Literal +{ + AST_NODE(NilLit); +}; + +class ThisLit : public Literal +{ + AST_NODE(ThisLit); +}; + +class VarExpr : public Expr +{ + AST_NODE(VarExpr); +public: + explicit VarExpr(const std::string &identifier) : identifier(identifier) {} + std::string identifier; +}; + +class UnOpExpr : public Expr +{ + AST_NODE(UnOpExpr); +public: + UnOpExpr(int op, ExprPtr &&operand) : op(op), operand(std::move(operand)) {} + ExprPtr operand; + int op; // TODO: Make this a proper operator type +}; + +class BinOpExpr : public Expr +{ + AST_NODE(BinOpExpr); +public: + BinOpExpr(int op, ExprPtr &&lhs, ExprPtr &&rhs) : op(op), lhs(std::move(lhs)), rhs(std::move(rhs)) {} + ExprPtr lhs, rhs; + int op; // TODO: Make this a proper operator type + + // Marker for '=' + enum { AssignmentOp = -1 }; +}; + +class SubscriptExpr : public Expr +{ + AST_NODE(SubscriptExpr); +public: + SubscriptExpr(ExprPtr &&object, ExprPtr &&index) : object(std::move(object)), index(std::move(index)) {} + ExprPtr object, index; +}; + +class SliceExpr : public Expr +{ + AST_NODE(SliceExpr); +public: + SliceExpr(ExprPtr &&object, ExprPtr &&start, ExprPtr &&end) : object(std::move(object)), start(std::move(start)), end(std::move(end)) {} + ExprPtr object, start, end; +}; + +class CallExpr : public Expr +{ + AST_NODE(CallExpr); +public: + bool safe_call = false; // Will this call fail gracefully when the function doesn't exist? + bool append_unnamed_pars = false; // Will this call append all unnamed parameters of the current function? + ExprPtr context; + std::vector args; + std::string callee; +}; + +class ParExpr : public Expr +{ + AST_NODE(ParExpr); +public: + explicit ParExpr(ExprPtr &&arg) : arg(std::move(arg)) {} + ExprPtr arg; +}; + +class Block : public Stmt +{ + AST_NODE(Block); +public: + std::vector children; +}; + +class ControlFlow : public Stmt +{}; + +class Return : public ControlFlow +{ + AST_NODE(Return); +public: + explicit Return(ExprPtr &&value) : value(std::move(value)) {} + ExprPtr value; +}; + +class Loop : public ControlFlow +{ +public: + ExprPtr cond; + StmtPtr body; +}; +typedef std::unique_ptr LoopPtr; + +class ForLoop : public Loop +{ + AST_NODE(ForLoop); +public: + StmtPtr init; + ExprPtr incr; +}; + +class RangeLoop : public Loop +{ + AST_NODE(RangeLoop); +public: + std::string var; + bool scoped_var = false; +}; + +class DoLoop : public Loop +{ + AST_NODE(DoLoop); +}; + +class WhileLoop : public Loop +{ + AST_NODE(WhileLoop); +}; + +class LoopControl : public ControlFlow +{}; + +class Break : public LoopControl +{ + AST_NODE(Break); +}; + +class Continue : public LoopControl +{ + AST_NODE(Continue); +}; + +class If : public ControlFlow +{ + AST_NODE(If); +public: + ExprPtr cond; + StmtPtr iftrue, iffalse; +}; + +class Decl : public Stmt +{}; +typedef std::unique_ptr DeclPtr; + +class VarDecl : public Decl +{ + AST_NODE(VarDecl); +public: + enum class Scope + { + Func, + Object, + Global + }; + + Scope scope; + bool constant; + + struct Var + { + std::string name; + ExprPtr init; + }; + std::vector decls; +}; + +class Function +{ +public: + struct Parameter + { + std::string name; + C4V_Type type; + explicit Parameter(const std::string &name, C4V_Type type = C4V_Any) : name(name), type(type) {} + }; + std::vector params; + bool has_unnamed_params = false; + std::unique_ptr body; + + virtual ~Function() = default; + virtual void accept(::aul::AstVisitor *v) const = 0; +}; + +class FunctionDecl : public Decl, public Function +{ + AST_NODE(FunctionDecl); +public: + explicit FunctionDecl(const std::string &name) : name(name) {} + std::string name; + bool is_global = false; +}; + +class FunctionExpr : public Expr, public Function +{ + // This node is used for constant proplists + AST_NODE(FunctionExpr); +public: +}; + +class Pragma : public Decl +{}; + +class IncludePragma : public Pragma +{ + AST_NODE(IncludePragma); +public: + explicit IncludePragma(const std::string &what) : what(what) {} + std::string what; +}; + +class AppendtoPragma : public Pragma +{ + AST_NODE(AppendtoPragma); +public: + AppendtoPragma() = default; + explicit AppendtoPragma(const std::string &what) : what(what) {} + std::string what; +}; + +class Script : public Node +{ + AST_NODE(Script); +public: + virtual ~Script() {} + std::vector declarations; +}; + +#undef AST_NODE + +}} + +namespace aul { +// A recursive visitor that visits the children of all nodes. Override the visit() functions you're interested in in child classes. +class DefaultRecursiveVisitor : public AstVisitor +{ +public: + virtual ~DefaultRecursiveVisitor() {} + + using AstVisitor::visit; + + virtual void visit(const ::aul::ast::ArrayLit *n) override + { + for (const auto &c : n->values) + c->accept(this); + } + virtual void visit(const ::aul::ast::ProplistLit *n) override + { + for (const auto &c : n->values) + c.second->accept(this); + } + virtual void visit(const ::aul::ast::UnOpExpr *n) override + { + n->operand->accept(this); + } + virtual void visit(const ::aul::ast::BinOpExpr *n) override + { + n->lhs->accept(this); + n->rhs->accept(this); + } + virtual void visit(const ::aul::ast::SubscriptExpr *n) override + { + n->object->accept(this); + n->index->accept(this); + } + virtual void visit(const ::aul::ast::SliceExpr *n) override + { + n->object->accept(this); + n->start->accept(this); + n->end->accept(this); + } + virtual void visit(const ::aul::ast::CallExpr *n) override + { + if (n->context) + n->context->accept(this); + for (const auto &a : n->args) + a->accept(this); + } + virtual void visit(const ::aul::ast::ParExpr *n) override + { + n->arg->accept(this); + } + virtual void visit(const ::aul::ast::Block *n) override + { + for (const auto &s : n->children) + s->accept(this); + } + virtual void visit(const ::aul::ast::Return *n) override + { + n->value->accept(this); + } + virtual void visit(const ::aul::ast::ForLoop *n) override + { + if (n->init) + n->init->accept(this); + if (n->cond) + n->cond->accept(this); + if (n->incr) + n->incr->accept(this); + n->body->accept(this); + } + virtual void visit(const ::aul::ast::RangeLoop *n) override + { + n->cond->accept(this); + n->body->accept(this); + } + virtual void visit(const ::aul::ast::DoLoop *n) override + { + n->body->accept(this); + n->cond->accept(this); + } + virtual void visit(const ::aul::ast::WhileLoop *n) override + { + n->cond->accept(this); + n->body->accept(this); + } + virtual void visit(const ::aul::ast::If *n) override + { + n->cond->accept(this); + n->iftrue->accept(this); + if (n->iffalse) + n->iffalse->accept(this); + } + virtual void visit(const ::aul::ast::VarDecl *n) override + { + for (const auto &d : n->decls) + if (d.init) + d.init->accept(this); + } + virtual void visit(const ::aul::ast::FunctionDecl *n) override + { + n->body->accept(this); + } + virtual void visit(const ::aul::ast::FunctionExpr *n) override + { + n->body->accept(this); + } + virtual void visit(const ::aul::ast::Script *n) override + { + for (const auto &d : n->declarations) + d->accept(this); + } +}; +} + +#endif diff --git a/src/script/C4AulCompiler.cpp b/src/script/C4AulCompiler.cpp index 693807bdd..8bec719c2 100644 --- a/src/script/C4AulCompiler.cpp +++ b/src/script/C4AulCompiler.cpp @@ -1,26 +1,513 @@ /* -* OpenClonk, http://www.openclonk.org -* -* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ -* Copyright (c) 2009-2016, 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. -*/ + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ + * Copyright (c) 2009-2016, 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/C4AulScriptFunc.h" +#include -static int GetStackValue(C4AulBCCType eType, intptr_t X) +#include "script/C4Aul.h" +#include "script/C4AulParse.h" +#include "script/C4AulScriptFunc.h" +#include "script/C4ScriptHost.h" + +#define C4AUL_Inherited "inherited" +#define C4AUL_SafeInherited "_inherited" +#define C4AUL_DebugBreak "__debugbreak" + +#undef NDEBUG +#include + +static std::string vstrprintf(const char *format, va_list args) +{ + va_list argcopy; + va_copy(argcopy, args); + int size = vsnprintf(nullptr, 0, format, argcopy); + if (size < 0) + throw std::invalid_argument("invalid argument to strprintf"); + va_end(argcopy); + std::string s; + s.resize(size + 1); + size = vsnprintf(&s[0], s.size(), format, args); + assert(size >= 0); + s.resize(size); + return s; +} + +static std::string strprintf(const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string s = vstrprintf(format, args); + va_end(args); + return s; +} + +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) + { + s += strprintf(" (as #appendto/#include to %s)", target_host->ScriptName.getData()); + } + return s; +} + +template +static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const char *SPos, const C4AulScriptFunc *func, const char *msg, T &&...args) +{ + std::string message = "WARNING: "; + + message += sizeof...(T) > 0 ? strprintf(msg, std::forward(args)...) : msg; + message += FormatCodePosition(host, SPos, target_host, func); + + ++::ScriptEngine.warnCnt; + DebugLog(message.c_str()); +} + +template +static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const ::aul::ast::Node *n, const C4AulScriptFunc *func, const char *msg, T &&...args) +{ + return Warn(target_host, host, n->loc, func, msg, std::forward(args)...); +} +template +static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const std::nullptr_t &, const C4AulScriptFunc *func, const char *msg, T &&...args) +{ + return Warn(target_host, host, static_cast(nullptr), func, msg, std::forward(args)...); +} + +template +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(args)...) : msg; + + message += FormatCodePosition(host, SPos, target_host, func); + return C4AulParseError(static_cast(nullptr), message.c_str()); +} + +template +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(args)...); +} +template +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(nullptr), func, msg, std::forward(args)...); +} + +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) {} + + virtual ~PreparseAstVisitor() {} + + using DefaultRecursiveVisitor::visit; + virtual void visit(const ::aul::ast::RangeLoop *n) override; + virtual void visit(const ::aul::ast::VarDecl *n) override; + virtual void visit(const ::aul::ast::FunctionDecl *n) override; + virtual void visit(const ::aul::ast::CallExpr *n) override; + virtual void visit(const ::aul::ast::ParExpr *n) override; + virtual void visit(const ::aul::ast::AppendtoPragma *n) override; + virtual void visit(const ::aul::ast::IncludePragma *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 continues; + std::vector breaks; + + enum class Control + { + Continue, + Break + }; + }; + + std::stack active_loops; + + 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 + void MaybePopValueOf(const std::unique_ptr &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); + +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) {} + + virtual ~CodegenAstVisitor() {} + + using DefaultRecursiveVisitor::visit; + virtual void visit(const ::aul::ast::Noop *) override; + virtual void visit(const ::aul::ast::StringLit *n) override; + virtual void visit(const ::aul::ast::IntLit *n) override; + virtual void visit(const ::aul::ast::BoolLit *n) override; + virtual void visit(const ::aul::ast::ArrayLit *n) override; + virtual void visit(const ::aul::ast::ProplistLit *n) override; + virtual void visit(const ::aul::ast::NilLit *n) override; + virtual void visit(const ::aul::ast::ThisLit *n) override; + virtual void visit(const ::aul::ast::VarExpr *n) override; + virtual void visit(const ::aul::ast::UnOpExpr *n) override; + virtual void visit(const ::aul::ast::BinOpExpr *n) override; + virtual void visit(const ::aul::ast::SubscriptExpr *n) override; + virtual void visit(const ::aul::ast::SliceExpr *n) override; + virtual void visit(const ::aul::ast::CallExpr *n) override; + virtual void visit(const ::aul::ast::ParExpr *n) override; + virtual void visit(const ::aul::ast::Block *n) override; + virtual void visit(const ::aul::ast::Return *n) override; + virtual void visit(const ::aul::ast::ForLoop *n) override; + virtual void visit(const ::aul::ast::RangeLoop *n) override; + virtual void visit(const ::aul::ast::DoLoop *n) override; + virtual void visit(const ::aul::ast::WhileLoop *n) override; + virtual void visit(const ::aul::ast::Break *n) override; + virtual void visit(const ::aul::ast::Continue *n) override; + virtual void visit(const ::aul::ast::If *n) override; + virtual void visit(const ::aul::ast::VarDecl *n) override; + virtual void visit(const ::aul::ast::FunctionDecl *n) override; + + template + void EmitFunctionCode(const T *n) { EmitFunctionCode(n, 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 throw + // ExpressionNotConstant. + IgnoreUnset = 1 + }; + typedef int EvalFlags; + + // Evaluates constant AST subtrees and returns the final C4Value. + // Throws 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; + + struct ProplistMagic + { + bool active = false; + C4PropListStatic *parent = nullptr; + std::string key; + + ProplistMagic() = default; + ProplistMagic(bool active, C4PropListStatic *parent, const std::string &key) : active(active), parent(parent), key(key) {} + } proplist_magic; + + explicit ConstexprEvaluator(C4ScriptHost *host) : host(host) {} + + NORETURN void nonconst(const ::aul::ast::Node *n) const + { + throw ExpressionNotConstant(host, n, nullptr, nullptr); + } + + 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, "operator \"%s\": got %s, but expected %s", opname, v.GetTypeName(), GetC4VName(Type1)); + } +public: + class ExpressionNotConstant : public C4AulParseError + { + public: + ExpressionNotConstant(const C4ScriptHost *host, const ::aul::ast::Node *n, C4AulScriptFunc *Fn, const char *expr) : + C4AulParseError(Error(host, host, n, Fn, "expression not constant: %s", expr)) {} + }; + + using AstVisitor::visit; + virtual void visit(const ::aul::ast::StringLit *n) override; + virtual void visit(const ::aul::ast::IntLit *n) override; + virtual void visit(const ::aul::ast::BoolLit *n) override; + virtual void visit(const ::aul::ast::ArrayLit *n) override; + virtual void visit(const ::aul::ast::ProplistLit *n) override; + virtual void visit(const ::aul::ast::NilLit *) override; + virtual void visit(const ::aul::ast::ThisLit *n) override; + virtual void visit(const ::aul::ast::VarExpr *n) override; + virtual void visit(const ::aul::ast::UnOpExpr *n) override; + virtual void visit(const ::aul::ast::BinOpExpr *n) override; + virtual void visit(const ::aul::ast::SubscriptExpr *n) override; + virtual void visit(const ::aul::ast::SliceExpr *n) override; + virtual void visit(const ::aul::ast::CallExpr *n) override; + virtual void visit(const ::aul::ast::FunctionExpr *n) override; +}; + +class C4AulCompiler::ConstantResolver : public ::aul::DefaultRecursiveVisitor +{ + C4ScriptHost *host; + explicit ConstantResolver(C4ScriptHost *host) : host(host) {} + +public: + 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); + } + virtual ~ConstantResolver() {} + + using DefaultRecursiveVisitor::visit; + 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(host, script); +} + +void C4AulCompiler::Compile(C4ScriptHost *host, C4ScriptHost *source_host, const ::aul::ast::Script *script) +{ + ConstantResolver::resolve(host, script); + + fprintf(stderr, "parsing %s...\n", source_host->FilePath.getData()); + CodegenAstVisitor v(host, source_host); + v.visit(script); +} + +void C4AulCompiler::Compile(C4AulScriptFunc *func, const ::aul::ast::Function *def) +{ + CodegenAstVisitor v(func); + // Don't visit the whole definition here; that would create a new function + // and we don't want that. + def->body->accept(&v); +} + +#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, "Implicit declaration of the loop variable in a for-in loop is deprecated: %s", 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, "Non-global variables cannot be constant"); + } + 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->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || target_host->Engine->GlobalConstNames.GetItemNr(cname) >= 0) + Warn(target_host, host, n, Fn, "function-local variable hides a global variable: %s", cname); + C4String *s = ::Strings.FindString(cname); + if (s && target_host->GetPropList()->HasProperty(s)) + Warn(target_host, host, n, Fn, "function-local variable hides an object-local variable: %s", cname); + 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, "object-local variable hides a global variable: %s", cname); + C4String *s = ::Strings.RegString(cname); + if (target_host->GetPropList()->HasProperty(s)) + Warn(target_host, host, n, Fn, "object-local variable declared multiple times: %s", 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, "global variable declared multiple times: %s", cname); + if (n->constant) + host->Engine->GlobalConstNames.AddName(cname); + else + host->Engine->GlobalNamedNames.AddName(cname); + break; + } + } + + 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); + for (const auto ¶m : n->params) + { + Fn->AddPar(param.name.c_str()); + } + 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)); + + DefaultRecursiveVisitor::visit(n); + + Fn = nullptr; +} + +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, "using Par() inside a function forces it to take variable arguments"); + 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()); +} + +int C4AulCompiler::CodegenAstVisitor::GetStackValue(C4AulBCCType eType, intptr_t X) { switch (eType) { @@ -112,12 +599,12 @@ static int GetStackValue(C4AulBCCType eType, intptr_t X) return 0; } -int C4AulCompiler::AddVarAccess(const char * TokenSPos, C4AulBCCType eType, intptr_t varnum) +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::AddBCC(const char * TokenSPos, C4AulBCCType eType, intptr_t X) +int C4AulCompiler::CodegenAstVisitor::AddBCC(const char *TokenSPos, C4AulBCCType eType, intptr_t X) { // Track stack size stack_height += GetStackValue(eType, X); @@ -129,6 +616,8 @@ int C4AulCompiler::AddBCC(const char * TokenSPos, C4AulBCCType eType, intptr_t X X = 1; } + assert(eType != AB_STACK || X != 0); + // Join checks only if it's not a jump target if (!at_jump_target && Fn->GetLastCode()) { @@ -230,7 +719,7 @@ int C4AulCompiler::AddBCC(const char * TokenSPos, C4AulBCCType eType, intptr_t X return Fn->GetCodePos() - 1; } -void C4AulCompiler::RemoveLastBCC() +void C4AulCompiler::CodegenAstVisitor::RemoveLastBCC() { // Security: This is unsafe on anything that might get optimized away C4AulBCC *pBCC = Fn->GetLastCode(); @@ -241,61 +730,14 @@ void C4AulCompiler::RemoveLastBCC() Fn->RemoveLastBCC(); } -C4V_Type C4AulCompiler::GetLastRetType(C4AulScriptEngine * Engine, C4V_Type to) +int C4AulCompiler::CodegenAstVisitor::AddBCC(const char *SPos, const C4AulBCC &bcc) { - C4V_Type from; - switch (Fn->GetLastCode()->bccType) - { - case AB_INT: from = Config.Developer.ExtraWarnings || Fn->GetLastCode()->Par.i ? C4V_Int : C4V_Any; break; - case AB_STRING: from = C4V_String; break; - case AB_NEW_ARRAY: case AB_CARRAY: case AB_ARRAY_SLICE: from = C4V_Array; break; - case AB_CFUNCTION: from = C4V_Function; break; - case AB_NEW_PROPLIST: case AB_CPROPLIST: from = C4V_PropList; break; - case AB_BOOL: from = C4V_Bool; break; - case AB_FUNC: - from = Fn->GetLastCode()->Par.f->GetRetType(); break; - case AB_CALL: case AB_CALLFS: - { - C4String * pName = Fn->GetLastCode()->Par.s; - C4AulFunc * pFunc2 = Engine->GetFirstFunc(pName->GetCStr()); - bool allwarn = true; - from = C4V_Any; - while (pFunc2 && allwarn) - { - from = pFunc2->GetRetType(); - if (!C4Value::WarnAboutConversion(from, to)) - { - allwarn = false; - from = C4V_Any; - } - pFunc2 = Engine->GetNextSNFunc(pFunc2); - } - break; - } - case AB_Inc: case AB_Dec: case AB_BitNot: case AB_Neg: - 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_BitAnd: case AB_BitXOr: case AB_BitOr: - from = C4V_Int; break; - case AB_Not: case AB_LessThan: case AB_LessThanEqual: case AB_GreaterThan: case AB_GreaterThanEqual: - case AB_Equal: case AB_NotEqual: - from = C4V_Bool; break; - case AB_DUP: - { - int pos = Fn->GetLastCode()->Par.i + stack_height - 2 + Fn->VarNamed.iSize + Fn->GetParCount(); - if (pos < Fn->GetParCount()) - from = Fn->GetParType()[pos]; - else - from = C4V_Any; - break; - } - default: - from = C4V_Any; break; - } - return from; + return AddBCC(SPos, bcc.bccType, bcc.Par.X); } -C4AulBCC C4AulCompiler::MakeSetter(const char * SPos, bool fLeaveValue) +C4AulBCC C4AulCompiler::CodegenAstVisitor::MakeSetter(const char *SPos, bool fLeaveValue) { + assert(Fn); C4AulBCC Value = *(Fn->GetLastCode()), Setter = Value; // Check type switch (Value.bccType) @@ -316,7 +758,7 @@ C4AulBCC C4AulCompiler::MakeSetter(const char * SPos, bool fLeaveValue) break; case AB_GLOBALN: Setter.bccType = AB_GLOBALN_SET; break; default: - throw C4AulParseError(Fn, SPos, "assignment to a constant"); + 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. @@ -349,98 +791,992 @@ C4AulBCC C4AulCompiler::MakeSetter(const char * SPos, bool fLeaveValue) return Setter; } -int C4AulCompiler::JumpHere() +int C4AulCompiler::CodegenAstVisitor::AddJumpTarget() { - // Set flag so the next generated code chunk won't get joined + 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(); } -static bool IsJump(C4AulBCCType t) +void C4AulCompiler::CodegenAstVisitor::UpdateJump(int jump, int target) { - return t == AB_JUMP || t == AB_JUMPAND || t == AB_JUMPOR || t == AB_JUMPNNIL || t == AB_CONDN || t == AB_COND; + C4AulBCC *code = Fn->GetCodeByPos(jump); + assert(IsJump(code->bccType)); + code->Par.i = target - jump; } -void C4AulCompiler::SetJumpHere(int iJumpOp) +void C4AulCompiler::CodegenAstVisitor::AddJumpTo(const char *loc, C4AulBCCType type, int target) { - // Set target - C4AulBCC *pBCC = Fn->GetCodeByPos(iJumpOp); - assert(IsJump(pBCC->bccType)); - pBCC->Par.i = Fn->GetCodePos() - iJumpOp; - // Set flag so the next generated code chunk won't get joined - at_jump_target = true; + AddBCC(loc, type, target - Fn->GetCodePos()); } -void C4AulCompiler::SetJump(int iJumpOp, int iWhere) +void C4AulCompiler::CodegenAstVisitor::PushLoop() { - // Set target - C4AulBCC *pBCC = Fn->GetCodeByPos(iJumpOp); - assert(IsJump(pBCC->bccType)); - pBCC->Par.i = iWhere - iJumpOp; + active_loops.emplace(stack_height); } -void C4AulCompiler::AddJump(const char * SPos, C4AulBCCType eType, int iWhere) +void C4AulCompiler::CodegenAstVisitor::PopLoop(int continue_target) { - AddBCC(SPos, eType, iWhere - Fn->GetCodePos()); + 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::PushLoop() +void C4AulCompiler::CodegenAstVisitor::AddLoopControl(const char *loc, Loop::Control c) { - Loop *pNew = new Loop(); - pNew->StackSize = stack_height; - pNew->Controls = NULL; - pNew->Next = active_loops; - active_loops = pNew; + 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::PopLoop(int ContinueJump) +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Noop *) {} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::StringLit *n) { - // Set targets for break/continue - for (Loop::Control *pCtrl = active_loops->Controls; pCtrl; pCtrl = pCtrl->Next) - if (pCtrl->Break) - SetJumpHere(pCtrl->Pos); + AddBCC(n->loc, AB_STRING, (intptr_t)::Strings.RegString(n->value.c_str())); +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::IntLit *n) +{ + AddBCC(n->loc, AB_INT, n->value); +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::BoolLit *n) +{ + AddBCC(n->loc, AB_BOOL, n->value); +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ArrayLit *n) +{ + for (const auto &e : n->values) + { + e->accept(this); + } + AddBCC(n->loc, AB_NEW_ARRAY, n->values.size()); +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ProplistLit *n) +{ + for (const auto &e : n->values) + { + AddBCC(n->loc, AB_STRING, (intptr_t)::Strings.RegString(e.first.c_str())); + e.second->accept(this); + } + AddBCC(n->loc, AB_NEW_PROPLIST, n->values.size()); +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::NilLit *n) +{ + AddBCC(n->loc, AB_NIL); +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ThisLit *n) +{ + AddBCC(n->loc, AB_THIS); +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::VarExpr *n) +{ + assert(Fn); + const char *cname = n->identifier.c_str(); + C4String *interned = ::Strings.FindString(cname); + // 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. + if (Fn->ParNamed.GetItemNr(cname) != -1) + { + AddVarAccess(n->loc, AB_DUP, -Fn->GetParCount() + Fn->ParNamed.GetItemNr(cname)); + } + else if (Fn->VarNamed.GetItemNr(cname) != -1) + { + AddVarAccess(n->loc, AB_DUP, Fn->VarNamed.GetItemNr(cname)); + } + else if (Fn->Parent && interned && Fn->Parent->HasProperty(interned)) + { + 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(v._getPropList())); + break; + case C4V_String: + AddBCC(n->loc, AB_STRING, reinterpret_cast(v._getStr())); + break; + case C4V_Array: + AddBCC(n->loc, AB_CARRAY, reinterpret_cast(v._getArray())); + break; + case C4V_Function: + AddBCC(n->loc, AB_CFUNCTION, reinterpret_cast(v._getFunction())); + default: + throw Error(target_host, host, n, Fn, "internal error: global constant of unexpected type: %s (of type %s)", cname, v.GetTypeName()); + } + } + else + { + 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) +{ + 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); + } +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::BinOpExpr *n) +{ + n->lhs->accept(this); + + if (n->op == ::aul::ast::BinOpExpr::AssignmentOp) + { + C4AulBCC setter = MakeSetter(n->loc, false); + n->rhs->accept(this); + AddBCC(n->loc, setter); + return; + } + + 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); + n->rhs->accept(this); + UpdateJump(jump, AddJumpTarget()); + } + else if (op.Changer) + { + C4AulBCC setter = MakeSetter(n->loc, true); + n->rhs->accept(this); + AddBCC(n->loc, op.Code); + AddBCC(n->loc, setter); + } + else + { + n->rhs->accept(this); + AddBCC(n->loc, op.Code, 0); + } +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::SubscriptExpr *n) +{ + n->object->accept(this); + n->index->accept(this); + AddBCC(n->loc, AB_ARRAYA); +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::SliceExpr *n) +{ + n->object->accept(this); + n->start->accept(this); + n->end->accept(this); + AddBCC(n->loc, AB_ARRAY_SLICE); +} + +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, "\"%s\" can't be called in a different context", cname); + if (!n->args.empty()) + throw Error(target_host, host, n, Fn, "\"%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); + return; + } + + const auto pre_call_stack = stack_height; + + if (n->context) + n->context->accept(this); + for (const auto &arg : n->args) + arg->accept(this); + + C4AulFunc *callee = nullptr; + + // Special handling for the overload chain + if (n->callee == C4AUL_Inherited || n->callee == C4AUL_SafeInherited) + { + if (n->context) + { + throw Error(target_host, host, n, Fn, "\"%s\" can't be called in a different context", cname); + } + callee = Fn->OwnerOverloaded; + if (!callee) + { + if (n->callee == C4AUL_SafeInherited) + { + // 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); + return; + } + else + { + throw Error(target_host, host, n, Fn, "inherited function not found (use " C4AUL_SafeInherited " to disable this message)"); + } + } + } + + int 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) + callee = target_host->Engine->GetFunc(cname); + + if (callee) + fn_argc = callee->GetParCount(); else - SetJump(pCtrl->Pos, ContinueJump); - // Delete loop controls - Loop *pLoop = active_loops; - while (pLoop->Controls) - { - // Unlink - Loop::Control *pCtrl = pLoop->Controls; - pLoop->Controls = pCtrl->Next; - // Delete - delete pCtrl; + throw Error(target_host, host, n, Fn, "called function not found: %s", cname); } - // Unlink & delete - active_loops = pLoop->Next; - delete pLoop; + + 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, + "call to %s passes %d parameters, of which only %d are used", cname, 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()); + } + } + + if (n->context) + { + AddBCC(n->loc, n->safe_call ? AB_CALLFS : AB_CALL, (intptr_t)::Strings.RegString(cname)); + } + else + { + assert(callee); + AddBCC(n->loc, AB_FUNC, (intptr_t)callee); + } + + // We leave one value (the return value) on the stack + assert(pre_call_stack + 1 == stack_height); } -void C4AulCompiler::AddLoopControl(const char * SPos, bool fBreak) +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ParExpr *n) { - // Insert code - if (active_loops->StackSize != stack_height) - AddBCC(SPos, AB_STACK, active_loops->StackSize - stack_height); - Loop::Control *pNew = new Loop::Control(); - pNew->Break = fBreak; - pNew->Pos = Fn->GetCodePos(); - pNew->Next = active_loops->Controls; - active_loops->Controls = pNew; - AddBCC(SPos, AB_JUMP); + n->arg->accept(this); + AddBCC(n->loc, AB_PAR); } -void C4AulCompiler::ErrorOut(const char * SPos, C4AulError & e) +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Block *n) { - // make all jumps that don't have their destination yet jump here - for (unsigned int i = 0; i < Fn->Code.size(); i++) + for (const auto &s : n->children) { - C4AulBCC *pBCC = &Fn->Code[i]; - if (IsJump(pBCC->bccType)) - if (!pBCC->Par.i) - pBCC->Par.i = Fn->Code.size() - i; + const auto pre_statement_stack = stack_height; + s->accept(this); + // If the statement has left a stack value, pop it off + MaybePopValueOf(s); + assert(pre_statement_stack == stack_height); + } +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::Return *n) +{ + n->value->accept(this); + AddBCC(n->loc, AB_RETURN); +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::ForLoop *n) +{ +#if 0 + // Bytecode arranged like this: + // initializer + // cond: condition + // CONDN exit + // body: body + // incr: incrementor + // JUMP cond + // exit: + // + // continue jumps to incr + // break jumps to exit + + if (n->init) + { + n->init->accept(this); + MaybePopValueOf(n->init); + } + int cond = -1, condition_jump = -1; + PushLoop(); + if (n->cond) + { + cond = AddJumpTarget(); + n->cond->accept(this); + active_loops.top().breaks.push_back(AddBCC(n->cond->loc, AB_CONDN)); + } + + int body = AddJumpTarget(); + if (!n->cond) + cond = body; + n->body->accept(this); + MaybePopValueOf(n->body); + + int incr = -1; + if (n->incr) + { + incr = AddJumpTarget(); + n->incr->accept(this); + MaybePopValueOf(n->incr); + } + else + { + // If no incrementor exists, just jump straight to the condition + incr = cond; + } + // start the next iteration of the loop + AddJumpTo(AB_JUMP, cond); + PopLoop(incr); +#else + // Bytecode arranged like this: + // initializer + // cond: condition + // CONDN exit + // JUMP body + // incr: incrementor + // JUMP cond + // body: body + // JUMP incr + // exit: + // + // continue jumps to incr + // break jumps to exit + + if (n->init) + { + n->init->accept(this); + MaybePopValueOf(n->init); + } + PushLoop(); + int cond = AddJumpTarget(); + if (n->cond) + { + n->cond->accept(this); + active_loops.top().breaks.push_back(AddBCC(n->cond->loc, AB_CONDN)); + } + int incr = cond; + if (n->incr) + { + int cond_jump = AddBCC(n->loc, AB_JUMP); + incr = AddJumpTarget(); + n->incr->accept(this); + MaybePopValueOf(n->incr); + AddJumpTo(n->loc, AB_JUMP, cond); + UpdateJump(cond_jump, AddJumpTarget()); + } + n->body->accept(this); + MaybePopValueOf(n->body); + AddJumpTo(n->loc, AB_JUMP, incr); + PopLoop(incr); +#endif +} + +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 + + 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 + n->cond->accept(this); + // 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 + n->body->accept(this); + 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); + + Fn->ClearCode(); + + // Reserve var stack space + if (Fn->VarNamed.iSize > 0) + AddBCC(n->loc, AB_STACK, Fn->VarNamed.iSize); + stack_height = 0; + + 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) +{ + int body = AddJumpTarget(); + PushLoop(); + try + { + n->body->accept(this); + MaybePopValueOf(n->body); + int cond = AddJumpTarget(); + n->cond->accept(this); + AddJumpTo(n->loc, AB_COND, body); + PopLoop(cond); + } + catch (...) + { + PopLoop(AddJumpTarget()); + throw; + } +} + +void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::WhileLoop *n) +{ + int cond = AddJumpTarget(); + PushLoop(); + try + { + n->cond->accept(this); + active_loops.top().breaks.push_back(AddBCC(n->cond->loc, AB_CONDN)); + n->body->accept(this); + MaybePopValueOf(n->body); + // continue starts the next iteration of the loop + AddLoopControl(n->loc, Loop::Control::Continue); + PopLoop(cond); + } + catch (...) + { + PopLoop(AddJumpTarget()); + throw; + } +} + +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) +{ + n->cond->accept(this); + int jump = AddBCC(n->loc, AB_CONDN); + n->iftrue->accept(this); + MaybePopValueOf(n->iftrue); + if (n->iffalse) + { + int jumpout = AddBCC(n->loc, AB_JUMP); + UpdateJump(jump, AddJumpTarget()); + jump = jumpout; + n->iffalse->accept(this); + 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: + if (dec.init) + { + // Emit code for the initializer + dec.init->accept(this); + 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) +{ + C4PropListStatic *Parent = n->is_global ? target_host->Engine->GetPropList() : target_host->GetPropList(); + C4AulFunc *f = Parent->GetFunc(n->name.c_str()); + while (f) + { + if (f->SFunc() && f->SFunc()->pOrgScript == host && f->Parent == Parent) + { + if (Fn) + Warn(target_host, host, n, Fn, "function declared multiple times"); + Fn = f->SFunc(); + } + f = f->SFunc() ? f->SFunc()->OwnerOverloaded : 0; + } + + 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); + } + + EmitFunctionCode(n); + + Fn = nullptr; +} + +#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; + e->accept(&ce); + return ce.v; +} + +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; + e->accept(&ce); + return ce.v; +} + +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(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 p; + + if (proplist_magic.active) + { + p.reset(C4PropList::NewStatic(NULL, proplist_magic.parent, ::Strings.RegString(proplist_magic.key.c_str()))); + } + else + { + p.reset(C4PropList::New()); + } + + // 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 + 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.release()); +} + +void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::NilLit *) { v = C4VNull; } + +void C4AulCompiler::ConstexprEvaluator::visit(const ::aul::ast::ThisLit *n) { nonconst(n); } + +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); +} + +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); + 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) +{ + if (n->op == ::aul::ast::BinOpExpr::AssignmentOp) + nonconst(n); + + assert(n->op > 0); + const auto &op = C4ScriptOpMap[n->op]; + if (op.Changer) + nonconst(n); + + 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::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); +} + +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; + + if (auto func = proplist_magic.parent->GetFunc(proplist_magic.key.c_str())) + { + sfunc = func->SFunc(); + } + 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"); + + 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()); + + CodegenAstVisitor cg(sfunc); + cg.EmitFunctionCode(n); + + v.SetFunction(sfunc); +} + +void C4AulCompiler::ConstantResolver::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: + // Function-scoped declarations and their initializers are handled by CodegenAstVisitor. + break; + case ::aul::ast::VarDecl::Scope::Object: + if (dec.init) + { + assert(host->GetPropList()->IsStatic()); + C4Value v = ConstexprEvaluator::eval_static(host, host->GetPropList()->IsStatic(), dec.name, dec.init.get(), ConstexprEvaluator::IgnoreUnset); + host->GetPropList()->SetPropertyByS(::Strings.RegString(cname), v); + } + else + { + host->GetPropList()->SetPropertyByS(::Strings.RegString(cname), C4VNull); + } + break; + case ::aul::ast::VarDecl::Scope::Global: + if ((dec.init != nullptr) != n->constant) + throw Error(host, host, n->loc, nullptr, "global variable must be either constant or uninitialized: %s", cname); + else if (dec.init) + { + 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); + } + break; + } } - // add an error chunk - const char * msg = e.what(); - if (SEqual2(msg, "ERROR: ")) msg += 7; - AddBCC(SPos, AB_ERR, reinterpret_cast(::Strings.RegString(msg))); } diff --git a/src/script/C4AulCompiler.h b/src/script/C4AulCompiler.h index cfc505424..062ab2b74 100644 --- a/src/script/C4AulCompiler.h +++ b/src/script/C4AulCompiler.h @@ -17,52 +17,21 @@ #ifndef INC_C4AulCompiler #define INC_C4AulCompiler -#include "script/C4Value.h" - -enum C4AulBCCType : int; +#include "script/C4AulAST.h" class C4AulCompiler { public: - C4AulScriptFunc *Fn; - bool at_jump_target = false; - int stack_height = 0; + static void Compile(C4AulScriptFunc *out, const ::aul::ast::Function *f); - int AddBCC(const char * SPos, C4AulBCCType eType, intptr_t X = 0); - void ErrorOut(const char * SPos, class C4AulError & e); - void RemoveLastBCC(); - C4V_Type GetLastRetType(C4AulScriptEngine * Engine, C4V_Type to); // for warning purposes + static void Preparse(C4ScriptHost *out, C4ScriptHost *source, const ::aul::ast::Script *s); + static void Compile(C4ScriptHost *out, C4ScriptHost *source, const ::aul::ast::Script *s); - int AddVarAccess(const char * TokenSPos, C4AulBCCType eType, intptr_t varnum); - C4AulBCC MakeSetter(const char * TokenSPos, bool fLeaveValue = false); // Prepares to generate a setter for the last value that was generated - - int JumpHere(); // Get position for a later jump to next instruction added - void SetJumpHere(int iJumpOp); // Use the next inserted instruction as jump target for the given jump operation - void SetJump(int iJumpOp, int iWhere); - void AddJump(const char * SPos, C4AulBCCType eType, int iWhere); - - // Keep track of loops and break/continue usages - struct Loop - { - struct Control - { - bool Break; - int Pos; - Control *Next; - }; - Control *Controls; - int StackSize; - Loop *Next; - }; - Loop *active_loops = NULL; - - void PushLoop(); - void PopLoop(int ContinueJump); - void AddLoopControl(const char * SPos, bool fBreak); - ~C4AulCompiler() - { - while (active_loops) PopLoop(0); - } +private: + class ConstexprEvaluator; + class ConstantResolver; + class PreparseAstVisitor; + class CodegenAstVisitor; }; #endif diff --git a/src/script/C4AulParse.cpp b/src/script/C4AulParse.cpp index 78a50e4d6..2a2bcff57 100644 --- a/src/script/C4AulParse.cpp +++ b/src/script/C4AulParse.cpp @@ -17,7 +17,9 @@ #include "C4Include.h" #include "script/C4AulParse.h" + #include +#include #include "script/C4Aul.h" #include "script/C4AulDebug.h" @@ -26,6 +28,8 @@ #include "game/C4Game.h" #include "lib/C4Log.h" #include "config/C4Config.h" +#include "script/C4AulAST.h" +#include "script/C4AulCompiler.h" #ifndef DEBUG_BYTECODE_DUMP #define DEBUG_BYTECODE_DUMP 0 @@ -54,8 +58,6 @@ #define C4AUL_Par "Par" #define C4AUL_Break "break" #define C4AUL_Continue "continue" -#define C4AUL_Inherited "inherited" -#define C4AUL_SafeInherited "_inherited" #define C4AUL_this "this" #define C4AUL_GlobalNamed "static" @@ -104,11 +106,10 @@ enum C4AulTokenType : int ATT_EOF // end of file }; -C4AulParse::C4AulParse(C4ScriptHost *a, enum Type Type) : +C4AulParse::C4AulParse(C4ScriptHost *a) : Fn(0), Host(a), pOrgScript(a), Engine(a->Engine), SPos(a->Script.getData()), TokenSPos(SPos), TokenType(ATT_INVALID), - Type(Type), ContextToExecIn(NULL) { } @@ -116,9 +117,8 @@ C4AulParse::C4AulParse(C4AulScriptFunc * Fn, C4AulScriptContext* context, C4AulS Fn(Fn), Host(NULL), pOrgScript(NULL), Engine(Engine), SPos(Fn->Script), TokenSPos(SPos), TokenType(ATT_INVALID), - Type(C4AulParse::PARSER), ContextToExecIn(context) -{ codegen.Fn = Fn; } +{ } C4AulParse::~C4AulParse() { @@ -159,35 +159,6 @@ void C4AulParse::Error(const char *pMsg, ...) throw C4AulParseError(this, Buf.getData()); } -C4AulParseError C4AulParseError::FromSPos(const C4ScriptHost *host, const char *SPos, C4AulScriptFunc *Fn, const char *msg, const char *Idtf, bool Warn) -{ - C4AulParseError e; - e.sMessage.Format("%s: %s%s", - Warn ? "WARNING" : "ERROR", - msg, - Idtf ? Idtf : ""); - - if (Fn && Fn->GetName()) - { - e.sMessage.AppendFormat(" (in %s", Fn->GetName()); - if (host && SPos) - e.sMessage.AppendFormat(", %s:%d:%d)", - host->ScriptName.getData(), - SGetLine(host->GetScript(), SPos), - SLineGetCharacters(host->GetScript(), SPos)); - else - e.sMessage.AppendChar(')'); - } - else if (host && SPos) - { - e.sMessage.AppendFormat(" (%s:%d:%d)", - host->ScriptName.getData(), - SGetLine(host->GetScript(), SPos), - SLineGetCharacters(host->GetScript(), SPos)); - } - return e; -} - void C4AulParse::AppendPosition(StdStrBuf & Buf) { if (Fn && Fn->GetName()) @@ -724,8 +695,10 @@ bool C4ScriptHost::Preparse() // Add any engine functions specific to this script AddEngineFunctions(); - C4AulParse state(this, C4AulParse::PREPARSER); - state.Parse_Script(this); + C4AulParse parser(this); + ast = parser.Parse_Script(this); + + C4AulCompiler::Preparse(this, this, ast.get()); // #include will have to be resolved now... IncludesResolved = false; @@ -738,12 +711,6 @@ bool C4ScriptHost::Preparse() return true; } -void C4AulParse::DebugChunk() -{ - if (C4AulDebug::GetDebugger()) - AddBCC(AB_DEBUG); -} - static const char * GetTokenName(C4AulTokenType TokenType) { switch (TokenType) @@ -797,26 +764,30 @@ void C4AulScriptFunc::ParseFn(C4AulScriptEngine *Engine, C4AulScriptContext* con ClearCode(); // parse C4AulParse state(this, context, Engine); - state.Parse_DirectExec(); + auto func = state.Parse_DirectExec(Script); + C4AulCompiler::Compile(this, func.get()); } -void C4AulParse::Parse_DirectExec() +std::unique_ptr<::aul::ast::FunctionDecl> C4AulParse::Parse_DirectExec(const char *code) { // get first token Shift(); - Parse_Expression(); + auto expr = Parse_Expression(); Match(ATT_EOF); - AddBCC(AB_RETURN); - AddBCC(AB_EOFN); + // Synthesize a wrapping function which we can call + auto func = std::make_unique<::aul::ast::FunctionDecl>("$internal$eval"); + func->body = std::make_unique<::aul::ast::Block>(); + func->body->children.push_back(std::make_unique<::aul::ast::Return>(std::move(expr))); + return func; } -void C4AulParse::Parse_Script(C4ScriptHost * scripthost) +std::unique_ptr<::aul::ast::Script> C4AulParse::Parse_Script(C4ScriptHost * scripthost) { pOrgScript = scripthost; SPos = pOrgScript->Script.getData(); const char * SPos0 = SPos; bool all_ok = true; - bool found_code = false; + auto script = ::aul::ast::Script::New(SPos0); while (true) try { // Go to the next token if the current token could not be processed or no token has yet been parsed @@ -828,19 +799,13 @@ void C4AulParse::Parse_Script(C4ScriptHost * scripthost) switch (TokenType) { case ATT_DIR: - if (found_code) - Warn("Found %s after declarations", Idtf); // check for include statement if (SEqual(Idtf, C4AUL_Include)) { Shift(); // get id of script to include Check(ATT_IDTF, "script name"); - if (Type == PREPARSER) - { - // add to include list - Host->Includes.push_back(StdCopyStrBuf(Idtf)); - } + script->declarations.push_back(::aul::ast::IncludePragma::New(TokenSPos, Idtf)); Shift(); } else if (SEqual(Idtf, C4AUL_Append)) @@ -848,28 +813,22 @@ void C4AulParse::Parse_Script(C4ScriptHost * scripthost) if (pOrgScript->GetPropList()->GetDef()) throw C4AulParseError(this, "#appendto in a Definition"); Shift(); - if (Type == PREPARSER) + // get id of script to include/append + switch (TokenType) { - // get id of script to include/append - StdCopyStrBuf Id; - switch (TokenType) + case ATT_IDTF: + script->declarations.push_back(::aul::ast::AppendtoPragma::New(TokenSPos, Idtf)); + break; + case ATT_OPERATOR: + if (SEqual(C4ScriptOpMap[cInt].Identifier, "*")) { - case ATT_IDTF: - Id = StdCopyStrBuf(Idtf); + script->declarations.push_back(::aul::ast::AppendtoPragma::New(TokenSPos)); break; - case ATT_OPERATOR: - if (SEqual(C4ScriptOpMap[cInt].Identifier, "*")) - { - Id = StdCopyStrBuf("*"); - break; - } - //fallthrough - default: - // -> ID expected - UnexpectedToken("identifier or '*'"); } - // add to append list - Host->Appends.push_back(Id); + //fallthrough + default: + // -> ID expected + UnexpectedToken("identifier or '*'"); } Shift(); } @@ -880,24 +839,18 @@ void C4AulParse::Parse_Script(C4ScriptHost * scripthost) case ATT_IDTF: // need a keyword here to avoid parsing random function contents // after a syntax error in a function - found_code = true; // check for object-local variable definition (local) - if (SEqual(Idtf, C4AUL_LocalNamed)) + if (SEqual(Idtf, C4AUL_LocalNamed) || SEqual(Idtf, C4AUL_GlobalNamed)) { - Parse_Local(); + script->declarations.push_back(Parse_Var()); Match(ATT_SCOLON); } // check for variable definition (static) - else if (SEqual(Idtf, C4AUL_GlobalNamed)) - { - Parse_Static(); - Match(ATT_SCOLON); - } else - Parse_Function(); + script->declarations.push_back(Parse_ToplevelFunctionDecl()); break; case ATT_EOF: - return; + return script; default: UnexpectedToken("declaration"); } @@ -914,16 +867,12 @@ void C4AulParse::Parse_Script(C4ScriptHost * scripthost) ++Engine->errCnt; } all_ok = false; - - if (Fn) - { - codegen.ErrorOut(TokenSPos, err); - } } } -void C4AulParse::Parse_Function() +std::unique_ptr<::aul::ast::FunctionDecl> C4AulParse::Parse_ToplevelFunctionDecl() { + const char *NodeStart = TokenSPos; bool is_global = SEqual(Idtf, C4AUL_Global); // skip access modifier if (SEqual(Idtf, C4AUL_Private) || @@ -940,75 +889,26 @@ void C4AulParse::Parse_Function() Shift(); // get next token, must be func name Check(ATT_IDTF, "function name"); - // check: symbol already in use? - if (is_global || !Host->GetPropList()) - { - if (Host != pOrgScript) - Error("global func in appendto/included script: %s", Idtf); - if (Engine->GlobalNamedNames.GetItemNr(Idtf) != -1) - throw C4AulParseError(this, "function definition: name already in use (global variable)"); - if (Engine->GlobalConstNames.GetItemNr(Idtf) != -1) - Error("function definition: name already in use (global constant)"); - } - // get script fn - C4PropListStatic * Parent; - if (is_global) - Parent = Engine->GetPropList(); - else - Parent = Host->GetPropList(); - Fn = 0; - C4AulFunc * f = Parent->GetFunc(Idtf); - // check: symbol already in use? - if (!f && Strings.FindString(Idtf) && Parent->HasProperty(Strings.FindString(Idtf))) - throw C4AulParseError(this, "function definition: name already in use (local variable)"); - while (f) - { - if (f->SFunc() && f->SFunc()->pOrgScript == pOrgScript && f->Parent == Parent) - { - if (Fn) - Warn("Duplicate function %s", Idtf); - Fn = f->SFunc(); - } - f = f->SFunc() ? f->SFunc()->OwnerOverloaded : 0; - } - // first preparser run or a new func in a reloaded script - if (!Fn && Type == PREPARSER) - { - Fn = new C4AulScriptFunc(Parent, pOrgScript, Idtf, SPos); - Fn->SetOverloaded(Parent->GetFunc(Fn->Name)); - Parent->SetPropertyByS(Fn->Name, C4VFunction(Fn)); - } + + auto func = ::aul::ast::FunctionDecl::New(NodeStart, Idtf); + func->is_global = is_global; Shift(); - Parse_FuncBody(); + Parse_Function(func.get()); + return func; } -void C4AulParse::Parse_FuncBody() +void C4AulParse::Parse_Function(::aul::ast::Function *func) { - // Parse function body - assert(Fn); - codegen.Fn = Fn; - if (Type == PREPARSER) - { - // This might be a reload, clear all parameters and local vars - Fn->ParCount = 0; - Fn->ParNamed.Reset(); - Fn->VarNamed.Reset(); - } - else if (Type == PARSER) - { - Fn->ClearCode(); - } Match(ATT_BOPEN); // get pars - int cpar = 0; while (TokenType != ATT_BCLOSE) { // too many parameters? - if (cpar >= C4AUL_MAX_Par) + if (func->params.size() >= C4AUL_MAX_Par) throw C4AulParseError(this, "'func' parameter list: too many parameters (max 10)"); if (TokenType == ATT_LDOTS) { - if (Type == PREPARSER) Fn->ParCount = C4AUL_MAX_Par; + func->has_unnamed_params = true; Shift(); // don't allow any more parameters after ellipsis break; @@ -1027,20 +927,21 @@ void C4AulParse::Parse_FuncBody() else if (SEqual(Idtf, C4AUL_TypeString)) { t = C4V_String; Shift(); } else if (SEqual(Idtf, C4AUL_TypeArray)) { t = C4V_Array; Shift(); } else if (SEqual(Idtf, C4AUL_TypeFunction)) { t = C4V_Function; Shift(); } - Fn->ParType[cpar] = t; // a parameter name which matched a type name? + std::string par_name; if (TokenType == ATT_BCLOSE || TokenType == ATT_COMMA) { - if (Type == PREPARSER) Fn->AddPar(Idtf); + par_name = Idtf; if (Config.Developer.ExtraWarnings) Warn("'%s' used as parameter name", Idtf); } else { Check(ATT_IDTF, "parameter name"); - if (Type == PREPARSER) Fn->AddPar(Idtf); + par_name = Idtf; Shift(); } + func->params.emplace_back(par_name, t); // end of params? if (TokenType == ATT_BCLOSE) { @@ -1048,80 +949,51 @@ void C4AulParse::Parse_FuncBody() } // must be a comma now Match(ATT_COMMA, "',' or ')'"); - cpar++; } Match(ATT_BCLOSE); - Match(ATT_BLOPEN); - // Push variables - if (Fn->VarNamed.iSize) - AddBCC(AB_STACK, Fn->VarNamed.iSize); - codegen.stack_height = 0; - while (TokenType != ATT_BLCLOSE) - { - Parse_Statement(); - assert(!codegen.stack_height); - } - // return nil if the function doesn't return anything - C4AulBCC * CPos = Fn->GetLastCode(); - if (!CPos || CPos->bccType != AB_RETURN || codegen.at_jump_target) - { - AddBCC(AB_NIL); - DebugChunk(); - AddBCC(AB_RETURN); - } - if (Type == PARSER) Fn->DumpByteCode(); - // add separator - AddBCC(AB_EOFN); - // Do not blame this function for script errors between functions - Fn = 0; - Shift(); + func->body = Parse_Block(); } -void C4AulParse::Parse_Block() +std::unique_ptr<::aul::ast::Block> C4AulParse::Parse_Block() { + auto block = ::aul::ast::Block::New(TokenSPos); Match(ATT_BLOPEN); while (TokenType != ATT_BLCLOSE) { - Parse_Statement(); + block->children.push_back(Parse_Statement()); } - DebugChunk(); Shift(); + return block; } -void C4AulParse::Parse_Statement() +std::unique_ptr<::aul::ast::Stmt> C4AulParse::Parse_Statement() { - if (TokenType != ATT_BLOPEN) - DebugChunk(); + const char *NodeStart = TokenSPos; + std::unique_ptr<::aul::ast::Stmt> stmt; switch (TokenType) { // do we have a block start? case ATT_BLOPEN: - Parse_Block(); - return; + return Parse_Block(); case ATT_BOPEN: case ATT_BOPEN2: case ATT_SET: case ATT_OPERATOR: case ATT_INT: case ATT_STRING: - Parse_Expression(); - AddBCC(AB_STACK, -1); + { + stmt = Parse_Expression(); Match(ATT_SCOLON); - return; + return stmt; + } // additional function separator case ATT_SCOLON: Shift(); - break; + return ::aul::ast::Noop::New(NodeStart); case ATT_IDTF: - // check for variable definition (var) - if (SEqual(Idtf, C4AUL_VarNamed)) - Parse_Var(); - // check for variable definition (local) - else if (SEqual(Idtf, C4AUL_LocalNamed)) - Parse_Local(); - // check for variable definition (static) - else if (SEqual(Idtf, C4AUL_GlobalNamed)) - Parse_Static(); + // check for variable definition + if (SEqual(Idtf, C4AUL_VarNamed) || SEqual(Idtf, C4AUL_LocalNamed) || SEqual(Idtf, C4AUL_GlobalNamed)) + stmt = Parse_Var(); // check new-form func begin else if (SEqual(Idtf, C4AUL_Func) || SEqual(Idtf, C4AUL_Private) || @@ -1134,8 +1006,7 @@ void C4AulParse::Parse_Statement() // get function by identifier: first check special functions else if (SEqual(Idtf, C4AUL_If)) // if { - Parse_If(); - break; + return Parse_If(); } else if (SEqual(Idtf, C4AUL_Else)) // else { @@ -1143,18 +1014,17 @@ void C4AulParse::Parse_Statement() } else if (SEqual(Idtf, C4AUL_Do)) // while { - Parse_DoWhile(); + stmt = Parse_DoWhile(); } else if (SEqual(Idtf, C4AUL_While)) // while { - Parse_While(); - break; + return Parse_While(); } else if (SEqual(Idtf, C4AUL_For)) // for { + PushParsePos(); Shift(); // Look if it's the for([var] foo in array)-form - const char * SPos0 = SPos; // must be followed by a bracket Match(ATT_BOPEN); // optional var @@ -1166,18 +1036,15 @@ void C4AulParse::Parse_Statement() && SEqual(Idtf, C4AUL_In)) { // reparse the stuff in the brackets like normal statements - SPos = SPos0; - Shift(); - Parse_ForEach(); + PopParsePos(); + return Parse_ForEach(); } else { // reparse the stuff in the brackets like normal statements - SPos = SPos0; - Shift(); - Parse_For(); + PopParsePos(); + return Parse_For(); } - break; } else if (SEqual(Idtf, C4AUL_Return)) // return { @@ -1185,147 +1052,87 @@ void C4AulParse::Parse_Statement() if (TokenType == ATT_SCOLON) { // allow return; without return value (implies nil) - AddBCC(AB_NIL); + stmt = ::aul::ast::Return::New(NodeStart, ::aul::ast::NilLit::New(NodeStart)); } else { // return retval; - Parse_Expression(); + stmt = ::aul::ast::Return::New(NodeStart, Parse_Expression()); } - AddBCC(AB_RETURN); } else if (SEqual(Idtf, C4AUL_Break)) // break { Shift(); - if (Type == PARSER) - { - // Must be inside a loop - if (!codegen.active_loops) - { - Error("'break' is only allowed inside loops"); - } - else - { - codegen.AddLoopControl(TokenSPos, true); - } - } + stmt = ::aul::ast::Break::New(NodeStart); } else if (SEqual(Idtf, C4AUL_Continue)) // continue { Shift(); - if (Type == PARSER) - { - // Must be inside a loop - if (!codegen.active_loops) - { - Error("'continue' is only allowed inside loops"); - } - else - { - codegen.AddLoopControl(TokenSPos, false); - } - } + stmt = ::aul::ast::Continue::New(NodeStart); } else { - Parse_Expression(); - AddBCC(AB_STACK, -1); + stmt = Parse_Expression(); } Match(ATT_SCOLON); - break; + assert(stmt); + return stmt; default: UnexpectedToken("statement"); } } -int C4AulParse::Parse_Params(int iMaxCnt, const char * sWarn, C4AulFunc * pFunc) +void C4AulParse::Parse_CallParams(::aul::ast::CallExpr *call) /*int (int iMaxCnt, const char * sWarn, C4AulFunc * pFunc)*/ { - int size = 0, WarnCnt = iMaxCnt; + assert(call != nullptr); + assert(call->args.empty()); + // so it's a regular function; force "(" Match(ATT_BOPEN); while(TokenType != ATT_BCLOSE) switch(TokenType) { case ATT_COMMA: // got no parameter before a "," - if (sWarn && Config.Developer.ExtraWarnings) - Warn(FormatString("parameter %d of call to %s is empty", size, sWarn).getData(), NULL); - AddBCC(AB_NIL); + if (Config.Developer.ExtraWarnings) + Warn(FormatString("parameter %d of call to %s is empty", call->args.size(), call->callee.c_str()).getData(), NULL); + call->args.push_back(::aul::ast::NilLit::New(TokenSPos)); Shift(); - ++size; break; case ATT_LDOTS: // functions using ... always take as many parameters as possible - assert(Type == PREPARSER || Fn->ParCount == C4AUL_MAX_Par); - if (Type == PREPARSER && Fn->ParCount != C4AUL_MAX_Par && Config.Developer.ExtraWarnings) - Warn("'...' in function body forces function to take varargs"); - Fn->ParCount = C4AUL_MAX_Par; Shift(); - // Push all unnamed parameters of the current function as parameters - for (int i = Fn->ParNamed.iSize; i < C4AUL_MAX_Par; ++i) - { - if (size >= iMaxCnt) - break; - AddVarAccess(AB_DUP, i - Fn->GetParCount()); - ++size; - } + call->append_unnamed_pars = true; // Do not allow more parameters even if there is space left Check(ATT_BCLOSE); break; default: // get a parameter - Parse_Expression(); - C4AulFunc * pFunc2 = pFunc ? pFunc : Engine->GetFirstFunc(sWarn); - if (pFunc2 && (Type == PARSER) && size < iMaxCnt) - { - WarnCnt = pFunc2->GetParCount(); - C4V_Type to = pFunc2->GetParType()[size]; - // While script can arrange to call any function by changing proplists, the parser has - // no hope of anticipating that, so checking functions of the same name will have to do. - if(!pFunc) while ((pFunc2 = Engine->GetNextSNFunc(pFunc2))) - { - WarnCnt = std::max(WarnCnt, pFunc2->GetParCount()); - if (pFunc2->GetParType()[size] != to) to = C4V_Any; - } - C4V_Type from = GetLastRetType(to); - if (C4Value::WarnAboutConversion(from, to)) - { - Warn(FormatString("parameter %d of call to %s is %s instead of %s", size, sWarn, GetC4VName(from), GetC4VName(to)).getData(), NULL); - } - } - ++size; + call->args.push_back(Parse_Expression()); // end of parameter list? if (TokenType != ATT_BCLOSE) Match(ATT_COMMA, "',' or ')'"); break; } Match(ATT_BCLOSE); - // too many parameters? - if (sWarn && size > WarnCnt && Type == PARSER && !SEqual(sWarn, C4AUL_Inherited) && (pFunc || Config.Developer.ExtraWarnings)) - Warn(FormatString("call to %s gives %d parameters, but only %d are used", sWarn, size, WarnCnt).getData(), NULL); - // Balance stack// FIXME: not for CALL/FUNC - if (size != iMaxCnt) - AddBCC(AB_STACK, iMaxCnt - size); - return size; } -void C4AulParse::Parse_Array() +std::unique_ptr<::aul::ast::ArrayLit> C4AulParse::Parse_Array() { + auto arr = ::aul::ast::ArrayLit::New(TokenSPos); // force "[" Match(ATT_BOPEN2); // Create an array - int size = 0; while (TokenType != ATT_BCLOSE2) { // got no parameter before a ","? then push nil if (TokenType == ATT_COMMA) { if (Config.Developer.ExtraWarnings) - Warn(FormatString("array entry %d is empty", size).getData(), NULL); - AddBCC(AB_NIL); + Warn(FormatString("array entry %d is empty", arr->values.size()).getData(), NULL); + arr->values.emplace_back(::aul::ast::NilLit::New(TokenSPos)); } else - Parse_Expression(); - ++size; + arr->values.emplace_back(Parse_Expression()); if (TokenType == ATT_BCLOSE2) break; Match(ATT_COMMA, "',' or ']'"); @@ -1333,320 +1140,151 @@ void C4AulParse::Parse_Array() if (TokenType == ATT_BCLOSE2) { if (Config.Developer.ExtraWarnings) - Warn(FormatString("array entry %d is empty", size).getData(), NULL); - AddBCC(AB_NIL); - ++size; + Warn(FormatString("array entry %d is empty", arr->values.size()).getData(), NULL); + arr->values.emplace_back(::aul::ast::NilLit::New(TokenSPos)); } } Shift(); - // add terminator - AddBCC(AB_NEW_ARRAY, size); + return arr; } -void C4AulParse::Parse_PropList() +std::unique_ptr<::aul::ast::ProplistLit> C4AulParse::Parse_PropList() { - int size = 0; + auto proplist = ::aul::ast::ProplistLit::New(TokenSPos); if (TokenType == ATT_IDTF && SEqual(Idtf, C4AUL_New)) { Shift(); - AddBCC(AB_STRING, (intptr_t) &Strings.P[P_Prototype]); - Parse_Expression(); - C4V_Type from = GetLastRetType(C4V_PropList); - if (C4Value::WarnAboutConversion(from, C4V_PropList)) - { - Warn(FormatString("Prototype is %s instead of %s", GetC4VName(from), GetC4VName(C4V_PropList)).getData(), NULL); - } - if (Fn->GetLastCode()->bccType == AB_CPROPLIST && Fn->GetLastCode()->Par.p->GetDef()) - { - throw C4AulParseError(this, "Can't use new on definitions yet."); - } - ++size; + proplist->values.emplace_back(Strings.P[P_Prototype].GetCStr(), Parse_Expression()); } Match(ATT_BLOPEN); while (TokenType != ATT_BLCLOSE) { - C4String * pKey; + std::string key; if (TokenType == ATT_IDTF) { - pKey = Strings.RegString(Idtf); - AddBCC(AB_STRING, (intptr_t) pKey); + key = Idtf; Shift(); } else if (TokenType == ATT_STRING) { - AddBCC(AB_STRING, reinterpret_cast(cStr)); + key = cStr->GetCStr(); Shift(); } else UnexpectedToken("string or identifier"); if (TokenType != ATT_COLON && TokenType != ATT_SET) UnexpectedToken("':' or '='"); Shift(); - Parse_Expression(); - ++size; + proplist->values.emplace_back(key, Parse_Expression()); if (TokenType == ATT_COMMA) Shift(); else if (TokenType != ATT_BLCLOSE) UnexpectedToken("'}' or ','"); } - AddBCC(AB_NEW_PROPLIST, size); Shift(); + return proplist; } -C4Value C4AulParse::Parse_ConstPropList(C4PropListStatic * parent, C4String * Name) +std::unique_ptr<::aul::ast::DoLoop> C4AulParse::Parse_DoWhile() { - C4Value v; - if (!Name) - throw C4AulParseError(this, "a static proplist is not allowed to be anonymous"); - C4PropListStatic * p; - if (Type == PREPARSER) - { - p = C4PropList::NewStatic(NULL, parent, Name); - v.SetPropList(p); - } - else - { - bool r; - if (parent) - r = parent->GetPropertyByS(Name, &v); - else - r = Engine->GetGlobalConstant(Name->GetCStr(), &v); - if (!r || !v.getPropList()) - { - // the proplist couldn't be parsed or was overwritten by a later constant. - // create a temporary replacement, make v hold the reference to it for now - v.SetPropList(C4PropList::NewStatic(NULL, parent, Name)); - } - p = v.getPropList()->IsStatic(); - if (!p) - throw C4AulParseError(this, "internal error: constant proplist is not static"); - if (p->GetParent() != parent || p->GetParentKeyName() != Name) - { - throw C4AulParseError(this, "internal error: constant proplist has the wrong parent"); - } - // In case of script reloads - p->Thaw(); - } - Store_Const(parent, Name, v); - if (TokenType == ATT_IDTF && SEqual(Idtf, C4AUL_New)) - { - Shift(); - Parse_ConstExpression(p, &Strings.P[P_Prototype]); - } - Match(ATT_BLOPEN); - while (TokenType != ATT_BLCLOSE) - { - C4String * pKey; - if (TokenType == ATT_IDTF) - { - pKey = Strings.RegString(Idtf); - Shift(); - } - else if (TokenType == ATT_STRING) - { - pKey = cStr; - Shift(); - } - else UnexpectedToken("string or identifier"); - if (TokenType != ATT_COLON && TokenType != ATT_SET) - UnexpectedToken("':' or '='"); - Shift(); - Parse_ConstExpression(p, pKey); - if (TokenType == ATT_COMMA) - Shift(); - else if (TokenType != ATT_BLCLOSE) - UnexpectedToken("'}' or ','"); - } - if (Type == PARSER) - p->Freeze(); + auto loop = ::aul::ast::DoLoop::New(TokenSPos); Shift(); - return C4VPropList(p); -} - -void C4AulParse::Parse_DoWhile() -{ - Shift(); - // Save position for later jump back - int Start = codegen.JumpHere(); - // We got a loop - PushLoop(); - // Execute body - Parse_Statement(); - int BeforeCond = -1; - if (Type == PARSER) - for (C4AulCompiler::Loop::Control *pCtrl2 = codegen.active_loops->Controls; pCtrl2; pCtrl2 = pCtrl2->Next) - if (!pCtrl2->Break) - BeforeCond = codegen.JumpHere(); + loop->body = Parse_Statement(); // Execute condition if (TokenType != ATT_IDTF || !SEqual(Idtf, C4AUL_While)) UnexpectedToken("'while'"); Shift(); Match(ATT_BOPEN); - Parse_Expression(); + loop->cond = Parse_Expression(); Match(ATT_BCLOSE); - // Jump back - AddJump(AB_COND, Start); - if (Type != PARSER) return; - PopLoop(BeforeCond); + return loop; } -void C4AulParse::Parse_While() +std::unique_ptr<::aul::ast::WhileLoop> C4AulParse::Parse_While() { + auto loop = ::aul::ast::WhileLoop::New(TokenSPos); Shift(); - // Save position for later jump back - int iStart = codegen.JumpHere(); // Execute condition Match(ATT_BOPEN); - Parse_Expression(); + loop->cond = Parse_Expression(); Match(ATT_BCLOSE); - // Check condition - int iCond = AddBCC(AB_CONDN); - // We got a loop - PushLoop(); // Execute body - Parse_Statement(); - if (Type != PARSER) return; - // Jump back - AddJump(AB_JUMP, iStart); - // Set target for conditional jump - SetJumpHere(iCond); - PopLoop(iStart); + loop->body = Parse_Statement(); + return loop; } -void C4AulParse::Parse_If() +std::unique_ptr<::aul::ast::If> C4AulParse::Parse_If() { + auto stmt = ::aul::ast::If::New(TokenSPos); Shift(); Match(ATT_BOPEN); - Parse_Expression(); + stmt->cond = Parse_Expression(); Match(ATT_BCLOSE); - // create bytecode, remember position - int iCond = AddBCC(AB_CONDN); // parse controlled statement - Parse_Statement(); + stmt->iftrue = Parse_Statement(); if (TokenType == ATT_IDTF && SEqual(Idtf, C4AUL_Else)) { - // add jump - int iJump = AddBCC(AB_JUMP); - // set condition jump target - SetJumpHere(iCond); Shift(); // expect a command now - Parse_Statement(); - // set jump target - SetJumpHere(iJump); + stmt->iffalse = Parse_Statement(); } - else - // set condition jump target - SetJumpHere(iCond); + return stmt; } -void C4AulParse::Parse_For() +std::unique_ptr<::aul::ast::ForLoop> C4AulParse::Parse_For() { + auto loop = ::aul::ast::ForLoop::New(TokenSPos); + Match(ATT_IDTF); Match(ATT_BOPEN); // Initialization if (TokenType == ATT_IDTF && SEqual(Idtf, C4AUL_VarNamed)) { - Parse_Var(); + loop->init = Parse_Var(); } else if (TokenType != ATT_SCOLON) { - Parse_Expression(); - AddBCC(AB_STACK, -1); + loop->init = Parse_Expression(); } // Consume first semicolon Match(ATT_SCOLON); // Condition - int iCondition = -1, iJumpBody = -1, iJumpOut = -1; if (TokenType != ATT_SCOLON) { - // Add condition code - iCondition = codegen.JumpHere(); - Parse_Expression(); - // Jump out - iJumpOut = AddBCC(AB_CONDN); + loop->cond = Parse_Expression(); } // Consume second semicolon Match(ATT_SCOLON); // Incrementor - int iIncrementor = -1; if (TokenType != ATT_BCLOSE) { - // Must jump over incrementor - iJumpBody = AddBCC(AB_JUMP); - // Add incrementor code - iIncrementor = codegen.JumpHere(); - Parse_Expression(); - AddBCC(AB_STACK, -1); - // Jump to condition - if (iCondition != -1) - AddJump(AB_JUMP, iCondition); + loop->incr = Parse_Expression(); } // Consume closing bracket Match(ATT_BCLOSE); - // Allow break/continue from now on - PushLoop(); - // Body - int iBody = codegen.JumpHere(); - if (iJumpBody != -1) - SetJumpHere(iJumpBody); - Parse_Statement(); - if (Type != PARSER) return; - // Where to jump back? - int iJumpBack; - if (iIncrementor != -1) - iJumpBack = iIncrementor; - else if (iCondition != -1) - iJumpBack = iCondition; - else - iJumpBack = iBody; - AddJump(AB_JUMP, iJumpBack); - // Set target for condition - if (iJumpOut != -1) - SetJumpHere(iJumpOut); - PopLoop(iJumpBack); + loop->body = Parse_Statement(); + return loop; } -void C4AulParse::Parse_ForEach() +std::unique_ptr<::aul::ast::RangeLoop> C4AulParse::Parse_ForEach() { + auto loop = ::aul::ast::RangeLoop::New(TokenSPos); + Match(ATT_IDTF); Match(ATT_BOPEN); if (TokenType == ATT_IDTF && SEqual(Idtf, C4AUL_VarNamed)) { + loop->scoped_var = true; Shift(); } // get variable name Check(ATT_IDTF, "variable name"); - if (Type == PREPARSER) - { - // insert variable - Fn->VarNamed.AddName(Idtf); - } - // search variable (fail if not found) - int iVarID = Fn->VarNamed.GetItemNr(Idtf); - if (iVarID < 0) - throw C4AulParseError(this, "internal error: var definition: var not found in variable table"); + loop->var = Idtf; Shift(); if (TokenType != ATT_IDTF || !SEqual(Idtf, C4AUL_In)) UnexpectedToken("'in'"); Shift(); // get expression for array - Parse_Expression(); + loop->cond = Parse_Expression(); Match(ATT_BCLOSE); - // push initial position (0) - AddBCC(AB_INT); - // get array element - int iStart = AddVarAccess(AB_FOREACH_NEXT, iVarID); - // jump out (FOREACH_NEXT will jump over this if - // we're not at the end of the array yet) - int iCond = AddBCC(AB_JUMP); - // got a loop... - PushLoop(); // loop body - Parse_Statement(); - if (Type != PARSER) return; - // jump back - AddJump(AB_JUMP, iStart); - // set condition jump target - SetJumpHere(iCond); - PopLoop(iStart); - // remove array and counter from stack - AddBCC(AB_STACK, -2); + loop->body = Parse_Statement(); + return loop; } static bool GetPropertyByS(const C4PropList * p, const char * s, C4Value & v) @@ -1656,8 +1294,10 @@ static bool GetPropertyByS(const C4PropList * p, const char * s, C4Value & v) return p->GetPropertyByS(k, &v); } -void C4AulParse::Parse_Expression(int iParentPrio) +std::unique_ptr<::aul::ast::Expr> C4AulParse::Parse_Expression(int iParentPrio) { + const char *NodeStart = TokenSPos; + std::unique_ptr<::aul::ast::Expr> expr; int ndx; const C4ScriptOpDef * op; C4AulFunc *FoundFn = 0; @@ -1665,95 +1305,24 @@ void C4AulParse::Parse_Expression(int iParentPrio) switch (TokenType) { case ATT_IDTF: - // check for parameter (par) - if (Fn->ParNamed.GetItemNr(Idtf) != -1) + // XXX: Resolving literals here means that you can't create a variable + // with the names "true", "false", "nil" or "this" anymore. I don't + // consider this too much of a problem, because there is never a reason + // to do this and it makes my job a lot easier + if (SEqual(Idtf, C4AUL_True)) { - // insert variable by id - AddVarAccess(AB_DUP, Fn->ParNamed.GetItemNr(Idtf) - Fn->GetParCount()); - Shift(); - } - // check for variable (var) - else if (Fn->VarNamed.GetItemNr(Idtf) != -1) - { - // insert variable by id - AddVarAccess(AB_DUP, Fn->VarNamed.GetItemNr(Idtf)); - Shift(); - } - else if (ContextToExecIn && (ndx = ContextToExecIn->Func->ParNamed.GetItemNr(Idtf)) != -1) - { - AddBCC(AB_DUP_CONTEXT, ndx); - Shift(); - } - else if (ContextToExecIn && (ndx = ContextToExecIn->Func->VarNamed.GetItemNr(Idtf)) != -1) - { - AddBCC(AB_DUP_CONTEXT, ContextToExecIn->Func->GetParCount() + ndx); - Shift(); - } - // check for variable (local) - else if (GetPropertyByS(Fn->Parent, Idtf, val)) - { - if ((FoundFn = val.getFunction())) - { - if (Config.Developer.ExtraWarnings && !FoundFn->GetPublic()) - Warn("using deprecated function %s", Idtf); - Shift(); - Parse_Params(FoundFn->GetParCount(), FoundFn->GetName(), FoundFn); - AddBCC(AB_FUNC, (intptr_t) FoundFn); - } - else - { - AddBCC(AB_LOCALN, (intptr_t) Strings.RegString(Idtf)); - Shift(); - } - } - else if (SEqual(Idtf, C4AUL_True)) - { - AddBCC(AB_BOOL, 1); Shift(); + expr = ::aul::ast::BoolLit::New(NodeStart, true); } else if (SEqual(Idtf, C4AUL_False)) { - AddBCC(AB_BOOL, 0); Shift(); + expr = ::aul::ast::BoolLit::New(NodeStart, false); } else if (SEqual(Idtf, C4AUL_Nil)) { - AddBCC(AB_NIL); Shift(); - } - else if (SEqual(Idtf, C4AUL_New)) - { - Parse_PropList(); - } - // function identifier: check special functions - else if (SEqual(Idtf, C4AUL_If)) - // -> if is not a valid parameter - throw C4AulParseError(this, "'if' may not be used as a parameter"); - else if (SEqual(Idtf, C4AUL_While)) - // -> while is not a valid parameter - throw C4AulParseError(this, "'while' may not be used as a parameter"); - else if (SEqual(Idtf, C4AUL_Else)) - // -> else is not a valid parameter - throw C4AulParseError(this, "misplaced 'else'"); - else if (SEqual(Idtf, C4AUL_For)) - // -> for is not a valid parameter - throw C4AulParseError(this, "'for' may not be used as a parameter"); - else if (SEqual(Idtf, C4AUL_Return)) - { - Error("return may not be used as a parameter"); - } - else if (SEqual(Idtf, C4AUL_Par)) - { - if (Type == PREPARSER && Fn->ParCount != C4AUL_MAX_Par && Config.Developer.ExtraWarnings) - Warn("calling 'Par' in function body forces function to take varargs"); - // functions using Par() always take as many parameters as possible - Fn->ParCount = C4AUL_MAX_Par; - // and for Par - Shift(); - Match(ATT_BOPEN); - Parse_Expression(); - Match(ATT_BCLOSE); - AddBCC(AB_PAR); + expr = ::aul::ast::NilLit::New(NodeStart); } else if (SEqual(Idtf, C4AUL_this)) { @@ -1762,86 +1331,85 @@ void C4AulParse::Parse_Expression(int iParentPrio) { Shift(); Match(ATT_BCLOSE); + // TODO: maybe warn about "this" with parentheses? } - AddBCC(AB_THIS); + expr = ::aul::ast::ThisLit::New(NodeStart); } - else if (SEqual(Idtf, C4AUL_Inherited) || SEqual(Idtf, C4AUL_SafeInherited)) + // XXX: Other things that people aren't allowed to do anymore: name their variables or functions any of: + // "if", "else", "for", "while", "do", "return", or "Par". + // We could allow variables with these names and disambiguate based on the syntax, but no. + else if (SEqual(Idtf, C4AUL_If) || SEqual(Idtf, C4AUL_Else) || SEqual(Idtf, C4AUL_For) || SEqual(Idtf, C4AUL_While) || SEqual(Idtf, C4AUL_Do) || SEqual(Idtf, C4AUL_Return)) + { + Error("reserved identifier not allowed in expressions: %s", Idtf); + } + else if (SEqual(Idtf, C4AUL_Par)) { Shift(); - // get function - if (Fn->OwnerOverloaded) + // "Par" is special in that it isn't a function and thus doesn't accept an arbitrary number of parameters + Match(ATT_BOPEN); + expr = ::aul::ast::ParExpr::New(NodeStart, Parse_Expression()); + Match(ATT_BCLOSE); + } + else if (SEqual(Idtf, C4AUL_New)) + { + // Because people might call a variables or functions "new", we need to look ahead and guess whether it's a proplist constructor. + PushParsePos(); + Shift(); + if (TokenType == ATT_IDTF) { - // add direct call to byte code - Parse_Params(Fn->OwnerOverloaded->GetParCount(), C4AUL_Inherited, Fn->OwnerOverloaded); - AddBCC(AB_FUNC, (intptr_t) Fn->OwnerOverloaded); + // this must be a proplist because two identifiers can't immediately follow each other + PopParsePos(); + expr = Parse_PropList(); } else - // not found? raise an error, if it's not a safe call - if (SEqual(Idtf, C4AUL_Inherited) && Type == PARSER) - throw C4AulParseError(this, "inherited function not found (use _inherited to disable this message)"); - else - { - // otherwise, parse parameters, but discard them - Parse_Params(0, NULL); - // Push a null as return value - AddBCC(AB_STACK, 1); - } - } - else if (Type == PREPARSER) - { - Shift(); - // The preparser just assumes that the syntax is correct and all identifiers - // will be defined: if no '(' follows, it must be a variable or constant, - // otherwise a function with parameters - if (TokenType == ATT_BOPEN) - Parse_Params(C4AUL_MAX_Par, NULL); - } - // check for global variables (static) or constants (static const) - // the global namespace has the lowest priority so that local - // functions and variables can overload it - else if ((ndx = Engine->GlobalNamedNames.GetItemNr(Idtf)) != -1) - { - // insert variable by id - AddBCC(AB_GLOBALN, ndx); - Shift(); - } - else if (Engine->GetGlobalConstant(Idtf, &val)) - { - // store as direct constant - switch (val.GetType()) { - case C4V_Nil: AddBCC(AB_NIL, 0); break; - case C4V_Int: AddBCC(AB_INT, val._getInt()); break; - case C4V_Bool: AddBCC(AB_BOOL, val._getBool()); break; - case C4V_String: - AddBCC(AB_STRING, reinterpret_cast(val._getStr())); - break; - case C4V_PropList: - AddBCC(AB_CPROPLIST, reinterpret_cast(val._getPropList())); - break; - case C4V_Array: - AddBCC(AB_CARRAY, reinterpret_cast(val._getArray())); - break; - case C4V_Function: - AddBCC(AB_CFUNCTION, reinterpret_cast(val._getFunction())); - break; - default: - throw C4AulParseError(this, FormatString("internal error: constant %s has unsupported type %d", Idtf, val.GetType()).getData()); + // Some non-identifier means this is either a variable, a function, or a syntax error. Which one exactly is something we'll figure out later. + PopParsePos(); } - Shift(); } - else + else if (SEqual(Idtf, C4AUL_Func)) { - // identifier could not be resolved - Error("unknown identifier: %s", Idtf); + PushParsePos(); + Shift(); + if (TokenType == ATT_BOPEN) + { + auto func = ::aul::ast::FunctionExpr::New(NodeStart); + Parse_Function(func.get()); + expr = std::move(func); + DiscardParsePos(); + } + else + { + PopParsePos(); + } + } + if (!expr) + { + // If we end up here, it must be a proper identifier (or a reserved word that's used as an identifier). + // Time to look ahead and see whether it's a function call. + std::string identifier = Idtf; + Shift(); + if (TokenType == ATT_BOPEN) + { + // Well, it looks like one, at least + auto func = ::aul::ast::CallExpr::New(NodeStart); + func->callee = identifier; + Parse_CallParams(func.get()); + expr = std::move(func); + } + else + { + // It's most certainly not a function call. + expr = ::aul::ast::VarExpr::New(NodeStart, identifier); + } } break; case ATT_INT: // constant in cInt - AddBCC(AB_INT, cInt); + expr = ::aul::ast::IntLit::New(NodeStart, cInt); Shift(); break; case ATT_STRING: // reference in cStr - AddBCC(AB_STRING, reinterpret_cast(cStr)); + expr = ::aul::ast::StringLit::New(NodeStart, cStr->GetCStr()); Shift(); break; case ATT_OPERATOR: @@ -1853,501 +1421,187 @@ void C4AulParse::Parse_Expression(int iParentPrio) throw C4AulParseError(this, "postfix operator without first expression"); Shift(); // generate code for the following expression - Parse_Expression(op->Priority); - { - C4V_Type to = op->Type1; - C4V_Type from = GetLastRetType(to); - if (C4Value::WarnAboutConversion(from, to)) - { - Warn(FormatString("operator \"%s\" gets %s instead of %s", op->Identifier, GetC4VName(from), GetC4VName(to)).getData(), NULL); - } - } - // ignore? + expr = Parse_Expression(op->Priority); if (SEqual(op->Identifier, "+")) - break; - // negate constant? - if (Type == PARSER && SEqual(op->Identifier, "-")) - if (Fn->GetLastCode()->bccType == AB_INT) - { - Fn->GetLastCode()->Par.i = - Fn->GetLastCode()->Par.i; - break; - } { - // changer? make a setter BCC, leave value for operator - C4AulBCC Changer; - if(op->Changer) - Changer = MakeSetter(true); - // write byte code - AddBCC(op->Code, 0); - // writter setter - if(op->Changer) - AddBCC(Changer.bccType, Changer.Par.X); + // This is a no-op. + } + else + { + expr = ::aul::ast::UnOpExpr::New(NodeStart, op - C4ScriptOpMap, std::move(expr)); } break; case ATT_BOPEN: Shift(); - Parse_Expression(); + expr = Parse_Expression(); Match(ATT_BCLOSE); break; case ATT_BOPEN2: - Parse_Array(); + expr = Parse_Array(); break; case ATT_BLOPEN: - Parse_PropList(); + expr = Parse_PropList(); break; default: UnexpectedToken("expression"); } - while (1) switch (TokenType) + assert(expr); + + while (1) { - case ATT_SET: - // back out of any kind of parent operator - // (except other setters, as those are right-associative) - if(iParentPrio > 1) - return; + NodeStart = TokenSPos; + switch (TokenType) { - // generate setter - C4AulBCC Setter = MakeSetter(false); - // parse value to set - Shift(); - Parse_Expression(1); - // write setter - AddBCC(Setter.bccType, Setter.Par.X); - } - break; - case ATT_OPERATOR: - { - // expect postfix operator - const C4ScriptOpDef * op = &C4ScriptOpMap[cInt]; - if (!op->Postfix) + case ATT_SET: { - // does an operator with the same name exist? - // when it's a postfix-operator, it can be used instead. - const C4ScriptOpDef * postfixop; - for (postfixop = op + 1; postfixop->Identifier; ++postfixop) - if (SEqual(op->Identifier, postfixop->Identifier)) - if (postfixop->Postfix) - break; - // not found? - if (!postfixop->Identifier) - { - Error("unexpected prefix operator: %s", op->Identifier); - } - // otherwise use the new-found correct postfix operator - op = postfixop; - } - - // changer? - C4AulBCC Setter; - if (op->Changer) - { - // changer: back out only if parent operator is stronger - // (everything but setters and other changers, as changers are right-associative) - if(iParentPrio > op->Priority) - return; - // generate setter, leave value on stack for operator - Setter = MakeSetter(true); - } - else - { - // normal operator: back out if parent operator is at least as strong - // (non-setter operators are left-associative) - if(iParentPrio >= op->Priority) - return; - } - Shift(); - - if (op->Code == AB_JUMPAND || op->Code == AB_JUMPOR || op->Code == AB_JUMPNNIL) - { - // create bytecode, remember position - // Jump or discard first parameter - int iCond = AddBCC(op->Code); - // parse second expression - Parse_Expression(op->Priority); - // set condition jump target - SetJumpHere(iCond); - // write setter (unused - could also optimize to skip self-assign, but must keep stack balanced) - if (op->Changer) - AddBCC(Setter.bccType, Setter.Par.X); + // back out of any kind of parent operator + // (except other setters, as those are right-associative) + if (iParentPrio > 1) + return expr; + Shift(); + expr = ::aul::ast::BinOpExpr::New(NodeStart, ::aul::ast::BinOpExpr::AssignmentOp, std::move(expr), Parse_Expression(1)); break; } - else + case ATT_OPERATOR: { - C4V_Type to = op->Type1; - C4V_Type from = GetLastRetType(to); - if (C4Value::WarnAboutConversion(from, to)) + // expect postfix operator + const C4ScriptOpDef * op = &C4ScriptOpMap[cInt]; + if (!op->Postfix) { - Warn(FormatString("operator \"%s\" left side gets %s instead of %s", op->Identifier, GetC4VName(from), GetC4VName(to)).getData(), NULL); - } - // expect second parameter for operator - if (!op->NoSecondStatement) - Parse_Expression(op->Priority); - to = op->Type2; - from = GetLastRetType(to); - if (C4Value::WarnAboutConversion(from, to)) - { - Warn(FormatString("operator \"%s\" right side gets %s instead of %s", op->Identifier, GetC4VName(from), GetC4VName(to)).getData(), NULL); - } - // write byte code - AddBCC(op->Code, 0); - // write setter and modifier - if (op->Changer) + // does an operator with the same name exist? + // when it's a postfix-operator, it can be used instead. + const C4ScriptOpDef * postfixop; + for (postfixop = op + 1; postfixop->Identifier; ++postfixop) + if (SEqual(op->Identifier, postfixop->Identifier)) + if (postfixop->Postfix) + break; + // not found? + if (!postfixop->Identifier) { - AddBCC(Setter.bccType, Setter.Par.X); - // postfix ++ works by increasing, storing, then decreasing - // in case the result is thrown away, AddBCC will remove the decrease operation - if((op->Code == AB_Inc || op->Code == AB_Dec) && op->Postfix) - AddBCC(op->Code == AB_Inc ? AB_Dec : AB_Inc, 1); + Error("unexpected prefix operator: %s", op->Identifier); } - } - } - break; - case ATT_BOPEN2: - // parse either [index], or [start:end] in which case either index is optional - Shift(); - if (TokenType == ATT_COLON) - AddBCC(AB_INT, 0); // slice with first index missing -> implicit start index zero - else - Parse_Expression(); - - if (TokenType == ATT_BCLOSE2) - { - Shift(); - AddBCC(AB_ARRAYA); - } - else if (TokenType == ATT_COLON) - { - Shift(); - if (TokenType == ATT_BCLOSE2) - { - Shift(); - AddBCC(AB_INT, INT_MAX); // second index missing -> implicit end index GetLength() - } - else - { - Parse_Expression(); - Match(ATT_BCLOSE2); - } - AddBCC(AB_ARRAY_SLICE); - } - else - { - UnexpectedToken("']' or ':'"); - } - break; - case ATT_DOT: - Shift(); - Check(ATT_IDTF, "property name"); - { - C4String * pKey = Strings.RegString(Idtf); - AddBCC(AB_PROP, (intptr_t) pKey); - } - Shift(); - break; - case ATT_CALL: case ATT_CALLFS: - { - C4String *pName = NULL; - C4AulBCCType eCallType = (TokenType == ATT_CALL) ? AB_CALL : AB_CALLFS; - Shift(); - // expect identifier of called function now - if (TokenType != ATT_IDTF) - throw C4AulParseError(this, "expecting func name after '->'"); - if (Type == PARSER) - { - pName = ::Strings.RegString(Idtf); - } - Shift(); - Parse_Params(C4AUL_MAX_Par, pName ? pName->GetCStr() : Idtf, NULL); - AddBCC(eCallType, reinterpret_cast(pName)); - } - break; - default: - return; - } -} - -void C4AulParse::Parse_Var() -{ - Shift(); - while (1) - { - // get desired variable name - Check(ATT_IDTF, "variable name"); - if (Type == PREPARSER) - { - // insert variable - Fn->VarNamed.AddName(Idtf); - } - // search variable (fail if not found) - int iVarID = Fn->VarNamed.GetItemNr(Idtf); - if (iVarID < 0) - throw C4AulParseError(this, "internal error: var definition: var not found in variable table"); - Shift(); - if(TokenType == ATT_SET) - { - // insert initialization in byte code - Shift(); - Parse_Expression(); - AddVarAccess(AB_POP_TO, iVarID); - } - if (TokenType == ATT_SCOLON) - return; - Match(ATT_COMMA, "',' or ';'"); - } -} - -void C4AulParse::Parse_Local() -{ - Shift(); - while (1) - { - Check(ATT_IDTF, "variable name"); - C4RefCntPointer key = ::Strings.RegString(Idtf); - if (Type == PREPARSER) - { - // get desired variable name - // check: symbol already in use? - if (Host->GetPropList() && Host->GetPropList()->GetFunc(Idtf)) - throw C4AulParseError(this, "variable definition: name already in use"); - // insert variable - Host->GetPropList()->SetPropertyByS(key, C4VNull); - } - Shift(); - if (TokenType == ATT_SET) - { - if (!Host->GetPropList()) - throw C4AulParseError(this, "local variables can only be initialized on proplists"); - Shift(); - Parse_ConstExpression(Host->GetPropList(), key); - } - if (TokenType == ATT_SCOLON) - return; - Match(ATT_COMMA, "',' or ';'"); - } -} - -void C4AulParse::Parse_Static() -{ - Shift(); - // constant? - if (TokenType == ATT_IDTF && SEqual(Idtf, C4AUL_Const)) - { - Parse_Const(); - return; - } - while (1) - { - Check(ATT_IDTF, "variable name"); - if (Type == PREPARSER) - { - // get desired variable name - // global variable definition - // check: symbol already in use? - if (Engine->GetPropList()->GetFunc(Idtf)) Error("function and variable with name %s", Idtf); - if (Engine->GetGlobalConstant(Idtf, NULL)) Error("constant and variable with name %s", Idtf); - // insert variable if not defined already - if (Engine->GlobalNamedNames.GetItemNr(Idtf) == -1) - { - Engine->GlobalNamedNames.AddName(Idtf); - } - } - Shift(); - if (TokenType == ATT_SCOLON) - return; - Match(ATT_COMMA, "',' or ';'"); - } -} - -C4Value C4AulParse::Parse_ConstExpression(C4PropListStatic * parent, C4String * Name) -{ - C4Value r; - switch (TokenType) - { - case ATT_INT: r.SetInt(cInt); Shift(); break; - case ATT_STRING: r.SetString(cStr); Shift(); break; // increases ref count of C4String in cStr - case ATT_IDTF: - // identifier is only OK if it's another constant - if (SEqual(Idtf, C4AUL_New)) - r = Parse_ConstPropList(parent, Name); - else if (SEqual(Idtf, C4AUL_Func)) - { - if (!parent) - throw C4AulParseError(this, "global functions must be declared with 'global func'"); - if (!Name) - throw C4AulParseError(this, "functions must have a name"); - if (Type == PREPARSER) - { - Fn = new C4AulScriptFunc(parent, pOrgScript, Name ? Name->GetCStr() : NULL, SPos); - r.SetFunction(Fn); - } - else - { - C4AulFunc * f; - if (!parent->GetPropertyByS(Name, &r) || !(f = r.getFunction()) || !(Fn = f->SFunc())) - { - throw C4AulParseError(this, FormatString("function %s was overloaded by %s", - Name ? Name->GetCStr() : "", r.GetDataString().getData()).getData()); + // otherwise use the new-found correct postfix operator + op = postfixop; } - } - Store_Const(parent, Name, r); - Shift(); - Parse_FuncBody(); - } - else - { - if (SEqual(Idtf, C4AUL_True)) - r.SetBool(true); - else if (SEqual(Idtf, C4AUL_False)) - r.SetBool(false); - else if (SEqual(Idtf, C4AUL_Nil)) - r.Set0(); - else if (!((Host && GetPropertyByS(Host->GetPropList(), Idtf, r)) || - Engine->GetGlobalConstant(Idtf, &r))) - if (Type == PARSER) - UnexpectedToken("constant value"); - Shift(); - } - break; - case ATT_BOPEN2: - { - Shift(); - // Create an array - r.SetArray(new C4ValueArray()); - int size = 0; - while (TokenType != ATT_BCLOSE2) - { - // got no parameter before a ","? then push nil - if (TokenType == ATT_COMMA) + + if (iParentPrio + !op->Changer > op->Priority) + return expr; + + Shift(); + if (op->NoSecondStatement) { - if (Config.Developer.ExtraWarnings) - Warn(FormatString("array entry %d is empty", size).getData(), NULL); - r._getArray()->SetItem(size, C4VNull); + // Postfix unary op + expr = ::aul::ast::UnOpExpr::New(NodeStart, op - C4ScriptOpMap, std::move(expr)); } else - r._getArray()->SetItem(size, Parse_ConstExpression(NULL, NULL)); - ++size; - if (TokenType == ATT_BCLOSE2) - break; - Match(ATT_COMMA, "',' or ']'"); - // [] -> size 0, [*,] -> size 2, [*,*,] -> size 3 + { + expr = ::aul::ast::BinOpExpr::New(NodeStart, op - C4ScriptOpMap, std::move(expr), Parse_Expression(op->Priority)); + } + break; + } + case ATT_BOPEN2: + { + // parse either [index], or [start:end] in which case either index is optional + Shift(); + ::aul::ast::ExprPtr start; + if (TokenType == ATT_COLON) + start = ::aul::ast::IntLit::New(TokenSPos, 0); // slice with first index missing -> implicit start index zero + else + start = Parse_Expression(); + if (TokenType == ATT_BCLOSE2) { - if (Config.Developer.ExtraWarnings) - Warn(FormatString("array entry %d is empty", size).getData(), NULL); - r._getArray()->SetItem(size, C4VNull); - ++size; + expr = ::aul::ast::SubscriptExpr::New(NodeStart, std::move(expr), std::move(start)); } + else if (TokenType == ATT_COLON) + { + Shift(); + ::aul::ast::ExprPtr end; + if (TokenType == ATT_BCLOSE2) + { + end = ::aul::ast::IntLit::New(TokenSPos, std::numeric_limits::max()); + } + else + { + end = Parse_Expression(); + } + expr = ::aul::ast::SliceExpr::New(NodeStart, std::move(expr), std::move(start), std::move(end)); + } + else + { + UnexpectedToken("']' or ':'"); + } + Match(ATT_BCLOSE2); + break; } + case ATT_DOT: + Shift(); + Check(ATT_IDTF, "property name"); + expr = ::aul::ast::SubscriptExpr::New(NodeStart, std::move(expr), ::aul::ast::StringLit::New(TokenSPos, Idtf)); Shift(); break; - } - case ATT_BLOPEN: - r = Parse_ConstPropList(parent, Name); - break; - case ATT_OPERATOR: - { - // -> must be a prefix operator - const C4ScriptOpDef * op = &C4ScriptOpMap[cInt]; - if (SEqual(op->Identifier, "+")) + case ATT_CALL: case ATT_CALLFS: { + auto call = ::aul::ast::CallExpr::New(NodeStart); + call->context = std::move(expr); + call->safe_call = TokenType == ATT_CALLFS; Shift(); - if (TokenType == ATT_INT) - { - r.SetInt(cInt); - Shift(); - break; - } - } - if (SEqual(op->Identifier, "-")) - { + Check(ATT_IDTF, "function name after '->'"); + call->callee = Idtf; Shift(); - if (TokenType == ATT_INT) - { - r.SetInt(-cInt); - Shift(); - break; - } + Parse_CallParams(call.get()); + expr = std::move(call); + break; } - } - // fallthrough - default: - UnexpectedToken("constant value"); - } - while (TokenType == ATT_DOT) - { - Shift(); - Check(ATT_IDTF, "property name"); - if (Type == PARSER) - { - C4String * k = ::Strings.FindString(Idtf); - if (!r.CheckConversion(C4V_PropList)) - throw C4AulParseError(this, FormatString("proplist access: proplist expected, got %s", r.GetTypeName()).getData()); - if (!k || !r._getPropList()->GetPropertyByS(k, &r)) - r.Set0(); - } - Shift(); - } - if (TokenType == ATT_OPERATOR) - { - const C4ScriptOpDef * op = &C4ScriptOpMap[cInt]; - if (op->Code == AB_BitOr) - { - Shift(); - C4Value r2 = Parse_ConstExpression(NULL, NULL); - r.SetInt(r.getInt() | r2.getInt()); - } - } - Store_Const(parent, Name, r); - return r; -} - -void C4AulParse::Store_Const(C4PropListStatic * parent, C4String * Name, const C4Value & v) -{ - // store as constant or property - if (Name) - { - if (parent) - parent->SetPropertyByS(Name, v); - else - { - C4Value oldval; - if (Type == PREPARSER && Engine->GetGlobalConstant(Name->GetCStr(), &oldval) && oldval != v) - Warn("redefining constant %s from %s to %s", - Name->GetCStr(), oldval.GetDataString().getData(), v.GetDataString().getData()); - Engine->RegisterGlobalConstant(Name->GetCStr(), v); + default: + return expr; } } } -void C4AulParse::Parse_Const() +std::unique_ptr<::aul::ast::VarDecl> C4AulParse::Parse_Var() { + auto decl = ::aul::ast::VarDecl::New(TokenSPos); + if (SEqual(Idtf, C4AUL_VarNamed)) + { + decl->scope = ::aul::ast::VarDecl::Scope::Func; + } + else if (SEqual(Idtf, C4AUL_LocalNamed)) + { + decl->scope = ::aul::ast::VarDecl::Scope::Object; + } + else if (SEqual(Idtf, C4AUL_GlobalNamed)) + { + decl->scope = ::aul::ast::VarDecl::Scope::Global; + } + else + { + assert(0 && "C4AulParse::Parse_Var called with invalid parse state (current token should be scope of variable)"); + // Uh this shouldn't happen, ever + Error("internal error: C4AulParse::Parse_Var called with invalid parse state (current token should be scope of variable, but is '%s')", Idtf); + } Shift(); - // get global constant definition(s) + if (TokenType == ATT_IDTF && SEqual(Idtf, C4AUL_Const)) + { + decl->constant = true; + Shift(); + } while (1) { - Check(ATT_IDTF, "constant name"); // get desired variable name - char Name[C4AUL_MAX_Identifier] = ""; - SCopy(Idtf, Name); - // check func lists - functions of same name are not allowed - if (Engine->GetPropList()->GetFunc(Idtf)) - Error("definition of constant hidden by function %s", Idtf); - if (Engine->GlobalNamedNames.GetItemNr(Idtf) != -1) - Error("constant and variable with name %s", Idtf); + Check(ATT_IDTF, "variable name"); + std::string identifier = Idtf; Shift(); - Match(ATT_SET); - // expect value. Theoretically, something like C4AulScript::ExecOperator could be used here - // this would allow for definitions like "static const OCF_CrewMember = 1<<20" - // However, such stuff should better be generalized, so the preparser (and parser) - // can evaluate any constant expression, including functions with constant retval (e.g. Sqrt) - // So allow only simple constants for now. - - C4RefCntPointer key = ::Strings.RegString(Name); - Parse_ConstExpression(NULL, key); - + ::aul::ast::ExprPtr init; + if (TokenType == ATT_SET) + { + Shift(); + init = Parse_Expression(); + } + decl->decls.push_back({ identifier, std::move(init) }); if (TokenType == ATT_SCOLON) - return; + return decl; Match(ATT_COMMA, "',' or ';'"); } } @@ -2425,16 +1679,9 @@ bool C4ScriptHost::Parse() GetPropList()->GetDef()->IncludeDefinition((*s)->GetPropList()->GetDef()); } - // parse - C4AulParse state(this, C4AulParse::PARSER); - for (std::list::iterator s = SourceScripts.begin(); s != SourceScripts.end(); ++s) - { - if (DEBUG_BYTECODE_DUMP) - { - fprintf(stderr, "parsing %s...\n", (*s)->ScriptName.getData()); - } - state.Parse_Script(*s); - } + // generate bytecode + for (auto &s : SourceScripts) + C4AulCompiler::Compile(this, s, s->ast.get()); // save line count Engine->lineCnt += SGetLine(Script.getData(), Script.getPtr(Script.getLength())); @@ -2445,4 +1692,21 @@ bool C4ScriptHost::Parse() return true; } -#undef DEBUG_BYTECODE_DUMP +void C4AulParse::PushParsePos() +{ + parse_pos_stack.push(TokenSPos); +} + +void C4AulParse::PopParsePos() +{ + assert(!parse_pos_stack.empty()); + SPos = parse_pos_stack.top(); + DiscardParsePos(); + Shift(); +} + +void C4AulParse::DiscardParsePos() +{ + assert(!parse_pos_stack.empty()); + parse_pos_stack.pop(); +} diff --git a/src/script/C4AulParse.h b/src/script/C4AulParse.h index 02b594ef1..c95d6c541 100644 --- a/src/script/C4AulParse.h +++ b/src/script/C4AulParse.h @@ -17,7 +17,10 @@ #ifndef INC_C4AulParse #define INC_C4AulParse +#include + #include "script/C4Aul.h" +#include "script/C4AulAST.h" #include "script/C4AulCompiler.h" #include "script/C4AulScriptFunc.h" @@ -42,12 +45,11 @@ extern const C4ScriptOpDef C4ScriptOpMap[]; class C4AulParse { public: - enum Type { PARSER, PREPARSER }; - C4AulParse(C4ScriptHost * a, enum Type Type); + C4AulParse(class C4ScriptHost *host); C4AulParse(C4AulScriptFunc * Fn, C4AulScriptContext* context, C4AulScriptEngine *Engine); ~C4AulParse(); - void Parse_DirectExec(); - void Parse_Script(C4ScriptHost *); + std::unique_ptr<::aul::ast::FunctionDecl> Parse_DirectExec(const char *code); + std::unique_ptr<::aul::ast::Script> Parse_Script(C4ScriptHost *); private: C4AulScriptFunc *Fn; C4ScriptHost * Host; C4ScriptHost * pOrgScript; @@ -58,28 +60,21 @@ private: C4AulTokenType TokenType; // current token type int32_t cInt; // current int constant C4String * cStr; // current string constant - enum Type Type; // emitting bytecode? C4AulScriptContext* ContextToExecIn; - void Parse_Function(); - void Parse_FuncBody(); - void Parse_Statement(); - void Parse_Block(); - int Parse_Params(int iMaxCnt, const char * sWarn, C4AulFunc * pFunc = 0); - void Parse_Array(); - void Parse_PropList(); - void Parse_DoWhile(); - void Parse_While(); - void Parse_If(); - void Parse_For(); - void Parse_ForEach(); - void Parse_Expression(int iParentPrio = -1); - void Parse_Var(); - void Parse_Local(); - void Parse_Static(); - void Parse_Const(); - C4Value Parse_ConstExpression(C4PropListStatic * parent, C4String * Name); - C4Value Parse_ConstPropList(C4PropListStatic * parent, C4String * Name); - void Store_Const(C4PropListStatic * parent, C4String * Name, const C4Value & v); + std::unique_ptr<::aul::ast::FunctionDecl> Parse_ToplevelFunctionDecl(); + void Parse_Function(::aul::ast::Function *func); + std::unique_ptr<::aul::ast::Stmt> Parse_Statement(); + std::unique_ptr<::aul::ast::Block> Parse_Block(); + std::unique_ptr<::aul::ast::ArrayLit> Parse_Array(); + std::unique_ptr<::aul::ast::ProplistLit> Parse_PropList(); + std::unique_ptr<::aul::ast::DoLoop> Parse_DoWhile(); + std::unique_ptr<::aul::ast::WhileLoop> Parse_While(); + std::unique_ptr<::aul::ast::If> Parse_If(); + std::unique_ptr<::aul::ast::ForLoop> Parse_For(); + std::unique_ptr<::aul::ast::RangeLoop> Parse_ForEach(); + void Parse_CallParams(::aul::ast::CallExpr *call); + std::unique_ptr<::aul::ast::Expr> Parse_Expression(int iParentPrio = -1); + std::unique_ptr<::aul::ast::VarDecl> Parse_Var(); bool AdvanceSpaces(); // skip whitespaces; return whether script ended int GetOperator(const char* pScript); @@ -95,26 +90,11 @@ private: void Error(const char *pMsg, ...) GNUC_FORMAT_ATTRIBUTE_O; void AppendPosition(StdStrBuf & Buf); - void DebugChunk(); - C4AulCompiler codegen; - int AddVarAccess(C4AulBCCType eType, intptr_t varnum) - { if (Type == PARSER) return codegen.AddVarAccess(TokenSPos, eType, varnum); else return -1; } - int AddBCC(C4AulBCCType eType, intptr_t X = 0) - { if (Type == PARSER) return codegen.AddBCC(TokenSPos, eType, X); else return -1; } - C4V_Type GetLastRetType(C4V_Type to) - { return codegen.GetLastRetType(Engine, to); } - C4AulBCC MakeSetter(bool fLeaveValue = false) - { return Type == PARSER ? codegen.MakeSetter(TokenSPos, fLeaveValue) : C4AulBCC(AB_ERR, 0); } - void SetJumpHere(int iJumpOp) - { if (Type == PARSER) codegen.SetJumpHere(iJumpOp); } - void AddJump(C4AulBCCType eType, int iWhere) - { if (Type == PARSER) codegen.AddJump(TokenSPos, eType, iWhere); } - void PushLoop() - { if (Type == PARSER) codegen.PushLoop(); } - void PopLoop(int Jump) - { if (Type == PARSER) codegen.PopLoop(Jump); } - friend class C4AulParseError; + std::stack parse_pos_stack; + void PushParsePos(); + void PopParsePos(); + void DiscardParsePos(); }; #endif diff --git a/src/script/C4ScriptHost.cpp b/src/script/C4ScriptHost.cpp index a097828c9..a7289a08f 100644 --- a/src/script/C4ScriptHost.cpp +++ b/src/script/C4ScriptHost.cpp @@ -20,6 +20,10 @@ #include "C4Include.h" #include "script/C4ScriptHost.h" +#include "script/C4AulAST.h" +#include "script/C4AulCompiler.h" +#include "script/C4AulParse.h" +#include "script/C4AulScriptFunc.h" #include "object/C4Def.h" #include "script/C4Effect.h" @@ -49,6 +53,7 @@ C4ScriptHost::~C4ScriptHost() void C4ScriptHost::Clear() { C4ComponentHost::Clear(); + ast.reset(); Script.Clear(); LocalValues.Clear(); SourceScripts.clear(); diff --git a/src/script/C4ScriptHost.h b/src/script/C4ScriptHost.h index f1eb837da..c29c22b3c 100644 --- a/src/script/C4ScriptHost.h +++ b/src/script/C4ScriptHost.h @@ -21,7 +21,9 @@ #define INC_C4ScriptHost #include "c4group/C4ComponentHost.h" + #include "script/C4Aul.h" +#include "script/C4AulAST.h" // aul script state enum C4AulScriptState @@ -85,6 +87,10 @@ protected: friend class C4AulProfiler; friend class C4AulScriptEngine; friend class C4AulDebug; + friend class C4AulCompiler; + +private: + std::unique_ptr<::aul::ast::Script> ast; }; // script host for System.ocg scripts and scenario section Objects.c diff --git a/tests/aul/AulTest.cpp b/tests/aul/AulTest.cpp index dd66200a6..1255e8a36 100644 --- a/tests/aul/AulTest.cpp +++ b/tests/aul/AulTest.cpp @@ -148,6 +148,17 @@ TEST_F(AulTest, Locals) EXPECT_EQ(C4VInt(42), RunCode("local p1 = { i = 42 }, p2 = new p1 {}; func Main() { return p2.i; }", false)); } +TEST_F(AulTest, ProplistFunctions) +{ + EXPECT_EQ(C4VInt(1), RunCode(R"( +local a = new Global { + a = func() { return b; }, + b = 1 +}; +func Main() { return a->Call(a.a); } +)", false)); +} + TEST_F(AulTest, Eval) { EXPECT_EQ(C4VInt(42), RunExpr("eval(\"42\")"));