Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Source/Parser/Expressions/AssignmentExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ void INestedExpressions.GetModifications(HashSet<string> modifies)
modifies.Add("." + classMember.Member.Name);
else
modifies.Add(variable.Name);

var nested = Value as INestedExpressions;
if (nested != null)
nested.GetModifications(modifies);
}
}
}
25 changes: 25 additions & 0 deletions Source/Parser/Expressions/FunctionCallExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal FunctionCallExpression(IValueExpression source, ICollection<ExpressionB
}

private readonly IValueExpression _source;
private IEnumerable<string> _referenceParameters;

/// <summary>
/// Gets the name of the function to call.
Expand Down Expand Up @@ -193,6 +194,10 @@ private bool Evaluate(InterpreterScope scope, bool isInvoking, out ExpressionBas
}
}

var userFunctionDefinition = functionDefinition as UserFunctionDefinitionExpression;
if (userFunctionDefinition != null)
userFunctionDefinition.UpdateReferenceParameters(scope);

var functionParametersScope = GetParameters(functionDefinition, scope, out result);
if (functionParametersScope == null || result is ErrorExpression)
return false;
Expand All @@ -203,6 +208,21 @@ private bool Evaluate(InterpreterScope scope, bool isInvoking, out ExpressionBas
return false;
}

_referenceParameters = null;
if (functionDefinition.Parameters.Any(p => p.IsMutableReference))
{
var referenceParameters = new HashSet<string>();
foreach (var mutableParameter in functionDefinition.Parameters.Where(p => p.IsMutableReference))
{
var value = functionParametersScope.GetVariable(mutableParameter.Name) as VariableReferenceExpression;
if (value != null)
referenceParameters.Add(value.Variable.Name);
}

if (referenceParameters.Count > 0)
_referenceParameters = referenceParameters.ToArray();
}

functionParametersScope.Context = this;
if (isInvoking)
functionDefinition.Invoke(functionParametersScope, out result);
Expand Down Expand Up @@ -661,6 +681,11 @@ void INestedExpressions.GetDependencies(HashSet<string> dependencies)

void INestedExpressions.GetModifications(HashSet<string> modifies)
{
if (_referenceParameters != null)
{
foreach (var parameter in _referenceParameters)
modifies.Add(parameter);
}
}

public override bool? IsTrue(InterpreterScope scope, out ErrorExpression error)
Expand Down
76 changes: 76 additions & 0 deletions Source/Parser/Expressions/FunctionDefinitionExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,82 @@ public override bool Invoke(InterpreterScope scope, out ExpressionBase result)
result = null;
return true;
}

private bool _referenceParametersUpdated = false;

internal void UpdateReferenceParameters(InterpreterScope scope)
{
if (_referenceParametersUpdated)
return;

// set first to prevent infinite recursion if function calls itself
_referenceParametersUpdated = true;

foreach (var expression in Expressions)
DetermineReferenceParameters(scope, expression);
}

private void DetermineReferenceParameters(InterpreterScope scope, ExpressionBase expression)
{
var nestedExpressions = expression as INestedExpressions;
if (nestedExpressions == null)
return;

var assignmentExpression = expression as AssignmentExpression;
if (assignmentExpression != null)
{
var indexingExpression = assignmentExpression.Variable as IndexedVariableExpression;
if (indexingExpression != null)
{
var parameter = Parameters.FirstOrDefault(p => p.Name == indexingExpression.Variable.Name);
if (parameter != null)
parameter.IsMutableReference = true;
}

return;
}

var functionCall = expression as FunctionCallExpression;
if (functionCall == null || functionCall.FunctionName == null)
{
foreach (var nestedExpression in nestedExpressions.NestedExpressions)
DetermineReferenceParameters(scope, nestedExpression);

return;
}

var functionDefinition = scope.GetFunction(functionCall.FunctionName.Name);
if (functionDefinition == null)
return;

var userFunctionDefinition = functionDefinition as UserFunctionDefinitionExpression;
if (userFunctionDefinition != null)
userFunctionDefinition.UpdateReferenceParameters(scope);

if (functionDefinition.Parameters.Any(p => p.IsMutableReference))
{
var extraScope = new InterpreterScope(scope);
var dummyValue = new IntegerConstantExpression(0);
foreach (var parameter in Parameters)
extraScope.DefineVariable(parameter, new VariableReferenceExpression(parameter, dummyValue));

ExpressionBase result;
var functionParametersScope = functionCall.GetParameters(functionDefinition, extraScope, out result);
if (functionParametersScope != null)
{
foreach (var mutableParameter in functionDefinition.Parameters.Where(p => p.IsMutableReference))
{
var value = functionParametersScope.GetVariable(mutableParameter.Name) as VariableReferenceExpression;
if (value != null)
{
var parameter = Parameters.FirstOrDefault(p => p.Name == value.Variable.Name);
if (parameter != null)
parameter.IsMutableReference = true;
}
}
}
}
}
}

internal class AnonymousUserFunctionDefinitionExpression : UserFunctionDefinitionExpression
Expand Down
2 changes: 2 additions & 0 deletions Source/Parser/Expressions/VariableExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ internal VariableDefinitionExpression(VariableExpressionBase variable)
Location = variable.Location;
}

public bool IsMutableReference { get; set;}

IEnumerable<ExpressionBase> INestedExpressions.NestedExpressions
{
get
Expand Down
2 changes: 1 addition & 1 deletion Source/Parser/Functions/ArrayPopFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal class ArrayPopFunction : FunctionDefinitionExpression
public ArrayPopFunction()
: base("array_pop")
{
Parameters.Add(new VariableDefinitionExpression("array"));
Parameters.Add(new VariableDefinitionExpression("array") { IsMutableReference = true });
}

public override bool Evaluate(InterpreterScope scope, out ExpressionBase result)
Expand Down
2 changes: 1 addition & 1 deletion Source/Parser/Functions/ArrayPushFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal class ArrayPushFunction : FunctionDefinitionExpression
public ArrayPushFunction()
: base("array_push")
{
Parameters.Add(new VariableDefinitionExpression("array"));
Parameters.Add(new VariableDefinitionExpression("array") { IsMutableReference = true });
Parameters.Add(new VariableDefinitionExpression("value"));
}

Expand Down
49 changes: 49 additions & 0 deletions Tests/Parser/Expressions/IndexedVariableExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -392,5 +392,54 @@ public void TestArrayIndexOutOfRange()

"3:5 Index 5 not in range 0-1");
}

[Test]
public void TestGetModificationsNestedAssignment()
{
var input =
"function f(a) { a[0] = 3 }\r\n" +
"arr = [1,2,3,4]\r\n" +
"f(arr)";
var tokenizer = Tokenizer.CreateTokenizer(input);
var parser = new AchievementScriptInterpreter();
var groups = parser.Parse(tokenizer);

// before execution, we don't know if a parameter will be a reference
var expr = groups.Groups.ElementAt(2).Expressions.ElementAt(0);
var modifications = new HashSet<string>();
((INestedExpressions)expr).GetModifications(modifications);

AchievementScriptInterpreter.InitializeScope(groups, null);
parser.Run(groups);

// after execution, we do
((INestedExpressions)expr).GetModifications(modifications);
Assert.That(modifications.Count, Is.EqualTo(1));
Assert.That(modifications.Contains("arr"));
}

[Test]
public void TestGetModificationsNestedRead()
{
var input =
"function f(a) { b = a[0] }\r\n" +
"arr = [1,2,3,4]\r\n" +
"f(arr)";
var tokenizer = Tokenizer.CreateTokenizer(input);
var parser = new AchievementScriptInterpreter();
var groups = parser.Parse(tokenizer);

// before execution, we don't know if a parameter will be a reference
var expr = groups.Groups.ElementAt(2).Expressions.ElementAt(0);
var modifications = new HashSet<string>();
((INestedExpressions)expr).GetModifications(modifications);

AchievementScriptInterpreter.InitializeScope(groups, null);
parser.Run(groups);

// after execution, we do, but read shouldn't mark the parameter as modified
((INestedExpressions)expr).GetModifications(modifications);
Assert.That(modifications.Count, Is.EqualTo(0));
}
}
}
54 changes: 54 additions & 0 deletions Tests/Parser/Functions/ArrayPopFunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using NUnit.Framework;
using RATools.Parser.Expressions;
using RATools.Parser.Functions;
using System.Collections.Generic;
using System.Linq;

namespace RATools.Parser.Tests.Functions
Expand All @@ -16,6 +17,7 @@ public void TestDefinition()
Assert.That(def.Name.Name, Is.EqualTo("array_pop"));
Assert.That(def.Parameters.Count, Is.EqualTo(1));
Assert.That(def.Parameters.ElementAt(0).Name, Is.EqualTo("array"));
Assert.That(def.Parameters.ElementAt(0).IsMutableReference, Is.True);
}

private static ExpressionBase Evaluate(string input, InterpreterScope scope)
Expand Down Expand Up @@ -154,5 +156,57 @@ public void TestPopComparison()
Assert.That(comparison.Right, Is.InstanceOf<IntegerConstantExpression>());
Assert.That(((IntegerConstantExpression)comparison.Right).Value, Is.EqualTo(2));
}

[Test]
public void TestGetModifications()
{
var input =
"arr = [1,2,3,4]\r\n" +
"b = array_pop(arr)";
var tokenizer = Tokenizer.CreateTokenizer(input);
var parser = new AchievementScriptInterpreter();
var groups = parser.Parse(tokenizer);

// before execution, we don't know if a parameter will be a reference
var expr = groups.Groups.ElementAt(1).Expressions.ElementAt(0);
var modifications = new HashSet<string>();
((INestedExpressions)expr).GetModifications(modifications);
Assert.That(modifications.Count, Is.EqualTo(1));
Assert.That(modifications.Contains("b"));

AchievementScriptInterpreter.InitializeScope(groups, null);
parser.Run(groups);

// after execution, we do
((INestedExpressions)expr).GetModifications(modifications);
Assert.That(modifications.Count, Is.EqualTo(2));
Assert.That(modifications.Contains("b"));
Assert.That(modifications.Contains("arr"));
}

[Test]
public void TestGetModificationsNested()
{
var input =
"function f(a) { array_pop(a) }\r\n" +
"arr = [1,2,3,4]\r\n" +
"f(arr)";
var tokenizer = Tokenizer.CreateTokenizer(input);
var parser = new AchievementScriptInterpreter();
var groups = parser.Parse(tokenizer);

// before execution, we don't know if a parameter will be a reference
var expr = groups.Groups.ElementAt(2).Expressions.ElementAt(0);
var modifications = new HashSet<string>();
((INestedExpressions)expr).GetModifications(modifications);

AchievementScriptInterpreter.InitializeScope(groups, null);
parser.Run(groups);

// after execution, we do
((INestedExpressions)expr).GetModifications(modifications);
Assert.That(modifications.Count, Is.EqualTo(1));
Assert.That(modifications.Contains("arr"));
}
}
}
51 changes: 51 additions & 0 deletions Tests/Parser/Functions/ArrayPushFunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using RATools.Parser.Expressions;
using RATools.Parser.Expressions.Trigger;
using RATools.Parser.Functions;
using System.Collections.Generic;
using System.Linq;

namespace RATools.Parser.Tests.Functions
Expand All @@ -18,6 +19,7 @@ public void TestDefinition()
Assert.That(def.Name.Name, Is.EqualTo("array_push"));
Assert.That(def.Parameters.Count, Is.EqualTo(2));
Assert.That(def.Parameters.ElementAt(0).Name, Is.EqualTo("array"));
Assert.That(def.Parameters.ElementAt(0).IsMutableReference, Is.True);
Assert.That(def.Parameters.ElementAt(1).Name, Is.EqualTo("value"));
}

Expand Down Expand Up @@ -148,5 +150,54 @@ public void TestPushMemoryComparison()
Assert.That(comparison.Right, Is.InstanceOf<IntegerConstantExpression>());
Assert.That(((IntegerConstantExpression)comparison.Right).Value, Is.EqualTo(2));
}

[Test]
public void TestGetModifications()
{
var input =
"arr = []\r\n" +
"array_push(arr, 3)";
var tokenizer = Tokenizer.CreateTokenizer(input);
var parser = new AchievementScriptInterpreter();
var groups = parser.Parse(tokenizer);

// before execution, we don't know if a parameter will be a reference
var expr = groups.Groups.ElementAt(1).Expressions.ElementAt(0);
var modifications = new HashSet<string>();
((INestedExpressions)expr).GetModifications(modifications);

AchievementScriptInterpreter.InitializeScope(groups, null);
parser.Run(groups);

// after execution, we do
((INestedExpressions)expr).GetModifications(modifications);
Assert.That(modifications.Count, Is.EqualTo(1));
Assert.That(modifications.Contains("arr"));
}

[Test]
public void TestGetModificationsNested()
{
var input =
"function f(a) { array_push(a, 3) }\r\n" +
"arr = []\r\n" +
"f(arr)";
var tokenizer = Tokenizer.CreateTokenizer(input);
var parser = new AchievementScriptInterpreter();
var groups = parser.Parse(tokenizer);

// before execution, we don't know if a parameter will be a reference
var expr = groups.Groups.ElementAt(2).Expressions.ElementAt(0);
var modifications = new HashSet<string>();
((INestedExpressions)expr).GetModifications(modifications);

AchievementScriptInterpreter.InitializeScope(groups, null);
parser.Run(groups);

// after execution, we do
((INestedExpressions)expr).GetModifications(modifications);
Assert.That(modifications.Count, Is.EqualTo(1));
Assert.That(modifications.Contains("arr"));
}
}
}
Loading