0x0001F36D 6 лет назад
Родитель
Сommit
b308981966
30 измененных файлов с 1238 добавлено и 0 удалено
  1. 25 0
      Yuuna.sln
  2. 15 0
      src/Program.cs
  3. 8 0
      src/Yuuna.CognitionServices.csproj
  4. 93 0
      src/Yuuna.Contracts/Group.cs
  5. 89 0
      src/Yuuna.Contracts/GroupManager.cs
  6. 39 0
      src/Yuuna.Contracts/Interaction/Mood.cs
  7. 43 0
      src/Yuuna.Contracts/Interaction/Moods.cs
  8. 35 0
      src/Yuuna.Contracts/Interaction/Response.cs
  9. 13 0
      src/Yuuna.Contracts/Optimization/DefaultStrategy.cs
  10. 10 0
      src/Yuuna.Contracts/Optimization/IPair.cs
  11. 19 0
      src/Yuuna.Contracts/Optimization/IScore.cs
  12. 18 0
      src/Yuuna.Contracts/Optimization/Pair.cs
  13. 35 0
      src/Yuuna.Contracts/Optimization/Score.cs
  14. 36 0
      src/Yuuna.Contracts/Optimization/ScoreComparer.cs
  15. 70 0
      src/Yuuna.Contracts/Optimization/StrategyBase.cs
  16. 19 0
      src/Yuuna.Contracts/Patterns/IBehaviourBuilder.cs
  17. 18 0
      src/Yuuna.Contracts/Patterns/IGrammar.cs
  18. 12 0
      src/Yuuna.Contracts/Patterns/IGrammarSet.cs
  19. 17 0
      src/Yuuna.Contracts/Patterns/IPatternBuilder.cs
  20. 34 0
      src/Yuuna.Contracts/Patterns/Internal/BehaviourBuilder.cs
  21. 57 0
      src/Yuuna.Contracts/Patterns/Internal/Grammar.cs
  22. 50 0
      src/Yuuna.Contracts/Patterns/Internal/PatternBuilder.cs
  23. 199 0
      src/Yuuna.Contracts/Plugins/PluginBase.cs
  24. 43 0
      src/Yuuna.Contracts/Semantics/IGroup.cs
  25. 41 0
      src/Yuuna.Contracts/Semantics/IGroupManager.cs
  26. 39 0
      src/Yuuna.Contracts/Semantics/ISynosym.cs
  27. 90 0
      src/Yuuna.Contracts/Synosym.cs
  28. 19 0
      src/Yuuna.Contracts/TextSegmention/ITextSegmenter.cs
  29. 40 0
      src/Yuuna.Contracts/TextSegmention/JiebaSegmenter.cs
  30. 12 0
      src/Yuuna.Contracts/Yuuna.Contracts.csproj

+ 25 - 0
Yuuna.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29503.13
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yuuna.Contracts", "src\Yuuna.Contracts\Yuuna.Contracts.csproj", "{9063A9AB-AD78-49F4-9B59-192CAC7A798A}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{9063A9AB-AD78-49F4-9B59-192CAC7A798A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9063A9AB-AD78-49F4-9B59-192CAC7A798A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9063A9AB-AD78-49F4-9B59-192CAC7A798A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9063A9AB-AD78-49F4-9B59-192CAC7A798A}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {945E64AB-2C7F-40D9-B8F1-AD94816BA7BC}
+	EndGlobalSection
+EndGlobal

+ 15 - 0
src/Program.cs

@@ -0,0 +1,15 @@
+
+
+namespace Yuuna
+{
+    using System.Threading.Tasks;
+
+
+    public class MAIN
+    {
+        public static async Task Main()
+        { 
+
+        }
+    }
+}

+ 8 - 0
src/Yuuna.CognitionServices.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 93 - 0
src/Yuuna.Contracts/Group.cs

