From ec013f8f6b03558e35c1088a7c975e24560bda2d Mon Sep 17 00:00:00 2001 From: Jamiras <32680403+Jamiras@users.noreply.github.com> Date: Thu, 8 Jun 2023 09:50:46 -0600 Subject: [PATCH] allow 'constants' to be used as values in default parameters (#399) --- .../FunctionDefinitionExpression.cs | 15 +++- Source/Parser/Internal/ExpressionBase.cs | 2 +- .../FunctionDefinitionExpressionTests.cs | 82 ++++++++++++++++++- 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/Source/Parser/Expressions/FunctionDefinitionExpression.cs b/Source/Parser/Expressions/FunctionDefinitionExpression.cs index 93b25856..1de6a133 100644 --- a/Source/Parser/Expressions/FunctionDefinitionExpression.cs +++ b/Source/Parser/Expressions/FunctionDefinitionExpression.cs @@ -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 != ')') { @@ -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; } @@ -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("=>")) diff --git a/Source/Parser/Internal/ExpressionBase.cs b/Source/Parser/Internal/ExpressionBase.cs index ff10c590..b2aa86c9 100644 --- a/Source/Parser/Internal/ExpressionBase.cs +++ b/Source/Parser/Internal/ExpressionBase.cs @@ -55,7 +55,7 @@ internal ExpressionBase MakeReadOnly() /// internal void CopyLocation(ExpressionBase target) { - if (!Location.IsEmpty && !target.IsReadOnly) + if (Location.End.Line != 0 && !target.IsReadOnly) target.Location = Location; } diff --git a/Tests/Parser/Expressions/FunctionDefinitionExpressionTests.cs b/Tests/Parser/Expressions/FunctionDefinitionExpressionTests.cs index ff42c18e..df05d3e4 100644 --- a/Tests/Parser/Expressions/FunctionDefinitionExpressionTests.cs +++ b/Tests/Parser/Expressions/FunctionDefinitionExpressionTests.cs @@ -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] @@ -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()); + 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()); + 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()); + + 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()); + 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]