This commit is contained in:
DismissedLight
2025-01-21 22:10:50 +08:00
parent d4aae4508a
commit 45f55f94cd
15 changed files with 217 additions and 58 deletions

1
.gitignore vendored
View File

@@ -29,3 +29,4 @@ node_modules/
# Rider
.idea
_ReSharper.Caches/

View File

@@ -12,34 +12,41 @@ public sealed class ScriptEmitTest
new LineBreakTriviaSymbol(),
new AvatarInstructionListSymbol(new("钟离"), [new SpaceTriviaSymbol()], new(
[
new WalkSymbol(WalkDirection.Backward, [new DoubleSymbol(0.1)], [new CommaTriviaSymbol()]),
new SkillSymbol(true, [new HoldSymbol()], [new CommaTriviaSymbol()]),
new WaitSymbol([new DoubleSymbol(0.3)], [new CommaTriviaSymbol()]),
new WalkSymbol(WalkDirection.Forward, [new DoubleSymbol(0.1)], []),
new WalkSymbol(WalkDirection.Backward, [new DoubleSymbol(0.1)], [], new CommaTriviaSymbol()),
new SkillSymbol(true, [new HoldSymbol()], [new SpaceTriviaSymbol()], new CommaTriviaSymbol()),
new WaitSymbol([new DoubleSymbol(0.3)], [new SpaceTriviaSymbol()], new CommaTriviaSymbol()),
new WalkSymbol(WalkDirection.Forward, [new DoubleSymbol(0.1)], [new SpaceTriviaSymbol()], default),
])),
new LineBreakTriviaSymbol(),
new AvatarInstructionListSymbol(new("芙宁娜"), [new SpaceTriviaSymbol()], new(
[
new SkillSymbol(true, [new CommaTriviaSymbol()]),
new BurstSymbol(true, [])
new SkillSymbol(true, [], new CommaTriviaSymbol()),
new BurstSymbol(true, [new SpaceTriviaSymbol()], default)
])),
new LineBreakTriviaSymbol(),
new AvatarInstructionListSymbol(new("行秋"), [new SpaceTriviaSymbol()], new(
[
new SkillSymbol(true, [new CommaTriviaSymbol()]),
new BurstSymbol(true, [new CommaTriviaSymbol()]),
new SkillSymbol(true, []),
new SkillSymbol(true, [], new CommaTriviaSymbol()),
new BurstSymbol(true, [new SpaceTriviaSymbol()], new CommaTriviaSymbol()),
new SkillSymbol(true, [new SpaceTriviaSymbol()], default),
])),
]);
Console.WriteLine(scriptUnit.Emit(new DefaultSymbolEmitter()));
Console.WriteLine(scriptUnit.Emit(new SymbolEmitter()));
}
[TestMethod]
public void Test()
public void TestParse()
{
ReadOnlySpan<char> raw = "ABCDEF;GHIJKL\r\nMNOPQR\nSTUVWX\rYZ\r\n";
SymbolParser parser = new();
ScriptUnit scriptUnit = parser.Parse(raw);
ReadOnlySpan<char> raw = """
// 测试注释
s(0.1),e(hold),wait(0.3),w(0.1)
e,q
e,q,e
""";
ScriptUnit scriptUnit = SymbolParser.Parse(raw);
Console.WriteLine(scriptUnit.Emit(new SymbolEmitter()));
}
}

View File