@@ -0,0 +1,93 @@
+namespace Yuuna.Contracts
+{
+
+    namespace Semantics
+    {
+        using System;
+        using System.Collections;
+        using System.Collections.Generic;
+        using System.Collections.Immutable;
+        using System.Diagnostics;
+        using System.Diagnostics.CodeAnalysis;
+        using System.Linq;
+
+        /// <summary>
+        /// 群體物件。用來存放相同性質類型的同義詞物件。
+        /// </summary>
+        internal sealed class Group : IGroup
+        {
+            /// <summary>
+            /// 建立新的群體物件實體。
+            /// </summary>
+            /// <param name="key">名稱</param>
+            internal Group(string key)
+            {
+                Debug.Assert(!string.IsNullOrWhiteSpace(key), "'groupName' can't be null or empty.");
+                this.Key = key;
+                this._synosyms = ImmutableArray.CreateBuilder<ISynosym>();
+            }
+
+
+            private readonly ImmutableArray<ISynosym>.Builder _synosyms;
+             
+
+            /// <summary> 
+            /// 嘗試使用交集取得同義詞物件。
+            /// 若具有交集則將參數中的值附加至該搜尋到的同義詞物件中;若無,則透過參數建立新的同義詞實體並傳回。
+            /// </summary>
+            /// <param name="words">欲關聯至此物件的多個單字。若此群組物件不包含任何與此參數相關的單字,則使用此參數建立新同義詞實體。</param> 
+            /// <param name="stringComparer">字串比對器。</param>
+            /// <exception cref="ArgumentNullException"/>
+            public ISynosym AppendOrCreate(IEnumerable<string> words, StringComparer stringComparer = null)
+            {
+                if (words is null)
+                    throw new ArgumentNullException(nameof(words));
+
+                //Console.WriteLine(string.Join(":", words));
+
+                foreach (var synosym in this._synosyms)
+                { 
+                    if (synosym.Equals(words))
+                    {
+                        synosym.AddRange(words);
+                        return synosym;
+                    }
+                }
+
+                var inst = new Synosym(this, stringComparer);
+                inst.AddRange(words);
+                this._synosyms.Add(inst);
+                return inst;
+            }
+
+            public bool TryGetSynosym(string word, out ISynosym synosyms)
+            {
+                foreach (var synosym in this._synosyms)
+                {
+                    if (synosym.Equals(word))
+                    {
+                        synosyms = synosym;
+                        return true;
+                    }
+                }
+                synosyms = null;
+                return false;
+            } 
+
+            public IImmutableList<ISynosym> ToImmutable()
+            {
+                return this._synosyms.ToImmutable();
+            }
+
+            /// <summary>
+            /// 群組名稱。
+            /// </summary>
+            public string Key { get; }
+
+            public bool Equals(IGroup other)
+            {
+                return this.Key.Equals(other.Key);
+            }
+        }
+    }
+}

+ 89 - 0
src/Yuuna.Contracts/GroupManager.cs

@@ -0,0 +1,89 @@
+namespace Yuuna.Contracts
+{
+
+    namespace Semantics
+    {
+        using System;
+        using System.Collections.Generic;
+        using System.Diagnostics;
+        using System.Runtime.CompilerServices;
+
+        internal sealed class GroupManager : IGroupManager
+        {
+            private volatile Dictionary<string, IGroup> _groups;
+            private readonly object _lock = new object();
+            internal GroupManager()
+            {
+                this._groups = new Dictionary<string, IGroup>();
+            }
+
+            [DebuggerNonUserCode]
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            internal static void EnsureIsValid(string key)
+            {
+                if (string.IsNullOrWhiteSpace(key))
+                    throw new ArgumentNullException(nameof(key));
+            }
+
+            /// <summary>
+            /// 建立新的群組物件
+            /// </summary>
+            /// <param name="key"></param>
+            /// <returns></returns>
+            public IGroup Define(string key)
+            {
+                EnsureIsValid(key);
+
+                lock (this._lock)
+                {
+                    var g = new Group(key);
+                    this._groups.Add(g.Key, g);
+                    return g;
+                }
+            }
+
+            /// <summary>
+            /// 群組名稱集合。
+            /// </summary>
+            public IReadOnlyCollection<string> Keys
+            {
+                get
+                {
+                    lock (this._lock)
+                    {
+                        return this._groups.Keys;
+                    }
+                }
+            }
+
+            /// <summary>
+            /// 嘗試透過群組名稱取得群組物件。
+            /// </summary>
+            /// <param name="key">群組名稱。</param>
+            /// <param name="group">群組物件。</param>
+            /// <returns></returns>
+            public bool TryGetGroup(string key, out IGroup group)
+            {
+                EnsureIsValid(key);
+                lock (this._lock)
+                {
+                    return this._groups.TryGetValue(key, out group);
+                }
+            }
+
+            /// <summary>
+            /// 透過群組名稱取得群組物件;若無法取得則回傳 <see langword="null"/>。
+            /// </summary>
+            /// <param name="key">群組名稱。</param>
+            /// <returns></returns>
+            public IGroup this[string key]
+            {
+                get
+                {
+                    this.TryGetGroup(key, out var g);
+                    return g;
+                }
+            }
+        }
+    }
+}

+ 39 - 0
src/Yuuna.Contracts/Interaction/Mood.cs

@@ -0,0 +1,39 @@
+
+
+namespace Yuuna.Contracts.Interaction
+{
+    using System;
+    using System.ComponentModel;
+
+
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public struct Mood : IEquatable<Mood>
+    {
+        private const string NormalString = "Normal";
+        public Mood(string name)
+        {
+            if (string.IsNullOrWhiteSpace(name))
+                throw new ArgumentNullException(nameof(name));
+            this._name = name;
+        }
+        private readonly string _name;
+
+        public bool IsNormal => this._name is null;
+
+        public string Name
+        {
+            get
+            {
+                if (this.IsNormal)
+                    return NormalString;
+                return this._name;
+            }
+        }
+
+        public override int GetHashCode() => this.Name.GetHashCode();
+
+        public bool Equals(Mood other) => this.GetHashCode() == other.GetHashCode();
+        public override string ToString() => this.Name;
+    }
+
+}

+ 43 - 0
src/Yuuna.Contracts/Interaction/Moods.cs

@@ -0,0 +1,43 @@
+
+
+namespace Yuuna.Contracts.Interaction
+{
+    using System;
+
+    public static class Moods
+    {
+        //public static Mood Random(Mood first, Mood second, params Mood[] rest)
+        //{
+        //    var seed = Guid.NewGuid().GetHashCode();
+        //    var random = new Random(seed);
+        //    if (rest is Mood[] && rest.Length > 0)
+        //    {
+        //        var rnd = random.Next(0, rest.Length + 2);
+        //        if (rnd == 0) return first;
+        //        if (rnd == 1) return second;
+
+        //        return rest[rnd - 2];
+        //    }
+        //    else
+        //    {
+        //        var v = random.Next(0, 2);
+        //        if (v == 0)
+        //            return first;
+        //        return second;
+        //    }
+        //}
+
+
+        public static readonly Mood Normal = default;
+
+        public static readonly Mood Happy = new Mood(nameof(Happy));
+        public static readonly Mood Sad = new Mood(nameof(Sad));
+        //public static readonly Mood Angry = new Mood(nameof(Angry));
+        //public static readonly Mood Tender = new Mood(nameof(Tender));
+        //public static readonly Mood Excited = new Mood(nameof(Excited));
+        //public static readonly Mood Scared = new Mood(nameof(Scared));
+
+        public static readonly Mood Confuse = new Mood(nameof(Confuse));
+    }
+
+}

+ 35 - 0
src/Yuuna.Contracts/Interaction/Response.cs

@@ -0,0 +1,35 @@
+
+namespace Yuuna.Contracts.Interaction
+{
+    using System;
+    using System.ComponentModel;
+
+    public sealed class Response
+    {
+        public Mood Mood { get; }
+        public string Message { get; }
+
+
+
+        public Response(Mood mood, string message)
+        {
+            this.Mood = mood;
+            this.Message = message ?? "";
+        }
+        public static implicit operator Response(ValueTuple<Mood, string> full)
+        {
+            return new Response(full.Item1, full.Item2);
+        }
+        public static implicit operator Response(string message)
+        {
+            return new Response(Moods.Normal, message);
+        }
+
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Deconstruct(out Mood mood, out string message)
+        {
+            mood = this.Mood;
+            message = this.Message;
+        }
+    }
+}

+ 13 - 0
src/Yuuna.Contracts/Optimization/DefaultStrategy.cs

@@ -0,0 +1,13 @@
+
+namespace Yuuna.Contracts.Optimization
+{
+    using System.Collections.Generic;
+
+    public sealed class DefaultStrategy : StrategyBase
+    {
+        public override string Name => "Defaule";
+
+
+        protected override IComparer<IScore> Comparer => ScoreComparer.Default;
+    }
+}

+ 10 - 0
src/Yuuna.Contracts/Optimization/IPair.cs

@@ -0,0 +1,10 @@
+
+namespace Yuuna.Contracts.Optimization
+{using Yuuna.Contracts.Semantics;
+
+    public interface IPair
+    {
+        string Key { get; }
+        ISynosym Synosym { get; }
+    }
+}

+ 19 - 0
src/Yuuna.Contracts/Optimization/IScore.cs

@@ -0,0 +1,19 @@
+
+namespace Yuuna.Contracts.Optimization
+{
+    using System.Collections.Immutable;
+    using Yuuna.Contracts.Patterns;
+    using Yuuna.Contracts.Plugins;
+    using Yuuna.Contracts.Semantics;
+
+    public interface IScore
+    {
+        IGrammar Contrast { get; }
+        IImmutableList<IPair> SequentialMatches { get; }
+
+        double Coverage { get; }
+        bool IsCompacted { get; }
+        IImmutableList<IGroup> Missing { get; }
+        PluginBase Plugin { get; }
+    }
+}

+ 18 - 0
src/Yuuna.Contracts/Optimization/Pair.cs

@@ -0,0 +1,18 @@
+
+namespace Yuuna.Contracts.Optimization
+{
+    using Yuuna.Contracts.Semantics;
+
+    internal struct Pair : IPair
+    {
+        internal Pair(string key, ISynosym synosym)
+        {
+            this.Key = key;
+            this.Synosym = synosym;
+        }
+
+        public string Key { get; }
+        public ISynosym Synosym { get; }
+    }
+
+}

+ 35 - 0
src/Yuuna.Contracts/Optimization/Score.cs

@@ -0,0 +1,35 @@
+
+namespace Yuuna.Contracts.Optimization
+{
+    using System.Collections.Immutable;
+    using Yuuna.Contracts.Patterns;
+    using System.Linq;
+    using Yuuna.Contracts.Semantics;
+    using Yuuna.Contracts.Plugins;
+
+    internal struct Score : IScore
+    {
+        internal Score(PluginBase owner,IImmutableList<IPair> matches, IGrammar contrast)
+        {
+            this.Contrast = contrast;
+            this.Plugin = owner;
+            this.SequentialMatches = matches;
+
+            this.IsCompacted = contrast.Count == matches.Count;
+            this.Coverage = (double)matches.Count / contrast.Count;
+            this.Missing = contrast.ToImmutable().Except(matches.Select(x => x.Synosym.Owner)).ToImmutableArray();
+        }
+
+        public PluginBase Plugin { get; }
+        public IImmutableList<IPair> SequentialMatches { get; }
+        /// <summary>
+        /// 比較的文法。
+        /// </summary>
+        public IGrammar Contrast { get; }
+
+        public bool IsCompacted { get; }
+        public double Coverage { get; }
+        public IImmutableList<IGroup> Missing { get; }
+    }
+
+}

