Skip to content

Commit 5696f3a

Browse files
committed
[+] Preprocess tryFix修复一些常见错误
1 parent 3b92542 commit 5696f3a

6 files changed

Lines changed: 299 additions & 27 deletions

File tree

i18n/Locale.Designer.cs

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

i18n/Locale.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,13 @@
151151
<data name="NoteTooNear" xml:space="preserve">
152152
<value>This note is closer than a 384th note to the previous note. MA2 can only represent timing down to 1/384 of a whole note, so these two notes cannot be represented exactly and may be emitted as a yellow EACH pair. If you want a "fake EACH", use syntax such as "1`2", or offset the notes by at least one 384th note.</value>
153153
</data>
154+
<data name="PrepFix1" xml:space="preserve">
155+
<value>TODO</value>
156+
</data>
157+
<data name="PrepFix2" xml:space="preserve">
158+
<value>TODO</value>
159+
</data>
160+
<data name="PrepFix3" xml:space="preserve">
161+
<value>TODO</value>
162+
</data>
154163
</root>

i18n/Locale.zh-hans.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,13 @@
151151
<data name="NoteTooNear" xml:space="preserve">
152152
<value>此音符与前一个音符的相距时间小于384分音!受MA2语法限制,MA2所能表示的时间精度仅能达到1/384,因此您的这两个音符无法被精确表示、并且可能被生成为黄色双押。如果您需要的是“伪双押”,请使用"1`2"这种语法来表示伪双押,或确保错开一个384分音的时间。</value>
153153
</data>
154+
<data name="PrepFix1" xml:space="preserve">
155+
<value>尝试修复了谱面中的{0}处语法错误(补上了可能缺失的逗号): {1}</value>
156+
</data>
157+
<data name="PrepFix2" xml:space="preserve">
158+
<value>尝试修复了谱面中的{0}处语法错误(持续时间标记中误用了"-",而不是":"): {1}</value>
159+
</data>
160+
<data name="PrepFix3" xml:space="preserve">
161+
<value>尝试修复了谱面中的{0}处语法错误(对星星头的修饰符,应该出现在键位号后、星星类型标记之前): {1}</value>
162+
</data>
154163
</root>

parser/simai/Simai.g4

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,8 @@ modifiers: (MODIFIER | TAP_TO_STAR)*;
3838

3939
chart: (notations ',')* CHART_END? EOF;
4040

41-
notations // 同一时刻的所有标记,包括note标记、bpm标记等等
42-
: (bpmTag
43-
| absulouteStepTag
44-
| metTag
45-
| noteGroup
46-
)*;
41+
// 同一时刻的所有标记,包括note标记、bpm标记等等
42+
notations: (bpmTag | absulouteStepTag | metTag)* noteGroup*;
4743

4844
noteGroup: note (eachNote | falseEachNote)*;
4945

parser/simai/SimaiParser.cs

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@
99

1010
namespace MuConvert.parser;
1111

