模块参数功能 (#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 System.Text.RegularExpressions;
using Katheryne.Abstractions;
using Katheryne.Exceptions;
using Katheryne.Models; using Katheryne.Models;
using Katheryne.Tests.Mocks;
namespace Katheryne.Tests; namespace Katheryne.Tests;
public class StringFormatterTests public class StringFormatterTests
{ {
private readonly Dictionary<string, IParamsModule> _modules = new();
public StringFormatterTests()
{
var module = new MockParamModule();
_modules.Add(module.ModuleName, module);
}
[Fact] [Fact]
public void FormatTest1() public void FormatTest1()
{ {
StringFormatter formatter = new("Hello $1"); StringFormatter formatter = new("Hello $1", _modules);
Regex regex = new("I'm (.*)"); Regex regex = new("I'm (.*)");
@ -23,7 +34,7 @@ public class StringFormatterTests
[Fact] [Fact]
public void FormatTest2() public void FormatTest2()
{ {
StringFormatter formatter = new("$你好呀 $1, 欢迎你离开垃圾的$2."); StringFormatter formatter = new("$你好呀 $1, 欢迎你离开垃圾的$2.", _modules);
Regex regex = new("你好,我是(.*),我来自(.*)\\."); Regex regex = new("你好,我是(.*),我来自(.*)\\.");
@ -34,4 +45,21 @@ public class StringFormatterTests
Assert.Equal("$你好呀 Ichirinko, 欢迎你离开垃圾的Trinity.", formatter.Format(match.Groups)); 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) if (answer.IsFormat)
{ {
string temp = answer.Format(match.Groups); 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); result.Add(temp);
} }
else else
{ {
result.Add(answer.RowString); result.Add(answer.RowString);
} }
_logger.LogInformation("Moving to stage {} on input {}.", _currentStage, input); _logger.LogInformation("Moving to stage {} on input {}.",
_currentStage, input);
break; break;
} }
} }
@ -93,7 +95,8 @@ public class KatheryneChatRobot : IChatRobot
_currentStage = transformer.NextStage; _currentStage = transformer.NextStage;
result.Add(_grammarTree[_currentStage].Answer.RowString); 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; 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; using Katheryne.Exceptions;
namespace Katheryne.Models; namespace Katheryne.Models;
@ -6,11 +7,11 @@ public class GrammarTree
{ {
private readonly Dictionary<string, InnerStage> _stages = new(); 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) foreach (Stage stage in model.Stages)
{ {
_stages[stage.Name] = new InnerStage(stage); _stages[stage.Name] = new InnerStage(stage, modules);
} }
if (!Validate(model)) if (!Validate(model))

View File

@ -1,3 +1,5 @@
using Katheryne.Abstractions;
namespace Katheryne.Models; namespace Katheryne.Models;
internal class InnerStage internal class InnerStage
@ -8,10 +10,10 @@ internal class InnerStage
public StringFormatter Answer { get; } public StringFormatter Answer { get; }
public InnerStage(Stage stage) public InnerStage(Stage stage, Dictionary<string, IParamsModule> modules)
{ {
Name = stage.Name; Name = stage.Name;
Answer = new StringFormatter(stage.Answer); Answer = new StringFormatter(stage.Answer, modules);
foreach (Transformer transformer in stage.Transformers) foreach (Transformer transformer in stage.Transformers)
{ {

View File

@ -1,4 +1,6 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Katheryne.Abstractions;
using Katheryne.Exceptions;
namespace Katheryne.Models; namespace Katheryne.Models;
@ -7,12 +9,20 @@ namespace Katheryne.Models;
/// </summary> /// </summary>
public class StringFormatter public class StringFormatter
{ {
private readonly string _originString; private static readonly HashSet<char> s_delimiters = new()
private readonly List<FormatTag> _formatTags = 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; _originString = originString;
_modules = modules;
GetFormatTags(); GetFormatTags();
} }
@ -35,22 +45,33 @@ public class StringFormatter
result = result.Replace(tag.Value, collection[tag.Index].Value); 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; return result;
} }
private void GetFormatTags() private void GetFormatTags()
{ {
List<int> indexes = new(); List<int> tagIndices = new();
List<int> paramIndices = new();
for (var i = 0; i < _originString.Length; i++) for (var i = 0; i < _originString.Length; i++)
{ {
if (_originString[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; string value = string.Empty;
int pos = index + 1; int pos = index + 1;
@ -74,5 +95,43 @@ public class StringFormatter
_formatTags.Add(new FormatTag('$' + value, int.Parse(value))); _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 ILogger<KatheryneChatRobot> _robotLogger;
private readonly DefaultChatRobot _defaultChatRobot; private readonly DefaultChatRobot _defaultChatRobot;
private readonly Dictionary<string, IParamsModule> _modules = new();
private Grammar? _grammar; private Grammar? _grammar;
public string GrammarText { get; private set; } = string.Empty; 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); 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() public IChatRobot GetRobot()