+ 36 - 0
src/Yuuna.Contracts/Optimization/ScoreComparer.cs

@@ -0,0 +1,36 @@
+
+namespace Yuuna.Contracts.Optimization
+{
+    using System.Collections.Generic;
+
+    public sealed class ScoreComparer : IComparer<IScore>
+    {
+        public readonly static IComparer<IScore> Default = new ScoreComparer();
+
+        private ScoreComparer()
+        { 
+        }
+
+        public int Compare(IScore x, IScore y)
+        {
+            var f1 = x.Contrast.SequentialKeys.Count == x.SequentialMatches.Count;
+            var f2 = y.Contrast.SequentialKeys.Count == y.SequentialMatches.Count;
+            if (f1 & f2)
+                return x.Contrast.SequentialKeys.Count - y.Contrast.SequentialKeys.Count;
+            else if (f1)
+                return 1;
+            else if (f2)
+                return -1;
+            else
+            {
+                var v1 = x.Contrast.SequentialKeys.Count - x.SequentialMatches.Count;
+                var v2 = y.Contrast.SequentialKeys.Count - y.SequentialMatches.Count;
+                if (v1 > v2)
+                    return -v2;
+                else if (v1 < v2)
+                    return v1;
+                return x.Contrast.SequentialKeys.Count - y.Contrast.SequentialKeys.Count;
+            }
+        }
+    }
+}

+ 70 - 0
src/Yuuna.Contracts/Optimization/StrategyBase.cs

@@ -0,0 +1,70 @@
+
+namespace Yuuna.Contracts.Optimization
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.Immutable;
+    using System.Diagnostics;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Linq;
+    using System.Runtime.CompilerServices;
+    using System.Text;
+    using System.Threading.Tasks;
+    using Yuuna.Contracts.Patterns;
+    using Yuuna.Contracts.Plugins;
+
+    public abstract class StrategyBase
+    {
+        protected StrategyBase()
+        { 
+        }
+
+        public abstract string Name { get; }
+
+        protected abstract IComparer<IScore> Comparer { get; }
+
+
+        internal IImmutableList<IScore> Assess(PluginBase plugin, IImmutableList<string> words, IGrammarSet set)
+        {
+            var scores = new List<IScore>();
+            var immutable = set.ToImmutable();
+            foreach (var grammar in immutable)
+            {
+                var score = this.Evaluate(plugin,words, grammar);
+                if (score.SequentialMatches.Count > 0) // 過濾掉符合為 0 的文法
+                    scores.Add(score);
+            }
+            scores.Sort(this.Comparer);
+
+            return scores.ToImmutableArray();
+        }
+
+        /// <summary>
+        /// 評估
+        /// </summary>
+        /// <param name="words"></param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private IScore Evaluate(PluginBase plugin ,IImmutableList<string> words, IGrammar grammar)
+        {
+            var matches = ImmutableArray.CreateBuilder<IPair>();
+
+            using (var iterator = grammar.ToImmutable().GetEnumerator())
+            {
+                iterator.MoveNext();
+                foreach (var word in words)
+                {
+                    if (iterator.Current.TryGetSynosym(word, out var s))
+                    {
+                        matches.Add(new Pair(s.Owner.Key, s));
+                        if (!iterator.MoveNext())
+                            break;
+                    } 
+                }
+            }
+
+            return new Score(plugin, matches.ToImmutable(), grammar);
+        }
+    }
+
+
+}

+ 19 - 0
src/Yuuna.Contracts/Patterns/IBehaviourBuilder.cs

@@ -0,0 +1,19 @@
+
+namespace Yuuna.Contracts.Patterns
+{
+    using System;
+    using System.Collections.Generic;
+    using Yuuna.Contracts.Interaction;
+    using Yuuna.Contracts.Optimization;
+    using Yuuna.Contracts.Semantics;
+
+    public interface IBehaviourBuilder
+    {
+        /// <summary>
+        /// 當文法完全吻合時將觸發這段函式。
+        /// </summary>
+        /// <param name="behaviour"></param>
+        void OnInvoke(Behaviour behaviour);
+    }
+
+}

+ 18 - 0
src/Yuuna.Contracts/Patterns/IGrammar.cs

@@ -0,0 +1,18 @@
+
+namespace Yuuna.Contracts.Patterns
+{
+    using System;
+    using System.Collections.Immutable;
+    using Yuuna.Contracts.Semantics;
+
+    public interface IGrammar : IEquatable<IGrammar>
+    {
+        int Count { get; }
+
+        IImmutableList<string> SequentialKeys { get; }
+
+        IImmutableList<IGroup> ToImmutable();
+    }
+
+
+}

+ 12 - 0
src/Yuuna.Contracts/Patterns/IGrammarSet.cs

@@ -0,0 +1,12 @@
+
+namespace Yuuna.Contracts.Patterns
+{
+    using System.Collections.Immutable;
+
+    public interface IGrammarSet
+    {
+        bool TryGet(IGrammar grammar, out Behaviour behaviour);
+        IImmutableList<IGrammar> ToImmutable();
+    }
+
+}

+ 17 - 0
src/Yuuna.Contracts/Patterns/IPatternBuilder.cs

