Skip to content

Commit

Permalink
allow 'constants' to be used as values in default parameters (#399)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamiras authored Jun 8, 2023
1 parent b5d0572 commit ec013f8
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 5 deletions.
15 changes: 14 additions & 1 deletion Source/Parser/Expressions/FunctionDefinitionExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,8 @@ internal static ExpressionBase Parse(PositionalTokenizer tokenizer, int line = 0
return ParseError(tokenizer, "Expected '(' after function name", Name);
tokenizer.Advance();

ErrorExpression nonConstantDefaultParameterError = null;

SkipWhitespace(tokenizer);
if (tokenizer.NextChar != ')')
{
Expand Down Expand Up @@ -625,7 +627,15 @@ internal static ExpressionBase Parse(PositionalTokenizer tokenizer, int line = 0

ExpressionBase evaluated;
if (!value.ReplaceVariables(scope, out evaluated))
return ParseError(tokenizer, "Default value for " + parameter.ToString() + " is not constant", evaluated);
{
// a variable reference could be a constant hiding a magic number or a dictionary.
// as long as it's not referencing another parameter, let it through (for now).
var variable = value as VariableExpression;
if (variable != null && !Parameters.Any(p => p.Name == variable.Name))
evaluated = value;
else if (nonConstantDefaultParameterError == null)
nonConstantDefaultParameterError = ParseError(tokenizer, "Default value for " + parameter.ToString() + " is not constant", value);
}

DefaultParameters[parameter.ToString()] = evaluated;
}
Expand All @@ -649,6 +659,9 @@ internal static ExpressionBase Parse(PositionalTokenizer tokenizer, int line = 0
tokenizer.Advance(); // closing parenthesis
SkipWhitespace(tokenizer);

if (nonConstantDefaultParameterError != null)
return nonConstantDefaultParameterError;

ExpressionBase expression;

if (tokenizer.Match("=>"))
Expand Down
2 changes: 1 addition & 1 deletion Source/Parser/Internal/ExpressionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ internal ExpressionBase MakeReadOnly()
/// </summary>
internal void CopyLocation(ExpressionBase target)
{
if (!Location.IsEmpty && !target.IsReadOnly)
if (Location.End.Line != 0 && !target.IsReadOnly)
target.Location = Location;
}

Expand Down
82 changes: 79 additions & 3 deletions Tests/Parser/Expressions/FunctionDefinitionExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public void TestParseDefaultParameters()
public void TestParseNonDefaultAfterDefaultParameters()
{
Parse("function func(i, j = 2, k) { l = i + j + k }",
"1:26 Non-default parameter k appears after default parameters");
"1:25 Non-default parameter k appears after default parameters");
}

[Test]
Expand Down Expand Up @@ -152,11 +152,87 @@ public void TestParseDefaultParameterMemoryReferece()
Assert.That(builder.ToString(), Is.EqualTo("k = i + j"));
}

[Test]
public void TestParseDefaultParameterVariable()
{
var expr = Parse("function func(i, j = z) { k = i + j }");
Assert.That(expr.Name.Name, Is.EqualTo("func"));
Assert.That(expr.Parameters.Count, Is.EqualTo(2));
Assert.That(expr.Parameters.ElementAt(0).Name, Is.EqualTo("i"));
Assert.That(expr.Parameters.ElementAt(1).Name, Is.EqualTo("j"));
Assert.That(expr.DefaultParameters.ContainsKey("j"));
Assert.That(expr.DefaultParameters["j"], Is.InstanceOf<VariableExpression>());
Assert.That(((VariableExpression)expr.DefaultParameters["j"]).Name, Is.EqualTo("z"));

Assert.That(expr.Expressions.Count, Is.EqualTo(1));

var builder = new StringBuilder();
expr.Expressions.First().AppendString(builder);
Assert.That(builder.ToString(), Is.EqualTo("k = i + j"));

var scope = new InterpreterScope();
scope.AddFunction(expr);
scope.DefineVariable(new VariableDefinitionExpression("z"), new IntegerConstantExpression(2));
scope.DefineVariable(new VariableDefinitionExpression("k"), new IntegerConstantExpression(0));

ExpressionBase result;
var funcCall = new FunctionCallExpression("func", new ExpressionBase[] { new IntegerConstantExpression(6) });
Assert.That(funcCall.Evaluate(scope, out result), Is.True);

result = scope.GetVariable("k");
Assert.That(result, Is.InstanceOf<IntegerConstantExpression>());
Assert.That(((IntegerConstantExpression)result).Value, Is.EqualTo(8));
}

[Test]
public void TestParseDefaultParameterIndexedVariable()
{
var expr = Parse("function func(i, j = z[3]) { k = i + j }");
Assert.That(expr.Name.Name, Is.EqualTo("func"));
Assert.That(expr.Parameters.Count, Is.EqualTo(2));
Assert.That(expr.Parameters.ElementAt(0).Name, Is.EqualTo("i"));
Assert.That(expr.Parameters.ElementAt(1).Name, Is.EqualTo("j"));
Assert.That(expr.DefaultParameters.ContainsKey("j"));
Assert.That(expr.DefaultParameters["j"], Is.InstanceOf<IndexedVariableExpression>());

var builder = new StringBuilder();
expr.DefaultParameters["j"].AppendString(builder);
Assert.That(builder.ToString(), Is.EqualTo("z[3]"));

Assert.That(expr.Expressions.Count, Is.EqualTo(1));
builder = new StringBuilder();
expr.Expressions.First().AppendString(builder);
Assert.That(builder.ToString(), Is.EqualTo("k = i + j"));

var dict = new DictionaryExpression();
dict.Add(new IntegerConstantExpression(3), new IntegerConstantExpression(2));

var scope = new InterpreterScope();
scope.AddFunction(expr);
scope.DefineVariable(new VariableDefinitionExpression("z"), dict);
scope.DefineVariable(new VariableDefinitionExpression("k"), new IntegerConstantExpression(0));

ExpressionBase result;
var funcCall = new FunctionCallExpression("func", new ExpressionBase[] { new IntegerConstantExpression(6) });
Assert.That(funcCall.Evaluate(scope, out result), Is.True);

result = scope.GetVariable("k");
Assert.That(result, Is.InstanceOf<IntegerConstantExpression>());
Assert.That(((IntegerConstantExpression)result).Value, Is.EqualTo(8));
}

[Test]
public void TestParseDefaultParameterNonConstant()
{
Parse("function func(i, j = i + 2) { k = i + j }",
"1:27 Default value for j is not constant");
Parse("function func(i, j = z + 2) { k = i + j }",
"1:22 Default value for j is not constant");
}

[Test]
public void TestParseDefaultParameterReferencingOtherParameter()
{
Parse("function func(i, j = i) { k = i + j }",
"1:22 Default value for j is not constant");
}

[Test]
Expand Down

0 comments on commit ec013f8

Please sign in to comment.