@@ -5,8 +5,8 @@ namespace BetterGenshinImpact.CombatScript;
public class AttackSymbol : InstructionSymbol, IInstructionSymbolHasDuration
{
public AttackSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
: base("attack", parameterList, trivia)
public AttackSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("attack", parameterList, leadingTriviaList, tailingTrivia)
{
InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList);
InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0, 1]);
@@ -20,8 +20,8 @@ public class AttackSymbol : InstructionSymbol, IInstructionSymbolHasDuration
}
}
public AttackSymbol(ImmutableArray<TriviaSymbol> trivia)
: base("attack", trivia)
public AttackSymbol(ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("attack", leadingTriviaList, tailingTrivia)
{
}

View File

@@ -4,8 +4,8 @@ namespace BetterGenshinImpact.CombatScript;
public class BurstSymbol : InstructionSymbol, IInstructionSymbolHasAlias
{
public BurstSymbol(bool isAlias, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
: base("burst", parameterList, trivia)
public BurstSymbol(bool isAlias, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("burst", parameterList, leadingTriviaList, tailingTrivia)
{
InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList);
InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0]);
@@ -13,8 +13,8 @@ public class BurstSymbol : InstructionSymbol, IInstructionSymbolHasAlias
IsAlias = isAlias;
}
public BurstSymbol(bool isAlias, ImmutableArray<TriviaSymbol> trivia)
: base("burst", trivia)
public BurstSymbol(bool isAlias, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("burst", leadingTriviaList, tailingTrivia)
{
IsAlias = isAlias;
}

View File

@@ -5,8 +5,8 @@ namespace BetterGenshinImpact.CombatScript;
public class ChargeSymbol : InstructionSymbol, IInstructionSymbolHasDuration
{
public ChargeSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
: base("charge", parameterList, trivia)
public ChargeSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("charge", parameterList, leadingTriviaList, tailingTrivia)
{
InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList);
InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0, 1]);
@@ -20,8 +20,8 @@ public class ChargeSymbol : InstructionSymbol, IInstructionSymbolHasDuration
}
}
public ChargeSymbol(ImmutableArray<TriviaSymbol> trivia)
: base("charge", trivia)
public ChargeSymbol(ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("charge", leadingTriviaList, tailingTrivia)
{
}

View File

@@ -5,8 +5,8 @@ namespace BetterGenshinImpact.CombatScript;
public class DashSymbol : InstructionSymbol, IInstructionSymbolHasDuration
{
public DashSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
: base("dash", parameterList, trivia)
public DashSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("dash", parameterList, leadingTriviaList, tailingTrivia)
{
InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList);
InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0, 1]);
@@ -20,8 +20,8 @@ public class DashSymbol : InstructionSymbol, IInstructionSymbolHasDuration
}
}
public DashSymbol(ImmutableArray<TriviaSymbol> trivia)
: base("dash", trivia)
public DashSymbol(ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("dash", leadingTriviaList, tailingTrivia)
{
}

View File

@@ -6,6 +6,8 @@ public interface ISymbolEmitter
ISymbolEmitter Append(char value);
ISymbolEmitter Append(char value, int repeatCount);
ISymbolEmitter Append(double value);
ISymbolEmitter Append(string value);

View File

@@ -4,19 +4,21 @@ namespace BetterGenshinImpact.CombatScript;
public abstract class InstructionSymbol : BaseSymbol
{
protected InstructionSymbol(string name, ImmutableArray<TriviaSymbol> trivia)
protected InstructionSymbol(string name, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
{
Name = name;
HasParameterList = false;
TriviaList = trivia;
LeadingTriviaList = leadingTriviaList;
TailingTrivia = tailingTrivia;
}
protected InstructionSymbol(string name, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
protected InstructionSymbol(string name, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
{
Name = name;
HasParameterList = true;
ParameterList = parameterList;
TriviaList = trivia;
LeadingTriviaList = leadingTriviaList;
TailingTrivia = tailingTrivia;
}
public string Name { get; }
@@ -25,10 +27,14 @@ public abstract class InstructionSymbol : BaseSymbol
public ImmutableArray<IParameterSymbol> ParameterList { get; }
public ImmutableArray<TriviaSymbol> TriviaList { get; }
public ImmutableArray<TriviaSymbol> LeadingTriviaList { get; }
public TriviaSymbol? TailingTrivia { get; set; }
public override void Emit(ISymbolEmitter emitter)
{
emitter.Append(LeadingTriviaList);
if (this is IInstructionSymbolHasAlias {IsAlias: true } hasAlias)
{
emitter.Append(hasAlias.AliasName);
@@ -45,6 +51,9 @@ public abstract class InstructionSymbol : BaseSymbol
emitter.Append(')');
}
emitter.Append(TriviaList);
if (TailingTrivia is not null)
{
emitter.Append(TailingTrivia);
}
}
}

View File

@@ -4,8 +4,8 @@ namespace BetterGenshinImpact.CombatScript;
public class JumpSymbol : InstructionSymbol, IInstructionSymbolHasAlias
{
public JumpSymbol(bool isAlias, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
: base("jump", parameterList, trivia)
public JumpSymbol(bool isAlias, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("jump", parameterList, leadingTriviaList, tailingTrivia)
{
InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList);
InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0]);
@@ -13,8 +13,8 @@ public class JumpSymbol : InstructionSymbol, IInstructionSymbolHasAlias
IsAlias = isAlias;
}
public JumpSymbol(bool isAlias, ImmutableArray<TriviaSymbol> trivia)
: base("jump", trivia)
public JumpSymbol(bool isAlias, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("jump", leadingTriviaList, tailingTrivia)
{
IsAlias = isAlias;
}

View File

@@ -4,8 +4,8 @@ namespace BetterGenshinImpact.CombatScript;
public class SkillSymbol : InstructionSymbol, IInstructionSymbolHasAlias
{
public SkillSymbol(bool isAlias, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
: base("skill", parameterList, trivia)
public SkillSymbol(bool isAlias, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("skill", parameterList, leadingTriviaList, tailingTrivia)
{
InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList);
InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0, 1]);
@@ -18,8 +18,8 @@ public class SkillSymbol : InstructionSymbol, IInstructionSymbolHasAlias
IsAlias = isAlias;
}
public SkillSymbol(bool isAlias, ImmutableArray<TriviaSymbol> trivia)
: base("skill", trivia)
public SkillSymbol(bool isAlias, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("skill", leadingTriviaList, tailingTrivia)
{
IsAlias = isAlias;
}

View File

@@ -2,8 +2,20 @@
public class SpaceTriviaSymbol : TriviaSymbol
{
private readonly int count;
public SpaceTriviaSymbol()
{
count = 1;
}
public SpaceTriviaSymbol(int count)
{
this.count = count;
}
public override void Emit(ISymbolEmitter emitter)
{
emitter.Append(' ');
emitter.Append(' ', count);
}
}

View File

@@ -2,7 +2,7 @@
namespace BetterGenshinImpact.CombatScript;
public sealed class DefaultSymbolEmitter : ISymbolEmitter
public sealed class SymbolEmitter : ISymbolEmitter
{
private readonly StringBuilder builder = new();
@@ -16,6 +16,12 @@ public sealed class DefaultSymbolEmitter : ISymbolEmitter
builder.Append(value);
return this;
}
public ISymbolEmitter Append(char value, int repeatCount)
{
builder.Append(value, repeatCount);
return this;
}
public ISymbolEmitter Append(double value)
{

View File

@@ -4,16 +4,16 @@ using System.Runtime.CompilerServices;
namespace BetterGenshinImpact.CombatScript;
public sealed class SymbolParser
public static class SymbolParser
{
public ScriptUnit Parse(ReadOnlySpan<char> raw)
public static ScriptUnit Parse(ReadOnlySpan<char> raw)
{
ImmutableArray<ISymbol>.Builder symbols = ImmutableArray.CreateBuilder<ISymbol>();
ParseLines(raw, symbols);
return new(symbols.ToImmutable());
}
private void ParseLines(ReadOnlySpan<char> raw, ImmutableArray<ISymbol>.Builder symbols)
private static void ParseLines(ReadOnlySpan<char> raw, ImmutableArray<ISymbol>.Builder symbols)
{
bool skipNextRange = false;
ref readonly char end = ref raw[^1];
@@ -63,9 +63,131 @@ public sealed class SymbolParser
throw new InvalidOperationException($"Failed to parse line break trivia at {range}.");
}
ReadOnlySpan<char> currentSpan = raw[range];
if (ParseLine(raw[range]) is { } symbol)
{
symbols.Add(symbol);
}
symbols.Add(lineBreakTrivia);
}
}
private static ISymbol? ParseLine(ReadOnlySpan<char> raw)
{
if (raw.IsEmpty)
{
return default;
}
if (raw.StartsWith("//"))
{
return new CommentSymbol(raw[2..].ToString());
}
int indexOfSpace = raw.IndexOf(' ');
ReadOnlySpan<char> avatarIdentifier = raw[..indexOfSpace];
AvatarSymbol avatarSymbol = new(avatarIdentifier.ToString());
ImmutableArray<TriviaSymbol> triviaList = ParseTriviaList(raw[indexOfSpace..], out int advanced);
InstructionListSymbol listSymbol = ParseInstructionList(raw[(indexOfSpace + advanced)..]);
return new AvatarInstructionListSymbol(avatarSymbol, triviaList, listSymbol);
}
private static InstructionListSymbol ParseInstructionList(ReadOnlySpan<char> raw)
{
ImmutableArray<InstructionSymbol>.Builder builder = ImmutableArray.CreateBuilder<InstructionSymbol>();
foreach (Range range in raw.Split(','))
{
ReadOnlySpan<char> current = raw[range];
ImmutableArray<TriviaSymbol> leadingTriviaList = ParseTriviaList(current, out int advanced);
if (ParseInstruction(current[advanced..], leadingTriviaList) is { } symbol)
{
builder.Add(symbol);
}
}
return new(builder.ToImmutable());
}
private static InstructionSymbol? ParseInstruction(ReadOnlySpan<char> raw, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
{
if (raw.IsEmpty)
{
return default;
}
switch (raw[0])
{
// attack|a
case 'a':
break;
// burst
case 'b':
return new BurstSymbol(false, [], leadingTriviaList, tailingTrivia);
break;
// charge
case 'c':
break;
// dash|d
case 'd':
break;
// jump|j
case 'j':
break;
// q
case 'q':
return new BurstSymbol(true, [], leadingTriviaList, tailingTrivia);
break;
// skill|s
case 's':
break;
// wait|walk|w
case 'w':
break;
}
return default;
}
// Handle ','&' '
private static ImmutableArray<TriviaSymbol> ParseTriviaList(ReadOnlySpan<char> raw, out int advanced)
{
advanced = 0;
if (raw.IsEmpty)
{
return ImmutableArray<TriviaSymbol>.Empty;
}
ImmutableArray<TriviaSymbol>.Builder builder = ImmutableArray.CreateBuilder<TriviaSymbol>();
int spaceCount = 0;
foreach (ref readonly char value in raw)
{
advanced++;
if (value is ' ')
{
spaceCount++;
}
else
{
if (spaceCount is not 0)
{
builder.Add(new SpaceTriviaSymbol(spaceCount));
spaceCount = 0;
}
if (value is ',')
{
builder.Add(new CommaTriviaSymbol());
}
else
{
break;
}
}
}
return builder.ToImmutable();
}
}

View File

@@ -5,8 +5,8 @@ namespace BetterGenshinImpact.CombatScript;
public class WaitSymbol : InstructionSymbol, IInstructionSymbolHasDuration
{
public WaitSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
: base("wait", parameterList, trivia)
public WaitSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("wait", parameterList, leadingTriviaList, tailingTrivia)
{
InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList);
InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [1]);

View File

@@ -5,8 +5,8 @@ namespace BetterGenshinImpact.CombatScript;
public class WalkSymbol : InstructionSymbol, IInstructionSymbolHasAlias, IInstructionSymbolHasDuration, IParameterSymbol
{
public WalkSymbol(WalkDirection direction, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
: base("walk", parameterList, trivia)
public WalkSymbol(WalkDirection direction, ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("walk", parameterList, leadingTriviaList, tailingTrivia)
{
InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList);
InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [1]);
@@ -22,15 +22,15 @@ public class WalkSymbol : InstructionSymbol, IInstructionSymbolHasAlias, IInstru
}
// Used for parameter
public WalkSymbol(WalkDirection direction, ImmutableArray<TriviaSymbol> trivia)
: base("walk", trivia)
public WalkSymbol(WalkDirection direction, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("walk", leadingTriviaList, tailingTrivia)
{
Direction = direction;
IsAlias = true;
}
public WalkSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> trivia)
: base("walk", parameterList, trivia)
public WalkSymbol(ImmutableArray<IParameterSymbol> parameterList, ImmutableArray<TriviaSymbol> leadingTriviaList, TriviaSymbol? tailingTrivia)
: base("walk", parameterList, leadingTriviaList, tailingTrivia)
{
InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList);
InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [2]);