@@ -0,0 +1,17 @@
+
+namespace Yuuna.Contracts.Patterns
+{
+    using System.Collections.Immutable;
+    using Yuuna.Contracts.Semantics;
+
+    public interface IPatternBuilder
+    {
+        /// <summary>
+        /// 建立新文法規則。
+        /// </summary>
+        /// <param name="group"></param>
+        /// <param name="groups"></param>
+        /// <returns></returns>
+        IBehaviourBuilder Build(IGroup group, params IGroup[] groups);
+    }
+}

+ 34 - 0
src/Yuuna.Contracts/Patterns/Internal/BehaviourBuilder.cs

@@ -0,0 +1,34 @@
+
+namespace Yuuna.Contracts.Patterns
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.Immutable;
+    using System.Diagnostics;
+    using Yuuna.Contracts.Interaction;
+    using Yuuna.Contracts.Optimization;
+    using Yuuna.Contracts.Semantics;
+
+    internal struct BehaviourBuilder : IBehaviourBuilder
+    {
+        private readonly Action<IGrammar, Behaviour> _addMethod;
+        private readonly IGrammar _grammar;
+
+        internal BehaviourBuilder(Action<IGrammar, Behaviour> addMethod, IGrammar grammar)
+        {
+            Debug.Assert(addMethod != null);
+            Debug.Assert(grammar != null);
+            this._addMethod = addMethod;
+            this._grammar = grammar;
+        }
+
+        void IBehaviourBuilder.OnInvoke(Behaviour callback)
+        {
+            if (callback is null)
+                throw new ArgumentNullException(nameof(callback));
+            this._addMethod.Invoke(this._grammar, callback);
+        }
+    }
+
+    public delegate Response Behaviour(IScore score);
+}

+ 57 - 0
src/Yuuna.Contracts/Patterns/Internal/Grammar.cs

@@ -0,0 +1,57 @@
+
+namespace Yuuna.Contracts.Patterns
+{
+    using System.Collections.Immutable;
+    using System.Diagnostics.CodeAnalysis;
+    using Yuuna.Contracts.Semantics;
+    using System.Linq;
+    using System.Diagnostics;
+
+    public sealed class Grammar : IGrammar
+    { 
+        public IImmutableList<string> SequentialKeys { get; private set; }
+        private IImmutableList<IGroup> _groups;
+
+        private readonly ImmutableArray<string>.Builder _keyBuilder;
+        private readonly ImmutableArray<IGroup>.Builder _groupBuilder;
+        internal Grammar()
+        {
+            this._keyBuilder = ImmutableArray.CreateBuilder<string>();
+            this._groupBuilder = ImmutableArray.CreateBuilder<IGroup>();
+        }
+        internal void Add(IGroup g)
+        {
+            if (g == null)
+                return;
+            this._keyBuilder.Add(g.Key);
+            this._groupBuilder.Add(g);
+        }
+
+        internal void Immute()
+        {
+            this.SequentialKeys = this._keyBuilder.ToImmutable();
+            this._groups = this._groupBuilder.ToImmutable();
+        }
+
+        public int Count
+        {
+            get
+            {
+                Debug.Assert(this._groups != null);
+                return this._groups.Count;
+            }
+        }
+
+        public IImmutableList<IGroup> ToImmutable()
+        {
+            Debug.Assert(this._groups != null);
+            return this._groups;
+        }
+
+        public bool Equals( IGrammar other)
+        {
+            Debug.Assert(this.SequentialKeys != null);
+            return this.SequentialKeys.SequenceEqual(other.SequentialKeys);
+        }
+    }
+}

+ 50 - 0
src/Yuuna.Contracts/Patterns/Internal/PatternBuilder.cs

@@ -0,0 +1,50 @@
+
+namespace Yuuna.Contracts.Patterns
+{
+    using System;
+    using System.Collections;
+    using System.Collections.Generic;
+    using System.Collections.Immutable;
+    using System.Diagnostics;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Linq;
+    using System.Text;
+    using Yuuna.Contracts.Interaction;
+    using Yuuna.Contracts.Optimization;
+    using Yuuna.Contracts.Semantics;
+     
+    internal sealed class PatternBuilder : IPatternBuilder, IGrammarSet
+    {
+        private readonly ImmutableDictionary<IGrammar, Behaviour>.Builder _rules;
+        internal PatternBuilder() 
+        {
+            this._rules = ImmutableDictionary.CreateBuilder<IGrammar, Behaviour>(EqualityComparer<IGrammar>.Default); 
+        }
+        public IImmutableList<IGrammar> ToImmutable()
+        {
+            return this._rules.Keys.ToImmutableArray();
+        }
+        public bool TryGet(IGrammar grammar, out Behaviour behaviour)
+        {
+            return this._rules.TryGetValue(grammar, out behaviour);
+        }
+
+
+        IBehaviourBuilder IPatternBuilder.Build(IGroup group, params IGroup[] rest)
+        {
+            if (group is null)
+                throw new ArgumentNullException(nameof(group));
+            var g = new Grammar();
+            g.Add(group);
+            if (rest != null)
+                foreach (var item in rest)
+                {
+                    g.Add(item);
+                }
+            g.Immute();
+            return new BehaviourBuilder(this._rules.Add, g);
+        }
+
+    }
+
+}