12-
public class SimaiParser : SimaiBaseVisitor<object>, IParser
12+
public partial class SimaiParser : SimaiBaseVisitor<object>, IParser
1313
{
1414
private readonly Chart chart;
1515
private readonly List<Alert> alerts = [];
16+
public bool DontTryFix = false; // 不准调用Preprocess中的tryFix,而是如遇问题直接报错。
1617

1718
private Rational now = 0;
1819
private Rational step = new(1, 4);
19-
private decimal? absoluteTimeStep = null; // 此项必须和step本体一起更改
20+
private decimal? absoluteTimeStep; // 此项必须和step本体一起更改
2021

2122
private ParserRuleContext? currContext; // 供调试报错AddAlert函数使用
2223
private Note? currNote; // 用于在部分visitor之间传递额外的参数,如visitDuration、visitSlideBody等,都需要Note对象作为参数传入的情况
2324
private readonly List<string> extraModifiers = [];
24-
private bool absoluteTimeStepWarned = false; // 用于确保Warning只打印一次
25+
private bool absoluteTimeStepWarned; // 用于确保Warning只打印一次
2526

2627
public SimaiParser(bool bigTouch = false, bool isUtage = false, int clockCount = 4)
2728
{
@@ -45,25 +46,55 @@ public class AntlrErrorListener(SimaiParser parser) : IAntlrErrorListener<IToken
4546
// 这个是给 Parser 用的 (IToken)
4647
public void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
4748
{
48-
parser.alerts.Add(new Alert(Error, Locale.SimaiGrammarFailed + msg, line: line, relevantNote: e.Context?.GetText()));
49+
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract 这是ANTLR的bug,e可以是空的但却被标记了非空类型
50+
parser.alerts.Add(new Alert(Error, Locale.SimaiGrammarFailed + msg, line: line, relevantNote: e?.Context?.GetText()??offendingSymbol.Text));
4951
throw new ConversionException(parser.alerts, e);
5052
}
5153

5254
// 这个是给 Lexer 用的 (int)
5355
public void SyntaxError(TextWriter output, IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
5456
{
55-
parser.alerts.Add(new Alert(Error, Locale.SimaiGrammarFailed + msg, line: line, relevantNote: e.Context?.GetText()));
57+
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract 这是ANTLR的bug,e可以是空的但却被标记了非空类型
58+
parser.alerts.Add(new Alert(Error, Locale.SimaiGrammarFailed + msg, line: line, relevantNote: e?.Context?.GetText()));
5659
throw new ConversionException(parser.alerts, e);
5760
}
5861
}
5962

60-
public string Preprocess(string text)
63+
private string _replaceAndRecord(Match m, string repl, List<string> msgArr)
64+
{
65+
var res = m.Result(repl);
66+
msgArr.Add($"{m.Value}{res}");
67+
return res;
68+
}
69+
70+
private void _warnPrepfixMsg(string messageTemplate, List<string> msgArr)
71+
{
72+
if (msgArr.Count == 0) return;
73+
alerts.Add(new Alert(Warning, string.Format(messageTemplate, msgArr.Count, string.Join("; ", msgArr))));
74+
}
75+
76+
[GeneratedRegex(@"(\d)([{(])")]
77+
private static partial Regex PrepFix1(); // 补上可能缺失的逗号
78+
79+
[GeneratedRegex(@"\[(\d+)-(\d+)\]")]
80+
private static partial Regex PrepFix2(); // 错误的Duration语法
81+
82+
public string Preprocess(string text, bool tryFix = false)
6183
{
6284
// 移除注释
6385
var commentRegex = new Regex(@"((?<!\[[^\]]*|\{[^\}]*)#|\|\|).*$", RegexOptions.Multiline);
6486
text = commentRegex.Replace(text, "");
65-
66-
// TODO 应用更多的纠错修改(从MCM现有代码里面抄)
87+
88+
if (tryFix)
89+
{
90+
List<string> prepFix1Msgs = [];
91+
text = PrepFix1().Replace(text, m => _replaceAndRecord(m, "$1,$2", prepFix1Msgs));
92+
_warnPrepfixMsg(Locale.PrepFix1, prepFix1Msgs);
93+
94+
List<string> prepFix2Msgs = [];
95+
text = PrepFix2().Replace(text, m => _replaceAndRecord(m, "[$1:$2]", prepFix2Msgs));
96+
_warnPrepfixMsg(Locale.PrepFix2, prepFix2Msgs);
97+
}
6798

6899
return text;
69100
}
@@ -76,19 +107,17 @@ public string Preprocess(string text)
76107
try
77108
{
78109
text = Preprocess(text);
79-
var inputStream = new AntlrInputStream(text);
80-
81-
var errorListener = new AntlrErrorListener(this);
82-
var lexer = new SimaiLexer(inputStream);
83-
lexer.RemoveErrorListeners();
84-
lexer.AddErrorListener(errorListener);
85-
var tokens = new CommonTokenStream(lexer);
86-
87-
var parser = new P(tokens); // MuConvert.Antlr.SimaiParser
88-
parser.RemoveErrorListeners();
89-
parser.AddErrorListener(errorListener);
90-
root = parser.chart();
91-
110+
try
111+
{
112+
root = RunAntlr(text); // 普通预处理下,antlr词语法分析一次(仅去除注释)
113+
}
114+
catch (ConversionException)
115+
{
116+
if (DontTryFix) throw;
117+
alerts.Clear();
118+
root = RunAntlr(Preprocess(text, true)); // 尝试应用tryFix之后再来一次
119+
}
120+
// 如果成功,则root就是语法分析树的根节点。从这里开始遍历语法分析树完成工作。
92121
VisitChart(root);
93122
}
94123
catch (ConversionException)
@@ -105,6 +134,24 @@ public string Preprocess(string text)
105134
return (chart, alerts);
106135
}
107136

137+
private P.ChartContext RunAntlr(string text)
138+
{
139+
var inputStream = new AntlrInputStream(text);
140+
var errorListener = new AntlrErrorListener(this);
141+
142+
var lexer = new SimaiLexer(inputStream);
143+
lexer.RemoveErrorListeners();
144+
lexer.AddErrorListener(errorListener);
145+
var tokens = new CommonTokenStream(lexer);
146+
147+
var parser = new P(tokens); // MuConvert.Antlr.SimaiParser
148+
parser.RemoveErrorListeners();
149+
parser.AddErrorListener(errorListener);
150+
var root = parser.chart();
151+
152+
return root;
153+
}
154+
108155
public sealed override object VisitChart(P.ChartContext context)
109156
{
110157
foreach (var notations in context.notations())

0 commit comments

Comments
 (0)