openclonk/tests/aul/AulSyntaxTest.cpp

651 lines
19 KiB
C++

/*
* 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);
}
TEST(AulSyntaxTest, PragmaWarningWithInsufficientData)
{
EXPECT_THROW(ParseScript("#warning"), C4AulParseError);
EXPECT_THROW(ParseScript("#warning\n"), C4AulParseError);
EXPECT_THROW(ParseScript("#warning "), C4AulParseError);
EXPECT_THROW(ParseScript("#warning \n"), C4AulParseError);
EXPECT_THROW(ParseScript("#warning e"), C4AulParseError);
EXPECT_THROW(ParseScript("#warning enabl"), C4AulParseError);
EXPECT_THROW(ParseScript("#warning d"), C4AulParseError);
EXPECT_THROW(ParseScript("#warning disabl"), 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));
}
}