+ 199 - 0
src/Yuuna.Contracts/Plugins/PluginBase.cs

@@ -0,0 +1,199 @@
+
+namespace Yuuna.Contracts.Plugins
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.Immutable;
+    using System.Linq;
+    using System.Runtime.CompilerServices;
+    using System.Text;
+    using Yuuna.Contracts.Interaction;
+    using Yuuna.Contracts.Optimization;
+    using Yuuna.Contracts.Patterns;
+    using Yuuna.Contracts.Semantics;
+    using Yuuna.Contracts.TextSegmention;
+
+    public sealed class Fake : PluginBase
+    {
+        protected override void BuildPatterns(IGroupManager g, IPatternBuilder p)
+        { 
+            g.Define("open").AppendOrCreate(new[] { "打開", "開" });
+            g.Define("door").AppendOrCreate(new[] { "門", "房間門" });
+            g.Define("light").AppendOrCreate(new[] { "燈", "檯燈" });
+            //g.Define("browser").AppendOrCreate(new[] { "瀏覽器", "chrome" });
+
+            p.Build(g["open"], g["door"]).OnInvoke(score =>
+            {
+                var DOOR = new { IS_OPENED = new[] { true, false, true, false }.Random() };
+                // 開門
+                if (!DOOR.IS_OPENED)
+                    return (Moods.Happy, "已經開好門囉 <3");
+                else
+                    return (Moods.Sad, "可是門本來就是開的欸 QAQ");  
+            });
+
+            p.Build(g["open"], g["light"]).OnInvoke(score =>
+            {
+                var LIGHT = new { IS_OPENED = new[] { true, false,true,false }.Random() };
+                // 開門
+                if (!LIGHT.IS_OPENED)
+                    return (Moods.Happy, "已經開好燈囉 <3");
+                else
+                    return (Moods.Sad, "可是燈本來就是開的欸 QAQ");
+            });
+
+            //p.Build(g["open"], g["browser"]).OnInvoke(score =>
+            //{
+            //    var DOOR = new { IS_OPENED = true };
+            //    // 開門
+            //    if (!DOOR.IS_OPENED)
+            //        return (Moods.Happy, "已經開好門囉 <3");
+            //    else
+            //        return (Moods.Sad, "可是門本來就是開的欸 QAQ");
+            //});
+        }
+    }
+
+    public static class RandomHelper
+    {
+        public static T Random<T>(this IEnumerable<T> collection)
+        {
+            if (collection == null)
+                throw new ArgumentNullException(nameof(collection));
+            var list = collection.ToArray();
+            if (list.Length == 0)
+                return default(T);
+            else if (list.Length == 1)
+                return list[0];
+            else
+            {
+                var rnd = new System.Random(Guid.NewGuid().GetHashCode());
+                var index = rnd.Next(0, list.Length);
+                return list[index];
+            }
+        }
+    }
+
+    public class PluginHub
+    {
+        public PluginHub()
+        {
+
+        }
+
+
+
+        public static void Main(string[] args)
+        {
+            // Send("打開門");
+            // Send("開燈");
+             Send("打開燈");
+
+
+        }
+
+        public static void Send(string text)
+        {
+            ITextSegmenter segmenter = new JiebaTextSegmenter();
+            var allPlugins = new PluginBase[] { new Fake() };
+            foreach (var item in allPlugins)
+            {
+                item.Initialize(segmenter);
+                Console.WriteLine("已載入模組: "+item.GetType().AssemblyQualifiedName);
+            }
+
+            Console.WriteLine("我: " + text);
+            var cutted = segmenter.Cut(text);
+            Console.WriteLine($"來自分詞器 {segmenter.Name} 的分詞結果: [ {string.Join(", ",cutted)} ]");
+            var strategy = new DefaultStrategy();
+            var list = new List<IScore>();
+            foreach (var p in allPlugins) 
+                list.AddRange(p.Evaluate(strategy, cutted));
+            list.Sort(ScoreComparer.Default);
+
+            var sb = new StringBuilder("Bot: ");
+            var best = list.LastOrDefault();
+            if (best is null)
+                sb.Append("我不懂你的意思");
+            else if (best.IsCompacted)
+            {
+                var resp = best.Plugin.SelectBest(best).Invoke(best);
+                Console.WriteLine(   resp.Mood.ToString());
+                sb.Append(resp.Message);
+            }
+            else
+            {
+                var choices = list.Where(s => s.Missing.Count <= 1).ToImmutableArray();
+                switch (choices.Length)
+                {
+                    case 1:
+                        {
+                            sb.Append("你" + new[] { "是想", "想要" }.Random() + " ");
+                            sb.Append(choices.Select(x => x.Contrast.ToImmutable().Aggregate(string.Empty, (s, g) => s += g.ToImmutable().Random().ToImmutable().Random())));
+                            sb.Append(" 嗎?");
+                        }
+                        break;
+
+
+                    case 2:
+                        {
+                            sb.Append("你" + new[] { "是想", "想要" }.Random() + " ");
+                            sb.AppendJoin(" 還是 ", choices.Select(x => x.Contrast.ToImmutable().Aggregate(string.Empty, (s, g) => s += g.ToImmutable().Random().ToImmutable().Random())));
+                            sb.Append(" ?");
+                        }
+                        break;
+
+
+                    default:
+                        sb.Append("我不太清楚你想做什麼");
+                        break;
+                }
+            }
+
+            Console.WriteLine(sb);
+
+            //foreach (var topmost in list.Where(x=>x.IsCompacted))
+            //{ 
+            //    Console.WriteLine(topmost.IsCompacted);
+            //    Console.WriteLine("coverage: " + topmost.Coverage * 100.0 +" %");
+            //    Console.WriteLine("missing: "+topmost.Missing.Count);
+
+            //    Console.WriteLine(string.Join(",", topmost.Contrast.SequentialKeys));
+            //    Console.WriteLine();
+            //}
+            //Console.ReadKey();
+        }
+    }
+
+    public abstract class PluginBase
+    {
+        private readonly PatternBuilder _grammarSet;
+        private readonly GroupManager _groupManager;
+         
+
+        public PluginBase()
+        {
+            this._grammarSet = new PatternBuilder();
+            this._groupManager = new GroupManager();
+        }
+
+        internal void Initialize(ITextSegmenter textSegmenter)
+        {
+            this.BuildPatterns(this._groupManager, this._grammarSet);
+            textSegmenter.Load(this._groupManager);
+        }
+
+        protected abstract void BuildPatterns(IGroupManager groupManager, IPatternBuilder grammarBuilder);
+
+        internal IImmutableList<IScore> Evaluate(StrategyBase strategy, IImmutableList<string> cutted)
+        {
+            return strategy.Assess(this, cutted, this._grammarSet);
+        }
+
+        internal Behaviour SelectBest(IScore score)
+        {
+            return this._grammarSet.TryGet(score.Contrast, out var b) ? b : null;
+        }
+    }
+
+}

+ 43 - 0
src/Yuuna.Contracts/Semantics/IGroup.cs

@@ -0,0 +1,43 @@
+namespace Yuuna.Contracts.Semantics
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.Immutable;
+
+    /// <summary>
+    /// 群體物件。用來存放相同性質類型的同義詞物件。
+    /// </summary>
+    public interface IGroup : IEquatable<IGroup>
+    {
+
+        /// <summary>
+        /// 群組名稱。
+        /// </summary>
+        string Key { get; }
+
+        /// <summary>
+        /// 嘗試使用交集取得同義詞物件。
+        /// 若具有交集則將參數中的值附加至該搜尋到的同義詞物件中;若無,則透過參數建立新的同義詞實體並傳回。
+        /// </summary> 
+        /// <param name="synosyms">待加入的同義詞清單。若此同義詞物件不包含任何與此清單相關的詞,則使用此參數建立新同義詞實體。</param>
+        /// <param name="stringComparer">字串比對器。</param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"/>
+        ISynosym AppendOrCreate(IEnumerable<string> synosyms, StringComparer stringComparer = null);
+
+
+        /// <summary>
+        /// 嘗試取得同義詞物件。
+        /// </summary>
+        /// <param name="word">單詞。</param>
+        /// <param name="synosyms">同義詞。</param>
+        /// <returns></returns>
+        bool TryGetSynosym(string word, out ISynosym synosyms);
+
+        /// <summary>
+        /// 轉換成不可變清單以使用 Linq 。
+        /// </summary>
+        /// <returns></returns>
+        IImmutableList<ISynosym> ToImmutable();
+    }
+}

+ 41 - 0
src/Yuuna.Contracts/Semantics/IGroupManager.cs

@@ -0,0 +1,41 @@
+namespace Yuuna.Contracts
+{
+
+    namespace Semantics
+    {
+        using System.Collections.Generic;
+
+        /// <summary>
+        /// 群組物件管理器
+        /// </summary>
+        public interface IGroupManager
+        {
+            /// <summary>
+           /// 嘗試透過群組名稱取得群組物件。
+           /// </summary>
+           /// <param name="key">群組名稱。</param>
+           /// <param name="group">群組物件。</param>
+           /// <returns></returns>
+            bool TryGetGroup(string key, out IGroup group);
+
+            /// <summary>
+            /// 建立新的群組物件。
+            /// </summary>
+            /// <param name="key"></param>
+            /// <returns></returns>
+            IGroup Define(string key);
+
+            /// <summary>
+            /// 群組名稱集合。
+            /// </summary>
+            IReadOnlyCollection<string> Keys { get; }
+
+            /// <summary>
+            /// 透過群組名稱取得群組物件;若無法取得則回傳 <see langword="null"/>。
+            /// </summary>
+            /// <param name="key">群組名稱。</param>
+            /// <returns></returns>
+            IGroup this[string key] { get; }
+        } 
+    }
+}

+ 39 - 0
src/Yuuna.Contracts/Semantics/ISynosym.cs

