Skip to content

Commit 9741013

Browse files
committed
Add fallback support for legacy keywords
Earlier versions of the scripting package supported case-invariant predefined type names. To avoid breaking changes we introduce here an exception fallback path which scans the expression for these known type identifiers and replaces them with the corresponding built-in type keyword.
1 parent 9b9f503 commit 9741013

File tree

5 files changed

+103
-3
lines changed

5 files changed

+103
-3
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Collections.Generic;
2+
using System.Linq.Dynamic.Core;
3+
using System.Linq.Dynamic.Core.Tokenizer;
4+
using System.Text;
5+
6+
namespace Bonsai.Scripting.Expressions
7+
{
8+
internal static class CompatibilityAnalyzer
9+
{
10+
internal static readonly Dictionary<string, string> LegacyKeywords = new()
11+
{
12+
{ "boolean", "bool" },
13+
{ "single", "float" },
14+
{ "int32", "int" },
15+
{ "int64", "long" },
16+
{ "int16", "short" },
17+
{ "uint32", "uint" },
18+
{ "uint64", "ulong" },
19+
{ "uint16", "ushort" },
20+
{ "math", "Math" },
21+
{ "convert", "Convert" }
22+
};
23+
24+
public static bool ReplaceLegacyKeywords(ParsingConfig? parsingConfig, string text, out string result)
25+
{
26+
result = text;
27+
if (string.IsNullOrEmpty(text))
28+
return false;
29+
30+
31+
List<(Token, string)> replacements = null;
32+
var previousTokenId = TokenId.Unknown;
33+
var textParser = new TextParser(parsingConfig, text);
34+
while (textParser.CurrentToken.Id != TokenId.End)
35+
{
36+
if (textParser.CurrentToken.Id == TokenId.Identifier &&
37+
previousTokenId != TokenId.Dot &&
38+
LegacyKeywords.TryGetValue(textParser.CurrentToken.Text, out var keyword))
39+
{
40+
replacements ??= new();
41+
replacements.Add((textParser.CurrentToken, keyword));
42+
}
43+
44+
previousTokenId = textParser.CurrentToken.Id;
45+
textParser.NextToken();
46+
}
47+
48+
if (replacements?.Count > 0)
49+
{
50+
var sb = new StringBuilder(text);
51+
for (int i = 0; i < replacements.Count; i++)
52+
{
53+
var (token, keyword) = replacements[i];
54+
sb.Remove(token.Pos, token.Text.Length);
55+
sb.Insert(token.Pos, keyword);
56+
}
57+
58+
result = sb.ToString();
59+
return true;
60+
}
61+
62+
return false;
63+
}
64+
}
65+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Linq.Dynamic.Core;
3+
using System.Linq.Dynamic.Core.Exceptions;
4+
using System.Linq.Expressions;
5+
6+
namespace Bonsai.Scripting.Expressions
7+
{
8+
internal class DynamicExpressionHelper
9+
{
10+
public static LambdaExpression ParseLambda(Type delegateType, ParsingConfig? parsingConfig, ParameterExpression[] parameters, Type? resultType, string expression, params object?[] values)
11+
{
12+
return ParseLambda(delegateType, parsingConfig, true, parameters, resultType, expression, values);
13+
}
14+
15+
public static LambdaExpression ParseLambda(ParsingConfig? parsingConfig, Type itType, Type? resultType, string expression, params object?[] values)
16+
{
17+
return ParseLambda(null, parsingConfig, true, new[] { Expression.Parameter(itType, "it") }, resultType, expression, values);
18+
}
19+
20+
public static LambdaExpression ParseLambda(Type? delegateType, ParsingConfig? parsingConfig, bool createParameterCtor, ParameterExpression[] parameters, Type? resultType, string expression, params object?[] values)
21+
{
22+
try
23+
{
24+
return DynamicExpressionParser.ParseLambda(delegateType, parsingConfig, createParameterCtor, parameters, resultType, expression, values);
25+
}
26+
catch (ParseException)
27+
{
28+
if (!CompatibilityAnalyzer.ReplaceLegacyKeywords(parsingConfig, expression, out expression))
29+
throw;
30+
31+
return DynamicExpressionParser.ParseLambda(delegateType, parsingConfig, createParameterCtor, parameters, resultType, expression, values);
32+
}
33+
}
34+
}
35+
}

src/Bonsai.Scripting.Expressions/ExpressionCondition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public override Expression Build(IEnumerable<Expression> arguments)
5454
var source = arguments.First();
5555
var sourceType = source.Type.GetGenericArguments()[0];
5656
var config = ParsingConfigHelper.CreateParsingConfig(sourceType);
57-
var predicate = DynamicExpressionParser.ParseLambda(config, sourceType, typeof(bool), Expression);
57+
var predicate = DynamicExpressionHelper.ParseLambda(config, sourceType, typeof(bool), Expression);
5858
return System.Linq.Expressions.Expression.Call(whereMethod.MakeGenericMethod(sourceType), source, predicate);
5959
}
6060

src/Bonsai.Scripting.Expressions/ExpressionSink.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public override Expression Build(IEnumerable<Expression> arguments)
6161
var actionType = System.Linq.Expressions.Expression.GetActionType(sourceType);
6262
var itParameter = new[] { System.Linq.Expressions.Expression.Parameter(sourceType, string.Empty) };
6363
var config = ParsingConfigHelper.CreateParsingConfig(sourceType);
64-
var onNext = DynamicExpressionParser.ParseLambda(actionType, config, itParameter, null, Expression);
64+
var onNext = DynamicExpressionHelper.ParseLambda(actionType, config, itParameter, null, Expression);
6565
return System.Linq.Expressions.Expression.Call(doMethod.MakeGenericMethod(sourceType), source, onNext);
6666
}
6767
else return source;

src/Bonsai.Scripting.Expressions/ExpressionTransform.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public override Expression Build(IEnumerable<Expression> arguments)
5555
var source = arguments.First();
5656
var sourceType = source.Type.GetGenericArguments()[0];
5757
var config = ParsingConfigHelper.CreateParsingConfig(sourceType);
58-
var selector = DynamicExpressionParser.ParseLambda(config, sourceType, null, Expression);
58+
var selector = DynamicExpressionHelper.ParseLambda(config, sourceType, null, Expression);
5959
return System.Linq.Expressions.Expression.Call(selectMethod.MakeGenericMethod(sourceType, selector.ReturnType), source, selector);
6060
}
6161

0 commit comments

Comments
 (0)