模块参数功能 (#4)

#3

Reviewed-on: jackfiled/Katheryne#4
This commit is contained in:
jackfiled 2023-10-30 22:25:20 +08:00
parent 52ede9fe96
commit 8e9df3fc52
9 changed files with 162 additions and 16 deletions

View File

@ -0,0 +1,34 @@
using Katheryne.Abstractions;
namespace Katheryne.Tests.Mocks;
public class MockParamModule : IParamsModule
{
private readonly Dictionary<string, Func<string>> _functions = new();
public string ModuleName => "test";
public MockParamModule()
{
_functions.Add("hello", Hello);
}
public string this[string param]
{
get
{
Func<string> func = _functions[param];
return func();
}
}
public bool ContainsParam(string param)
{
return _functions.ContainsKey(param);
}
private string Hello()
{
return "Hello, Katheryne";
}
}

View File

@ -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<string, IParamsModule> _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<GrammarException>(() => new StringFormatter("Test @test/nonexistent", _modules));
}
}

View File

@ -0,0 +1,13 @@
namespace Katheryne.Abstractions;
/// <summary>
/// 参数实现模块
/// </summary>
public interface IParamsModule
{
public string ModuleName { get; }
public string this[string param] { get; }
public bool ContainsParam(string param);
}

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
namespace Katheryne.Models;
public record GrammarParam(string OriginString, string Module, string Param);

View File

@ -1,3 +1,4 @@
using Katheryne.Abstractions;
using Katheryne.Exceptions;
namespace Katheryne.Models;
@ -6,11 +7,11 @@ public class GrammarTree
{
private readonly Dictionary<string, InnerStage> _stages = new();
public GrammarTree(LexicalModel model)
public GrammarTree(LexicalModel model, Dictionary<string, IParamsModule> modules)
{
foreach (Stage stage in model.Stages)
{
_stages[stage.Name] = new InnerStage(stage);
_stages[stage.Name] = new InnerStage(stage, modules);
}
if (!Validate(model))

View File

@ -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<string, IParamsModule> modules)
{
Name = stage.Name;
Answer = new StringFormatter(stage.Answer);
Answer = new StringFormatter(stage.Answer, modules);
foreach (Transformer transformer in stage.Transformers)
{

View File

@ -1,4 +1,6 @@
using System.Text.RegularExpressions;
using Katheryne.Abstractions;
using Katheryne.Exceptions;
namespace Katheryne.Models;
@ -7,12 +9,20 @@ namespace Katheryne.Models;
/// </summary>
public class StringFormatter
{
private readonly string _originString;
private readonly List<FormatTag> _formatTags = new();
private static readonly HashSet<char> s_delimiters = new()
{
',', '.', '!', ';', '?', ' ', '', '。', ''
};
public StringFormatter(string originString)
private readonly string _originString;
private readonly Dictionary<string, IParamsModule> _modules;
private readonly List<FormatTag> _formatTags = new();
private readonly List<GrammarParam> _params = new();
public StringFormatter(string originString, Dictionary<string, IParamsModule> 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<int> indexes = new();
List<int> tagIndices = new();
List<int> 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<char> 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]
));
}
}
}

View File

@ -13,6 +13,8 @@ public class KatheryneChatRobotFactory : IChatRobotFactory
private readonly ILogger<KatheryneChatRobot> _robotLogger;
private readonly DefaultChatRobot _defaultChatRobot;
private readonly Dictionary<string, IParamsModule> _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()