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.txt
directional-lights
Nicolas Hake 2016-10-20 17:33:02 +02:00
commit e2fd7095c1
22 changed files with 4100 additions and 1442 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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));
}
}

View File

@ -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 &target;
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

View File

@ -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); }"));
}

View File

@ -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;

View File

@ -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