Skip to content

Commit

Permalink
improve messaging for improperly structured requirements (#400)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamiras authored Jun 26, 2023
1 parent ec013f8 commit 1c937bc
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 40 deletions.
5 changes: 3 additions & 2 deletions Source/Parser/Expressions/FunctionCallExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,12 @@ private bool Evaluate(InterpreterScope scope, bool inAssignment, out ExpressionB
functionDefinition.Evaluate(functionParametersScope, out result);
}

if (result != null && result.Location.Start.Line == 0)
CopyLocation(result);

var error = result as ErrorExpression;
if (error != null)
{
if (error.Location.Start.Line == 0)
CopyLocation(error);
result = ErrorExpression.WrapError(error, FunctionName.Name + " call failed", FunctionName);
return false;
}
Expand Down
7 changes: 4 additions & 3 deletions Source/Parser/Expressions/FunctionDefinitionExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -451,14 +451,15 @@ protected RequirementExpressionBase GetRequirementParameter(InterpreterScope sco
}
}

parseError = new ErrorExpression(name + " is not a requirement", originalParameter ?? parameter);
var parameterError = new ErrorExpression(name + " is not a requirement", originalParameter ?? parameter);

if (!ReferenceEquals(invalidClause, parameter))
if (invalidClause != null)
{
((ErrorExpression)parseError).InnerError =
parameterError.InnerError =
new ErrorExpression("Cannot convert " + invalidClause.Type.ToString().ToLower() + " to requirement", invalidClause);
}

parseError = parameterError;
return null;
}

Expand Down
7 changes: 5 additions & 2 deletions Source/Parser/Internal/ExpressionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,11 @@ internal ExpressionBase MakeReadOnly()
/// </summary>
internal void CopyLocation(ExpressionBase target)
{
if (Location.End.Line != 0 && !target.IsReadOnly)
target.Location = Location;
if (Location.End.Line != 0)
{
if (!target.IsReadOnly || target.Location.End.Line == 0)
target.Location = Location;
}
}

/// <summary>
Expand Down
16 changes: 6 additions & 10 deletions Tests/Parser/AchievementScriptInterpreterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ public void TestOnce()
public void TestOnceMalformed()
{
var parser = Parse("achievement(\"T\", \"D\", 5, once(byte(0x1234)) == 1)", false);
Assert.That(GetInnerErrorMessage(parser), Is.EqualTo("1:31 comparison is not a requirement"));
Assert.That(GetInnerErrorMessage(parser), Is.EqualTo("1:31 Cannot convert memoryaccessor to requirement"));
}

[Test]
Expand Down Expand Up @@ -834,18 +834,14 @@ public void TestFunctionCallInFunctionInExpression()
var tokenizer = Tokenizer.CreateTokenizer(input);
var parser = new AchievementScriptInterpreter();
Assert.That(parser.Run(tokenizer), Is.False);
Assert.That(parser.ErrorMessage, Is.EqualTo("2:30 always_true has no meaning outside of a trigger clause"));
Assert.That(parser.ErrorMessage, Is.EqualTo(
"10:40 Invalid value for parameter: trigger\r\n" +
"- 10:40 foo call failed\r\n" +
"- 2:30 always_true call failed\r\n" +
"- 2:30 always_true has no meaning outside of a trigger clause"));
}
*/

[Test]
public void TestErrorInFunctionInExpression()
{
var parser = Parse("function foo() => byte(1)\n" +
"achievement(\"Title\", \"Description\", 5, once(foo()))\n", false);
Assert.That(GetInnerErrorMessage(parser), Is.EqualTo("2:45 comparison is not a requirement"));
}

[Test]
public void TestErrorInFunctionParameterLocation()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,21 @@ public void TestBuildTrigger(string input, string expected)
}

[Test]
[TestCase("never(6+2)")] // numeric
[TestCase("never(byte(0x1234))")] // no comparison
[TestCase("never(f)")] // function reference
[TestCase("unless(6+2)")] // numeric
[TestCase("unless(byte(0x1234))")] // no comparison
[TestCase("unless(f)")] // function reference
[TestCase("trigger_when(6+2)")] // numeric
[TestCase("trigger_when(byte(0x1234))")] // no comparison
[TestCase("trigger_when(f)")] // function reference
public void TestUnsupportedComparisons(string input)
[TestCase("never(6+2)", "integerconstant")] // numeric
[TestCase("never(byte(0x1234))", "memoryaccessor")] // no comparison
[TestCase("never(f)", "variable")] // function reference
[TestCase("unless(6+2)", "integerconstant")] // numeric
[TestCase("unless(byte(0x1234))", "memoryaccessor")] // no comparison
[TestCase("unless(f)", "variable")] // function reference
[TestCase("trigger_when(6+2)", "integerconstant")] // numeric
[TestCase("trigger_when(byte(0x1234))", "memoryaccessor")] // no comparison
[TestCase("trigger_when(f)", "variable")] // function reference
public void TestUnsupportedComparisons(string input, string unsupportedType)
{
var scope = TriggerExpressionTests.CreateScope();
scope.AssignVariable(new VariableExpression("f"), new FunctionReferenceExpression("f2"));

TriggerExpressionTests.AssertParseError(input, scope, "comparison is not a requirement");
TriggerExpressionTests.AssertParseError(input, scope, "Cannot convert " + unsupportedType + " to requirement");
}

[Test]
Expand Down
48 changes: 48 additions & 0 deletions Tests/Parser/Functions/AchievementFunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,53 @@ public void TestInvalidComparisonInHelperFunction()
"- 3:26 b call failed\r\n" +
"- 2:17 Cannot compare function reference and IntegerConstant"));
}

[Test]
public void TestIncompleteComparison()
{
var input = "function a() => byte(0x1234)\n" +
"achievement(\"T\", \"D\", 5, a())";

var tokenizer = new PositionalTokenizer(Tokenizer.CreateTokenizer(input));
var parser = new AchievementScriptInterpreter();
Assert.That(parser.Run(tokenizer), Is.False);
Assert.That(parser.ErrorMessage, Is.EqualTo(
"2:1 achievement call failed\r\n" +
"- 2:26 trigger is not a requirement\r\n" +
"- 1:17 Cannot convert memoryaccessor to requirement"));
}

[Test]
public void TestIncompleteComparisonInHelperFunction()
{
var input = "function a() => byte(0x1234)\n" +
"function b() => a() + 1\r\n" +
"achievement(\"T\", \"D\", 5, b())";

var tokenizer = new PositionalTokenizer(Tokenizer.CreateTokenizer(input));
var parser = new AchievementScriptInterpreter();
Assert.That(parser.Run(tokenizer), Is.False);
Assert.That(parser.ErrorMessage, Is.EqualTo(
"3:1 achievement call failed\r\n" +
"- 3:26 trigger is not a requirement\r\n" +
// this actually reports the entire "a() + 1" as invalid
"- 2:17 Cannot convert memoryaccessor to requirement"));
}

[Test]
public void TestIncompleteComparisonInHelperFunctionLogical()
{
var input = "function a() => byte(0x1234)\n" +
"function b() => a() && byte(0x2345) == 6\r\n" +
"achievement(\"T\", \"D\", 5, b())";

var tokenizer = new PositionalTokenizer(Tokenizer.CreateTokenizer(input));
var parser = new AchievementScriptInterpreter();
Assert.That(parser.Run(tokenizer), Is.False);
Assert.That(parser.ErrorMessage, Is.EqualTo(
"3:1 achievement call failed\r\n" +
"- 3:26 trigger is not a requirement\r\n" +
"- 1:17 Cannot convert memoryaccessor to requirement"));
}
}
}
11 changes: 5 additions & 6 deletions Tests/Parser/Functions/OnceFunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,15 @@ public void TestBuildTrigger(string input, string expected)
TriggerExpressionTests.AssertSerialize(clause, expected);
}

[Test]
[TestCase("once(6+2)")] // numeric
[TestCase("once(byte(0x1234))")] // no comparison
[TestCase("once(f)")] // function reference
public void TestUnsupportedComparisons(string input)
[TestCase("once(6+2)", "integerconstant")] // numeric
[TestCase("once(byte(0x1234))", "memoryaccessor")] // no comparison
[TestCase("once(f)", "variable")] // function reference
public void TestUnsupportedComparisons(string input, string unsupportedType)
{
var scope = TriggerExpressionTests.CreateScope();
scope.AssignVariable(new VariableExpression("f"), new FunctionReferenceExpression("f2"));

TriggerExpressionTests.AssertParseError(input, scope, "comparison is not a requirement");
TriggerExpressionTests.AssertParseError(input, scope, "Cannot convert " + unsupportedType + " to requirement");
}
}
}
11 changes: 5 additions & 6 deletions Tests/Parser/Functions/RepeatedFunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,15 @@ public void TestBuildTrigger(string input, string expected)
TriggerExpressionTests.AssertSerialize(clause, expected);
}

[Test]
[TestCase("repeated(5, 6+2)")] // numeric
[TestCase("repeated(5, byte(0x1234))")] // no comparison
[TestCase("repeated(5, f)")] // function reference
public void TestUnsupportedComparisons(string input)
[TestCase("repeated(5, 6+2)", "integerconstant")] // numeric
[TestCase("repeated(5, byte(0x1234))", "memoryaccessor")] // no comparison
[TestCase("repeated(5, f)", "variable")] // function reference
public void TestUnsupportedComparisons(string input, string unsupportedType)
{
var scope = TriggerExpressionTests.CreateScope();
scope.AssignVariable(new VariableExpression("f"), new FunctionReferenceExpression("f2"));

TriggerExpressionTests.AssertParseError(input, scope, "comparison is not a requirement");
TriggerExpressionTests.AssertParseError(input, scope, "Cannot convert " + unsupportedType + " to requirement");
}

[Test]
Expand Down

0 comments on commit 1c937bc

Please sign in to comment.