aboutsummaryrefslogtreecommitdiffstats
path: root/src/Qt.DotNet.Generator/Qt/DotNet/CodeGeneration/Placeholder.cs
blob: 65c3b7dae7ddc20f08993ac0deb96b610d2f0a02 (plain)
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/***************************************************************************************************
 Copyright (C) 2025 The Qt Company Ltd.
 SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
***************************************************************************************************/

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;

namespace Qt.DotNet.CodeGeneration
{
    using Utils.Concurrent;

    public class Placeholder
    {
        public const int AutoIndent = -1;
        public int Indent { get; set; } = AutoIndent;
        public bool Sorted { get; set; } = true;
        public bool Distinct { get; set; } = false;

        public string Id { get; private set; }
        public MemberInfo Source { get; set; }

        public IEnumerable<string> Content {
            init => value.ToList().ForEach(text => AddText(text));
        }

        public Placeholder(string id = null, MemberInfo src = null)
        {
            Id = id ?? Path.GetRandomFileName();
            Source = src;
        }

        public Placeholder(Enum id, MemberInfo src = null) : this(IdName(id), src)
        { }

        public virtual Placeholder AddText(string text) => AddText(AutoIndent, text);

        public Placeholder AddText(int indent, string text)
        {
            var refs = Regex.Matches(text, @"(?<=\n)(?<Indent>[ ]*)(?<Ref>[\uE000-\uEFFF])");
            var children = Children.ToArray();
            foreach (Match match in refs) {
                if (match.Groups["Ref"] is not { Success: true } refMatch)
                    continue;
                var refIdx = refMatch.Value[0] - '\uE000';
                if (children.ElementAtOrDefault(refIdx) is not { Indent: AutoIndent } child)
                    continue;
                var indentValue = string.Empty;
                if (match.Groups["Indent"] is { Success: true } indentMatch)
                    indentValue = indentMatch.Value;
                child.Indent = indent switch
                {
                    AutoIndent => indentValue.Length / 4,
                    _ => indent
                };
            }

            text = Regex.Replace(text, $@"{Wrap}\s*", "");
            text = text.Trim('\r', '\n', ' ').Replace($"\r\n{Blank}", $"{Blank}") + End;
            text = Regex.Replace(text, @"(?<=\n)[ ]+(?=[\uE000-\uEFFF])", "");
            text = Regex.Replace(text, @"(?<=\n)\r?\n", $"{Blank}");

            lock (criticalSection)
                Text.Append(text);

            return this;
        }

        public virtual Placeholder AddPlaceholder(Placeholder placeholder)
        {
            if (Connect(this, placeholder))
                AddText($"{RefIdx(Array.IndexOf(Children.ToArray(), placeholder))}");
            return placeholder;
        }

        public virtual char RefPlaceholder(Placeholder placeholder)
        {
            if (placeholder.Parent == this || Connect(this, placeholder))
                return RefIdx(Array.IndexOf(Children.ToArray(), placeholder));
            return Nul;
        }

        public static Placeholder operator +(Placeholder self, string text)
        {
            self.AddText(text);
            return self;
        }

        public static Placeholder operator +(Placeholder self, Placeholder placeholder)
        {
            self.AddPlaceholder(placeholder);
            return self;
        }

        public char this[Placeholder placeholder] => RefPlaceholder(placeholder);

        public void Reset()
        {
            lock (criticalSection) {
                foreach (var child in Children) {
                    child.Reset();
                    child.RemoveFromIndex();
                    child.Parent = null;
                }
                Children.Clear();
                Text.Clear();
            }
        }

        public const char Nul = '\uF000';
        public const char BkSpc = '\uF008';
        public const char Tab = '\uF009';
        public const char Blank = '\uF00A';
        public const char Wrap = '\uF00D';
        protected const char End = '\uF017';

        protected const char RefBase = '\uE000';
        protected const char RefError = '\uF0EE';

        private static char RefIdx(int index)
        {
            if (index is < 0 or > 0xFFF)
                return RefError;
            return (char)(RefBase + index);
        }

        private readonly object criticalSection = new();

        private StringBuilder Text { get; } = new();
        private Placeholder Parent { get; set; } = null;
        private ConcurrentQueue<Placeholder> Children { get; } = new();
        private static ConcurrentDictionary
            <(MemberInfo, string), Placeholder> Index { get; } = new();

        internal static void ResetIndex()
        {
            Index.Clear();
        }

        protected static string IdName(Enum id) => $"{id.GetType().Name}.{id}";

