From d4aae4508a8b407fb95a7251351e277fc464f330 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Tue, 21 Jan 2025 17:31:09 +0800 Subject: [PATCH] init test --- ...tterGenshinImpact.CombatScript.Test.csproj | 19 +++++ .../ScriptEmitTest.cs | 45 ++++++++++++ .../AttackSymbol.cs | 31 ++++++++ .../AvatarInstructionListSymbol.cs | 24 +++++++ .../AvatarSymbol.cs | 16 +++++ .../BaseSymbol.cs | 6 ++ .../BetterGenshinImpact.CombatScript.csproj | 9 +++ .../BurstSymbol.cs | 25 +++++++ .../ChargeSymbol.cs | 31 ++++++++ .../CommaTriviaSymbol.cs | 17 +++++ .../CommentSymbol.cs | 16 +++++ .../DashSymbol.cs | 31 ++++++++ .../DefaultSymbolEmitter.cs | 31 ++++++++ .../DoubleSymbol.cs | 16 +++++ .../HoldSymbol.cs | 9 +++ .../IInstructionSymbolHasAlias.cs | 8 +++ .../IInstructionSymbolHasDuration.cs | 10 +++ .../IParameterSymbol.cs | 5 ++ BetterGenshinImpact.CombatScript/ISymbol.cs | 6 ++ .../ISymbolEmitter.cs | 12 ++++ .../InstructionListSymbol.cs | 18 +++++ .../InstructionSymbol.cs | 50 +++++++++++++ .../InstructionThrowHelper.cs | 35 +++++++++ .../JumpSymbol.cs | 25 +++++++ .../LineBreakTriviaSymbol.cs | 28 ++++++++ .../ScriptUnit.cs | 23 ++++++ .../SkillSymbol.cs | 30 ++++++++ .../SpaceTriviaSymbol.cs | 9 +++ .../SymbolEmitterExtensions.cs | 23 ++++++ .../SymbolParser.cs | 71 +++++++++++++++++++ .../TriviaSymbol.cs | 5 ++ .../WaitSymbol.cs | 23 ++++++ .../WalkDirection.cs | 9 +++ .../WalkSymbol.cs | 70 ++++++++++++++++++ BetterGenshinImpact.sln | 20 ++++++ 35 files changed, 806 insertions(+) create mode 100644 BetterGenshinImpact.CombatScript.Test/BetterGenshinImpact.CombatScript.Test.csproj create mode 100644 BetterGenshinImpact.CombatScript.Test/ScriptEmitTest.cs create mode 100644 BetterGenshinImpact.CombatScript/AttackSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/AvatarInstructionListSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/AvatarSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/BaseSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/BetterGenshinImpact.CombatScript.csproj create mode 100644 BetterGenshinImpact.CombatScript/BurstSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/ChargeSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/CommaTriviaSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/CommentSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/DashSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/DefaultSymbolEmitter.cs create mode 100644 BetterGenshinImpact.CombatScript/DoubleSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/HoldSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/IInstructionSymbolHasAlias.cs create mode 100644 BetterGenshinImpact.CombatScript/IInstructionSymbolHasDuration.cs create mode 100644 BetterGenshinImpact.CombatScript/IParameterSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/ISymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/ISymbolEmitter.cs create mode 100644 BetterGenshinImpact.CombatScript/InstructionListSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/InstructionSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/InstructionThrowHelper.cs create mode 100644 BetterGenshinImpact.CombatScript/JumpSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/LineBreakTriviaSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/ScriptUnit.cs create mode 100644 BetterGenshinImpact.CombatScript/SkillSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/SpaceTriviaSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/SymbolEmitterExtensions.cs create mode 100644 BetterGenshinImpact.CombatScript/SymbolParser.cs create mode 100644 BetterGenshinImpact.CombatScript/TriviaSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/WaitSymbol.cs create mode 100644 BetterGenshinImpact.CombatScript/WalkDirection.cs create mode 100644 BetterGenshinImpact.CombatScript/WalkSymbol.cs diff --git a/BetterGenshinImpact.CombatScript.Test/BetterGenshinImpact.CombatScript.Test.csproj b/BetterGenshinImpact.CombatScript.Test/BetterGenshinImpact.CombatScript.Test.csproj new file mode 100644 index 00000000..91168046 --- /dev/null +++ b/BetterGenshinImpact.CombatScript.Test/BetterGenshinImpact.CombatScript.Test.csproj @@ -0,0 +1,19 @@ + + + + net9.0 + latest + enable + enable + true + + + + + + + + + + + diff --git a/BetterGenshinImpact.CombatScript.Test/ScriptEmitTest.cs b/BetterGenshinImpact.CombatScript.Test/ScriptEmitTest.cs new file mode 100644 index 00000000..21821434 --- /dev/null +++ b/BetterGenshinImpact.CombatScript.Test/ScriptEmitTest.cs @@ -0,0 +1,45 @@ +namespace BetterGenshinImpact.CombatScript.Test; + +[TestClass] +public sealed class ScriptEmitTest +{ + [TestMethod] + public void TestEmit() + { + ScriptUnit scriptUnit = new( + [ + new CommentSymbol("测试注释"), + 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 LineBreakTriviaSymbol(), + new AvatarInstructionListSymbol(new("芙宁娜"), [new SpaceTriviaSymbol()], new( + [ + new SkillSymbol(true, [new CommaTriviaSymbol()]), + new BurstSymbol(true, []) + ])), + new LineBreakTriviaSymbol(), + new AvatarInstructionListSymbol(new("行秋"), [new SpaceTriviaSymbol()], new( + [ + new SkillSymbol(true, [new CommaTriviaSymbol()]), + new BurstSymbol(true, [new CommaTriviaSymbol()]), + new SkillSymbol(true, []), + ])), + ]); + + Console.WriteLine(scriptUnit.Emit(new DefaultSymbolEmitter())); + } + + [TestMethod] + public void Test() + { + ReadOnlySpan raw = "ABCDEF;GHIJKL\r\nMNOPQR\nSTUVWX\rYZ\r\n"; + SymbolParser parser = new(); + ScriptUnit scriptUnit = parser.Parse(raw); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/AttackSymbol.cs b/BetterGenshinImpact.CombatScript/AttackSymbol.cs new file mode 100644 index 00000000..115022e9 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/AttackSymbol.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public class AttackSymbol : InstructionSymbol, IInstructionSymbolHasDuration +{ + public AttackSymbol(ImmutableArray parameterList, ImmutableArray trivia) + : base("attack", parameterList, trivia) + { + InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList); + InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0, 1]); + + if (parameterList.Length is 1) + { + InstructionThrowHelper.ThrowIfParameterAtIndexIsNot(parameterList, 0, out DoubleSymbol doubleSymbol); + + HasDuration = true; + Duration = TimeSpan.FromSeconds(doubleSymbol.Value); + } + } + + public AttackSymbol(ImmutableArray trivia) + : base("attack", trivia) + { + } + + public bool HasDuration { get; } + + public TimeSpan Duration { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/AvatarInstructionListSymbol.cs b/BetterGenshinImpact.CombatScript/AvatarInstructionListSymbol.cs new file mode 100644 index 00000000..a4882178 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/AvatarInstructionListSymbol.cs @@ -0,0 +1,24 @@ +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public sealed class AvatarInstructionListSymbol : BaseSymbol +{ + public AvatarInstructionListSymbol(AvatarSymbol avatar, ImmutableArray triviaList, InstructionListSymbol instructionList) + { + Avatar = avatar; + TriviaList = triviaList; + InstructionList = instructionList; + } + + public AvatarSymbol Avatar { get; } + + public ImmutableArray TriviaList { get; } + + public InstructionListSymbol InstructionList { get; } + + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append(Avatar).Append(TriviaList).Append(InstructionList); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/AvatarSymbol.cs b/BetterGenshinImpact.CombatScript/AvatarSymbol.cs new file mode 100644 index 00000000..031c3952 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/AvatarSymbol.cs @@ -0,0 +1,16 @@ +namespace BetterGenshinImpact.CombatScript; + +public sealed class AvatarSymbol : BaseSymbol +{ + public AvatarSymbol(string name) + { + Name = name; + } + + public string Name { get; } + + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append(Name); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/BaseSymbol.cs b/BetterGenshinImpact.CombatScript/BaseSymbol.cs new file mode 100644 index 00000000..456b4cc7 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/BaseSymbol.cs @@ -0,0 +1,6 @@ +namespace BetterGenshinImpact.CombatScript; + +public abstract class BaseSymbol : ISymbol +{ + public abstract void Emit(ISymbolEmitter emitter); +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/BetterGenshinImpact.CombatScript.csproj b/BetterGenshinImpact.CombatScript/BetterGenshinImpact.CombatScript.csproj new file mode 100644 index 00000000..93bf97d1 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/BetterGenshinImpact.CombatScript.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + disable + enable + + + diff --git a/BetterGenshinImpact.CombatScript/BurstSymbol.cs b/BetterGenshinImpact.CombatScript/BurstSymbol.cs new file mode 100644 index 00000000..9843d244 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/BurstSymbol.cs @@ -0,0 +1,25 @@ +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public class BurstSymbol : InstructionSymbol, IInstructionSymbolHasAlias +{ + public BurstSymbol(bool isAlias, ImmutableArray parameterList, ImmutableArray trivia) + : base("burst", parameterList, trivia) + { + InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList); + InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0]); + + IsAlias = isAlias; + } + + public BurstSymbol(bool isAlias, ImmutableArray trivia) + : base("burst", trivia) + { + IsAlias = isAlias; + } + + public string AliasName { get; } = "q"; + + public bool IsAlias { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/ChargeSymbol.cs b/BetterGenshinImpact.CombatScript/ChargeSymbol.cs new file mode 100644 index 00000000..b30611c2 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/ChargeSymbol.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public class ChargeSymbol : InstructionSymbol, IInstructionSymbolHasDuration +{ + public ChargeSymbol(ImmutableArray parameterList, ImmutableArray trivia) + : base("charge", parameterList, trivia) + { + InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList); + InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0, 1]); + + if (parameterList.Length is 1) + { + InstructionThrowHelper.ThrowIfParameterAtIndexIsNot(parameterList, 0, out DoubleSymbol doubleSymbol); + + HasDuration = true; + Duration = TimeSpan.FromSeconds(doubleSymbol.Value); + } + } + + public ChargeSymbol(ImmutableArray trivia) + : base("charge", trivia) + { + } + + public bool HasDuration { get; } + + public TimeSpan Duration { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/CommaTriviaSymbol.cs b/BetterGenshinImpact.CombatScript/CommaTriviaSymbol.cs new file mode 100644 index 00000000..6ad905b4 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/CommaTriviaSymbol.cs @@ -0,0 +1,17 @@ +namespace BetterGenshinImpact.CombatScript; + +public sealed class CommaTriviaSymbol : TriviaSymbol +{ + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append(','); + } +} + +public sealed class SemicolonTriviaSymbol : TriviaSymbol +{ + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append(';'); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/CommentSymbol.cs b/BetterGenshinImpact.CombatScript/CommentSymbol.cs new file mode 100644 index 00000000..a0eada04 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/CommentSymbol.cs @@ -0,0 +1,16 @@ +namespace BetterGenshinImpact.CombatScript; + +public sealed class CommentSymbol : TriviaSymbol +{ + public CommentSymbol(string comment) + { + Comment = comment; + } + + public string Comment { get; } + + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append("//").Append(Comment); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/DashSymbol.cs b/BetterGenshinImpact.CombatScript/DashSymbol.cs new file mode 100644 index 00000000..c8f8549f --- /dev/null +++ b/BetterGenshinImpact.CombatScript/DashSymbol.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public class DashSymbol : InstructionSymbol, IInstructionSymbolHasDuration +{ + public DashSymbol(ImmutableArray parameterList, ImmutableArray trivia) + : base("dash", parameterList, trivia) + { + InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList); + InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0, 1]); + + if (parameterList.Length is 1) + { + InstructionThrowHelper.ThrowIfParameterAtIndexIsNot(parameterList, 0, out DoubleSymbol doubleSymbol); + + HasDuration = true; + Duration = TimeSpan.FromSeconds(doubleSymbol.Value); + } + } + + public DashSymbol(ImmutableArray trivia) + : base("dash", trivia) + { + } + + public bool HasDuration { get; } + + public TimeSpan Duration { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/DefaultSymbolEmitter.cs b/BetterGenshinImpact.CombatScript/DefaultSymbolEmitter.cs new file mode 100644 index 00000000..03d833f1 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/DefaultSymbolEmitter.cs @@ -0,0 +1,31 @@ +using System.Text; + +namespace BetterGenshinImpact.CombatScript; + +public sealed class DefaultSymbolEmitter : ISymbolEmitter +{ + private readonly StringBuilder builder = new(); + + public string Emit() + { + return builder.ToString(); + } + + public ISymbolEmitter Append(char value) + { + builder.Append(value); + return this; + } + + public ISymbolEmitter Append(double value) + { + builder.Append(value); + return this; + } + + public ISymbolEmitter Append(string value) + { + builder.Append(value); + return this; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/DoubleSymbol.cs b/BetterGenshinImpact.CombatScript/DoubleSymbol.cs new file mode 100644 index 00000000..bf8b4376 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/DoubleSymbol.cs @@ -0,0 +1,16 @@ +namespace BetterGenshinImpact.CombatScript; + +public sealed class DoubleSymbol : BaseSymbol, IParameterSymbol +{ + public DoubleSymbol(double value) + { + Value = value; + } + + public double Value { get; } + + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append(Value); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/HoldSymbol.cs b/BetterGenshinImpact.CombatScript/HoldSymbol.cs new file mode 100644 index 00000000..dcb4ebd6 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/HoldSymbol.cs @@ -0,0 +1,9 @@ +namespace BetterGenshinImpact.CombatScript; + +public sealed class HoldSymbol : BaseSymbol, IParameterSymbol +{ + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append("hold"); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/IInstructionSymbolHasAlias.cs b/BetterGenshinImpact.CombatScript/IInstructionSymbolHasAlias.cs new file mode 100644 index 00000000..0e9ead45 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/IInstructionSymbolHasAlias.cs @@ -0,0 +1,8 @@ +namespace BetterGenshinImpact.CombatScript; + +public interface IInstructionSymbolHasAlias +{ + public string AliasName { get; } + + public bool IsAlias { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/IInstructionSymbolHasDuration.cs b/BetterGenshinImpact.CombatScript/IInstructionSymbolHasDuration.cs new file mode 100644 index 00000000..9d56114e --- /dev/null +++ b/BetterGenshinImpact.CombatScript/IInstructionSymbolHasDuration.cs @@ -0,0 +1,10 @@ +using System; + +namespace BetterGenshinImpact.CombatScript; + +public interface IInstructionSymbolHasDuration +{ + public bool HasDuration { get; } + + public TimeSpan Duration { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/IParameterSymbol.cs b/BetterGenshinImpact.CombatScript/IParameterSymbol.cs new file mode 100644 index 00000000..d18f41af --- /dev/null +++ b/BetterGenshinImpact.CombatScript/IParameterSymbol.cs @@ -0,0 +1,5 @@ +namespace BetterGenshinImpact.CombatScript; + +public interface IParameterSymbol : ISymbol +{ +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/ISymbol.cs b/BetterGenshinImpact.CombatScript/ISymbol.cs new file mode 100644 index 00000000..845cc45d --- /dev/null +++ b/BetterGenshinImpact.CombatScript/ISymbol.cs @@ -0,0 +1,6 @@ +namespace BetterGenshinImpact.CombatScript; + +public interface ISymbol +{ + void Emit(ISymbolEmitter emitter); +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/ISymbolEmitter.cs b/BetterGenshinImpact.CombatScript/ISymbolEmitter.cs new file mode 100644 index 00000000..5706faa3 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/ISymbolEmitter.cs @@ -0,0 +1,12 @@ +namespace BetterGenshinImpact.CombatScript; + +public interface ISymbolEmitter +{ + string Emit(); + + ISymbolEmitter Append(char value); + + ISymbolEmitter Append(double value); + + ISymbolEmitter Append(string value); +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/InstructionListSymbol.cs b/BetterGenshinImpact.CombatScript/InstructionListSymbol.cs new file mode 100644 index 00000000..cb491e05 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/InstructionListSymbol.cs @@ -0,0 +1,18 @@ +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public sealed class InstructionListSymbol : BaseSymbol +{ + public InstructionListSymbol(ImmutableArray instructions) + { + Instructions = instructions; + } + + public ImmutableArray Instructions { get; } + + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append(Instructions); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/InstructionSymbol.cs b/BetterGenshinImpact.CombatScript/InstructionSymbol.cs new file mode 100644 index 00000000..a6662ace --- /dev/null +++ b/BetterGenshinImpact.CombatScript/InstructionSymbol.cs @@ -0,0 +1,50 @@ +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public abstract class InstructionSymbol : BaseSymbol +{ + protected InstructionSymbol(string name, ImmutableArray trivia) + { + Name = name; + HasParameterList = false; + TriviaList = trivia; + } + + protected InstructionSymbol(string name, ImmutableArray parameterList, ImmutableArray trivia) + { + Name = name; + HasParameterList = true; + ParameterList = parameterList; + TriviaList = trivia; + } + + public string Name { get; } + + public bool HasParameterList { get; } + + public ImmutableArray ParameterList { get; } + + public ImmutableArray TriviaList { get; } + + public override void Emit(ISymbolEmitter emitter) + { + if (this is IInstructionSymbolHasAlias {IsAlias: true } hasAlias) + { + emitter.Append(hasAlias.AliasName); + } + else + { + emitter.Append(Name); + } + + if (HasParameterList) + { + emitter.Append('('); + emitter.Append(ParameterList); + emitter.Append(')'); + } + + emitter.Append(TriviaList); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/InstructionThrowHelper.cs b/BetterGenshinImpact.CombatScript/InstructionThrowHelper.cs new file mode 100644 index 00000000..def48c1e --- /dev/null +++ b/BetterGenshinImpact.CombatScript/InstructionThrowHelper.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Frozen; +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public static class InstructionThrowHelper +{ + public static void ThrowIfParameterListIsDefault(ImmutableArray parameterList) + { + if (parameterList.IsDefault) + { + throw new ArgumentException($"Parameter list can not be default."); + } + } + + public static void ThrowIfParameterListCountNotCorrect(ImmutableArray parameterList, FrozenSet allowedCounts) + { + if (!allowedCounts.Contains(parameterList.Length)) + { + throw new ArgumentException($"Instruction parameter count not correct."); + } + } + + public static void ThrowIfParameterAtIndexIsNot(ImmutableArray parameterList, int index, out T symbol) + where T : IParameterSymbol + { + if (parameterList.Length <= index || parameterList[index] is not T result) + { + throw new ArgumentException($"Instruction's parameter at index {index} must be a {typeof(T).Name}."); + } + + symbol = result; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/JumpSymbol.cs b/BetterGenshinImpact.CombatScript/JumpSymbol.cs new file mode 100644 index 00000000..cbbc3149 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/JumpSymbol.cs @@ -0,0 +1,25 @@ +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public class JumpSymbol : InstructionSymbol, IInstructionSymbolHasAlias +{ + public JumpSymbol(bool isAlias, ImmutableArray parameterList, ImmutableArray trivia) + : base("jump", parameterList, trivia) + { + InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList); + InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0]); + + IsAlias = isAlias; + } + + public JumpSymbol(bool isAlias, ImmutableArray trivia) + : base("jump", trivia) + { + IsAlias = isAlias; + } + + public string AliasName { get; } = "j"; + + public bool IsAlias { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/LineBreakTriviaSymbol.cs b/BetterGenshinImpact.CombatScript/LineBreakTriviaSymbol.cs new file mode 100644 index 00000000..f9b200c1 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/LineBreakTriviaSymbol.cs @@ -0,0 +1,28 @@ +using System; + +namespace BetterGenshinImpact.CombatScript; + +public class LineBreakTriviaSymbol : TriviaSymbol +{ + private readonly string lineBreak; + + public LineBreakTriviaSymbol() + { + lineBreak = Environment.NewLine; + } + + public LineBreakTriviaSymbol(string lineBreak) + { + if (lineBreak is not ("\r\n" or "\n" or "\r" or ";")) + { + throw new ArgumentException("Invalid line break."); + } + + this.lineBreak = lineBreak; + } + + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append(lineBreak); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/ScriptUnit.cs b/BetterGenshinImpact.CombatScript/ScriptUnit.cs new file mode 100644 index 00000000..a1260429 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/ScriptUnit.cs @@ -0,0 +1,23 @@ +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public sealed class ScriptUnit +{ + public ScriptUnit(ImmutableArray symbols) + { + Symbols = symbols; + } + + public ImmutableArray Symbols { get; } + + public string Emit(ISymbolEmitter emitter) + { + foreach (ref readonly ISymbol symbol in Symbols.AsSpan()) + { + symbol.Emit(emitter); + } + + return emitter.Emit(); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/SkillSymbol.cs b/BetterGenshinImpact.CombatScript/SkillSymbol.cs new file mode 100644 index 00000000..d554df7a --- /dev/null +++ b/BetterGenshinImpact.CombatScript/SkillSymbol.cs @@ -0,0 +1,30 @@ +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public class SkillSymbol : InstructionSymbol, IInstructionSymbolHasAlias +{ + public SkillSymbol(bool isAlias, ImmutableArray parameterList, ImmutableArray trivia) + : base("skill", parameterList, trivia) + { + InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList); + InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [0, 1]); + + if (parameterList.Length is 1) + { + InstructionThrowHelper.ThrowIfParameterAtIndexIsNot(parameterList, 0, out HoldSymbol _); + } + + IsAlias = isAlias; + } + + public SkillSymbol(bool isAlias, ImmutableArray trivia) + : base("skill", trivia) + { + IsAlias = isAlias; + } + + public string AliasName { get; } = "e"; + + public bool IsAlias { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/SpaceTriviaSymbol.cs b/BetterGenshinImpact.CombatScript/SpaceTriviaSymbol.cs new file mode 100644 index 00000000..13e0d33b --- /dev/null +++ b/BetterGenshinImpact.CombatScript/SpaceTriviaSymbol.cs @@ -0,0 +1,9 @@ +namespace BetterGenshinImpact.CombatScript; + +public class SpaceTriviaSymbol : TriviaSymbol +{ + public override void Emit(ISymbolEmitter emitter) + { + emitter.Append(' '); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/SymbolEmitterExtensions.cs b/BetterGenshinImpact.CombatScript/SymbolEmitterExtensions.cs new file mode 100644 index 00000000..5c5fd043 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/SymbolEmitterExtensions.cs @@ -0,0 +1,23 @@ +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public static class SymbolEmitterExtensions +{ + public static ISymbolEmitter Append(this ISymbolEmitter emitter, ISymbol symbol) + { + symbol.Emit(emitter); + return emitter; + } + + public static ISymbolEmitter Append(this ISymbolEmitter emitter, ImmutableArray symbolList) + where TSymbol : ISymbol + { + foreach(ref readonly TSymbol symbol in symbolList.AsSpan()) + { + symbol.Emit(emitter); + } + + return emitter; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/SymbolParser.cs b/BetterGenshinImpact.CombatScript/SymbolParser.cs new file mode 100644 index 00000000..eee39f30 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/SymbolParser.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Immutable; +using System.Runtime.CompilerServices; + +namespace BetterGenshinImpact.CombatScript; + +public sealed class SymbolParser +{ + public ScriptUnit Parse(ReadOnlySpan raw) + { + ImmutableArray.Builder symbols = ImmutableArray.CreateBuilder(); + ParseLines(raw, symbols); + return new(symbols.ToImmutable()); + } + + private void ParseLines(ReadOnlySpan raw, ImmutableArray.Builder symbols) + { + bool skipNextRange = false; + ref readonly char end = ref raw[^1]; + + foreach(Range range in raw.SplitAny(['\r', '\n', ';'])) + { + if (skipNextRange) + { + skipNextRange = false; + continue; + } + + int offset = range.End.GetOffset(raw.Length); + if (offset >= raw.Length) + { + break; + } + + ref readonly char peek = ref raw[offset]; + + LineBreakTriviaSymbol lineBreakTrivia; + if (peek is '\r') + { + if (Unsafe.IsAddressLessThan(in peek, in end) && Unsafe.Add(ref Unsafe.AsRef(in peek), 1) is '\n') + { + // It's a CRLF + lineBreakTrivia = new("\r\n"); + skipNextRange = true; + } + else + { + // It's a CR + lineBreakTrivia = new("\r"); + } + } + else if (peek is '\n') + { + // It's a LF + lineBreakTrivia = new("\n"); + } + else if (peek is ';') + { + lineBreakTrivia = new(";"); + } + else + { + throw new InvalidOperationException($"Failed to parse line break trivia at {range}."); + } + + ReadOnlySpan currentSpan = raw[range]; + + symbols.Add(lineBreakTrivia); + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/TriviaSymbol.cs b/BetterGenshinImpact.CombatScript/TriviaSymbol.cs new file mode 100644 index 00000000..90522adf --- /dev/null +++ b/BetterGenshinImpact.CombatScript/TriviaSymbol.cs @@ -0,0 +1,5 @@ +namespace BetterGenshinImpact.CombatScript; + +public abstract class TriviaSymbol : BaseSymbol +{ +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/WaitSymbol.cs b/BetterGenshinImpact.CombatScript/WaitSymbol.cs new file mode 100644 index 00000000..40161a6a --- /dev/null +++ b/BetterGenshinImpact.CombatScript/WaitSymbol.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public class WaitSymbol : InstructionSymbol, IInstructionSymbolHasDuration +{ + public WaitSymbol(ImmutableArray parameterList, ImmutableArray trivia) + : base("wait", parameterList, trivia) + { + InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList); + InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [1]); + + InstructionThrowHelper.ThrowIfParameterAtIndexIsNot(parameterList, 0, out DoubleSymbol doubleSymbol); + + HasDuration = true; + Duration = TimeSpan.FromSeconds(doubleSymbol.Value); + } + + public bool HasDuration { get; } + + public TimeSpan Duration { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/WalkDirection.cs b/BetterGenshinImpact.CombatScript/WalkDirection.cs new file mode 100644 index 00000000..9f2cf182 --- /dev/null +++ b/BetterGenshinImpact.CombatScript/WalkDirection.cs @@ -0,0 +1,9 @@ +namespace BetterGenshinImpact.CombatScript; + +public enum WalkDirection +{ + Forward, + Backward, + Left, + Right +} \ No newline at end of file diff --git a/BetterGenshinImpact.CombatScript/WalkSymbol.cs b/BetterGenshinImpact.CombatScript/WalkSymbol.cs new file mode 100644 index 00000000..0cde969d --- /dev/null +++ b/BetterGenshinImpact.CombatScript/WalkSymbol.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Immutable; + +namespace BetterGenshinImpact.CombatScript; + +public class WalkSymbol : InstructionSymbol, IInstructionSymbolHasAlias, IInstructionSymbolHasDuration, IParameterSymbol +{ + public WalkSymbol(WalkDirection direction, ImmutableArray parameterList, ImmutableArray trivia) + : base("walk", parameterList, trivia) + { + InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList); + InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [1]); + + InstructionThrowHelper.ThrowIfParameterAtIndexIsNot(parameterList, 0, out DoubleSymbol doubleSymbol); + + HasDuration = true; + Duration = TimeSpan.FromSeconds(doubleSymbol.Value); + + Direction = direction; + + IsAlias = true; + } + + // Used for parameter + public WalkSymbol(WalkDirection direction, ImmutableArray trivia) + : base("walk", trivia) + { + Direction = direction; + IsAlias = true; + } + + public WalkSymbol(ImmutableArray parameterList, ImmutableArray trivia) + : base("walk", parameterList, trivia) + { + InstructionThrowHelper.ThrowIfParameterListIsDefault(parameterList); + InstructionThrowHelper.ThrowIfParameterListCountNotCorrect(parameterList, [2]); + + InstructionThrowHelper.ThrowIfParameterAtIndexIsNot(parameterList, 0, out WalkSymbol directionAlias); + InstructionThrowHelper.ThrowIfParameterAtIndexIsNot(parameterList, 1, out DoubleSymbol doubleSymbol); + + Direction = directionAlias.Direction; + HasDuration = true; + Duration = TimeSpan.FromSeconds(doubleSymbol.Value); + + IsAlias = false; + } + + public string AliasName + { + get + { + return Direction switch + { + WalkDirection.Forward => "w", + WalkDirection.Backward => "s", + WalkDirection.Left => "a", + WalkDirection.Right => "d", + _ => throw new ArgumentOutOfRangeException(), + }; + } + } + + public bool IsAlias { get; } + + public WalkDirection Direction { get; } + + public bool HasDuration { get; } + + public TimeSpan Duration { get; } +} \ No newline at end of file diff --git a/BetterGenshinImpact.sln b/BetterGenshinImpact.sln index 4d1709c8..9def17ad 100644 --- a/BetterGenshinImpact.sln +++ b/BetterGenshinImpact.sln @@ -19,6 +19,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BetterGenshinImpact.Test", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fischless.WindowsInput", "Fischless.WindowsInput\Fischless.WindowsInput.csproj", "{9D00BC7A-9280-4AC9-8951-4502EDB71B76}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterGenshinImpact.CombatScript", "BetterGenshinImpact.CombatScript\BetterGenshinImpact.CombatScript.csproj", "{57C278F9-F62A-480A-B929-B0E5D8174194}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterGenshinImpact.CombatScript.Test", "BetterGenshinImpact.CombatScript.Test\BetterGenshinImpact.CombatScript.Test.csproj", "{09427DA8-665E-441A-A576-C11FBC6F9E5C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,6 +87,22 @@ Global {9D00BC7A-9280-4AC9-8951-4502EDB71B76}.Release|Any CPU.Build.0 = Release|x64 {9D00BC7A-9280-4AC9-8951-4502EDB71B76}.Release|x64.ActiveCfg = Release|x64 {9D00BC7A-9280-4AC9-8951-4502EDB71B76}.Release|x64.Build.0 = Release|x64 + {57C278F9-F62A-480A-B929-B0E5D8174194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57C278F9-F62A-480A-B929-B0E5D8174194}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57C278F9-F62A-480A-B929-B0E5D8174194}.Debug|x64.ActiveCfg = Debug|Any CPU + {57C278F9-F62A-480A-B929-B0E5D8174194}.Debug|x64.Build.0 = Debug|Any CPU + {57C278F9-F62A-480A-B929-B0E5D8174194}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57C278F9-F62A-480A-B929-B0E5D8174194}.Release|Any CPU.Build.0 = Release|Any CPU + {57C278F9-F62A-480A-B929-B0E5D8174194}.Release|x64.ActiveCfg = Release|Any CPU + {57C278F9-F62A-480A-B929-B0E5D8174194}.Release|x64.Build.0 = Release|Any CPU + {09427DA8-665E-441A-A576-C11FBC6F9E5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09427DA8-665E-441A-A576-C11FBC6F9E5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09427DA8-665E-441A-A576-C11FBC6F9E5C}.Debug|x64.ActiveCfg = Debug|Any CPU + {09427DA8-665E-441A-A576-C11FBC6F9E5C}.Debug|x64.Build.0 = Debug|Any CPU + {09427DA8-665E-441A-A576-C11FBC6F9E5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09427DA8-665E-441A-A576-C11FBC6F9E5C}.Release|Any CPU.Build.0 = Release|Any CPU + {09427DA8-665E-441A-A576-C11FBC6F9E5C}.Release|x64.ActiveCfg = Release|Any CPU + {09427DA8-665E-441A-A576-C11FBC6F9E5C}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE