Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow assigning values to nested dictionaries #440

Merged
merged 1 commit into from
Mar 4, 2024
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
15 changes: 13 additions & 2 deletions Source/Parser/Expressions/AssignmentExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,19 @@ public ErrorExpression Evaluate(InterpreterScope scope)
}
else
{
if (!Value.ReplaceVariables(assignmentScope, out result))
return (ErrorExpression)result;
var variable = Value as VariableExpression;
if (variable != null)
{
result = variable.GetValue(assignmentScope);
var error = result as ErrorExpression;
if (error != null)
return error;
}
else
{
if (!Value.ReplaceVariables(assignmentScope, out result))
return (ErrorExpression)result;
}
}

return scope.AssignVariable(Variable, result);
Expand Down
28 changes: 10 additions & 18 deletions Source/Parser/Expressions/FunctionCallExpression.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using RATools.Parser.Internal;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

Expand Down Expand Up @@ -201,25 +202,16 @@ private static ExpressionBase GetParameter(InterpreterScope parameterScope, Inte
var variable = value as VariableExpression;
if (variable != null)
{
value = scope.GetVariable(variable.Name);
value = variable.GetValue(scope);

if (value == null)
{
// could not find variable, fallback to VariableExpression.ReplaceVariables generating an error
value = assignment.Value;
}
else
{
// when a parameter is assigned to a variable that is an array or dictionary,
// assume it has already been evaluated and pass it by reference. this is magnitudes
// more performant, and allows the function to modify the data in the container.
if (value.Type == ExpressionType.Dictionary || value.Type == ExpressionType.Array)
{
value = scope.GetVariableReference(variable.Name);
assignment.Value.CopyLocation(value);
return value;
}
}
var error = value as ErrorExpression;
if (error != null)
return new ErrorExpression("Invalid value for parameter: " + assignment.Variable.Name, assignment.Value) { InnerError = error };

if (value is VariableReferenceExpression)
return value;

Debug.Assert(value != null);
}

if (value.IsConstant)
Expand Down
73 changes: 25 additions & 48 deletions Source/Parser/Expressions/IndexedVariableExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,57 +37,43 @@ internal override void AppendString(StringBuilder builder)
}

/// <summary>
/// Replaces the variables in the expression with values from <paramref name="scope" />.
/// Gets the un-evaluated value at the referenced index.
/// </summary>
/// <param name="scope">The scope object containing variable values.</param>
/// <param name="result">[out] The new expression containing the replaced variables.</param>
/// <returns>
/// <c>true</c> if substitution was successful, <c>false</c> if something went wrong, in which case <paramref name="result" /> will likely be a <see cref="ErrorExpression" />.
/// </returns>
public override bool ReplaceVariables(InterpreterScope scope, out ExpressionBase result)
public override ExpressionBase GetValue(InterpreterScope scope)
{
ExpressionBase container, index;
StringBuilder builder;
ExpressionBase container, index, result;

GetContainerIndex(scope, out container, out index);

switch (container.Type)
{
case ExpressionType.Dictionary:
result = ((DictionaryExpression)container).GetEntry(index);
if (result == null)
{
var builder = new StringBuilder();
builder.Append("No entry in dictionary for key: ");
index.AppendString(builder);
result = new ErrorExpression(builder.ToString(), Index);
return false;
}
break;
if (result != null)
return result;

builder = new StringBuilder();
builder.Append("No entry in dictionary for key: ");
index.AppendString(builder);
return new ErrorExpression(builder.ToString(), Index);

case ExpressionType.Array:
result = ((ArrayExpression)container).Entries[((IntegerConstantExpression)index).Value];
break;
// ASSERT: index was validated in GetContainerIndex
return ((ArrayExpression)container).Entries[((IntegerConstantExpression)index).Value];

case ExpressionType.Error:
result = container;
return false;
return container;

default:
{
var builder = new StringBuilder();
builder.Append("Cannot index: ");
Variable.AppendString(builder);
builder.Append(" (");
builder.Append(container.Type);
builder.Append(')');
result = new ErrorExpression(builder.ToString(), Variable);
}
return false;
builder = new StringBuilder();
builder.Append("Cannot index: ");
Variable.AppendString(builder);
builder.Append(" (");
builder.Append(container.Type);
builder.Append(')');
return new ErrorExpression(builder.ToString(), Variable);
}

if (result == null)
return false;

return result.ReplaceVariables(scope, out result);
}

public ErrorExpression Assign(InterpreterScope scope, ExpressionBase newValue)
Expand All @@ -102,6 +88,7 @@ public ErrorExpression Assign(InterpreterScope scope, ExpressionBase newValue)
break;

