Skip to content

Commit

Permalink
Fix bugs, add UnitTest
Browse files Browse the repository at this point in the history
  • Loading branch information
akemimadoka committed Aug 29, 2017
1 parent b03af6e commit 3fd4c74
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/MineCase.Nbt/Tags/NbtCompound.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public void Add(NbtTag tag)

if (_childTags.ContainsKey(tag.Name))
{
throw new ArgumentException($"试图加入具有名称{tag.Name}的 Tag,但因已有重名的子 Tag 而失败", nameof(tag));
throw new ArgumentException($"试图加入具有名称 \"{tag.Name}\" 的 Tag,但因已有重名的子 Tag 而失败", nameof(tag));
}

Contract.EndContractBlock();
Expand Down
5 changes: 4 additions & 1 deletion src/MineCase.Server.Grains/Game/GameSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ public async Task SendChatMessage(IUser sender, string message)
{
if (!_commandMap.Dispatch(await sender.GetPlayer(), message))
{
// TODO: 处理命令未成功执行的情形
await sender.SendChatMessage(
await CreateStandardChatMessage(
senderName,
$"试图执行指令 \"{command}\" 时被拒绝,请检查是否具有足够的权限以及指令语法是否正确"), 0);
}

return;
Expand Down
20 changes: 12 additions & 8 deletions src/MineCase.Server.Interfaces/Game/Commands/CommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,42 +39,46 @@ public static class CommandParser
/// <summary>
/// 分析命令
/// </summary>
/// <param name="input">输入,即作为命令被分析的文本</param>
/// <param name="input">输入,即作为命令被分析的文本,应当不为 null、经过 <see cref="string.Trim()"/> 处理且以 '/' 开头</param>
/// <returns>命令名及命令的参数</returns>
/// <exception cref="ArgumentException"><paramref name="input"/> 不合法</exception>
public static (string, IList<ICommandArgument>) ParseCommand(string input)
{
if (input == null || input.Length < 2)
if (input == null || input.Length < 2 || input[0] != '/')
{
throw new ArgumentException("输入不合法", nameof(input));
}

var splitResult = input.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (splitResult.Length == 0)
{
throw new ArgumentException($"输入 ({input}) 不合法");
throw new ArgumentException($"输入 ({input}) 不合法", nameof(input));
}

return (splitResult[0], ParseCommandArgument(splitResult.Skip(1)));
return (splitResult[0].Substring(1), ParseCommandArgument(splitResult.Skip(1)));
}

// 参数必须保持有序,因此返回值使用 IList 而不是 IEnumerable
// 参数必须保持原来的顺序,因此返回值使用 IList 而不是 IEnumerable
private static IList<ICommandArgument> ParseCommandArgument(IEnumerable<string> input)
{
var result = new List<ICommandArgument>();

foreach (var arg in input)
{
Contract.Assert(arg != null && arg.Length > 1);
Contract.Assert(!string.IsNullOrWhiteSpace(arg));

// TODO: 使用更加具有可扩展性的方法
switch (arg[0])
{
case '@':
case TargetSelectorArgument.PrefixToken:
result.Add(new TargetSelectorArgument(arg));
break;
case '{':
case DataTagArgument.PrefixToken:
result.Add(new DataTagArgument(arg));
break;
case TildeNotationArgument.PrefixToken:
result.Add(new TildeNotationArgument(arg));
break;
default:
result.Add(new UnresolvedArgument(arg));
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@

namespace MineCase.Server.Game.Commands
{
/// <summary>
/// 数据标签参数
/// </summary>
/// <remarks>表示一个 <see cref="NbtTag"/></remarks>
public class DataTagArgument : UnresolvedArgument
{
internal const char PrefixToken = '{';

public NbtCompound Tag { get; }

public DataTagArgument(string rawContent)
Expand Down
54 changes: 39 additions & 15 deletions src/MineCase.Server.Interfaces/Game/Commands/TargetSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal class TargetSelectorAliasAsAttribute : Attribute
}

/// <summary>
/// TargetSelector 类型
/// 目标选择器类型
/// </summary>
public enum TargetSelectorType
{
Expand Down Expand Up @@ -70,17 +70,17 @@ private enum ParseStatus
Rejected
}

private const char PrefixToken = '@';
internal const char PrefixToken = '@';
private const char ArgumentListStartToken = '[';
private const char ArgumentListEndToken = ']';
private const char ArgumentAssignmentToken = '=';
private const char ArgumentSeparatorToken = ',';

private static readonly Dictionary<char, TargetSelectorType> TargetSelectorMap =
typeof(TargetSelectorType).GetFields()
typeof(TargetSelectorType).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static)
.ToDictionary(
v => v.GetType().GetTypeInfo().GetCustomAttribute<TargetSelectorAliasAsAttribute>().Alias,
v => (TargetSelectorType)v.GetValue(null));
v => v.GetCustomAttribute<TargetSelectorAliasAsAttribute>().Alias,
v => (TargetSelectorType)v.GetRawConstantValue());

/// <summary>
/// Gets 指示选择了哪一类型的目标
Expand All @@ -91,11 +91,11 @@ private enum ParseStatus

/// <summary>
/// Initializes a new instance of the <see cref="TargetSelectorArgument"/> class.<para />
/// 构造并分析一个 TargetSelector
/// 构造并分析一个目标选择器
/// </summary>
/// <param name="rawContent">作为 TargetSelector 的内容</param>
/// <param name="rawContent">作为目标选择器的内容</param>
/// <exception cref="ArgumentNullException"><paramref name="rawContent"/> 为 null</exception>
/// <exception cref="ArgumentException"><paramref name="rawContent"/> 无法作为 TargetSelector 解析</exception>
/// <exception cref="ArgumentException"><paramref name="rawContent"/> 无法作为目标选择器解析</exception>
public TargetSelectorArgument(string rawContent)
: base(rawContent)
{
Expand All @@ -108,13 +108,20 @@ public TargetSelectorArgument(string rawContent)
switch (status)
{
case ParseStatus.Prefix:
status = cur == PrefixToken ? ParseStatus.VariableTag : ParseStatus.Rejected;
if (cur == PrefixToken)
{
status = ParseStatus.VariableTag;
}
else
{
goto case ParseStatus.Rejected;
}

break;
case ParseStatus.VariableTag:
if (!TargetSelectorMap.TryGetValue(cur, out var type))
{
status = ParseStatus.Rejected;
break;
goto case ParseStatus.Rejected;
}

Type = type;
Expand All @@ -123,11 +130,18 @@ public TargetSelectorArgument(string rawContent)
case ParseStatus.OptionalArgumentListStart:
if (cur != ArgumentListStartToken)
{
status = ParseStatus.Rejected;
goto case ParseStatus.Rejected;
}

status = ParseStatus.ArgumentElementName;
break;
case ParseStatus.ArgumentElementName:
if (char.IsWhiteSpace(cur) && tmpString.Length == 0)
{
// 略过开头的空白字符,但是这不可能发生。。。
continue;
}

if (cur == ArgumentAssignmentToken)
{
argName = tmpString.ToString();
Expand All @@ -144,7 +158,15 @@ public TargetSelectorArgument(string rawContent)
Contract.Assert(argName != null);
_arguments.Add(argName, tmpString.ToString());
tmpString = new StringBuilder();
status = cur == ArgumentSeparatorToken ? ParseStatus.ArgumentElementName : ParseStatus.ArgumentListEnd;
if (cur == ArgumentSeparatorToken)
{
status = ParseStatus.ArgumentElementName;
}
else
{
goto case ParseStatus.ArgumentListEnd;
}

break;
}

Expand All @@ -154,7 +176,9 @@ public TargetSelectorArgument(string rawContent)
status = ParseStatus.Accepted;
break;
case ParseStatus.Accepted:
return;
// 尾部有多余的字符
status = ParseStatus.Rejected;
break;
case ParseStatus.Rejected:
throw new ArgumentException($"\"{rawContent}\" 不能被解析为合法的 TargetSelector", nameof(rawContent));
default:
Expand All @@ -163,7 +187,7 @@ public TargetSelectorArgument(string rawContent)
}
}

if (status != ParseStatus.Accepted || status != ParseStatus.OptionalArgumentListStart)
if (status != ParseStatus.Accepted && status != ParseStatus.OptionalArgumentListStart)
{
throw new ArgumentException($"在解析 \"{rawContent}\" 的过程中,解析被过早地中止");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace MineCase.Server.Game.Commands
{
/// <summary>
/// 波浪号记号参数
/// </summary>
/// <remarks>用于表示一个相对的值,具体含义由具体命令定义</remarks>
public class TildeNotationArgument : UnresolvedArgument
{
internal const char PrefixToken = '~';

/// <summary>
/// Gets 波浪号记号参数表示的偏移量
/// </summary>
public int Offset { get; }

/// <summary>
/// Initializes a new instance of the <see cref="TildeNotationArgument"/> class.
/// 构造并分析一个波浪号记号参数
/// </summary>
/// <param name="rawContent">作为波浪号记号的内容</param>
public TildeNotationArgument(string rawContent)
: base(rawContent)
{
if (rawContent.Length == 1)
{
Offset = 0;
return;
}

var strToParse = rawContent.Substring(1);
if (!int.TryParse(strToParse, out int offset))
{
throw new ArgumentException($"\"{strToParse}\" 不是合法的 offset 值", nameof(rawContent));
}

Offset = offset;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
<PackageReference Include="Microsoft.Orleans.Core" Version="2.0.0-preview2-20170724" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004" PrivateAssets="All" />
<PackageReference Include="System.Numerics.Vectors" Version="4.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup>

<ItemGroup>
Expand Down
65 changes: 65 additions & 0 deletions tests/UnitTest/CommandTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MineCase.Server.Game;
using MineCase.Server.Game.Commands;
using Xunit;

namespace MineCase.UnitTest
{
public class CommandTest
{
private class OutputCommandSender : ICommandSender
{
private readonly TextWriter _tw;

public OutputCommandSender(TextWriter tw)
{
_tw = tw;
}

public Task<bool> HasPermission(Permission permission)
{
return Task.FromResult(true);
}

public Task SendMessage(string msg)
{
_tw.WriteLine(msg);
return Task.CompletedTask;
}
}

private class TestCommand : SimpleCommand
{
public TestCommand()
: base("test", null, null, null)
{
}

public override bool Execute(ICommandSender commandSender, IList<ICommandArgument> args)
{
commandSender.SendMessage(string.Join(", ", args.Select(arg => arg.ToString())));
return true;
}
}

[Fact]
public void Test1()
{
var commandMap = new CommandMap();
commandMap.RegisterCommand(new TestCommand());

var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
commandMap.Dispatch(new OutputCommandSender(sw), "/test 1 ~2 @p[arg1=233,arg2=]");
var str = sb.ToString();
Console.Write(str);
}
}
}
}
1 change: 1 addition & 0 deletions tests/UnitTest/MineCase.UnitTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<ProjectReference Include="..\..\src\MineCase.Algorithm\MineCase.Algorithm.csproj" />
<ProjectReference Include="..\..\src\MineCase.Nbt\MineCase.Nbt.csproj" />
<ProjectReference Include="..\..\src\MineCase.Protocol\MineCase.Protocol.csproj" />
<ProjectReference Include="..\..\src\MineCase.Server.Interfaces\MineCase.Server.Interfaces.csproj" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion tests/UnitTest/NbtTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,4 @@ public void Test1()
}
}
}
}
}

0 comments on commit 3fd4c74

Please sign in to comment.