        protected void AddToIndex()
        {
            if (!Index.TryAdd((Source, Id), this)) {
                throw new InvalidOperationException(
                    $"Duplicate placeholder definition for (${Source}, ${Id})");
            }
        }

        protected void RemoveFromIndex()
        {
            if (!Index.TryGetValue((Source, Id), out var placeholder))
                return;
            // If the pair '(this.Source, this.Id)' is in the index, it can only map to 'this'.
            Debug.Assert(placeholder == this);
            if (placeholder != this)
                return;
            Index.TryRemove((Source, Id), out _);
        }

        private static bool Connect(Placeholder parent, Placeholder child)
        {
            if (child is FilePlaceholder)
                return false;
            lock (child.criticalSection) {
                if (child.Parent != null)
                    return false;
                lock (parent.criticalSection) {
                    child.Parent = parent;
                    child.Source ??= parent.Source;
                    parent.Children.Enqueue(child);
                    child.AddToIndex();
                }
            }
            return true;
        }

        private class TextComparer : IComparer<string>, IEqualityComparer<string>
        {
            private string N(string s)
            {
                return Regex
                    .Replace(s, $@"[{Nul},{Tab},{End},{BkSpc},{Blank},{RefBase}-{RefError}]+", "");
            }

            public int Compare(string x, string y)
            {
                return StringComparer.Ordinal.Compare(N(x), N(y));
            }

            public bool Equals(string x, string y)
            {
                return StringComparer.Ordinal.Equals(N(x), N(y));
            }

            public int GetHashCode([DisallowNull] string obj)
            {
                return StringComparer.Ordinal.GetHashCode(N(obj));
            }
        }

        private static TextComparer Comparer { get; } = new();

        protected virtual async Task<string> RenderAsync()
        {
            if (Indent == AutoIndent)
                Indent = 0;

            var text = Text.ToString();
            var childrenText = await Task.WhenAll(Children.ToArray()
                .Select(x => Task.Run(async () => await x.RenderAsync())));
            for (int i = 0; i < childrenText.Length; ++i)
                text = text.Replace($"{RefIdx(i)}", childrenText[i]);

            IEnumerable<string> blocks = text
                .Split(End, StringSplitOptions.RemoveEmptyEntries);
            if ((Sorted || Distinct) && blocks.Count() > 1) {
                blocks = blocks.Order(Comparer);
                if (Distinct)
                    blocks = blocks.Distinct(Comparer);
            }
            text = string.Join("\r\n", blocks);
            var lines = text
                .Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
            text = string.Join("\r\n", lines
                .Select(x => $"{new string(Tab, Indent)}{x}"));
            return text;
        }

        public static Placeholder Get(Enum id, MemberInfo src)
        {
            return Get(IdName(id), src);
        }

        public static Placeholder Get(string id, MemberInfo src)
        {
            if (!Index.TryGetValue((src, id), out var placeholder))
                return null;
            return placeholder;
        }

        private class Alias : Placeholder
        {
            private Placeholder Actual { get; set; }

            public Alias(Placeholder actual, string id, MemberInfo src) : base(id, src)
            {
                Actual = actual;
                AddToIndex();
            }

            public Alias(Placeholder actual, Enum id, MemberInfo src) : base(id, src)
            {
                Actual = actual;
                AddToIndex();
            }

            public override Placeholder AddText(string text)
            {
                return Actual.AddText(text);
            }

            public override Placeholder AddPlaceholder(Placeholder placeholder)
            {
                placeholder.Source ??= Source;
                return Actual.AddPlaceholder(placeholder);
            }

            public override char RefPlaceholder(Placeholder placeholder)
            {
                placeholder.Source ??= Source;
                return Actual.RefPlaceholder(placeholder);
            }

            protected override async Task<string> RenderAsync()
            {
                Debug.Assert(false, "Alias placeholders must not be rendered.");
                return await Task.FromResult(string.Empty);
            }
        }

        public Placeholder CreateAlias(MemberInfo src, string id = null)
        {
            return new Alias(this, id ?? Id, src);
        }

        public Placeholder CreateAlias(MemberInfo src, Enum id) {
            return new Alias(this, id, src);
        }
    }

    public static class SourcePlaceholderExtensions
    {
        public static Placeholder GetPlaceholder(this MemberInfo src, string id)
            => Placeholder.Get(id, src);

        public static Placeholder GetPlaceholder(this MemberInfo src, Enum id)
            => Placeholder.Get(id, src);
    }
}