Forge
csharp4405de34
1using Eto.Parse;
2
3namespace CascadeIDE.Services.ChordNotation;
4
5/// <summary>
6/// Разбор нотации из <c>docs/chord-notation-cascadeide.md</c> (EBNF там же).
7/// Грамматика задаётся через <see cref="Eto.Parse"/> (fluent API, один-в-один с EBNF).
8/// </summary>
9public static class ChordNotationParser
10{
11 /// <summary>Парсит строку (например <c>&lt;C-k&gt; s p</c>). Пустая строка — успех с нулём шагов.</summary>
12 public static ChordNotationParseResult Parse(string? input)
13 {
14 if (string.IsNullOrEmpty(input))
15 return ChordNotationParseResult.Ok(Array.Empty<ChordNotationStep>());
16
17 var trimmed = input.Trim();
18 var gm = ChordNotationGrammar.Instance.Match(trimmed);
19 if (!gm.Success)
20 return ChordNotationParseResult.Fail(FormatGrammarError(gm, trimmed));
21
22 var tokens = trimmed.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
23 var steps = new List<ChordNotationStep>(tokens.Length);
24 foreach (var t in tokens)
25 {
26 if (t.Length >= 2 && t[0] == '<' && t[^1] == '>')
27 {
28 steps.Add(ParseBracketInner(t.AsSpan(1, t.Length - 2)));
29 continue;
30 }
31
32 steps.Add(new ChordNotationPlainStep(t));
33 }
34
35 return ChordNotationParseResult.Ok(steps);
36 }
37
38 /// <summary>Vim-стиль (<c>&lt;C-k&gt; s p</c>) → нормализованная модель (см. два слоя в доке).</summary>
39 public static bool TryParseVimToNormalized(string? input, out NormalizedKeySequence? sequence, out string error)
40 {
41 sequence = null;
42 var r = Parse(input);
43 if (!r.IsSuccess)
44 {
45 error = r.Error;
46 return false;
47 }
48
49 sequence = ChordSemanticNormalizer.FromVimSteps(r.Steps);
50 error = "";
51 return true;
52 }
53
54 private static ChordNotationChordStep ParseBracketInner(ReadOnlySpan<char> inner)
55 {
56 var mods = new List<string>(4);
57 while (!inner.IsEmpty)
58 {
59 if (inner.StartsWith("Alt-", StringComparison.Ordinal))
60 {
61 mods.Add("Alt-");
62 inner = inner[4..];
63 continue;
64 }
65
66 if (inner.StartsWith("C-", StringComparison.Ordinal))
67 {
68 mods.Add("C-");
69 inner = inner[2..];
70 continue;
71 }
72
73 if (inner.StartsWith("M-", StringComparison.Ordinal))
74 {
75 mods.Add("M-");
76 inner = inner[2..];
77 continue;
78 }
79
80 if (inner.StartsWith("A-", StringComparison.Ordinal))
81 {
82 mods.Add("A-");
83 inner = inner[2..];
84 continue;
85 }
86
87 if (inner.StartsWith("S-", StringComparison.Ordinal))
88 {
89 mods.Add("S-");
90 inner = inner[2..];
91 continue;
92 }
93
94 if (inner.StartsWith("D-", StringComparison.Ordinal))
95 {
96 mods.Add("D-");
97 inner = inner[2..];
98 continue;
99 }
100
101 break;
102 }
103
104 var key = inner.ToString();
105 if (key.Length == 0)
106 throw new InvalidOperationException("Chord notation: пустой key внутри <…> (грамматика должна была отсечь).");
107
108 return new ChordNotationChordStep(mods, key);
109 }
110
111 private static string FormatGrammarError(GrammarMatch gm, string text)
112 {
113 var idx = gm.ErrorIndex >= 0 ? gm.ErrorIndex : 0;
114 var tail = idx < text.Length ? text[idx..] : "";
115 tail = tail.Length > 24 ? tail[..24] + "…" : tail;
116 return $"Chord notation: разбор остановился на позиции {idx} (остаток: «{tail}»).";
117 }
118}
119
120/// <param name="IsSuccess">Успех полного разбора.</param>
121/// <param name="Steps">Шаги последовательности (после успеха).</param>
122/// <param name="Error">Сообщение при неуспехе.</param>
123public readonly record struct ChordNotationParseResult(bool IsSuccess, IReadOnlyList<ChordNotationStep> Steps, string Error)
124{
125 public static ChordNotationParseResult Ok(IReadOnlyList<ChordNotationStep> steps) =>
126 new(true, steps, "");
127
128 public static ChordNotationParseResult Fail(string message) =>
129 new(false, Array.Empty<ChordNotationStep>(), message);
130}
131
132public abstract record ChordNotationStep;
133
134/// <summary>Совпадающее нажатие: <c>&lt;C-k&gt;</c>, <c>&lt;C-M-n&gt;</c>.</summary>
135public sealed record ChordNotationChordStep(IReadOnlyList<string> ModifierPrefixes, string Key) : ChordNotationStep;
136
137/// <summary>Токен без скобок: <c>m</c>, <c>Esc</c>, <c>L1</c>.</summary>
138public sealed record ChordNotationPlainStep(string Token) : ChordNotationStep;
139
View only · write via MCP/CIDE