forked from Mirrors/openclonk
Merge branch 'ast'
# Conflicts: # src/C4Include.h # src/script/C4AulCompiler.cpp # src/script/C4AulParse.cpp # src/script/C4AulParse.h # src/script/C4ScriptHost.cpp # src/script/C4ScriptHost.h # tests/CMakeLists.txtdirectional-lights
commit
e2fd7095c1
|
@ -1083,6 +1083,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
|
||||
|
|
|
@ -26,15 +26,18 @@
|
|||
#include "c4group/C4Components.h"
|
||||
#include "c4group/C4LangStringTable.h"
|
||||
|
||||
C4AulError::C4AulError(): shown(false) {}
|
||||
|
||||
void C4AulError::show()
|
||||
static class DefaultErrorHandler : public C4AulErrorHandler
|
||||
{
|
||||
shown = true;
|
||||
// simply log error message
|
||||
if (sMessage)
|
||||
DebugLog(sMessage.getData());
|
||||
}
|
||||
public:
|
||||
void OnError(const char *msg) override
|
||||
{
|
||||
DebugLogF("ERROR: %s", msg);
|
||||
}
|
||||
void OnWarning(const char *msg) override
|
||||
{
|
||||
DebugLogF("WARNING: %s", msg);
|
||||
}
|
||||
} DefaultErrorHandler;
|
||||
|
||||
const char *C4AulError::what() const noexcept
|
||||
{
|
||||
|
@ -44,8 +47,9 @@ const char *C4AulError::what() const noexcept
|
|||
/*--- C4AulScriptEngine ---*/
|
||||
|
||||
C4AulScriptEngine::C4AulScriptEngine():
|
||||
C4PropListStaticMember(NULL, NULL, ::Strings.RegString("Global")),
|
||||
warnCnt(0), errCnt(0), lineCnt(0)
|
||||
C4PropListStaticMember(NULL, NULL, ::Strings.RegString("Global")),
|
||||
ErrorHandler(&DefaultErrorHandler),
|
||||
warnCnt(0), errCnt(0), lineCnt(0)
|
||||
{
|
||||
GlobalNamedNames.Reset();
|
||||
GlobalNamed.Reset();
|
||||
|
@ -241,6 +245,18 @@ C4AulUserFile *C4AulScriptEngine::GetUserFile(int32_t handle)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
void C4AulScriptEngine::RegisterErrorHandler(C4AulErrorHandler *handler)
|
||||
{
|
||||
assert(ErrorHandler == &DefaultErrorHandler);
|
||||
ErrorHandler = handler;
|
||||
}
|
||||
|
||||
void C4AulScriptEngine::UnregisterErrorHandler(C4AulErrorHandler *handler)
|
||||
{
|
||||
assert(ErrorHandler == handler);
|
||||
ErrorHandler = &DefaultErrorHandler;
|
||||
}
|
||||
|
||||
/*--- C4AulFuncMap ---*/
|
||||
|
||||
C4AulFuncMap::C4AulFuncMap(): FuncCnt(0)
|
||||
|
@ -301,3 +317,8 @@ void C4AulFuncMap::Remove(C4AulFunc * func)
|
|||
*pFunc = (*pFunc)->MapNext;
|
||||
--FuncCnt;
|
||||
}
|
||||
|
||||
C4AulErrorHandler::~C4AulErrorHandler()
|
||||
{
|
||||
::ScriptEngine.UnregisterErrorHandler(this);
|
||||
}
|
||||
|
|
|
@ -34,11 +34,8 @@ protected:
|
|||
StdCopyStrBuf sMessage;
|
||||
|
||||
public:
|
||||
bool shown;
|
||||
C4AulError();
|
||||
virtual ~C4AulError() { } // destructor
|
||||
virtual const char *what() const noexcept;
|
||||
void show(); // present error message
|
||||
};
|
||||
|
||||
// parse error
|
||||
|
@ -49,7 +46,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
|
||||
|
@ -95,6 +91,14 @@ public:
|
|||
int32_t GetHandle() const { return handle; }
|
||||
};
|
||||
|
||||
class C4AulErrorHandler
|
||||
{
|
||||
public:
|
||||
virtual ~C4AulErrorHandler();
|
||||
virtual void OnError(const char *msg) = 0;
|
||||
virtual void OnWarning(const char *msg) = 0;
|
||||
};
|
||||
|
||||
// holds all C4AulScripts
|
||||
class C4AulScriptEngine: public C4PropListStaticMember
|
||||
{
|
||||
|
@ -111,6 +115,8 @@ protected:
|
|||
std::list<C4AulUserFile> UserFiles;
|
||||
std::vector<C4Value> OwnedPropLists;
|
||||
|
||||
C4AulErrorHandler *ErrorHandler;
|
||||
|
||||
public:
|
||||
int warnCnt, errCnt; // number of warnings/errors
|
||||
int lineCnt; // line count parsed
|
||||
|
@ -152,6 +158,13 @@ public:
|
|||
void CloseUserFile(int32_t handle); // close user file given by handle
|
||||
C4AulUserFile *GetUserFile(int32_t handle); // get user file given by handle
|
||||
|
||||
void RegisterErrorHandler(C4AulErrorHandler *handler);
|
||||
void UnregisterErrorHandler(C4AulErrorHandler *handler);
|
||||
C4AulErrorHandler *GetErrorHandler() const
|
||||
{
|
||||
return ErrorHandler;
|
||||
}
|
||||
|
||||
friend class C4AulFunc;
|
||||
friend class C4AulProfiler;
|
||||
friend class C4ScriptHost;
|
||||
|
|
|
@ -0,0 +1,556 @@
|
|||
/*
|
||||
* 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 <memory>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#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 AssignmentExpr;
|
||||
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::AssignmentExpr *) {}
|
||||
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<class T>
|
||||
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<class... T> static std::unique_ptr<cls> New(const char *loc, T &&...t) { auto n = std::make_unique<cls>(std::forward<T>(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 = nullptr;
|
||||
|
||||
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<Stmt> 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<Expr> 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<ExprPtr> values;
|
||||
};
|
||||
|
||||
class ProplistLit : public Literal
|
||||
{
|
||||
AST_NODE(ProplistLit);
|
||||
public:
|
||||
std::vector<std::pair<std::string, ExprPtr>> 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
|
||||
};
|
||||
|
||||
class AssignmentExpr : public Expr
|
||||
{
|
||||
AST_NODE(AssignmentExpr);
|
||||
public:
|
||||
AssignmentExpr(ExprPtr &&lhs, ExprPtr &&rhs) : lhs(std::move(lhs)), rhs(std::move(rhs)) {}
|
||||
ExprPtr lhs, rhs;
|
||||
};
|
||||
|
||||
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<ExprPtr> 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<StmtPtr> 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<Loop> 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<Decl> 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<Var> 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<Parameter> params;
|
||||
bool has_unnamed_params = false;
|
||||
std::unique_ptr<Block> 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<DeclPtr> 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::AssignmentExpr *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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -172,8 +172,10 @@ C4Value C4AulExec::Exec(C4AulScriptFunc *pSFunc, C4PropList * p, C4Value *pnPars
|
|||
}
|
||||
catch (C4AulError &e)
|
||||
{
|
||||
if(!e.shown) e.show();
|
||||
if (!fPassErrors)
|
||||
::ScriptEngine.GetErrorHandler()->OnError(e.what());
|
||||
// Unwind stack
|
||||
// TODO: The stack dump should be passed to the error handler somehow
|
||||
while (pCurCtx > pOldCtx)
|
||||
{
|
||||
pCurCtx->dump(StdStrBuf(" by: "));
|
||||
|
@ -1056,7 +1058,7 @@ C4Value C4AulExec::DirectExec(C4PropList *p, const char *szScript, const char *s
|
|||
{
|
||||
if(fPassErrors)
|
||||
throw;
|
||||
ex.show();
|
||||
::ScriptEngine.GetErrorHandler()->OnError(ex.what());
|
||||
LogCallStack();
|
||||
StopDirectExec();
|
||||
return C4VNull;
|
||||
|
|
|
@ -69,7 +69,7 @@ bool C4AulFunc::CheckParTypes(const C4Value pPars[], bool fPassErrors) const {
|
|||
throw e;
|
||||
else
|
||||
{
|
||||
e.show();
|
||||
::ScriptEngine.GetErrorHandler()->OnError(e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ bool C4ScriptHost::ResolveIncludes(C4DefList *rDefs)
|
|||
// catch circular includes
|
||||
if (Resolving)
|
||||
{
|
||||
C4AulParseError(this, "Circular include chain detected - ignoring all includes!").show();
|
||||
Engine->GetErrorHandler()->OnError(C4AulParseError(this, "Circular include chain detected - ignoring all includes!").what());
|
||||
IncludesResolved = true;
|
||||
State = ASS_LINKED;
|
||||
return false;
|
||||
|
@ -189,7 +189,7 @@ void C4AulScriptEngine::Link(C4DefList *rDefs)
|
|||
catch (C4AulError &err)
|
||||
{
|
||||
// error??! show it!
|
||||
err.show();
|
||||
ErrorHandler->OnError(err.what());
|
||||
}
|
||||
|
||||
// Set name list for globals (FIXME: is this necessary?)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -17,7 +17,10 @@
|
|||
#ifndef INC_C4AulParse
|
||||
#define INC_C4AulParse
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "script/C4Aul.h"
|
||||
#include "script/C4AulAST.h"
|
||||
#include "script/C4AulCompiler.h"
|
||||
#include "script/C4AulScriptFunc.h"
|
||||
|
||||
|
@ -42,13 +45,11 @@ extern const C4ScriptOpDef C4ScriptOpMap[];
|
|||
class C4AulParse
|
||||
{
|
||||
public:
|
||||
enum Type { PARSER, PREPARSER };
|
||||
C4AulParse(C4ScriptHost * a, enum Type Type);
|
||||
C4AulParse(C4AulScriptFunc * Fn, C4AulScriptContext* context, C4AulScriptEngine *Engine, enum Type Type = C4AulParse::PARSER);
|
||||
C4AulParse(class C4ScriptHost *host);
|
||||
C4AulParse(C4AulScriptFunc * Fn, C4AulScriptContext* context, C4AulScriptEngine *Engine);
|
||||
~C4AulParse();
|
||||
void Parse_DirectExecFunc();
|
||||
void Parse_DirectExecStatement();
|
||||
void Parse_Script(C4ScriptHost *);
|
||||
std::unique_ptr<::aul::ast::FunctionDecl> Parse_DirectExec(const char *code, bool whole_function);
|
||||
std::unique_ptr<::aul::ast::Script> Parse_Script(C4ScriptHost *);
|
||||
|
||||
private:
|
||||
C4AulScriptFunc *Fn; C4ScriptHost * Host; C4ScriptHost * pOrgScript;
|
||||
|
@ -59,35 +60,35 @@ 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(bool parse_for_direct_exec);
|
||||
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);
|
||||
|
||||
protected:
|
||||
// All of the Parse_* functions need to be protected (not private!) so
|
||||
// we can make them public in a derived class for unit testing purposes
|
||||
std::unique_ptr<::aul::ast::FunctionDecl> Parse_ToplevelFunctionDecl();
|
||||
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();
|
||||
std::unique_ptr<::aul::ast::Expr> Parse_Expression(int iParentPrio = -1);
|
||||
std::unique_ptr<::aul::ast::VarDecl> Parse_Var();
|
||||
void Shift();
|
||||
|
||||
private:
|
||||
void Parse_Function(::aul::ast::Function *func);
|
||||
void Parse_CallParams(::aul::ast::CallExpr *call);
|
||||
|
||||
bool AdvanceSpaces(); // skip whitespaces; return whether script ended
|
||||
int GetOperator(const char* pScript);
|
||||
void ClearToken(); // clear any data held with the current token
|
||||
C4AulTokenType GetNextToken(); // get next token of SPos
|
||||
|
||||
void Shift();
|
||||
void Match(C4AulTokenType TokenType, const char * Expected = NULL);
|
||||
void Check(C4AulTokenType TokenType, const char * Expected = NULL);
|
||||
NORETURN void UnexpectedToken(const char * Expected);
|
||||
|
@ -96,26 +97,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<const char *> parse_pos_stack;
|
||||
void PushParsePos();
|
||||
void PopParsePos();
|
||||
void DiscardParsePos();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -193,11 +193,12 @@ public:
|
|||
const char *Script; // script pos
|
||||
C4ValueMapNames VarNamed; // list of named vars in this function
|
||||
C4ValueMapNames ParNamed; // list of named pars in this function
|
||||
void AddPar(const char * Idtf)
|
||||
void AddPar(const char * Idtf, C4V_Type type = C4V_Any)
|
||||
{
|
||||
assert(ParCount < C4AUL_MAX_Par);
|
||||
assert(ParCount == ParNamed.iSize);
|
||||
ParNamed.AddName(Idtf);
|
||||
ParType[ParCount] = type;
|
||||
++ParCount;
|
||||
}
|
||||
C4ScriptHost *pOrgScript; // the orginal script (!= Owner if included or appended)
|
||||
|
|
|
@ -782,7 +782,7 @@ C4Value C4PropList::Call(const char * s, C4AulParSet *Pars, bool fPassErrors)
|
|||
C4AulExecError err(FormatString("Undefined function: %s", s).getData());
|
||||
if (fPassErrors)
|
||||
throw err;
|
||||
err.show();
|
||||
::ScriptEngine.GetErrorHandler()->OnError(err.what());
|
||||
}
|
||||
return C4Value();
|
||||
}
|
||||
|
|
|
@ -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/C4AulScriptFunc.h"
|
||||
#include "script/C4Effect.h"
|
||||
|
@ -51,6 +55,7 @@ void C4ScriptHost::Clear()
|
|||
{
|
||||
UnlinkOwnedFunctions();
|
||||
C4ComponentHost::Clear();
|
||||
ast.reset();
|
||||
Script.Clear();
|
||||
LocalValues.Clear();
|
||||
DeleteOwnedPropLists();
|
||||
|
@ -68,30 +73,15 @@ void C4ScriptHost::Clear()
|
|||
State = ASS_NONE;
|
||||
}
|
||||
|
||||
void C4ScriptHost::DeleteOwnedPropLists()
|
||||
{
|
||||
// delete all static proplists associated to this script host.
|
||||
// Note that just clearing the vector is not enough in case of
|
||||
// cyclic references.
|
||||
for (C4Value& value: ownedPropLists)
|
||||
{
|
||||
C4PropList* plist = value.getPropList();
|
||||
if (plist)
|
||||
{
|
||||
if (plist->Delete()) delete plist;
|
||||
else plist->Clear();
|
||||
}
|
||||
}
|
||||
ownedPropLists.clear();
|
||||
}
|
||||
|
||||
void C4ScriptHost::UnlinkOwnedFunctions()
|
||||
{
|
||||
// Remove owned functions from their parents. This solves a problem
|
||||
// where overloading a definition would unload the C4ScriptHost, but
|
||||
// keep around global functions, which then contained dangling pointers.
|
||||
for (auto func : ownedFunctions)
|
||||
for (const auto &box : ownedFunctions)
|
||||
{
|
||||
assert(box.GetType() == C4V_Function);
|
||||
C4AulScriptFunc *func = box._getFunction()->SFunc();
|
||||
C4PropList *parent = func->Parent;
|
||||
if (parent == GetPropList())
|
||||
continue;
|
||||
|
@ -118,7 +108,7 @@ void C4ScriptHost::UnlinkOwnedFunctions()
|
|||
// Unlink the removed function from the inheritance chain
|
||||
if (func_chain->OwnerOverloaded == func)
|
||||
{
|
||||
func_chain->SetOverloaded(func->OwnerOverloaded);
|
||||
func_chain->OwnerOverloaded = func->OwnerOverloaded;
|
||||
break;
|
||||
}
|
||||
assert(func_chain->OwnerOverloaded && "Removed function not found in inheritance chain");
|
||||
|
@ -129,6 +119,23 @@ void C4ScriptHost::UnlinkOwnedFunctions()
|
|||
ownedFunctions.clear();
|
||||
}
|
||||
|
||||
void C4ScriptHost::DeleteOwnedPropLists()
|
||||
{
|
||||
// delete all static proplists associated to this script host.
|
||||
// Note that just clearing the vector is not enough in case of
|
||||
// cyclic references.
|
||||
for (C4Value& value: ownedPropLists)
|
||||
{
|
||||
C4PropList* plist = value.getPropList();
|
||||
if (plist)
|
||||
{
|
||||
if (plist->Delete()) delete plist;
|
||||
else plist->Clear();
|
||||
}
|
||||
}
|
||||
ownedPropLists.clear();
|
||||
}
|
||||
|
||||
void C4ScriptHost::Unreg()
|
||||
{
|
||||
// remove from list
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
#define INC_C4ScriptHost
|
||||
|
||||
#include "c4group/C4ComponentHost.h"
|
||||
|
||||
#include "script/C4Aul.h"
|
||||
#include "script/C4AulAST.h"
|
||||
|
||||
// aul script state
|
||||
enum C4AulScriptState
|
||||
|
@ -88,7 +90,7 @@ protected:
|
|||
C4AulScriptState State; // script state
|
||||
|
||||
// list of all functions generated from code in this script host
|
||||
std::set<C4AulScriptFunc*> ownedFunctions;
|
||||
std::vector<C4Value> ownedFunctions;
|
||||
|
||||
// list of all static proplists that refer to this script host
|
||||
// filled in at link time and used to delete all proplists
|
||||
|
@ -99,6 +101,11 @@ protected:
|
|||
friend class C4AulProfiler;
|
||||
friend class C4AulScriptEngine;
|
||||
friend class C4AulDebug;
|
||||
friend class C4AulCompiler;
|
||||
friend class C4AulScriptFunc;
|
||||
|
||||
private:
|
||||
std::unique_ptr<::aul::ast::Script> ast;
|
||||
};
|
||||
|
||||
// script host for System.ocg scripts and scenario section Objects.c
|
||||
|
|
|
@ -121,6 +121,9 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
|||
aul/AulMathTest.cpp
|
||||
aul/AulPredefinedFunctionTest.cpp
|
||||
aul/AulDeathTest.cpp
|
||||
aul/AulSyntaxTest.cpp
|
||||
aul/AulSyntaxTestDetail.h
|
||||
aul/ErrorHandler.h
|
||||
../src/script/C4ScriptStandaloneStubs.cpp
|
||||
../src/script/C4ScriptStandalone.cpp
|
||||
LIBRARIES
|
||||
|
|
|
@ -143,8 +143,8 @@ TEST_F(AulPredefFunctionTest, Abs)
|
|||
|
||||
TEST_F(AulPredefFunctionTest, CreateEffect)
|
||||
{
|
||||
EXPECT_EQ(C4VInt(3), RunCode("local A = { Start=func() { this.Magicnumber = 3; } }; func Main() { return CreateEffect(A, 1).Magicnumber; }", false));
|
||||
EXPECT_EQ(C4VInt(3), RunCode("local A = { Construction=func() { this.Magicnumber = 3; } }; func Main() { return CreateEffect(A, 1).Magicnumber; }", false));
|
||||
EXPECT_EQ(C4VInt(3), RunScript("local A = { Start=func() { this.Magicnumber = 3; } }; func Main() { return CreateEffect(A, 1).Magicnumber; }"));
|
||||
EXPECT_EQ(C4VInt(3), RunScript("local A = { Construction=func() { this.Magicnumber = 3; } }; func Main() { return CreateEffect(A, 1).Magicnumber; }"));
|
||||
}
|
||||
|
||||
TEST_F(AulPredefFunctionTest, Trivial)
|
||||
|
|
|
@ -0,0 +1,638 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "C4Include.h"
|
||||
|
||||
#include "ErrorHandler.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "AulSyntaxTestDetail.h"
|
||||
|
||||
#include "script/C4AulAST.h"
|
||||
#include "script/C4AulParse.h"
|
||||
#include "script/C4ScriptHost.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
|
||||
class DummyScriptHost : public C4ScriptHost
|
||||
{
|
||||
public:
|
||||
explicit DummyScriptHost(const char *code)
|
||||
{
|
||||
Script.Copy(code);
|
||||
}
|
||||
};
|
||||
|
||||
class TestableAulParse : public C4AulParse
|
||||
{
|
||||
public:
|
||||
template<class... T>
|
||||
TestableAulParse(T &&...t) : C4AulParse(std::forward<T>(t)...)
|
||||
{
|
||||
Shift();
|
||||
}
|
||||
|
||||
// Make all of the Parse_* function public so we can test them
|
||||
using C4AulParse::Parse_ToplevelFunctionDecl;
|
||||
using C4AulParse::Parse_Statement;
|
||||
using C4AulParse::Parse_Block;
|
||||
using C4AulParse::Parse_Array;
|
||||
using C4AulParse::Parse_PropList;
|
||||
using C4AulParse::Parse_DoWhile;
|
||||
using C4AulParse::Parse_While;
|
||||
using C4AulParse::Parse_If;
|
||||
using C4AulParse::Parse_For;
|
||||
using C4AulParse::Parse_ForEach;
|
||||
using C4AulParse::Parse_Expression;
|
||||
using C4AulParse::Parse_Var;
|
||||
};
|
||||
|
||||
class AulSyntaxTest
|
||||
{};
|
||||
|
||||
static std::unique_ptr<::aul::ast::Stmt> ParseStatement(const char *code)
|
||||
{
|
||||
DummyScriptHost host{ code };
|
||||
TestableAulParse parser{ &host };
|
||||
return parser.Parse_Statement();
|
||||
}
|
||||
static std::unique_ptr<::aul::ast::Expr> ParseExpression(const char *code)
|
||||
{
|
||||
DummyScriptHost host{ code };
|
||||
TestableAulParse parser{ &host };
|
||||
return parser.Parse_Expression();
|
||||
}
|
||||
static std::unique_ptr<::aul::ast::Script> ParseScript(const char *code)
|
||||
{
|
||||
DummyScriptHost host{ code };
|
||||
TestableAulParse parser{ &host };
|
||||
return parser.Parse_Script(&host);
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseSyntaxErrors)
|
||||
{
|
||||
EXPECT_THROW(ParseStatement("1"), C4AulParseError);
|
||||
EXPECT_THROW(ParseStatement("func Main() {}"), C4AulParseError);
|
||||
}
|
||||
|
||||
using namespace ::aul::ast;
|
||||
TEST(AulSyntaxTest, ParseLiterals)
|
||||
{
|
||||
// Basic literals
|
||||
EXPECT_THAT(ParseExpression("1"), MatchesAst(IntLit(1)));
|
||||
EXPECT_THAT(ParseExpression("2147483647"), MatchesAst(IntLit(2147483647)));
|
||||
// While a leading + seems like it should be an operator expression,
|
||||
// we don't ever emit a no-op + operator from the parser
|
||||
EXPECT_THAT(ParseExpression("+4"), MatchesAst(IntLit(4)));
|
||||
EXPECT_THAT(ParseExpression("0xFFFFFFFF"), MatchesAst(IntLit(0xFFFFFFFF)));
|
||||
EXPECT_THAT(ParseExpression("this"), MatchesAst(ThisLit()));
|
||||
EXPECT_THAT(ParseExpression("nil"), MatchesAst(NilLit()));
|
||||
EXPECT_THAT(ParseExpression("false"), MatchesAst(BoolLit(false)));
|
||||
EXPECT_THAT(ParseExpression("true"), MatchesAst(BoolLit(true)));
|
||||
|
||||
// String literals
|
||||
EXPECT_THAT(ParseExpression("\"\""), MatchesAst(StringLit("")));
|
||||
EXPECT_THAT(ParseExpression("\"[]\""), MatchesAst(StringLit("[]")));
|
||||
// can't use a raw string for this because MSVC chokes on it w/ C2017: illegal escape sequence
|
||||
//EXPECT_THAT(ParseExpression(R"("\"")"), MatchesAst(StringLit("\"")));
|
||||
EXPECT_THAT(ParseExpression("\"\\\"\""), MatchesAst(StringLit("\"")));
|
||||
EXPECT_THAT(ParseExpression("\"\\xaF\\x41\""), MatchesAst(StringLit("\xaf\x41")));
|
||||
EXPECT_THAT(ParseExpression("\"\\142\""), MatchesAst(StringLit("\142")));
|
||||
EXPECT_THAT(ParseExpression("\"\\\\\\t\\n\""), MatchesAst(StringLit("\\\t\n")));
|
||||
|
||||
// Compound literals
|
||||
{
|
||||
auto ast = ArrayLit();
|
||||
EXPECT_THAT(ParseExpression("[]"), MatchesAst(ast));
|
||||
ast.values.push_back(std::make_unique<IntLit>(1));
|
||||
EXPECT_THAT(ParseExpression("[1]"), MatchesAst(ast));
|
||||
ast.values.push_back(std::make_unique<IntLit>(2));
|
||||
ast.values.push_back(std::make_unique<IntLit>(3));
|
||||
EXPECT_THAT(ParseExpression("[1, 2, 3]"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
auto ast = ArrayLit();
|
||||
ast.values.push_back(std::make_unique<StringLit>("Hello"));
|
||||
EXPECT_THAT(ParseExpression("[\"Hello\"]"), MatchesAst(ast));
|
||||
ast.values.push_back(std::make_unique<IntLit>(2));
|
||||
ast.values.push_back(std::make_unique<ArrayLit>());
|
||||
EXPECT_THAT(ParseExpression("[\"Hello\", 2, []]"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
auto ast = ProplistLit();
|
||||
ast.values.emplace_back("foo", std::make_unique<StringLit>("bar"));
|
||||
EXPECT_THAT(ParseExpression(R"({foo: "bar"})"), MatchesAst(ast));
|
||||
ast.values.emplace_back("Prototype", std::make_unique<ProplistLit>());
|
||||
EXPECT_THAT(ParseExpression(R"({foo: "bar", Prototype: {}})"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseUnOps)
|
||||
{
|
||||
// Basic unary operator expressions
|
||||
{
|
||||
auto ast = UnOpExpr(0,
|
||||
std::make_unique<VarExpr>("foo")
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("++foo"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
auto ast = UnOpExpr(7,
|
||||
std::make_unique<VarExpr>("foo")
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("foo--"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
auto ast = UnOpExpr(5,
|
||||
std::make_unique<IntLit>(0)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("-0"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseBinOps)
|
||||
{
|
||||
// Basic binary operator expressions
|
||||
{
|
||||
auto ast = BinOpExpr(13,
|
||||
std::make_unique<IntLit>(1),
|
||||
std::make_unique<IntLit>(1)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("1 + 1"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// This expression doesn't make any sense semantically, but
|
||||
// syntactically it's correct
|
||||
auto ast = BinOpExpr(31,
|
||||
std::make_unique<IntLit>(1),
|
||||
std::make_unique<IntLit>(1)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("1 += 1"), MatchesAst(ast));
|
||||
}
|
||||
|
||||
// Assignment operator
|
||||
{
|
||||
auto ast = AssignmentExpr(
|
||||
std::make_unique<VarExpr>("foo"),
|
||||
std::make_unique<VarExpr>("bar")
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("foo = bar"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
auto ast = AssignmentExpr(
|
||||
std::make_unique<VarExpr>("foo"),
|
||||
std::make_unique<BoolLit>(false)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("foo = false"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseOpPriority)
|
||||
{
|
||||
{
|
||||
// Ensure ambiguities are resolved correctly
|
||||
auto ast = BinOpExpr(13,
|
||||
std::make_unique<UnOpExpr>(6,
|
||||
std::make_unique<VarExpr>("a")
|
||||
),
|
||||
std::make_unique<VarExpr>("b")
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a+++b"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
auto ast = BinOpExpr(13,
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<UnOpExpr>(0,
|
||||
std::make_unique<VarExpr>("b")
|
||||
)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a+ ++b"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// This looks strange but prefix + is never emitted.
|
||||
// We should consider whether this should be allowed, however
|
||||
auto ast = BinOpExpr(13,
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<UnOpExpr>(5,
|
||||
std::make_unique<VarExpr>("b")
|
||||
)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a+-+b"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// * has higher priority than +
|
||||
auto ast = BinOpExpr(13,
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<BinOpExpr>(10,
|
||||
std::make_unique<VarExpr>("b"),
|
||||
std::make_unique<VarExpr>("c")
|
||||
)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a + b * c"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Parentheses override operator priorities
|
||||
auto ast = BinOpExpr(10,
|
||||
std::make_unique<BinOpExpr>(13,
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<VarExpr>("b")
|
||||
),
|
||||
std::make_unique<VarExpr>("c")
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("(a + b) * c"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseSubscripts)
|
||||
{
|
||||
{
|
||||
// Standard integer literal subscript
|
||||
auto ast = SubscriptExpr(
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<IntLit>(0)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a[0]"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Standard string literal subscript
|
||||
auto ast = SubscriptExpr(
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<StringLit>("b")
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a[\"b\"]"), MatchesAst(ast));
|
||||
// also accept syntactic sugar with .
|
||||
EXPECT_THAT(ParseExpression("a.b"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Expression-based subscript
|
||||
auto ast = SubscriptExpr(
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<BinOpExpr>(
|
||||
13,
|
||||
std::make_unique<IntLit>(1),
|
||||
std::make_unique<VarExpr>("b")
|
||||
)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a[1 + b]"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseSlices)
|
||||
{
|
||||
{
|
||||
// Slice with no bounds
|
||||
auto ast = SliceExpr(
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<IntLit>(0),
|
||||
std::make_unique<IntLit>(std::numeric_limits<int32_t>::max())
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a[:]"), MatchesAst(ast));
|
||||
}
|
||||
|
||||
{
|
||||
// Slice with lower bound
|
||||
auto ast = SliceExpr(
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<IntLit>(7),
|
||||
std::make_unique<IntLit>(std::numeric_limits<int32_t>::max())
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a[7:]"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Slice with upper bound
|
||||
auto ast = SliceExpr(
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<IntLit>(0),
|
||||
std::make_unique<IntLit>(42)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a[:42]"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Slice with both bounds
|
||||
auto ast = SliceExpr(
|
||||
std::make_unique<VarExpr>("a"),
|
||||
std::make_unique<IntLit>(7),
|
||||
std::make_unique<IntLit>(42)
|
||||
);
|
||||
EXPECT_THAT(ParseExpression("a[7:42]"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseCalls)
|
||||
{
|
||||
{
|
||||
// Standard call without context or args
|
||||
auto ast = CallExpr();
|
||||
ast.callee = "a";
|
||||
EXPECT_THAT(ParseExpression("a()"), MatchesAst(ast));
|
||||
// Standard call with context but no args
|
||||
ast.context = std::make_unique<VarExpr>("b");
|
||||
EXPECT_THAT(ParseExpression("b->a()"), MatchesAst(ast));
|
||||
// Fail-safe call with context but no args
|
||||
ast.safe_call = true;
|
||||
EXPECT_THAT(ParseExpression("b->~a()"), MatchesAst(ast));
|
||||
// Standard call with context and args
|
||||
ast.safe_call = false;
|
||||
ast.args.push_back(std::make_unique<BoolLit>(false));
|
||||
EXPECT_THAT(ParseExpression("b->a(false)"), MatchesAst(ast));
|
||||
// Standard call with context and args, passing unnamed parameters
|
||||
ast.append_unnamed_pars = true;
|
||||
EXPECT_THAT(ParseExpression("b->a(false, ...)"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Nested call, outer fail-safe with context, inner standard without context
|
||||
auto inner = std::make_unique<CallExpr>();
|
||||
inner->callee = "a";
|
||||
inner->args.push_back(std::make_unique<IntLit>(42));
|
||||
auto ast = CallExpr();
|
||||
ast.callee = "c";
|
||||
ast.context = std::make_unique<VarExpr>("b");
|
||||
ast.args.push_back(std::move(inner));
|
||||
ast.append_unnamed_pars = true;
|
||||
EXPECT_THAT(ParseExpression("b->c(a(42), ...)"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseBlocks)
|
||||
{
|
||||
{
|
||||
// Empty block
|
||||
auto ast = Block();
|
||||
EXPECT_THAT(ParseStatement("{}"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Single-statement block
|
||||
auto stmt = std::make_unique<AssignmentExpr>(
|
||||
std::make_unique<VarExpr>("i"),
|
||||
std::make_unique<IntLit>(0)
|
||||
);
|
||||
auto ast = Block();
|
||||
ast.children.push_back(std::move(stmt));
|
||||
EXPECT_THAT(ParseStatement("{ i = 0; }"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Nested block
|
||||
auto inner = std::make_unique<Block>();
|
||||
inner->children.push_back(
|
||||
std::make_unique<AssignmentExpr>(
|
||||
std::make_unique<VarExpr>("i"),
|
||||
std::make_unique<IntLit>(0)
|
||||
));
|
||||
auto ast = Block();
|
||||
ast.children.push_back(std::move(inner));
|
||||
ast.children.push_back(
|
||||
std::make_unique<AssignmentExpr>(
|
||||
std::make_unique<VarExpr>("i"),
|
||||
std::make_unique<IntLit>(1)
|
||||
));
|
||||
EXPECT_THAT(ParseStatement("{ { i = 0; } i = 1; }"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseForLoops)
|
||||
{
|
||||
{
|
||||
// No initializer, condition, nor incrementor
|
||||
auto ast = ForLoop();
|
||||
ast.body = std::make_unique<Noop>();
|
||||
EXPECT_THAT(ParseStatement("for (;;);"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Initializer without variable declaration
|
||||
auto ast = ForLoop();
|
||||
ast.body = std::make_unique<Noop>();
|
||||
ast.init = std::make_unique<AssignmentExpr>(
|
||||
std::make_unique<VarExpr>("i"),
|
||||
std::make_unique<IntLit>(0)
|
||||
);
|
||||
EXPECT_THAT(ParseStatement("for (i = 0;;);"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Initializer with variable declaration
|
||||
auto decl = std::make_unique<VarDecl>();
|
||||
decl->decls.push_back(VarDecl::Var{ "i", std::make_unique<IntLit>(0) });
|
||||
auto ast = ForLoop();
|
||||
ast.body = std::make_unique<Noop>();
|
||||
ast.init = std::move(decl);
|
||||
EXPECT_THAT(ParseStatement("for (var i = 0;;);"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// Full for loop
|
||||
auto init = std::make_unique<VarDecl>();
|
||||
init->decls.push_back(VarDecl::Var{ "i", std::make_unique<IntLit>(0) });
|
||||
auto cond = std::make_unique<BinOpExpr>(
|
||||
16,
|
||||
std::make_unique<VarExpr>("i"),
|
||||
std::make_unique<IntLit>(10)
|
||||
);
|
||||
auto incr = std::make_unique<UnOpExpr>(
|
||||
0,
|
||||
std::make_unique<VarExpr>("i")
|
||||
);
|
||||
auto body = std::make_unique<CallExpr>();
|
||||
body->callee = "Log";
|
||||
body->args.push_back(std::make_unique<StringLit>("%d"));
|
||||
body->args.push_back(std::make_unique<VarExpr>("i"));
|
||||
auto ast = ForLoop();
|
||||
ast.init = std::move(init);
|
||||
ast.cond = std::move(cond);
|
||||
ast.incr = std::move(incr);
|
||||
ast.body = std::move(body);
|
||||
EXPECT_THAT(ParseStatement(R"(for (var i = 0; i < 10; ++i) Log("%d", i);)"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseForEachLoops)
|
||||
{
|
||||
{
|
||||
// for-each without explicit variable declaration
|
||||
auto ast = RangeLoop();
|
||||
ast.var = "i";
|
||||
ast.body = std::make_unique<Noop>();
|
||||
ast.cond = std::make_unique<ArrayLit>();
|
||||
EXPECT_THAT(ParseStatement("for (i in []);"), MatchesAst(ast));
|
||||
// and with explicit variable declaration
|
||||
ast.scoped_var = true;
|
||||
EXPECT_THAT(ParseStatement("for (var i in []);"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseDoLoops)
|
||||
{
|
||||
{
|
||||
// empty do loop with trivial condition
|
||||
auto ast = DoLoop();
|
||||
ast.body = std::make_unique<Noop>();
|
||||
ast.cond = std::make_unique<BoolLit>(false);
|
||||
EXPECT_THAT(ParseStatement("do ; while(false);"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// nested do loops with trivial condition
|
||||
auto inner = std::make_unique<DoLoop>();
|
||||
inner->body = std::make_unique<Noop>();
|
||||
inner->cond = std::make_unique<BoolLit>(false);
|
||||
auto ast = DoLoop();
|
||||
ast.body = std::move(inner);
|
||||
ast.cond = std::make_unique<BoolLit>(false);
|
||||
EXPECT_THAT(ParseStatement("do do ; while (false); while (false);"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseWhileLoops)
|
||||
{
|
||||
{
|
||||
// empty while loop with trivial condition
|
||||
auto ast = WhileLoop();
|
||||
ast.cond = std::make_unique<BoolLit>(true);
|
||||
ast.body = std::make_unique<Noop>();
|
||||
EXPECT_THAT(ParseStatement("while(true);"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// nested while loop with trivial condition
|
||||
auto inner = std::make_unique<WhileLoop>();
|
||||
inner->cond = std::make_unique<BoolLit>(false);
|
||||
inner->body = std::make_unique<Noop>();
|
||||
auto ast = WhileLoop();
|
||||
ast.cond = std::make_unique<BoolLit>(true);
|
||||
ast.body = std::move(inner);
|
||||
EXPECT_THAT(ParseStatement("while(true) while(false);"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseIfs)
|
||||
{
|
||||
{
|
||||
// trivial condition, no else branch
|
||||
auto ast = If();
|
||||
ast.cond = std::make_unique<BoolLit>(false);
|
||||
ast.iftrue = std::make_unique<Noop>();
|
||||
EXPECT_THAT(ParseStatement("if(false);"), MatchesAst(ast));
|
||||
// trivial condition, with else branch
|
||||
ast.iffalse = std::make_unique<Noop>();
|
||||
EXPECT_THAT(ParseStatement("if(false); else;"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// trivial condition, nested ifs. else binds to the inner if.
|
||||
auto inner = std::make_unique<If>();
|
||||
inner->cond = std::make_unique<BoolLit>(false);
|
||||
inner->iftrue = std::make_unique<AssignmentExpr>(
|
||||
std::make_unique<VarExpr>("i"),
|
||||
std::make_unique<IntLit>(0)
|
||||
);
|
||||
inner->iffalse = std::make_unique<Noop>();
|
||||
auto ast = If();
|
||||
ast.cond = std::make_unique<NilLit>();
|
||||
ast.iftrue = std::move(inner);
|
||||
EXPECT_THAT(ParseStatement("if(nil) if(false) i = 0; else;"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseFunctionDecls)
|
||||
{
|
||||
{
|
||||
// local function, no parameters
|
||||
auto func = std::make_unique<FunctionDecl>("f");
|
||||
func->body = std::make_unique<Block>();
|
||||
auto ast = Script();
|
||||
ast.declarations.push_back(std::move(func));
|
||||
EXPECT_THAT(ParseScript("func f() {}"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// global function, unnamed parameters only
|
||||
auto func = std::make_unique<FunctionDecl>("f");
|
||||
func->body = std::make_unique<Block>();
|
||||
func->has_unnamed_params = true;
|
||||
func->is_global = true;
|
||||
auto ast = Script();
|
||||
ast.declarations.push_back(std::move(func));
|
||||
EXPECT_THAT(ParseScript("global func f(...) {}"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// local function, named parameters only
|
||||
auto func = std::make_unique<FunctionDecl>("f");
|
||||
func->body = std::make_unique<Block>();
|
||||
func->params.push_back(Function::Parameter{ "a", C4V_Array });
|
||||
func->params.push_back(Function::Parameter{ "b", C4V_Int });
|
||||
func->params.push_back(Function::Parameter{ "c", C4V_Any });
|
||||
auto ast = Script();
|
||||
ast.declarations.push_back(std::move(func));
|
||||
EXPECT_THAT(ParseScript("func f(array a, int b, c) {}"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// local function, named and unnamed parameters
|
||||
auto func = std::make_unique<FunctionDecl>("f");
|
||||
func->body = std::make_unique<Block>();
|
||||
func->params.push_back(Function::Parameter{ "a", C4V_Array });
|
||||
func->has_unnamed_params = true;
|
||||
auto ast = Script();
|
||||
ast.declarations.push_back(std::move(func));
|
||||
EXPECT_THAT(ParseScript("func f(array a, ...) {}"), MatchesAst(ast));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AulSyntaxTest, ParseVarDecls)
|
||||
{
|
||||
{
|
||||
// function-scoped variables, no initializer
|
||||
auto ast = VarDecl();
|
||||
ast.scope = VarDecl::Scope::Func;
|
||||
ast.decls.push_back(VarDecl::Var{ "a", nullptr });
|
||||
EXPECT_THAT(ParseStatement("var a;"), MatchesAst(ast));
|
||||
ast.decls.push_back(VarDecl::Var{ "b", nullptr });
|
||||
EXPECT_THAT(ParseStatement("var a, b;"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// function-scoped variables, partially initialized
|
||||
auto ast = VarDecl();
|
||||
ast.scope = VarDecl::Scope::Func;
|
||||
ast.decls.push_back(VarDecl::Var{ "a", std::make_unique<IntLit>(1) });
|
||||
EXPECT_THAT(ParseStatement("var a = 1;"), MatchesAst(ast));
|
||||
ast.decls.push_back(VarDecl::Var{ "b", nullptr });
|
||||
EXPECT_THAT(ParseStatement("var a = 1, b;"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// object-scoped variables, partially initialized
|
||||
auto ast = VarDecl();
|
||||
ast.scope = VarDecl::Scope::Object;
|
||||
ast.decls.push_back(VarDecl::Var{ "a", std::make_unique<IntLit>(1) });
|
||||
EXPECT_THAT(ParseStatement("local a = 1;"), MatchesAst(ast));
|
||||
ast.decls.push_back(VarDecl::Var{ "b", nullptr });
|
||||
EXPECT_THAT(ParseStatement("local a = 1, b;"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// global variables, partially initialized
|
||||
auto var = std::make_unique<VarDecl>();
|
||||
var->scope = VarDecl::Scope::Global;
|
||||
var->decls.push_back(VarDecl::Var{ "a", std::make_unique<IntLit>(1) });
|
||||
var->decls.push_back(VarDecl::Var{ "b", nullptr });
|
||||
auto ast = Script();
|
||||
ast.declarations.push_back(std::move(var));
|
||||
EXPECT_THAT(ParseScript("static a = 1, b;"), MatchesAst(ast));
|
||||
}
|
||||
{
|
||||
// global constant, initialized
|
||||
auto var = std::make_unique<VarDecl>();
|
||||
var->scope = VarDecl::Scope::Global;
|
||||
var->constant = true;
|
||||
var->decls.push_back(VarDecl::Var{ "a", std::make_unique<IntLit>(1) });
|
||||
auto call = std::make_unique<CallExpr>();
|
||||
call->callee = "f";
|
||||
var->decls.push_back(VarDecl::Var{ "b", std::move(call) });
|
||||
auto ast = Script();
|
||||
ast.declarations.push_back(std::move(var));
|
||||
EXPECT_THAT(ParseScript("static const a = 1, b = f();"), MatchesAst(ast));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,656 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// A lot of ugly helper code to
|
||||
// a) check whether two ASTs are the same
|
||||
// b) format an AST into human-readable format in case they aren't
|
||||
|
||||
#ifndef INC_AulSyntaxTestDetail
|
||||
#define INC_AulSyntaxTestDetail
|
||||
|
||||
#include <ostream>
|
||||
#include <assert.h>
|
||||
#include "script/C4AulAST.h"
|
||||
#include "script/C4AulParse.h"
|
||||
|
||||
class AstFormattingVisitor : public ::aul::AstVisitor
|
||||
{
|
||||
std::ostream ⌖
|
||||
public:
|
||||
AstFormattingVisitor(std::ostream &target) : target(target)
|
||||
{}
|
||||
|
||||
virtual void visit(const ::aul::ast::Noop *) override
|
||||
{
|
||||
target << "no-op";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::StringLit *n) override
|
||||
{
|
||||
target << "\"" << n->value << "\"";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::IntLit *n) override
|
||||
{
|
||||
target << n->value;
|
||||
}
|
||||
virtual void visit(const ::aul::ast::BoolLit *n) override
|
||||
{
|
||||
target << n->value ? "true" : "false";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::ArrayLit *n) override
|
||||
{
|
||||
target << "(array";
|
||||
for (auto &v : n->values)
|
||||
{
|
||||
target << " ";
|
||||
v->accept(this);
|
||||
}
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::ProplistLit *n) override
|
||||
{
|
||||
target << "(proplist";
|
||||
for (auto &v : n->values)
|
||||
{
|
||||
target << " (\"" << v.first << "\" ";
|
||||
v.second->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::NilLit *) override
|
||||
{
|
||||
target << "nil";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::ThisLit *) override
|
||||
{
|
||||
target << "this";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::VarExpr *n) override
|
||||
{
|
||||
target << "(var-expr " << n->identifier << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::UnOpExpr *n) override
|
||||
{
|
||||
target << "(" << C4ScriptOpMap[n->op].Identifier << " ";
|
||||
if (C4ScriptOpMap[n->op].Postfix)
|
||||
target << "postfix ";
|
||||
n->operand->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::BinOpExpr *n) override
|
||||
{
|
||||
target << "(" << C4ScriptOpMap[n->op].Identifier << " ";
|
||||
n->lhs->accept(this);
|
||||
target << " ";
|
||||
n->rhs->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::AssignmentExpr *n) override
|
||||
{
|
||||
target << "(= ";
|
||||
n->lhs->accept(this);
|
||||
target << " ";
|
||||
n->rhs->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::SubscriptExpr *n) override
|
||||
{
|
||||
target << "(subscript ";
|
||||
n->object->accept(this);
|
||||
target << " ";
|
||||
n->index->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::SliceExpr *n) override
|
||||
{
|
||||
target << "(slice ";
|
||||
n->object->accept(this);
|
||||
target << " ";
|
||||
n->start->accept(this);
|
||||
target << " ";
|
||||
n->end->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::CallExpr *n) override
|
||||
{
|
||||
target << "(";
|
||||
if (n->safe_call)
|
||||
target << "safe-";
|
||||
target << "call";
|
||||
if (n->context)
|
||||
{
|
||||
target << "-with-context";
|
||||
n->context->accept(this);
|
||||
}
|
||||
target << " (args";
|
||||
for (auto &v : n->args)
|
||||
{
|
||||
target << " ";
|
||||
v->accept(this);
|
||||
}
|
||||
target << ")";
|
||||
if (n->append_unnamed_pars)
|
||||
target << " append-unnamed";
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::ParExpr *n) override
|
||||
{
|
||||
target << "(par ";
|
||||
n->arg->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::Block *n) override
|
||||
{
|
||||
target << "(block";
|
||||
for (auto &v : n->children)
|
||||
{
|
||||
target << " ";
|
||||
v->accept(this);
|
||||
}
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::Return *n) override
|
||||
{
|
||||
if (n->value)
|
||||
{
|
||||
target << "(return ";
|
||||
n->value->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
target << "(return)";
|
||||
}
|
||||
}
|
||||
virtual void visit(const ::aul::ast::ForLoop *n) override
|
||||
{
|
||||
target << "(for";
|
||||
if (n->init)
|
||||
{
|
||||
target << " (init ";
|
||||
n->init->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
if (n->cond)
|
||||
{
|
||||
target << " (cond ";
|
||||
n->cond->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
if (n->incr)
|
||||
{
|
||||
target << " (incr ";
|
||||
n->incr->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
target << " ";
|
||||
n->body->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::RangeLoop *n) override
|
||||
{
|
||||
target << "(for-in";
|
||||
if (n->scoped_var)
|
||||
target << "-with-scope";
|
||||
target << " \"" << n->var << "\" ";
|
||||
n->cond->accept(this);
|
||||
target << " ";
|
||||
n->body->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::DoLoop *n) override
|
||||
{
|
||||
target << "(do ";
|
||||
n->cond->accept(this);
|
||||
target << " ";
|
||||
n->body->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::WhileLoop *n) override
|
||||
{
|
||||
target << "(while ";
|
||||
n->cond->accept(this);
|
||||
target << " ";
|
||||
n->body->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::Break *n) override
|
||||
{
|
||||
target << "break";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::Continue *n) override
|
||||
{
|
||||
target << "continue";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::If *n) override
|
||||
{
|
||||
target << "(if ";
|
||||
n->cond->accept(this);
|
||||
target << " ";
|
||||
n->iftrue->accept(this);
|
||||
if (n->iffalse)
|
||||
{
|
||||
target << " ";
|
||||
n->iffalse->accept(this);
|
||||
}
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::VarDecl *n) override
|
||||
{
|
||||
target << "(var-decl ";
|
||||
if (n->constant)
|
||||
target << "const ";
|
||||
switch (n->scope)
|
||||
{
|
||||
case ::aul::ast::VarDecl::Scope::Func:
|
||||
target << "func-scope"; break;
|
||||
case ::aul::ast::VarDecl::Scope::Object:
|
||||
target << "obj-scope"; break;
|
||||
case ::aul::ast::VarDecl::Scope::Global:
|
||||
target << "global-scope"; break;
|
||||
}
|
||||
for (auto &d : n->decls)
|
||||
{
|
||||
target << " (" << d.name;
|
||||
if (d.init)
|
||||
{
|
||||
target << " ";
|
||||
d.init->accept(this);
|
||||
}
|
||||
target << ")";
|
||||
}
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::FunctionDecl *n) override
|
||||
{
|
||||
target << "(func-decl " << n->name << " (";
|
||||
for (auto &p : n->params)
|
||||
{
|
||||
target << "(" << GetC4VName(p.type) << " " << p.name << ")";
|
||||
}
|
||||
if (n->has_unnamed_params)
|
||||
target << " variable-args";
|
||||
n->body->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::FunctionExpr *n) override
|
||||
{
|
||||
target << "(func-expr " << " (";
|
||||
for (auto &p : n->params)
|
||||
{
|
||||
target << "(" << GetC4VName(p.type) << " " << p.name << ")";
|
||||
}
|
||||
if (n->has_unnamed_params)
|
||||
target << " variable-args";
|
||||
n->body->accept(this);
|
||||
target << ")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::IncludePragma *n) override
|
||||
{
|
||||
target << "(include-pragma \"" << n->what << "\")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::AppendtoPragma *n) override
|
||||
{
|
||||
target << "(appendto-pragma \"" << n->what << "\")";
|
||||
}
|
||||
virtual void visit(const ::aul::ast::Script *n) override
|
||||
{
|
||||
target << "(script";
|
||||
for (auto &d : n->declarations)
|
||||
{
|
||||
target << " ";
|
||||
d->accept(this);
|
||||
}
|
||||
target << ")";
|
||||
}
|
||||
};
|
||||
|
||||
// These templates use the above formatter to write an AST as a human-readable
|
||||
// expression to the output if a test fails, instead of the default which is a
|
||||
// hex memory dump
|
||||
template<class T>
|
||||
std::enable_if_t<std::is_base_of<::aul::ast::Node, T>::value, std::ostream &>
|
||||
operator<<(::std::ostream &os, const T &node)
|
||||
{
|
||||
AstFormattingVisitor v(os);
|
||||
node.accept(&v);
|
||||
return os;
|
||||
}
|
||||
template<class T>
|
||||
std::enable_if_t<std::is_base_of<::aul::ast::Node, T>::value, std::ostream &>
|
||||
operator<<(::std::ostream &os, const std::unique_ptr<T> &node)
|
||||
{
|
||||
return os << *node;
|
||||
}
|
||||
template<class T>
|
||||
std::enable_if_t<std::is_base_of<::aul::ast::Node, T>::value, std::ostream &>
|
||||
operator<<(::std::ostream &os, const std::reference_wrapper<T> &node)
|
||||
{
|
||||
return os << node.get();
|
||||
}
|
||||
|
||||
static bool MatchesAstImpl(const ::aul::ast::Node *a_, const ::aul::ast::Node *b_);
|
||||
template<class T, class U>
|
||||
static bool MatchesAstImpl(const std::unique_ptr<T> &a_, const std::unique_ptr<U> &b_)
|
||||
{
|
||||
return MatchesAstImpl(a_.get(), b_.get());
|
||||
}
|
||||
template<class T>
|
||||
static bool MatchesAstImpl(const std::unique_ptr<T> &a_, const ::aul::ast::Node *b_)
|
||||
{
|
||||
return MatchesAstImpl(a_.get(), b_);
|
||||
}
|
||||
template<class U>
|
||||
static bool MatchesAstImpl(const ::aul::ast::Node *a_, const std::unique_ptr<U> &b_)
|
||||
{
|
||||
return MatchesAstImpl(a_, b_.get());
|
||||
}
|
||||
static bool MatchesAstImpl(const ::aul::ast::Node *a_, const ::aul::ast::Node *b_)
|
||||
{
|
||||
// It would be real nice if C++ had proper multimethods, but alas it
|
||||
// does not.
|
||||
// Since this method is only used in testing, the overhead of all
|
||||
// the dynamic_cast'ing we're doing here should be fine.
|
||||
|
||||
// If a and b are both nullptr, they match.
|
||||
if (a_ == nullptr && b_ == nullptr) return true;
|
||||
// If one of a and b is a nullptr, but not the other, they don't match.
|
||||
if ((a_ == nullptr) != (b_ == nullptr)) return false;
|
||||
|
||||
// If a and b are not of the same (dynamic) type, they don't match.
|
||||
if (typeid(*a_) != typeid(*b_)) return false;
|
||||
|
||||
// Ok this is ugly as sin but I don't think we can do it any cleaner
|
||||
// without adding specialized acceptors to the AST nodes.
|
||||
// We're dynamic_cast'ing both nodes to the expected type, test the
|
||||
// result to make sure the cast succeeded, then run the body, then
|
||||
// set a and b to nullptr to break out of the loop.
|
||||
// The body gets a and b cast to the expected type instead of the base
|
||||
// Node*, so we can check all members without additional, explicit
|
||||
// casting.
|
||||
#define WHEN(type) for (const type *a = dynamic_cast<const type*>(a_), *b = dynamic_cast<const type*>(b_); a && b; a = b = nullptr)
|
||||
|
||||
// The base (non-composite) literals all just compare values, but since
|
||||
// they're different types we can't just use one common case.
|
||||
WHEN(::aul::ast::StringLit)
|
||||
{
|
||||
return a->value == b->value;
|
||||
}
|
||||
WHEN(::aul::ast::IntLit)
|
||||
{
|
||||
return a->value == b->value;
|
||||
}
|
||||
WHEN(::aul::ast::BoolLit)
|
||||
{
|
||||
return a->value == b->value;
|
||||
}
|
||||
|
||||
// nil and this don't have any values to compare, so just checking type
|
||||
// is sufficient
|
||||
WHEN(::aul::ast::NilLit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
WHEN(::aul::ast::ThisLit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// No-ops don't have anything to compare either
|
||||
WHEN(::aul::ast::Noop)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Array literals need to compare all entries recursively
|
||||
WHEN(::aul::ast::ArrayLit)
|
||||
{
|
||||
return std::equal(a->values.begin(), a->values.end(), b->values.begin(), b->values.end(), [](const auto &a0, const auto &b0)
|
||||
{
|
||||
return MatchesAstImpl(a0, b0);
|
||||
});
|
||||
}
|
||||
|
||||
// Proplist literals need to compare all entries by key and value
|
||||
WHEN(::aul::ast::ProplistLit)
|
||||
{
|
||||
return std::equal(a->values.begin(), a->values.end(), b->values.begin(), b->values.end(), [](const auto &a0, const auto &b0)
|
||||
{
|
||||
if (a0.first != b0.first)
|
||||
return false;
|
||||
return MatchesAstImpl(a0.second, b0.second);
|
||||
});
|
||||
}
|
||||
|
||||
// Operators need to have matching opcodes and LHS/RHS
|
||||
WHEN(::aul::ast::UnOpExpr)
|
||||
{
|
||||
return a->op == b->op
|
||||
&& MatchesAstImpl(a->operand, b->operand);
|
||||
}
|
||||
WHEN(::aul::ast::BinOpExpr)
|
||||
{
|
||||
return a->op == b->op
|
||||
&& MatchesAstImpl(a->lhs, b->lhs)
|
||||
&& MatchesAstImpl(a->rhs, b->rhs);
|
||||
}
|
||||
WHEN(::aul::ast::AssignmentExpr)
|
||||
{
|
||||
return MatchesAstImpl(a->lhs, b->lhs)
|
||||
&& MatchesAstImpl(a->rhs, b->rhs);
|
||||
}
|
||||
|
||||
// Variable expressions just need to reference the same identifier
|
||||
WHEN(::aul::ast::VarExpr)
|
||||
{
|
||||
return a->identifier == b->identifier;
|
||||
}
|
||||
|
||||
// Subscript expressions need to have the same object and index
|
||||
WHEN(::aul::ast::SubscriptExpr)
|
||||
{
|
||||
return MatchesAstImpl(a->index, b->index)
|
||||
&& MatchesAstImpl(a->object, b->object);
|
||||
}
|
||||
|
||||
// Slice expressions need to have the same base object and start/end indices
|
||||
WHEN(::aul::ast::SliceExpr)
|
||||
{
|
||||
return MatchesAstImpl(a->object, b->object)
|
||||
&& MatchesAstImpl(a->start, b->start)
|
||||
&& MatchesAstImpl(a->end, b->end);
|
||||
}
|
||||
|
||||
// Call expressions need to match safety, context, identifier and args
|
||||
// (including unnamed arg passthrough)
|
||||
WHEN(::aul::ast::CallExpr)
|
||||
{
|
||||
if (!(a->safe_call == b->safe_call
|
||||
&& a->append_unnamed_pars == b->append_unnamed_pars
|
||||
&& MatchesAstImpl(a->context, b->context)
|
||||
&& a->callee == b->callee))
|
||||
return false;
|
||||
|
||||
|
||||
return std::equal(a->args.begin(), a->args.end(), b->args.begin(), b->args.end(), [](const auto &a0, const auto &b0)
|
||||
{
|
||||
return MatchesAstImpl(a0, b0);
|
||||
});
|
||||
}
|
||||
|
||||
// Par() expressions need the same index
|
||||
WHEN(::aul::ast::ParExpr)
|
||||
{
|
||||
return MatchesAstImpl(a->arg, b->arg);
|
||||
}
|
||||
|
||||
// Blocks need to have the same children
|
||||
WHEN(::aul::ast::Block)
|
||||
{
|
||||
return std::equal(a->children.begin(), a->children.end(), b->children.begin(), b->children.end(), [](const auto &a0, const auto &b0)
|
||||
{
|
||||
return MatchesAstImpl(a0, b0);
|
||||
});
|
||||
}
|
||||
|
||||
// Return statements need to have the same parameters
|
||||
WHEN(::aul::ast::Return)
|
||||
{
|
||||
return MatchesAstImpl(a->value, b->value);
|
||||
}
|
||||
|
||||
// for loops need to have the same initializer, condition, incrementor,
|
||||
// and body
|
||||
WHEN(::aul::ast::ForLoop)
|
||||
{
|
||||
return MatchesAstImpl(a->init, b->init)
|
||||
&& MatchesAstImpl(a->cond, b->cond)
|
||||
&& MatchesAstImpl(a->incr, b->incr)
|
||||
&& MatchesAstImpl(a->body, b->body);
|
||||
}
|
||||
|
||||
// range loops need to have the same scoping, loop variable, target object,
|
||||
// and body
|
||||
WHEN(::aul::ast::RangeLoop)
|
||||
{
|
||||
return a->scoped_var == b->scoped_var
|
||||
&& a->var == b->var
|
||||
&& MatchesAstImpl(a->cond, b->cond)
|
||||
&& MatchesAstImpl(a->body, b->body);
|
||||
}
|
||||
|
||||
// do and while loops need to have the same condition and body
|
||||
WHEN(::aul::ast::Loop)
|
||||
{
|
||||
assert(typeid(*a) == typeid(::aul::ast::DoLoop) || typeid(*a) == typeid(::aul::ast::WhileLoop));
|
||||
return MatchesAstImpl(a->cond, b->cond)
|
||||
&& MatchesAstImpl(a->body, b->body);
|
||||
}
|
||||
|
||||
// break and continue just need to have the same type
|
||||
WHEN(::aul::ast::LoopControl)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// if-else needs to have the same condition, then-branch and else-branch
|
||||
WHEN(::aul::ast::If)
|
||||
{
|
||||
return MatchesAstImpl(a->cond, b->cond)
|
||||
&& MatchesAstImpl(a->iftrue, b->iftrue)
|
||||
&& MatchesAstImpl(a->iffalse, b->iffalse);
|
||||
}
|
||||
|
||||
// variable declarations need to have the same scope, constancy, and
|
||||
// for each declaration have the same identifier and initializer
|
||||
WHEN(::aul::ast::VarDecl)
|
||||
{
|
||||
if (a->scope != b->scope || a->constant != b->constant)
|
||||
return false;
|
||||
|
||||
return std::equal(a->decls.begin(), a->decls.end(), b->decls.begin(), b->decls.end(), [](const auto &a0, const auto &b0)
|
||||
{
|
||||
return a0.name == b0.name
|
||||
&& MatchesAstImpl(a0.init, b0.init);
|
||||
});
|
||||
}
|
||||
|
||||
// function declarations need to have the same name, and scope,
|
||||
// plus the common function parts
|
||||
WHEN(::aul::ast::FunctionDecl)
|
||||
{
|
||||
if (a->name != b->name)
|
||||
return false;
|
||||
if (a->is_global != b->is_global)
|
||||
return false;
|
||||
// but keep checking
|
||||
}
|
||||
// all functions (declarations and expressions) need to have the same
|
||||
// parameter list and body
|
||||
WHEN(::aul::ast::Function)
|
||||
{
|
||||
if (a->has_unnamed_params != b->has_unnamed_params)
|
||||
return false;
|
||||
return std::equal(a->params.begin(), a->params.end(), b->params.begin(), b->params.end(), [](const auto &a0, const auto &b0)
|
||||
{
|
||||
return a0.name == b0.name
|
||||
&& a0.type == b0.type;
|
||||
})
|
||||
&& MatchesAstImpl(a->body, b->body);
|
||||
}
|
||||
// include and appendto pragmas need to include/appendto the same identifier
|
||||
WHEN(::aul::ast::IncludePragma)
|
||||
{
|
||||
return a->what == b->what;
|
||||
}
|
||||
WHEN(::aul::ast::AppendtoPragma)
|
||||
{
|
||||
return a->what == b->what;
|
||||
}
|
||||
// scripts need to have the same list of declarations
|
||||
WHEN(::aul::ast::Script)
|
||||
{
|
||||
return std::equal(a->declarations.begin(), a->declarations.end(), b->declarations.begin(), b->declarations.end(), [](const auto &a0, const auto &b0)
|
||||
{
|
||||
return MatchesAstImpl(a0, b0);
|
||||
});
|
||||
}
|
||||
|
||||
assert(!"AST matching fell through to the default case");
|
||||
return false;
|
||||
#undef WHEN
|
||||
}
|
||||
|
||||
// helper templates to turn a pointer, unique_ptr or reference to T
|
||||
// into an unconditional reference to T. We're using this so we only
|
||||
// have to handle references in MatchesAst instead of requiring
|
||||
// several overloads.
|
||||
template<class T>
|
||||
static const T &deref(const std::unique_ptr<T> &p)
|
||||
{
|
||||
return *p;
|
||||
}
|
||||
template<class T>
|
||||
static const T &deref(const std::reference_wrapper<T> &p)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
template<class T>
|
||||
static const T &deref(const T *p)
|
||||
{
|
||||
return *p;
|
||||
}
|
||||
template<class T>
|
||||
static const T &deref(const T &p)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
// The actual matcher. Just delegates to the recursive function above.
|
||||
MATCHER_P(MatchesAstP, ast, "")
|
||||
{
|
||||
return MatchesAstImpl(&deref(arg), &deref(ast));
|
||||
}
|
||||
// And a convenience wrapper that stores the AST we're matching against
|
||||
// in a std::reference_wrapper because we can't copy ASTs, but GTest
|
||||
// requires that. Don't keep the matcher around longer than the AST or
|
||||
// things will go sour.
|
||||
template<class T>
|
||||
auto MatchesAst(const T &t)
|
||||
{
|
||||
return MatchesAstP(std::cref(t));
|
||||
}
|
||||
|
||||
#endif
|
|
@ -18,13 +18,14 @@
|
|||
#include <C4Include.h>
|
||||
|
||||
#include "AulTest.h"
|
||||
#include "ErrorHandler.h"
|
||||
|
||||
#include "script/C4ScriptHost.h"
|
||||
#include "lib/C4Random.h"
|
||||
#include "object/C4DefList.h"
|
||||
#include "TestLog.h"
|
||||
|
||||
C4Value AulTest::RunCode(const char *code, bool wrap)
|
||||
C4Value AulTest::RunScript(const std::string &code)
|
||||
{
|
||||
class OnScopeExit
|
||||
{
|
||||
|
@ -39,17 +40,6 @@ C4Value AulTest::RunCode(const char *code, bool wrap)
|
|||
InitCoreFunctionMap(&ScriptEngine);
|
||||
FixedRandom(0x40490fdb);
|
||||
|
||||
std::string wrapped;
|
||||
if (wrap)
|
||||
{
|
||||
wrapped = "func Main() {\n";
|
||||
wrapped += code;
|
||||
wrapped += "\n}\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
wrapped = code;
|
||||
}
|
||||
std::string src("<");
|
||||
auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||
src += test_info->test_case_name();
|
||||
|
@ -59,17 +49,27 @@ C4Value AulTest::RunCode(const char *code, bool wrap)
|
|||
src += std::to_string(test_info->result()->total_part_count());
|
||||
src += ">";
|
||||
|
||||
GameScript.LoadData(src.c_str(), wrapped.c_str(), NULL);
|
||||
GameScript.LoadData(src.c_str(), code.c_str(), NULL);
|
||||
ScriptEngine.Link(NULL);
|
||||
|
||||
return GameScript.Call("Main", nullptr, true);
|
||||
}
|
||||
C4Value AulTest::RunExpr(const char *expr)
|
||||
|
||||
C4Value AulTest::RunCode(const std::string &code)
|
||||
{
|
||||
std::string wrapped = "func Main() {\n";
|
||||
wrapped += code;
|
||||
wrapped += "\n}\n";
|
||||
|
||||
return RunScript(wrapped);
|
||||
}
|
||||
|
||||
C4Value AulTest::RunExpr(const std::string &expr)
|
||||
{
|
||||
std::string code = "return ";
|
||||
code += expr;
|
||||
code += ';';
|
||||
return RunCode(code.c_str());
|
||||
return RunCode(code);
|
||||
}
|
||||
|
||||
const C4Value AulTest::C4VINT_MIN = C4VInt(-2147483647 - 1);
|
||||
|
@ -140,19 +140,40 @@ return b;
|
|||
|
||||
TEST_F(AulTest, Locals)
|
||||
{
|
||||
EXPECT_EQ(C4VInt(42), RunCode("local i = 42; func Main() { return i; }", false));
|
||||
EXPECT_EQ(C4VInt(42), RunCode("local i; func Main() { i = 42; return i; }", false));
|
||||
EXPECT_EQ(C4VInt(42), RunCode("func Main() { local i = 42; return i; }", false));
|
||||
EXPECT_EQ(C4VInt(42), RunCode("local i = [42]; func Main() { return i[0]; }", false));
|
||||
EXPECT_EQ(C4VInt(42), RunCode("local p = { i = 42 }; func Main() { return p.i; }", false));
|
||||
EXPECT_EQ(C4VInt(42), RunCode("local p1 = { i = 42 }, p2 = new p1 {}; func Main() { return p2.i; }", false));
|
||||
EXPECT_EQ(C4VInt(42), RunScript("local i = 42; func Main() { return i; }"));
|
||||
EXPECT_EQ(C4VInt(42), RunScript("local i; func Main() { i = 42; return i; }"));
|
||||
EXPECT_EQ(C4VInt(42), RunScript("func Main() { local i = 42; return i; }"));
|
||||
EXPECT_EQ(C4VInt(42), RunScript("local i = [42]; func Main() { return i[0]; }"));
|
||||
EXPECT_EQ(C4VInt(42), RunScript("local p = { i = 42 }; func Main() { return p.i; }"));
|
||||
EXPECT_EQ(C4VInt(42), RunScript("local p1 = { i = 42 }, p2 = new p1 {}; func Main() { return p2.i; }"));
|
||||
}
|
||||
|
||||
TEST_F(AulTest, ProplistFunctions)
|
||||
{
|
||||
EXPECT_EQ(C4VInt(1), RunScript(R"(
|
||||
local a = new Global {
|
||||
a = func() { return b; },
|
||||
b = 1
|
||||
};
|
||||
func Main() { return a->Call(a.a); }
|
||||
)"));
|
||||
|
||||
EXPECT_EQ(C4VInt(1), RunScript(R"(
|
||||
static const a = { v = 1 };
|
||||
static const b = new a {
|
||||
c = func() { return v; }
|
||||
};
|
||||
func Main() { return b->c(); }
|
||||
)"));
|
||||
|
||||
EXPECT_THROW(RunScript("func foo() { return { bar: func() {} }; }"), C4AulError);
|
||||
}
|
||||
|
||||
TEST_F(AulTest, Eval)
|
||||
{
|
||||
EXPECT_EQ(C4VInt(42), RunExpr("eval(\"42\")"));
|
||||
EXPECT_EQ(C4VInt(42), RunCode("local i = 42; func Main() { return eval(\"this.i\"); }", false));
|
||||
EXPECT_EQ(C4VInt(42), RunCode("local i; func Main() { eval(\"this.i = 42\"); return i; }", false));
|
||||
EXPECT_EQ(C4VInt(42), RunScript("local i = 42; func Main() { return eval(\"this.i\"); }"));
|
||||
EXPECT_EQ(C4VInt(42), RunScript("local i; func Main() { eval(\"this.i = 42\"); return i; }"));
|
||||
}
|
||||
|
||||
TEST_F(AulTest, Vars)
|
||||
|
@ -166,7 +187,7 @@ TEST_F(AulTest, ParameterPassing)
|
|||
EXPECT_EQ(C4VArray(
|
||||
C4VInt(1), C4VInt(2), C4VInt(3), C4VInt(4), C4VInt(5),
|
||||
C4VInt(6), C4VInt(7), C4VInt(8), C4VInt(9), C4VInt(10)),
|
||||
RunCode(R"(
|
||||
RunScript(R"(
|
||||
func f(...)
|
||||
{
|
||||
return [Par(0), Par(1), Par(2), Par(3), Par(4), Par(5), Par(6), Par(7), Par(8), Par(9)];
|
||||
|
@ -176,12 +197,12 @@ func Main()
|
|||
{
|
||||
return f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
}
|
||||
)", false));
|
||||
)"));
|
||||
|
||||
EXPECT_EQ(C4VArray(
|
||||
C4VInt(1), C4VInt(2), C4VInt(3), C4VInt(4), C4VInt(5),
|
||||
C4VInt(6), C4VInt(7), C4VInt(8), C4VInt(9), C4VInt(10)),
|
||||
RunCode(R"(
|
||||
RunScript(R"(
|
||||
func f(a, b, ...)
|
||||
{
|
||||
return g(b, a, ...);
|
||||
|
@ -196,7 +217,7 @@ func Main()
|
|||
{
|
||||
return f(2, 1, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
}
|
||||
)", false));
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_F(AulTest, Conditionals)
|
||||
|
@ -207,16 +228,16 @@ TEST_F(AulTest, Conditionals)
|
|||
|
||||
TEST_F(AulTest, Warnings)
|
||||
{
|
||||
LogMock log;
|
||||
EXPECT_CALL(log, DebugLog(testing::StartsWith("WARNING:"))).Times(3);
|
||||
EXPECT_EQ(C4Value(), RunCode("func Main(string s, object o, array a) { Sin(s); }", false));
|
||||
EXPECT_EQ(C4Value(), RunCode("func Main(string s, object o, array a) { Sin(o); }", false));
|
||||
EXPECT_EQ(C4Value(), RunCode("func Main(string s, object o, array a) { Sin(a); }", false));
|
||||
ErrorHandler errh;
|
||||
EXPECT_CALL(errh, OnWarning(::testing::_)).Times(3);
|
||||
EXPECT_EQ(C4Value(), RunScript("func Main(string s, object o, array a) { Sin(s); }"));
|
||||
EXPECT_EQ(C4Value(), RunScript("func Main(string s, object o, array a) { Sin(o); }"));
|
||||
EXPECT_EQ(C4Value(), RunScript("func Main(string s, object o, array a) { Sin(a); }"));
|
||||
}
|
||||
|
||||
TEST_F(AulTest, NoWarnings)
|
||||
{
|
||||
LogMock log;
|
||||
EXPECT_CALL(log, DebugLog(testing::StartsWith("WARNING:"))).Times(0);
|
||||
EXPECT_EQ(C4Value(), RunCode("func Main(string s, object o, array a) { var x; Sin(x); }", false));
|
||||
ErrorHandler errh;
|
||||
EXPECT_CALL(errh, OnWarning(::testing::_)).Times(0);
|
||||
EXPECT_EQ(C4Value(), RunScript("func Main(string s, object o, array a) { var x; Sin(x); }"));
|
||||
}
|
||||
|
|
|
@ -28,8 +28,9 @@ inline std::ostream &operator<<(std::ostream &os, const C4Value &val)
|
|||
class AulTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
C4Value RunCode(const char *code, bool wrap = true);
|
||||
C4Value RunExpr(const char *expr);
|
||||
C4Value RunCode(const std::string &code);
|
||||
C4Value RunScript(const std::string &code);
|
||||
C4Value RunExpr(const std::string &expr);
|
||||
|
||||
static const C4Value C4VINT_MIN;
|
||||
static const C4Value C4VINT_MAX;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef INC_ErrorHandler
|
||||
#define INC_ErrorHandler
|
||||
|
||||
#include "script/C4Aul.h"
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
class ErrorHandler : public C4AulErrorHandler
|
||||
{
|
||||
public:
|
||||
ErrorHandler()
|
||||
{
|
||||
::ScriptEngine.RegisterErrorHandler(this);
|
||||
}
|
||||
MOCK_METHOD1(OnError, void(const char*));
|
||||
MOCK_METHOD1(OnWarning, void(const char*));
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue