Skip to content

Commit

Permalink
Added support for ifless ifs
Browse files Browse the repository at this point in the history
  • Loading branch information
Pogromca-SCP committed Feb 22, 2024
1 parent 33a3ba3 commit 9309ac3
Showing 6 changed files with 85 additions and 19 deletions.
4 changes: 2 additions & 2 deletions Grammar.txt
Original file line number Diff line number Diff line change
@@ -14,13 +14,13 @@ variable -> \S* ("$(" "^"* TEXT ")" \S*)+
directive -> "[" directive_body "]"
directive_body -> foreach_loop | if_expr | delay_expr | forrandom_loop | sequence
foreach_loop -> expression "foreach" TEXT # Inner expression gets access to iterated objects properties through variables
if_expr -> expression "if" expression ("else" expression)?
if_expr -> (expression "if")? expression ("else" expression)?
delay_expr -> expression "delayby" NUMBER TEXT?
forrandom_loop -> expression "forrandom" TEXT NUMBER? ("else" expression)? # Inner expressions get access to iterated objects properties through variables
sequence -> expression ("|" expression)+

comment -> "#" comment_body
comment_body -> permissions_guard | scope_guard | .*
comment_body -> permissions_guard | scope_guard | [^!?] .*
permissions_guard -> "!" TEXT*
scope_guard -> "?" scope_name*
scope_name -> "RemoteAdmin" | "Console" | "GameConsole"
35 changes: 29 additions & 6 deletions SLCommandScript.Core.UnitTests/Language/InterpreterTests.cs
Original file line number Diff line number Diff line change
@@ -556,35 +556,36 @@ public void VisitIfExpr_ShouldFail_WhenExpressionIsNull()
}

[Test]
public void VisitIfExpr_ShouldFail_WhenThenBranchIsNull()
public void VisitIfExpr_ShouldFail_WhenConditionIsNull()
{
// Arrange
var interpreter = new Interpreter(null);
var expr = new IfExpr(null, null, null);
var expr = new IfExpr(new ForeachExpr(null, null), null, null);

// Act
var result = interpreter.VisitIfExpr(expr);

// Assert
result.Should().BeFalse();
interpreter.Sender.Should().BeNull();
interpreter.ErrorMessage.Should().Be("If expression then branch is null");
interpreter.ErrorMessage.Should().Be("If expression condition is null");
}

[Test]
public void VisitIfExpr_ShouldFail_WhenConditionIsNull()
public void VisitIfExpr_ShouldFail_WhenBothBranchesAreNull()
{
// Arrange
var interpreter = new Interpreter(null);
var expr = new IfExpr(new ForeachExpr(null, null), null, null);

var expr = new IfExpr(null, new CommandExpr(null, [], false), null);

// Act
var result = interpreter.VisitIfExpr(expr);

// Assert
result.Should().BeFalse();
interpreter.Sender.Should().BeNull();
interpreter.ErrorMessage.Should().Be("If expression condition is null");
interpreter.ErrorMessage.Should().Be("If expression branches are null");
}

[Test]
@@ -632,6 +633,28 @@ public void VisitIfExpr_ShouldFail_WhenElseBranchFails()
commandMock.VerifyNoOtherCalls();
}

[Test]
public void VisitIfExpr_ShouldSucceed_WhenThenBranchIsNull()
{
// Arrange
var interpreter = new Interpreter(null);
var message = "Command succeeded";
var commandMock = new Mock<ICommand>(MockBehavior.Strict);
commandMock.Setup(x => x.Execute(new(new[] { "condition" }, 1, 0), null, out message)).Returns(true);

var expr = new IfExpr(null, new CommandExpr(commandMock.Object, ["condition"], false), new ForeachExpr(null, null));

// Act
var result = interpreter.VisitIfExpr(expr);

// Assert
result.Should().BeTrue();
interpreter.Sender.Should().BeNull();
interpreter.ErrorMessage.Should().BeNull();
commandMock.VerifyAll();
commandMock.VerifyNoOtherCalls();
}

[Test]
public void VisitIfExpr_ShouldSucceed_WhenThenBranchSucceeds()
{
22 changes: 22 additions & 0 deletions SLCommandScript.Core.UnitTests/Language/ParserTests.cs
Original file line number Diff line number Diff line change
@@ -61,6 +61,20 @@ public class ParserTests
[new Core.Language.Token[] { new(TokenType.LeftSquare, "[", 1), new(TokenType.Text, "bc", 1), new(TokenType.If, "if", 1),
new(TokenType.Text, "bc", 1), new(TokenType.Else, "else", 1) }, "Else branch expression is missing"],

// [ else ]
[new Core.Language.Token[] { new(TokenType.LeftSquare, "[", 1), new(TokenType.Else, "else", 1),
new(TokenType.RightSquare, "]", 1) },
"Command 'else' was not found\nin if condition expression"],

// [ bc else ]
[new Core.Language.Token[] { new(TokenType.LeftSquare, "[", 1), new(TokenType.Text, "bc", 1), new(TokenType.Else, "else", 1),
new(TokenType.RightSquare, "]", 1) },
"Command ']' was not found\nin else branch expression"],

// [ bc else
[new Core.Language.Token[] { new(TokenType.LeftSquare, "[", 1), new(TokenType.Text, "bc", 1), new(TokenType.Else, "else", 1) },
"Else branch expression is missing"],

// [ foreach ]
[new Core.Language.Token[] { new(TokenType.LeftSquare, "[", 1), new(TokenType.Foreach, "foreach", 1),
new(TokenType.RightSquare, "]", 1) }, "Command 'foreach' was not found\nin foreach loop body expression"],
@@ -196,6 +210,14 @@ public class ParserTests
new CommandExpr(new BroadcastCommand(), ["bc", "5", "$(Test)"], true),
new CommandExpr(new BroadcastCommand(), ["bc", "5", "Test"], false)), CommandsUtils.AllScopes],

// [ bc 5 $(Test) else bc 5 Test ]
[new Core.Language.Token[] { new(TokenType.LeftSquare, "[", 1), new(TokenType.Text, "bc", 1), new(TokenType.Text, "5", 1),
new(TokenType.Variable, "$(Test)", 1), new(TokenType.Else, "else", 1), new(TokenType.Text, "bc", 1), new(TokenType.Text, "5", 1),
new(TokenType.Text, "Test", 1), new(TokenType.RightSquare, "]", 1) },
new IfExpr(null, new CommandExpr(new BroadcastCommand(), ["bc", "5", "$(Test)"], true),
new CommandExpr(new BroadcastCommand(), ["bc", "5", "Test"], false)),
CommandsUtils.AllScopes],

// [ bc 5 $(Test) foreach test ]
[new Core.Language.Token[] { new(TokenType.LeftSquare, "[", 1), new(TokenType.Text, "bc", 1), new(TokenType.Text, "5", 1),
new(TokenType.Variable, "$(Test)", 1), new(TokenType.Foreach, "foreach", 1), new(TokenType.Text, "test", 1), new(TokenType.RightSquare, "]", 1) },
16 changes: 6 additions & 10 deletions SLCommandScript.Core/Language/Interpreter.cs
Original file line number Diff line number Diff line change
@@ -292,15 +292,15 @@ public bool VisitIfExpr(IfExpr expr)
return false;
}

if (expr.Then is null)
if (expr.Condition is null)
{
ErrorMessage = "If expression then branch is null";
ErrorMessage = "If expression condition is null";
return false;
}

if (expr.Condition is null)
if (expr.Then is null && expr.Else is null)
{
ErrorMessage = "If expression condition is null";
ErrorMessage = "If expression branches are null";
return false;
}

@@ -309,15 +309,11 @@ public bool VisitIfExpr(IfExpr expr)

if (cond)
{
return expr.Then.Accept(this);
}
else if (expr.Else is not null)
{
return expr.Else.Accept(this);
return expr.Then is null || expr.Then.Accept(this);
}
else
{
return true;
return expr.Else is null || expr.Else.Accept(this);
}
}

25 changes: 25 additions & 0 deletions SLCommandScript.Core/Language/Parser.cs
Original file line number Diff line number Diff line change
@@ -180,6 +180,7 @@ private Expr Directive()
Expr body = keyword.Type switch
{
TokenType.If => If(expr),
TokenType.Else => Else(expr),
TokenType.Foreach => Foreach(expr),
TokenType.DelayBy => Delay(expr),
TokenType.ForRandom => ForRandom(expr),
@@ -296,6 +297,30 @@ private IfExpr If(Expr expr)
return new(expr, condition, els);
}

/// <summary>
/// Parses an if expression without then branch.
/// </summary>
/// <param name="expr">Expression to use as condition expression.</param>
/// <returns>Parsed if expression or <see langword="null" /> if something went wrong.</returns>
private IfExpr Else(Expr expr)
{
if (expr is null)
{
ErrorMessage += "\nin if condition expression";
return null;
}

var els = ParseExpr();

if (els is null)
{
ErrorMessage = ErrorMessage is null ? "Else branch expression is missing" : $"{ErrorMessage}\nin else branch expression";
return null;
}

return new(null, expr, els);
}

/// <summary>
/// Parses a foreach expression.
/// </summary>
2 changes: 1 addition & 1 deletion SLCommandScript/Commands/SyntaxCommand.cs
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ public class SyntaxCommand : ICommand
{ "perm", "Permissions guard:\n#! <permission_names...>\n(guards cannot be placed inside expressions)" },
{ "scope", "Scope guard:\n#? <scope_names...>\n(guards cannot be placed inside expressions)" },
{ "cmd", "Command expression:\n<command_name> <arguments...>" },
{ "if", "If expression:\n[ <expression> if <expression> ]\n[ <expression> if <expression> else <expression> ]" },
{ "if", "If expression:\n[ <expression> if <expression> ]\n[ <expression> else <expression> ]\n[ <expression> if <expression> else <expression> ]" },
{ "foreach", "Foreach expression:\n[ <expression> foreach <iterable_name> ]" },
{ "delay", "Delay expression:\n[ <expression> delayby <time_in_ms> ]\n[ <expression> delayby <time_in_ms> <name_to_use_for_error_log> ]" },
{ "forrandom", "Forrandom expression:\n[ <expression> forrandom <iterable_name> ]\n[ <expression> forrandom <iterable_name> <limit_number> ]\n"

0 comments on commit 9309ac3

Please sign in to comment.