alielie
2026-09-10 f3889f2d36c50cf99468594854def9b7ed069fb5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using DynamicExpresso.Parsing;
 
namespace DynamicExpresso
{
    internal class Detector
    {
        private readonly ParserSettings _settings;
 
        private static readonly Regex RootIdentifierDetectionRegex =
            new Regex(@"(?<=[^\w@]|^)(?<id>@?[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}_]*)", RegexOptions.Compiled);
 
        private static readonly string Id = RootIdentifierDetectionRegex.ToString();
        private static readonly string Type = Id.Replace("<id>", "<type>");
 
        private static readonly Regex LambdaDetectionRegex =
            new Regex($@"(\((((?<withtype>({Type}\s+)?{Id}))(\s*,\s*)?)+\)|(?<withtype>{Id}))\s*=>",
                RegexOptions.Compiled);
 
        private static readonly Regex StringDetectionRegex =
            new Regex(@"(?<!\\)?"".*?(?<!\\)""", RegexOptions.Compiled);
 
        private static readonly Regex CharDetectionRegex =
            new Regex(@"(?<!\\)?'.{1,2}?(?<!\\)'", RegexOptions.Compiled);
 
        public Detector(ParserSettings settings)
        {
            _settings = settings;
        }
 
        public IdentifiersInfo DetectIdentifiers(string expression, DetectorOptions option)
        {
            expression = PrepareExpression(expression);
 
            var unknownIdentifiers = new HashSet<string>(_settings.KeyComparer);
            var knownIdentifiers = new HashSet<Identifier>();
            var knownTypes = new HashSet<ReferenceType>();
 
            // find lambda parameters
            var lambdaParameters = new Dictionary<string, Identifier>();
            foreach (Match match in LambdaDetectionRegex.Matches(expression))
            {
                var withtypes = match.Groups["withtype"].Captures;
                var types = match.Groups["type"].Captures;
                var identifiers = match.Groups["id"].Captures;
 
                // match identifier with its type
                var t = 0;
                for (var i = 0; i < withtypes.Count; i++)
                {
                    var withtype = withtypes[i].Value;
                    var identifier = identifiers[i].Value;
                    var type = typeof(object);
                    if (withtype != identifier)
                    {
                        var typeName = types[t].Value;
                        if (_settings.KnownTypes.TryGetValue(typeName, out var knownType))
                            type = knownType.Type;
 
                        t++;
                    }
 
                    // there might be several lambda parameters with the same name
                    //  -> in that case, we ignore the detected type
                    if (lambdaParameters.TryGetValue(identifier, out var already) &&
                        already.Expression.Type != type)
                        type = typeof(object);
 
                    var defaultValue = type.IsValueType ? Activator.CreateInstance(type) : null;
                    lambdaParameters[identifier] = new Identifier(identifier, Expression.Constant(defaultValue, type));
                }
            }
 
            foreach (Match match in RootIdentifierDetectionRegex.Matches(expression))
            {
                var idGroup = match.Groups["id"];
                var identifier = idGroup.Value;
 
                if (IsReservedKeyword(identifier))
                    continue;
 
                if (option == DetectorOptions.None && idGroup.Index > 0)
                {
                    var previousChar = expression[idGroup.Index - 1];
 
                    // don't consider member accesses as identifiers (e.g. "x.Length" will only return x but not Length)
                    if (previousChar == '.')
                        continue;
 
                    // don't consider number literals as identifiers
                    if (char.IsDigit(previousChar))
                        continue;
                }
 
                if (_settings.Identifiers.TryGetValue(identifier, out var knownIdentifier))
                    knownIdentifiers.Add(knownIdentifier);
                else if (lambdaParameters.TryGetValue(identifier, out var knownLambdaParam))
                    knownIdentifiers.Add(knownLambdaParam);
                else if (_settings.KnownTypes.TryGetValue(identifier, out var knownType))
                    knownTypes.Add(knownType);
                else
                    unknownIdentifiers.Add(identifier);
            }
 
            return new IdentifiersInfo(unknownIdentifiers, knownIdentifiers, knownTypes);
        }
 
        private static string PrepareExpression(string expression)
        {
            expression = expression ?? string.Empty;
 
            expression = RemoveStringLiterals(expression);
 
            expression = RemoveCharLiterals(expression);
 
            return expression;
        }
 
        private static string RemoveStringLiterals(string expression)
        {
            return StringDetectionRegex.Replace(expression, "");
        }
 
        private static string RemoveCharLiterals(string expression)
        {
            return CharDetectionRegex.Replace(expression, "");
        }
 
        private bool IsReservedKeyword(string identifier)
        {
            return ParserConstants.ReservedKeywords.Contains(identifier, _settings.KeyComparer);
        }
    }
}