diff --git a/Katheryne.Tests/Mocks/MockParamModule.cs b/Katheryne.Tests/Mocks/MockParamModule.cs new file mode 100644 index 0000000..e824dda --- /dev/null +++ b/Katheryne.Tests/Mocks/MockParamModule.cs @@ -0,0 +1,34 @@ +using Katheryne.Abstractions; + +namespace Katheryne.Tests.Mocks; + +public class MockParamModule : IParamsModule +{ + private readonly Dictionary> _functions = new(); + + public string ModuleName => "test"; + + public MockParamModule() + { + _functions.Add("hello", Hello); + } + + public string this[string param] + { + get + { + Func func = _functions[param]; + return func(); + } + } + + public bool ContainsParam(string param) + { + return _functions.ContainsKey(param); + } + + private string Hello() + { + return "Hello, Katheryne"; + } +} \ No newline at end of file diff --git a/Katheryne.Tests/StringFormatterTests.cs b/Katheryne.Tests/StringFormatterTests.cs index d9d697d..9f2703c 100644 --- a/Katheryne.Tests/StringFormatterTests.cs +++ b/Katheryne.Tests/StringFormatterTests.cs @@ -1,14 +1,25 @@ using System.Text.RegularExpressions; +using Katheryne.Abstractions; +using Katheryne.Exceptions; using Katheryne.Models; +using Katheryne.Tests.Mocks; namespace Katheryne.Tests; public class StringFormatterTests { + private readonly Dictionary _modules = new(); + + public StringFormatterTests() + { + var module = new MockParamModule(); + _modules.Add(module.ModuleName, module); + } + [Fact] public void FormatTest1() { - StringFormatter formatter = new("Hello $1"); + StringFormatter formatter = new("Hello $1", _modules); Regex regex = new("I'm (.*)"); @@ -23,7 +34,7 @@ public class StringFormatterTests [Fact] public void FormatTest2() { - StringFormatter formatter = new("$你好呀 $1, 欢迎你离开垃圾的$2."); + StringFormatter formatter = new("$你好呀 $1, 欢迎你离开垃圾的$2.", _modules); Regex regex = new("你好,我是(.*),我来自(.*)\\."); @@ -34,4 +45,21 @@ public class StringFormatterTests Assert.Equal("$你好呀 Ichirinko, 欢迎你离开垃圾的Trinity.", formatter.Format(match.Groups)); } + + [Fact] + public void ParamFormatTest1() + { + StringFormatter formatter = new("Test @test/hello", _modules); + + Regex regex = new(".*?"); + Match match = regex.Match("Test Input"); + + Assert.Equal("Test Hello, Katheryne", formatter.Format(match.Groups)); + } + + [Fact] + public void ParamFormatTest2() + { + Assert.Throws(() => new StringFormatter("Test @test/nonexistent", _modules)); + } } \ No newline at end of file diff --git a/Katheryne/Abstractions/IParamsModule.cs b/Katheryne/Abstractions/IParamsModule.cs new file mode 100644 index 0000000..ddaa168 --- /dev/null +++ b/Katheryne/Abstractions/IParamsModule.cs @@ -0,0 +1,13 @@ +namespace Katheryne.Abstractions; + +/// +/// 参数实现模块 +/// +public interface IParamsModule +{ + public string ModuleName { get; } + + public string this[string param] { get; } + + public bool ContainsParam(string param); +} \ No newline at end of file diff --git a/Katheryne/KatheryneChatRobot.cs b/Katheryne/KatheryneChatRobot.cs index 9537f56..14b93e8 100644 --- a/Katheryne/KatheryneChatRobot.cs +++ b/Katheryne/KatheryneChatRobot.cs @@ -59,14 +59,16 @@ public class KatheryneChatRobot : IChatRobot if (answer.IsFormat) { string temp = answer.Format(match.Groups); - _logger.LogInformation("Format answer {} to {}.", answer.RowString, temp); + _logger.LogInformation("Format answer {} to {}.", + answer.RowString, temp); result.Add(temp); } else { result.Add(answer.RowString); } - _logger.LogInformation("Moving to stage {} on input {}.", _currentStage, input); + _logger.LogInformation("Moving to stage {} on input {}.", + _currentStage, input); break; } } @@ -93,7 +95,8 @@ public class KatheryneChatRobot : IChatRobot _currentStage = transformer.NextStage; result.Add(_grammarTree[_currentStage].Answer.RowString); - _logger.LogInformation("Moving to stage {} with empty transform.", _currentStage); + _logger.LogInformation("Moving to stage {} with empty transform.", + _currentStage); break; } } diff --git a/Katheryne/Models/GrammarParam.cs b/Katheryne/Models/GrammarParam.cs new file mode 100644 index 0000000..f6e400b --- /dev/null +++ b/Katheryne/Models/GrammarParam.cs @@ -0,0 +1,3 @@ +namespace Katheryne.Models; + +public record GrammarParam(string OriginString, string Module, string Param); \ No newline at end of file diff --git a/Katheryne/Models/GrammarTree.cs b/Katheryne/Models/GrammarTree.cs index 2c4effa..070594f 100644 --- a/Katheryne/Models/GrammarTree.cs +++ b/Katheryne/Models/GrammarTree.cs @@ -1,3 +1,4 @@ +using Katheryne.Abstractions; using Katheryne.Exceptions; namespace Katheryne.Models; @@ -6,11 +7,11 @@ public class GrammarTree { private readonly Dictionary _stages = new(); - public GrammarTree(LexicalModel model) + public GrammarTree(LexicalModel model, Dictionary modules) { foreach (Stage stage in model.Stages) { - _stages[stage.Name] = new InnerStage(stage); + _stages[stage.Name] = new InnerStage(stage, modules); } if (!Validate(model)) diff --git a/Katheryne/Models/InnerStage.cs b/Katheryne/Models/InnerStage.cs index 4eacdd8..2235256 100644 --- a/Katheryne/Models/InnerStage.cs +++ b/Katheryne/Models/InnerStage.cs @@ -1,3 +1,5 @@ +using Katheryne.Abstractions; + namespace Katheryne.Models; internal class InnerStage @@ -8,10 +10,10 @@ internal class InnerStage public StringFormatter Answer { get; } - public InnerStage(Stage stage) + public InnerStage(Stage stage, Dictionary modules) { Name = stage.Name; - Answer = new StringFormatter(stage.Answer); + Answer = new StringFormatter(stage.Answer, modules); foreach (Transformer transformer in stage.Transformers) { diff --git a/Katheryne/Models/StringFormatter.cs b/Katheryne/Models/StringFormatter.cs index 76b7da9..9b37481 100644 --- a/Katheryne/Models/StringFormatter.cs +++ b/Katheryne/Models/StringFormatter.cs @@ -1,4 +1,6 @@ using System.Text.RegularExpressions; +using Katheryne.Abstractions; +using Katheryne.Exceptions; namespace Katheryne.Models; @@ -7,12 +9,20 @@ namespace Katheryne.Models; /// public class StringFormatter { - private readonly string _originString; - private readonly List _formatTags = new(); + private static readonly HashSet s_delimiters = new() + { + ',', '.', '!', ';', '?', ' ', ',', '。', ';' + }; - public StringFormatter(string originString) + private readonly string _originString; + private readonly Dictionary _modules; + private readonly List _formatTags = new(); + private readonly List _params = new(); + + public StringFormatter(string originString, Dictionary modules) { _originString = originString; + _modules = modules; GetFormatTags(); } @@ -35,22 +45,33 @@ public class StringFormatter result = result.Replace(tag.Value, collection[tag.Index].Value); } + foreach (GrammarParam param in _params) + { + result = result.Replace(param.OriginString, + _modules[param.Module][param.Param]); + } + return result; } private void GetFormatTags() { - List indexes = new(); + List tagIndices = new(); + List paramIndices = new(); for (var i = 0; i < _originString.Length; i++) { if (_originString[i] == '$') { - indexes.Add(i); + tagIndices.Add(i); + } + else if (_originString[i] == '@') + { + paramIndices.Add(i); } } - foreach (int index in indexes) + foreach (int index in tagIndices) { string value = string.Empty; int pos = index + 1; @@ -74,5 +95,43 @@ public class StringFormatter _formatTags.Add(new FormatTag('$' + value, int.Parse(value))); } + + List chars = new(); + foreach (int index in paramIndices) + { + chars.Clear(); + int i = index + 1; + + while (i < _originString.Length && !s_delimiters.Contains(_originString[i])) + { + chars.Add(_originString[i]); + i++; + } + + string text = new(chars.ToArray()); + + string[] array = text.Split('/'); + + if (array.Length != 2) + { + throw new GrammarException($"Failed to parse grammar param: {text}."); + } + + if (!_modules.ContainsKey(array[0])) + { + throw new GrammarException($"Unknown module {array[0]}."); + } + + if (!_modules[array[0]].ContainsParam(array[1])) + { + throw new GrammarException($"Module {array[0]} doesn't support {array[1]}."); + } + + _params.Add(new GrammarParam( + '@' + text, + array[0], + array[1] + )); + } } } \ No newline at end of file diff --git a/Katheryne/Services/KatheryneChatRobotFactory.cs b/Katheryne/Services/KatheryneChatRobotFactory.cs index 43af940..1b4bff4 100644 --- a/Katheryne/Services/KatheryneChatRobotFactory.cs +++ b/Katheryne/Services/KatheryneChatRobotFactory.cs @@ -13,6 +13,8 @@ public class KatheryneChatRobotFactory : IChatRobotFactory private readonly ILogger _robotLogger; private readonly DefaultChatRobot _defaultChatRobot; + private readonly Dictionary _modules = new(); + private Grammar? _grammar; public string GrammarText { get; private set; } = string.Empty; @@ -48,7 +50,8 @@ public class KatheryneChatRobotFactory : IChatRobotFactory { throw new GrammarException("Failed to parse lexical model.", ex); } - _grammar = new Grammar(new GrammarTree(model), model.RobotName, model.BeginStageName); + _grammar = new Grammar(new GrammarTree(model, _modules), + model.RobotName, model.BeginStageName); } public IChatRobot GetRobot()