case ExpressionType.Array:
// ASSERT: index was validated in GetContainerIndex
((ArrayExpression)container).Entries[((IntegerConstantExpression)index).Value] = newValue;
break;

Expand Down Expand Up @@ -138,19 +125,9 @@ private void GetContainerIndex(InterpreterScope scope, out ExpressionBase contai
return;
}

var indexed = Variable as IndexedVariableExpression;
if (indexed != null)
{
indexed.ReplaceVariables(scope, out container);
return;
}

container = scope.GetVariable(Variable.Name);
if (container == null)
{
container = new UnknownVariableParseErrorExpression("Unknown variable: " + Variable.Name, Variable);
container = Variable.GetValue(scope);
if (container is ErrorExpression)
return;
}

var variableReference = container as VariableReferenceExpression;
if (variableReference != null)
Expand Down
44 changes: 34 additions & 10 deletions Source/Parser/Expressions/VariableExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,48 @@ internal VariableExpression(string name, int line, int column)
/// <c>true</c> if substitution was successful, <c>false</c> if something went wrong, in which case <paramref name="result" /> will likely be a <see cref="ErrorExpression" />.
/// </returns>
public override bool ReplaceVariables(InterpreterScope scope, out ExpressionBase result)
{
ExpressionBase value = GetValue(scope);
if (value == null || value is ErrorExpression)
{
result = value;
return false;
}

return value.ReplaceVariables(scope, out result);
}

/// <summary>
/// Gets the un-evaluated value of the variable.
/// </summary>
public virtual ExpressionBase GetValue(InterpreterScope scope)
{
ExpressionBase value = scope.GetVariable(Name);
if (value == null)
if (value != null)
{
var func = scope.GetFunction(Name);
if (func != null)
// when a parameter is assigned to a variable that is an array or dictionary,
// assume it has already been evaluated and pass it by reference. this is magnitudes
// more performant, and allows the function to modify the data in the container.
if (value.Type == ExpressionType.Dictionary || value.Type == ExpressionType.Array)
{
// special wrapper for returning a function as a variable
result = new FunctionReferenceExpression(Name);
CopyLocation(result);
return true;
var reference = scope.GetVariableReference(Name);
CopyLocation(reference);
return reference;
}

result = new UnknownVariableParseErrorExpression("Unknown variable: " + Name, this);
return false;
return value;
}

return value.ReplaceVariables(scope, out result);
var func = scope.GetFunction(Name);
if (func != null)
{
// special wrapper for returning a function as a variable
var result = new FunctionReferenceExpression(Name);
CopyLocation(result);
return result;
}

return new UnknownVariableParseErrorExpression("Unknown variable: " + Name, this);
}

/// <summary>
Expand Down
31 changes: 31 additions & 0 deletions Tests/Parser/Expressions/DictionaryExpressionTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using Jamiras.Components;
using NUnit.Framework;
using RATools.Parser.Expressions;
using RATools.Parser.Expressions.Trigger;
using RATools.Parser.Internal;
using RATools.Parser.Tests.Expressions.Trigger;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace RATools.Parser.Tests.Expressions
{
Expand Down Expand Up @@ -350,5 +353,33 @@ public void TestGetModifications()

Assert.That(modifications.Count, Is.EqualTo(0));
}

[Test]
public void TestAddNestedEntry()
{
var dict = new DictionaryExpression();
var subdict = new DictionaryExpression();
var key = new IntegerConstantExpression(1);
dict.Add(key, subdict);

var scope = new InterpreterScope();
scope.DefineVariable(new VariableDefinitionExpression("dict"), dict);

var assignment = ExpressionTests.Parse<AssignmentExpression>("dict[1][2] = 3");
Assert.That(assignment.Evaluate(scope), Is.Null);

Assert.That(subdict.Entries.Count, Is.EqualTo(1));
var entry = subdict.Entries.First();
Assert.That(entry.Key, Is.EqualTo(new IntegerConstantExpression(2)));
Assert.That(entry.Value, Is.EqualTo(new IntegerConstantExpression(3)));

assignment = ExpressionTests.Parse<AssignmentExpression>("dict[1][2] = 4");
Assert.That(assignment.Evaluate(scope), Is.Null);

Assert.That(subdict.Entries.Count, Is.EqualTo(1));
entry = subdict.Entries.First();
Assert.That(entry.Key, Is.EqualTo(new IntegerConstantExpression(2)));
Assert.That(entry.Value, Is.EqualTo(new IntegerConstantExpression(4)));
}
}
}
Loading