diff --git a/Katheryne/DefaultChatRobot.cs b/Katheryne/DefaultChatRobot.cs index 7cac34b..f473e8a 100644 --- a/Katheryne/DefaultChatRobot.cs +++ b/Katheryne/DefaultChatRobot.cs @@ -12,15 +12,16 @@ public class DefaultChatRobot : IChatRobot _logger = logger; } - public string RobotName => "凯瑟琳"; + public string RobotName => "Default"; public IEnumerable OnChatStart() { _logger.LogDebug("Start default chat robot."); return new[] { - "向着星辰与深渊!", - "欢迎来到冒险家协会。" + "坏了,被你发现默认机器人了。", + "使用这个粪机器人,怎么能得高分呢?", + "必须要出重拳!" }; } @@ -29,7 +30,7 @@ public class DefaultChatRobot : IChatRobot _logger.LogDebug("End default chat robot."); return new[] { - "再见,感谢您对协会做出的贡献,冒险家。" + "我不到啊。" }; } @@ -38,7 +39,7 @@ public class DefaultChatRobot : IChatRobot _logger.LogDebug("Robot receive message: \"{}\".", input); return new[] { - "暂时不支持该功能,请联系维护人员。" + "啊对对对。" }; } } \ No newline at end of file diff --git a/Katheryne/Exceptions/GrammarException.cs b/Katheryne/Exceptions/GrammarException.cs new file mode 100644 index 0000000..e4b9ab7 --- /dev/null +++ b/Katheryne/Exceptions/GrammarException.cs @@ -0,0 +1,22 @@ +namespace Katheryne.Exceptions; + +/// +/// 语法中存在的错误 +/// +public class GrammarException : Exception +{ + public GrammarException() : base() + { + + } + + public GrammarException(string message) : base(message) + { + + } + + public GrammarException(string message, Exception innerException) : base(message, innerException) + { + + } +} \ No newline at end of file diff --git a/Katheryne/KatheryneChatRobot.cs b/Katheryne/KatheryneChatRobot.cs new file mode 100644 index 0000000..c84e5f3 --- /dev/null +++ b/Katheryne/KatheryneChatRobot.cs @@ -0,0 +1,54 @@ +using Katheryne.Abstractions; +using Katheryne.Models; +using Katheryne.Services; +using Microsoft.Extensions.Logging; +using YamlDotNet.Serialization; + +namespace Katheryne; + +public class KatheryneChatRobot : IChatRobot +{ + private readonly ILogger _logger; + private readonly GrammarTree _grammarTree; + + private string _currentStage; + + public KatheryneChatRobot(GrammarTree grammarTree, ILogger logger, + string beginStage, string robotName) + { + _logger = logger; + + _grammarTree = grammarTree; + _currentStage = beginStage; + RobotName = robotName; + } + + public string RobotName { get; } + public IEnumerable OnChatStart() + { + return new[] + { + _grammarTree[_currentStage] + }; + } + + public IEnumerable OnChatStop() + { + return new[] + { + "再见。" + }; + } + + public IEnumerable ChatNext(string input) + { + _logger.LogDebug("Receive input {} on stage {}.", input, _currentStage); + (_currentStage, string answer) = _grammarTree.NextStage(_currentStage, input); + _logger.LogDebug("Change stage to {}.", _currentStage); + + return new[] + { + answer + }; + } +} \ No newline at end of file diff --git a/Katheryne/Models/GrammarTree.cs b/Katheryne/Models/GrammarTree.cs new file mode 100644 index 0000000..c76d694 --- /dev/null +++ b/Katheryne/Models/GrammarTree.cs @@ -0,0 +1,109 @@ +using System.Text.RegularExpressions; +using Katheryne.Exceptions; + +namespace Katheryne.Models; + +public class GrammarTree +{ + private readonly Dictionary _stages = new(); + + public GrammarTree(LexicalModel model) + { + foreach (Stage stage in model.Stages) + { + _stages[stage.Name] = new InnerStage(stage); + } + + if (!Validate(model)) + { + throw new GrammarException("使用了未声明的阶段名"); + } + } + + /// + /// 获得下一个阶段 + /// + /// 当前所在阶段 + /// 用户输入 + /// 元组,第一个参数是下一个阶段名称 第二个参数是机器人回答 + /// + public (string, string) NextStage(string currentStage, string input) + { + List transformers = _stages[currentStage].Transformers; + + foreach (InnerTransformer transformer in transformers) + { + Match match = transformer.Pattern.Match(input); + + if (match.Success) + { + return (_stages[transformer.NextStage].Name, + _stages[transformer.NextStage].Answer); + } + } + + throw new GrammarException("Failed to get next stage."); + } + + public string this[string index] => _stages[index].Answer; + + /// + /// 主要验证语法的两个特点 + /// 1. 起始阶段是否被定义 + /// 2. 每个Transformer的下一个阶段是否被定义 + /// + /// + private bool Validate(LexicalModel model) + { + if (!_stages.ContainsKey(model.BeginStageName)) + { + return false; + } + + return model.Stages.All((stage) => + { + return stage.Transformers.All(t => _stages.ContainsKey(t.NextStageName)); + }); + } + + private class InnerStage + { + public string Name { get; } + + public List Transformers { get; } = new(); + + public string Answer { get; } + + public InnerStage(Stage stage) + { + Name = stage.Name; + Answer = stage.Answer; + + foreach (Transformer transformer in stage.Transformers) + { + Transformers.Add(new InnerTransformer(transformer)); + } + } + } + + private class InnerTransformer + { + public Regex Pattern { get; } + + public string NextStage { get; } + + public InnerTransformer(Transformer transformer) + { + NextStage = transformer.NextStageName; + + try + { + Pattern = new Regex(transformer.Pattern); + } + catch (ArgumentException e) + { + throw new GrammarException($"Failed to Parse regex:{transformer.Pattern}.", e); + } + } + } +} \ No newline at end of file diff --git a/Katheryne/ServiceCollectionExtensions.cs b/Katheryne/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..5ad2dd0 --- /dev/null +++ b/Katheryne/ServiceCollectionExtensions.cs @@ -0,0 +1,14 @@ +using Katheryne.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Katheryne; + +public static class ServiceCollectionExtensions +{ + public static void AddYamlDeserializerFactory(this IServiceCollection collection) + { + collection.AddSingleton(); + collection.AddSingleton(); + collection.AddScoped(); + } +} \ No newline at end of file diff --git a/Katheryne/Services/KatheryneChatRobotFactory.cs b/Katheryne/Services/KatheryneChatRobotFactory.cs new file mode 100644 index 0000000..dcfedd3 --- /dev/null +++ b/Katheryne/Services/KatheryneChatRobotFactory.cs @@ -0,0 +1,51 @@ +using Katheryne.Abstractions; +using Katheryne.Models; +using Microsoft.Extensions.Logging; +using YamlDotNet.Serialization; + +namespace Katheryne.Services; + +public class KatheryneChatRobotFactory +{ + private readonly YamlDeserializerFactory _deserializerFactory; + private readonly ILogger _factoryLogger; + private readonly ILogger _robotLogger; + private readonly DefaultChatRobot _defaultChatRobot; + + private Grammar? _grammar; + + public KatheryneChatRobotFactory(YamlDeserializerFactory deserializerFactory, + ILogger factoryLogger, + ILogger robotLogger, + DefaultChatRobot defaultChatRobot) + { + _deserializerFactory = deserializerFactory; + _factoryLogger = factoryLogger; + _robotLogger = robotLogger; + _defaultChatRobot = defaultChatRobot; + } + + public void SetGrammar(string grammarText) + { + _factoryLogger.LogInformation("Receive new grammar: {}.", grammarText); + IDeserializer deserializer = _deserializerFactory.GetDeserializer(); + + LexicalModel model = deserializer.Deserialize(grammarText); + _grammar = new Grammar(new GrammarTree(model), model.RobotName, model.BeginStageName); + } + + public IChatRobot GetRobot() + { + if (_grammar is null) + { + _factoryLogger.LogDebug("Get default chat robot."); + return _defaultChatRobot; + } + + _factoryLogger.LogDebug("Get chat robot: {}.", _grammar.RobotName); + return new KatheryneChatRobot(_grammar.GrammarTree, _robotLogger, + _grammar.BeginStage, _grammar.RobotName); + } + + private record Grammar(GrammarTree GrammarTree, string RobotName, string BeginStage); +} \ No newline at end of file