PatternMachine.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. /*
  2. Copyright (c) 2020 Omar Duarte
  3. Unauthorized copying of this file, via any medium is strictly prohibited.
  4. Writen by Omar Duarte, 2020.
  5. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  6. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  7. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  8. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  9. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  10. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  11. THE SOFTWARE.
  12. */
  13. using System.Linq;
  14. namespace PluginMaster
  15. {
  16. public class PatternMachine
  17. {
  18. #region STATES AND TOKENS
  19. public enum PatternState
  20. {
  21. START,
  22. INDEX,
  23. OPENING_PARENTHESIS,
  24. CLOSING_PARENTHESIS,
  25. MULTIPLIER,
  26. ELLIPSIS,
  27. RANDOM_INDEX,
  28. END
  29. }
  30. public class Token
  31. {
  32. public readonly PatternState state = PatternState.START;
  33. protected Token(PatternState state) => this.state = state;
  34. public static Token START = new Token(PatternState.START);
  35. public static Token OPENING_PARENTHESIS = new Token(PatternState.OPENING_PARENTHESIS);
  36. public static Token CLOSING_PARENTHESIS = new Token(PatternState.CLOSING_PARENTHESIS);
  37. public static Token ELLIPSIS = new Token(PatternState.ELLIPSIS);
  38. public static Token END = new Token(PatternState.END);
  39. }
  40. public class IntToken : Token
  41. {
  42. public readonly int value = -1;
  43. public IntToken(int value) : base(PatternState.INDEX) => this.value = value;
  44. }
  45. public class MultiplierToken : Token
  46. {
  47. public readonly int value = -1;
  48. private int _count = 0;
  49. public int count => _count;
  50. public MultiplierToken(int value) : base(PatternState.MULTIPLIER) => this.value = value;
  51. public int IncreaseCount() => ++_count;
  52. public void Reset() => _count = 0;
  53. }
  54. public class RandomIndexToken : Token
  55. {
  56. private static readonly System.Random _random = new System.Random();
  57. public readonly (int index, float weight)[] weights;
  58. private readonly float _totalWeight;
  59. public int GetRandomIndex()
  60. {
  61. var randomValue = (float)(_random.NextDouble() * _totalWeight);
  62. foreach (var (index, weight) in weights)
  63. {
  64. if (randomValue < weight) return index;
  65. randomValue -= weight;
  66. }
  67. return weights.Last().index;
  68. }
  69. public RandomIndexToken((int index, float weight)[] weights) : base(PatternState.RANDOM_INDEX)
  70. {
  71. this.weights = weights;
  72. _totalWeight = weights.Sum(w => w.weight);
  73. }
  74. }
  75. #endregion
  76. #region VALIDATE
  77. public enum ValidationResult
  78. {
  79. VALID,
  80. EMPTY,
  81. INDEX_OUT_OF_RANGE,
  82. MISPLACED_PERIOD,
  83. MISPLACED_ASTERISK,
  84. MISSING_COMMA,
  85. MISPLACED_COMMA,
  86. UNPAIRED_PARENTHESIS,
  87. EMPTY_PARENTHESIS,
  88. INVALID_MULTIPLIER,
  89. UNPAIRED_BRACKET,
  90. EMPTY_BRACKET,
  91. INVALID_NESTED_BRACKETS,
  92. INVALID_PARENTHESES_WITHIN_BRACKETS,
  93. MISPLACED_VERTICAL_BAR,
  94. MISPLACED_COLON,
  95. INVALID_CHARACTER
  96. }
  97. public static ValidationResult Validate(string frequencyPattern, int lastIndex, out Token[] tokens,
  98. out Token[] endTokens)
  99. {
  100. tokens = null;
  101. endTokens = null;
  102. frequencyPattern = frequencyPattern.Replace(" ", "");
  103. if (frequencyPattern == string.Empty) return ValidationResult.EMPTY;
  104. // Validate allowed characters, now including brackets, vertical bars, and colons
  105. var validCharactersRemoved = System.Text.RegularExpressions.Regex.Replace(frequencyPattern,
  106. @"[0-9.,()\[\]:|*]+", "");
  107. if (validCharactersRemoved != string.Empty) return ValidationResult.INVALID_CHARACTER;
  108. // Validate unpaired parentheses
  109. var validBracketsRemoved = System.Text.RegularExpressions.Regex.Replace(frequencyPattern,
  110. @"\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)", "");
  111. if (System.Text.RegularExpressions.Regex.Match(validBracketsRemoved, @"\(|\)").Success)
  112. return ValidationResult.UNPAIRED_PARENTHESIS;
  113. // Validate empty parentheses
  114. if (System.Text.RegularExpressions.Regex.Match(frequencyPattern, @"\(\)").Success)
  115. return ValidationResult.EMPTY_PARENTHESIS;
  116. // Validate unpaired brackets
  117. var validSquareBracketsRemoved = System.Text.RegularExpressions.Regex.Replace(frequencyPattern,
  118. @"\[(?>\[(?<c>)|[^\[\]]+|\](?<-c>))*(?(c)(?!))\]", "");
  119. if (System.Text.RegularExpressions.Regex.Match(validSquareBracketsRemoved, @"\[|\]").Success)
  120. return ValidationResult.UNPAIRED_BRACKET;
  121. // Validate empty brackets
  122. if (System.Text.RegularExpressions.Regex.Match(frequencyPattern, @"\[\]").Success)
  123. return ValidationResult.EMPTY_BRACKET;
  124. // Validate invalid nested structures in brackets
  125. if (System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern, @"\[\[|\]\]"))
  126. return ValidationResult.INVALID_NESTED_BRACKETS;
  127. if (System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern, @"\[\([^\]]*\)\]"))
  128. return ValidationResult.INVALID_PARENTHESES_WITHIN_BRACKETS;
  129. // Validate vertical bars outside brackets
  130. var noBrackets = System.Text.RegularExpressions.Regex.Replace(frequencyPattern, @"\[.*?\]", "");
  131. if (System.Text.RegularExpressions.Regex.IsMatch(noBrackets, @"\|"))
  132. return ValidationResult.MISPLACED_VERTICAL_BAR;
  133. // Validate vertical bars not between numbers within brackets
  134. var noValidBars = System.Text.RegularExpressions.Regex.Replace(frequencyPattern, @"(?<=\d)\|(?=\d)", "");
  135. if (System.Text.RegularExpressions.Regex.IsMatch(noValidBars, @"\|"))
  136. return ValidationResult.MISPLACED_VERTICAL_BAR;
  137. // Validate misplaced colons by removing valid index:weight patterns inside brackets
  138. var validColonsRemoved = System.Text.RegularExpressions.Regex.Replace(frequencyPattern,
  139. @"\[(?>\d+(:\d+)?(\|\d+(:\d+)?)*|[^:\[\]]+)*(?<!:)\]", "");
  140. if (System.Text.RegularExpressions.Regex.Match(validColonsRemoved, @":").Success)
  141. return ValidationResult.MISPLACED_COLON;
  142. // Validate multiplications, allowing them after brackets
  143. var validMultiplicationsRemoved = System.Text.RegularExpressions.Regex.Replace(frequencyPattern,
  144. @"(\d+|\)|\])\*\d+", "");
  145. if (System.Text.RegularExpressions.Regex.Match(validMultiplicationsRemoved, @"\*").Success)
  146. return ValidationResult.MISPLACED_ASTERISK;
  147. // Validate missing commas
  148. if (System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern, @"\]\(|\)\[|\]\[|\)\("))
  149. return ValidationResult.MISSING_COMMA;
  150. if (System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern, @"(?<=[\]\)]|\d)(?=[\[\(])"))
  151. return ValidationResult.MISSING_COMMA;
  152. if (System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern, @"(?<=[\]\)])(?=\d)"))
  153. return ValidationResult.MISSING_COMMA;
  154. if (System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern, @"\[[^\]]*?,[^\]]*?\]"))
  155. return ValidationResult.MISPLACED_COMMA;
  156. var validCommasRemoved = System.Text.RegularExpressions.Regex.Replace(frequencyPattern,
  157. @"(?<=^|\)|\]|\d|\.\.\.),(?=\d|\(|\[)", "");
  158. if (System.Text.RegularExpressions.Regex.Match(validCommasRemoved, @",").Success)
  159. return ValidationResult.MISPLACED_COMMA;
  160. // Validate ellipses inside parentheses
  161. if (System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern, @"\([^\)]*?\.\.\.[^\)]*?\)"))
  162. return ValidationResult.MISPLACED_PERIOD;
  163. // Validate ellipses inside brackets
  164. if (System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern, @"\[[^\]]*?\.\.\.[^\]]*?\]"))
  165. return ValidationResult.MISPLACED_PERIOD;
  166. // Validate single ellipsis in the entire pattern
  167. var ellipsisMatches = System.Text.RegularExpressions.Regex.Matches(frequencyPattern, @"\.\.\.");
  168. if (ellipsisMatches.Count > 1)
  169. return ValidationResult.MISPLACED_PERIOD;
  170. // If there is a single ellipsis, validate its position
  171. if (ellipsisMatches.Count == 1)
  172. {
  173. var ellipsisPosition = ellipsisMatches[0].Index;
  174. // Validate ellipsis is after a number, closing parenthesis, or closing bracket
  175. if (!System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern.Substring(0, ellipsisPosition),
  176. @".*(\d|\)|\])$"))
  177. return ValidationResult.MISPLACED_PERIOD;
  178. // Validate ellipsis is at the end or before a comma
  179. if (!System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern.Substring(ellipsisPosition),
  180. @"^\.\.\.(,|$)"))
  181. return ValidationResult.MISPLACED_PERIOD;
  182. }
  183. // Validate points inside brackets
  184. if (System.Text.RegularExpressions.Regex.IsMatch(frequencyPattern, @"\[[^\]]*?(?<!:)\d+\.\d*[^\]]*?\]"))
  185. return ValidationResult.MISPLACED_PERIOD;
  186. // Validate points outside brackets by removing ellipses and brackets
  187. var noEllipsesOrBrackets = System.Text.RegularExpressions.Regex.Replace(frequencyPattern, @"\[.*?\]|\.\.\.", "");
  188. if (System.Text.RegularExpressions.Regex.IsMatch(noEllipsesOrBrackets, @"\."))
  189. return ValidationResult.MISPLACED_PERIOD;
  190. // Tokenize the input
  191. var matches = System.Text.RegularExpressions.Regex.Matches(frequencyPattern, @"\[.*?\]|\d+|\*\d+|[()|]|\.\.\.");
  192. var tokenList = new System.Collections.Generic.List<Token>();
  193. var endTokenList = new System.Collections.Generic.List<Token>();
  194. tokenList.Add(Token.START);
  195. bool addTokenstoEndList = false;
  196. void AddTokenToList(Token t)
  197. {
  198. if (addTokenstoEndList)
  199. {
  200. endTokenList.Add(t);
  201. }
  202. else
  203. {
  204. tokenList.Add(t);
  205. if (t == Token.ELLIPSIS) addTokenstoEndList = true;
  206. }
  207. }
  208. foreach (System.Text.RegularExpressions.Match match in matches)
  209. {
  210. if (match.Value == "(") AddTokenToList(Token.OPENING_PARENTHESIS);
  211. else if (match.Value == ")") AddTokenToList(Token.CLOSING_PARENTHESIS);
  212. else if (match.Value.Contains("*"))
  213. {
  214. var value = int.Parse(match.Value.Substring(1));
  215. if (value < 2) return ValidationResult.INVALID_MULTIPLIER;
  216. AddTokenToList(new MultiplierToken(value));
  217. }
  218. else if (match.Value == "...") AddTokenToList(Token.ELLIPSIS);
  219. else if (match.Value.StartsWith("["))
  220. {
  221. var content = match.Value.Substring(1, match.Value.Length - 2);
  222. var entries = content.Split('|');
  223. var weights = new System.Collections.Generic.List<(int index, float weight)>();
  224. foreach (var entry in entries)
  225. {
  226. var parts = entry.Split(':');
  227. int index = int.Parse(parts[0]);
  228. float weight = parts.Length > 1 ? float.Parse(parts[1]) : 1.0f;
  229. weights.Add((index, weight));
  230. }
  231. AddTokenToList(new RandomIndexToken(weights.ToArray()));
  232. }
  233. else
  234. {
  235. var value = int.Parse(match.Value);
  236. if (value > lastIndex) return ValidationResult.INDEX_OUT_OF_RANGE;
  237. AddTokenToList(new IntToken(value));
  238. }
  239. }
  240. tokenList.Add(Token.END);
  241. endTokenList.Add(Token.END);
  242. tokens = tokenList.ToArray();
  243. endTokens = endTokenList.ToArray();
  244. return ValidationResult.VALID;
  245. }
  246. #endregion
  247. #region MACHINE
  248. private Token[] _tokens = null;
  249. private int _tokenIndex = 0;
  250. private System.Collections.Generic.Stack<int> _parenthesisStack = new System.Collections.Generic.Stack<int>();
  251. private int _lastParenthesis = -1;
  252. private Token[] _endTokens = null;
  253. public PatternMachine(Token[] tokens, Token[] endTokens) => (_tokens, _endTokens) = (tokens, endTokens);
  254. public void SetTokens(Token[] tokens, Token[] endTokens)
  255. {
  256. if (!Enumerable.SequenceEqual(tokens, _tokens)) _tokens = tokens;
  257. if (!Enumerable.SequenceEqual(endTokens, _endTokens)) _endTokens = endTokens;
  258. }
  259. public void Reset()
  260. {
  261. _tokenIndex = 0;
  262. foreach (var token in _tokens) if (token is MultiplierToken) (token as MultiplierToken).Reset();
  263. }
  264. public int nextIndex
  265. {
  266. get
  267. {
  268. if (_tokenIndex == -1) return -1;
  269. var currentState = _tokens[_tokenIndex].state;
  270. if (currentState == PatternState.END) return -1;
  271. ++_tokenIndex;
  272. var nextToken = _tokens[_tokenIndex];
  273. switch (nextToken.state)
  274. {
  275. case PatternState.INDEX:
  276. return (nextToken as IntToken).value;
  277. case PatternState.OPENING_PARENTHESIS:
  278. _parenthesisStack.Push(_tokenIndex);
  279. break;
  280. case PatternState.CLOSING_PARENTHESIS:
  281. _lastParenthesis = _parenthesisStack.Pop();
  282. break;
  283. case PatternState.MULTIPLIER:
  284. var mult = nextToken as MultiplierToken;
  285. if (mult.IncreaseCount() < mult.value)
  286. _tokenIndex = currentState == PatternState.CLOSING_PARENTHESIS
  287. ? _lastParenthesis - 1 : _tokenIndex -= 2;
  288. break;
  289. case PatternState.ELLIPSIS:
  290. _tokenIndex = currentState == PatternState.CLOSING_PARENTHESIS
  291. ? _lastParenthesis - 1 : _tokenIndex -= 2;
  292. break;
  293. case PatternState.RANDOM_INDEX:
  294. var randomIndexToken = nextToken as RandomIndexToken;
  295. return randomIndexToken.GetRandomIndex();
  296. case PatternState.END:
  297. return -1;
  298. default:
  299. break;
  300. }
  301. return nextIndex;
  302. }
  303. }
  304. public int[] GetEndIndexes()
  305. {
  306. int tokenIdx = 0;
  307. foreach (var token in _endTokens) if (token is MultiplierToken) (token as MultiplierToken).Reset();
  308. var currentState = _endTokens[0].state;
  309. if (currentState == PatternState.END) return new int[0];
  310. var indexesList = new System.Collections.Generic.List<int>();
  311. var parenthesisStack = new System.Collections.Generic.Stack<int>();
  312. var lastParenthesis = -1;
  313. while (currentState != PatternState.END)
  314. {
  315. var nextToken = _endTokens[tokenIdx];
  316. switch (nextToken.state)
  317. {
  318. case PatternState.INDEX:
  319. indexesList.Add((nextToken as IntToken).value);
  320. break;
  321. case PatternState.OPENING_PARENTHESIS:
  322. parenthesisStack.Push(tokenIdx);
  323. break;
  324. case PatternState.CLOSING_PARENTHESIS:
  325. lastParenthesis = parenthesisStack.Pop();
  326. break;
  327. case PatternState.MULTIPLIER:
  328. var mult = nextToken as MultiplierToken;
  329. if (mult.IncreaseCount() < mult.value)
  330. tokenIdx = currentState == PatternState.CLOSING_PARENTHESIS
  331. ? lastParenthesis - 1 : tokenIdx = tokenIdx - 2;
  332. break;
  333. case PatternState.ELLIPSIS:
  334. tokenIdx = currentState == PatternState.CLOSING_PARENTHESIS ? lastParenthesis - 1 : tokenIdx - 2;
  335. break;
  336. case PatternState.RANDOM_INDEX:
  337. var randomIndexToken = nextToken as RandomIndexToken;
  338. indexesList.Add(randomIndexToken.GetRandomIndex());
  339. break;
  340. default: break;
  341. }
  342. ++tokenIdx;
  343. currentState = nextToken.state;
  344. }
  345. return indexesList.ToArray();
  346. }
  347. public int tokenIndex => _tokenIndex;
  348. public void SetTokenIndex(int value)
  349. {
  350. if (value <= 0 || value >= _tokens.Length) return;
  351. _tokenIndex = value;
  352. }
  353. #endregion
  354. }
  355. }