@@ -0,0 +1,39 @@
+
+namespace Yuuna.Contracts.Semantics
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.Immutable;
+
+    /// <summary>
+    /// 同義詞物件
+    /// </summary>
+    public interface ISynosym : IEquatable<IEnumerable<string>>, IEquatable<string>, IEquatable<ISynosym>
+    {
+        /// <summary>
+        /// 表示該物件隸屬的群體。
+        /// </summary>
+        IGroup Owner { get; }
+        /// <summary>
+        /// 字串比較器。
+        /// </summary>
+        StringComparer StringComparer { get; }
+
+        /// <summary>
+        /// 加入單個單字。
+        /// </summary>
+        /// <param name="word">單字。</param>
+        /// <returns></returns>
+        bool Add(string word);
+
+        /// <summary>
+        /// 加入多個單字。
+        /// </summary>
+        /// <param name="words">單字列表。</param>
+        /// <returns></returns>
+        void AddRange(IEnumerable<string> words);
+
+        IImmutableList<string> ToImmutable();
+    }
+
+}

+ 90 - 0
src/Yuuna.Contracts/Synosym.cs

@@ -0,0 +1,90 @@
+namespace Yuuna.Contracts
+{
+
+    namespace Semantics
+    {
+        using System;
+        using System.Collections;
+        using System.Collections.Generic;
+        using System.Collections.Immutable;
+        using System.Diagnostics;
+        using System.Linq;
+
+        internal sealed class Synosym : ISynosym
+        {
+            public bool Equals(IEnumerable<string> manyStrings)
+            {
+                if (manyStrings is null)
+                    return false;
+                foreach (var str in manyStrings)
+                {
+                    if (this.Equals(str))
+                        return true;
+                }
+                return false;
+            }
+
+            /// <summary>
+            /// 比較該同義詞物件是否與單字等價。這個比較方法會搜尋集合中是否包含該單字。
+            /// </summary>
+            /// <param name="word">單字。</param>
+            /// <returns></returns>
+            public bool Equals(string word)
+            {
+                if (string.IsNullOrWhiteSpace(word))
+                    return false;
+
+                return this._set.Contains(word, this.StringComparer);
+            }
+
+            public bool Equals(ISynosym synosym)
+            {
+                if (synosym is null)
+                    return false;
+                return this.Owner.Equals(synosym.Owner);
+            }
+
+            private readonly static StringComparer DefaultComparer = StringComparer.CurrentCultureIgnoreCase;
+
+            public IGroup Owner { get; }
+
+            private readonly HashSet<string> _set;
+
+            internal Synosym(Group owner, StringComparer stringComparer)
+            {
+                this.StringComparer = stringComparer is null ? DefaultComparer : stringComparer;
+                this._set = new HashSet<string>(this.StringComparer);
+                Debug.Assert(owner != null, "'owner' can't be null");
+                this.Owner = owner ?? throw new ArgumentNullException(nameof(owner));
+            }
+
+            public StringComparer StringComparer { get; }
+
+            public bool Add(string word)
+            {
+                if (string.IsNullOrWhiteSpace(word))
+                    return false;
+
+                // todo: word 可能需要其他檢查
+                // 檢查是否全為 CJK
+
+                return this._set.Add(word.Trim());
+            }
+
+            public void AddRange(IEnumerable<string> words)
+            {
+                if (words is null)
+                    return;
+                foreach (var s in words)
+                     this.Add(s);
+            }
+
+
+
+            public IImmutableList<string> ToImmutable()
+            {
+                return this._set.ToImmutableArray();
+            }
+        }
+    }
+}

+ 19 - 0
src/Yuuna.Contracts/TextSegmention/ITextSegmenter.cs

@@ -0,0 +1,19 @@
+namespace Yuuna.Contracts.TextSegmention
+{
+
+    using Semantics;
+    using System.Collections.Generic;
+    using System.Collections.Immutable;
+    using System.Globalization;
+
+    public interface ITextSegmenter
+    {
+        CultureInfo Culture { get; }
+        string Name { get; }
+
+        IImmutableList<string> Cut(string text);
+
+        void Load(IGroupManager manager);
+    }
+
+}

+ 40 - 0
src/Yuuna.Contracts/TextSegmention/JiebaSegmenter.cs

@@ -0,0 +1,40 @@
+
+namespace Yuuna.Contracts.TextSegmention
+{
+    using JiebaNet.Segmenter;
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.Immutable;
+    using System.Globalization;
+    using System.Text;
+    using Yuuna.Contracts.Semantics;
+
+    public sealed class JiebaTextSegmenter : ITextSegmenter
+    {
+        private readonly JiebaSegmenter _jieba = new JiebaSegmenter();
+
+        string ITextSegmenter.Name => "Jieba.Net";
+        public IImmutableList<string> Cut(string text) => this._jieba.Cut(text, true, true).ToImmutableArray();
+        public void Load(IGroupManager manager)
+        {
+            if (manager == null)
+                return;
+
+            foreach (var name in manager.Keys)
+            {
+                //Console.WriteLine(name);
+                foreach (var synosym in manager[name].ToImmutable())
+                {
+                    //Console.WriteLine("added: " + synosym.ToImmutable().Count);
+                    foreach (var w in synosym.ToImmutable())
+                    {
+                        //Console.WriteLine("added: " + w);
+                        this._jieba.AddWord(w);
+                    }
+                }
+            }
+        }
+
+        public CultureInfo Culture => CultureInfo.GetCultureInfo("zh-TW");
+    }
+}

+ 12 - 0
src/Yuuna.Contracts/Yuuna.Contracts.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Jieba.Net.Core" Version="1.1.0" />
+  </ItemGroup>
+
+</Project>