Advertisement
Guest User

Untitled

a guest
Oct 28th, 2016
65
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.94 KB | None | 0 0
  1. /**
  2. * Parses a message and replaces all [a,b,c] structures with a random
  3. * selection.
  4. *
  5. * Supports nested structures and escaping those special characters.
  6. *
  7. * The general approach is to make a single pass through the whole message
  8. * character by character, keeping a stack of lists representing all the choices
  9. * at the current index. The outer stack tracks the current nesting level while
  10. * the inner list tracks the choices at the given nesting level.
  11. *
  12. * There are two approaches to dealing with the lowest nesting level (zero):
  13. * - A special case understood at every operation (operation = processing a special char)
  14. * - Treat it just like any other nesting level, except it will only ever have one choice
  15. *
  16. * We chose the second approach.
  17. */
  18. function parse(message) {
  19. // These characters can be escaped - attempting to escape any other character (eg \a)
  20. // will simply result in the literal string '\\a'. This behaviour may or may not be
  21. // wanted and can be easily changed to silently "escape" any character.
  22. var specialChars = [
  23. '\\',
  24. '[',
  25. ',',
  26. ']'
  27. ];
  28.  
  29. // Current character index
  30. var ptr = 0;
  31.  
  32. /**
  33. * Each nesting level contains a list of the current choices as well as a
  34. * single boolean indicating whether the latest choice is 'complete'.
  35. *
  36. * This is important when you have something like '[pre[a,b],c]' and the string 'pre'
  37. * needs to be concatenated with the result of '[a,b]' instead of being treated
  38. * as a seperate choice.
  39. */
  40. var stack = [{
  41. 'incomplete': false,
  42. 'choices': []
  43. }];
  44.  
  45. // Temporary 'register' of sorts for the current string until a control character is reached
  46. var curString = '';
  47.  
  48. // Whether we've hit an escaping backslash and the current character should be ignored
  49. var escaped = false;
  50.  
  51. while (ptr < message.length) {
  52. if (escaped) {
  53. if (specialChars.indexOf(message[ptr]) !== -1) {
  54. curString += message[ptr];
  55. } else {
  56. curString += '\\' + message[ptr];
  57. }
  58.  
  59. escaped = false;
  60. ptr++;
  61. continue;
  62. }
  63.  
  64. if (message[ptr] === '\\') {
  65. escaped = true;
  66. } else if (message[ptr] === '[') {
  67. // Store the current partial string as an incomplete choice
  68. addToStack(stack, curString);
  69. stack[0].incomplete = true;
  70.  
  71. // New nesting level
  72. stack.unshift({
  73. 'incomplete': false,
  74. 'choices': []
  75. });
  76.  
  77. curString = '';
  78. } else if (message[ptr] === ',') {
  79. // Store the current string as a complete choice
  80. addToStack(stack, curString);
  81. stack[0].incomplete = false;
  82.  
  83. curString = '';
  84. } else if (message[ptr] === ']') {
  85. if (stack.length <= 1) {
  86. // Ignore any ] that don't have a matching [
  87. // Lowest nesting level is a special case that doesn't truly have a [
  88. curString += message[ptr];
  89. } else {
  90. // Store the current string as a complete choice
  91. addToStack(stack, curString);
  92. stack[0].incomplete = false;
  93.  
  94. // After picking a random choice from the current completed nesting level,
  95. // remove this level and add the string to the underlying nesting level
  96. curString = selectRand(stack[0].choices);
  97. stack.shift();
  98. addToStack(stack, curString);
  99.  
  100. curString = '';
  101. }
  102. } else {
  103. curString += message[ptr];
  104. }
  105.  
  106. ptr++;
  107. }
  108.  
  109. // Commit any straggling partial string
  110. addToStack(stack, curString);
  111.  
  112. /**
  113. * Collapse any unfinished stacks - improperly formed syntax resulted
  114. * in [ without closing ] so they will be reconstructed into the original string
  115. */
  116. while (stack.length > 0) {
  117. if (stack.length === 1) {
  118. // Special case for the lowest nesting level which didn't really start with a [
  119. curString = '';
  120. } else {
  121. curString = '[';
  122. }
  123.  
  124. // Gotta reverse because we stored the list in reverse
  125. stack[0].choices.reverse();
  126. curString += stack[0].choices.join(',');
  127. stack.shift();
  128.  
  129. addToStack(stack, curString);
  130. }
  131.  
  132. return curString;
  133. }
  134.  
  135. /**
  136. * Pick a random item from an array.
  137. */
  138. function selectRand(arr) {
  139. var index = Math.floor(Math.random() * arr.length);
  140. return arr[index];
  141. }
  142.  
  143. /**
  144. * Adds the current partially built string to the list of choices
  145. * at the current nesting level.
  146. *
  147. * Correctly concatenates to the last choice or appends a new choice
  148. * depending on the 'incomplete' flag.
  149. */
  150. function addToStack(stack, str) {
  151. if (stack.length === 0) {
  152. return;
  153. }
  154.  
  155. if (stack[0].incomplete) {
  156. stack[0].choices[0] += str;
  157. } else {
  158. stack[0].choices.unshift(str);
  159. }
  160. }
  161.  
  162. function runTests() {
  163. var tests = [{
  164. 'input': '',
  165. 'output': ['']
  166. }, {
  167. 'input': 'hello',
  168. 'output': ['hello']
  169. }, {
  170. 'input': '[hello',
  171. 'output': ['[hello'],
  172. }, {
  173. 'input': 'hello]',
  174. 'output': ['hello]'],
  175. }, {
  176. 'input': 'a [ hello',
  177. 'output': ['a [ hello']
  178. }, {
  179. 'input': 'hello ] a',
  180. 'output': ['hello ] a']
  181. }, {
  182. 'input': '[hello]',
  183. 'output': ['hello'],
  184. }, {
  185. 'input': '[a,b,c]',
  186. 'output': ['a', 'b', 'c']
  187. }, {
  188. 'input': '[a,[1,2,3],c]',
  189. 'output': ['a', 'b', '1', '2', '3', 'c']
  190. }, {
  191. 'input': '[a,b][c,d]',
  192. 'output': ['ac', 'ad', 'bc', 'bd']
  193. }, {
  194. 'input': '[[a,b],[1,2]]',
  195. 'output': ['a', 'b', '1', '2']
  196. }, {
  197. 'input': 'pre[a,b,c]post',
  198. 'output': ['preapost', 'prebpost', 'precpost']
  199. }, {
  200. 'input': 'a[hi[1,2]ho,ha]b',
  201. 'output': ['ahi1hob', 'ahi2hob', 'ahab']
  202. }, {
  203. 'input': 'a[b[[[',
  204. 'output': ['a[b[[[']
  205. }, {
  206. 'input': '[a,b][[',
  207. 'output': ['a[[', 'b[[']
  208. }, {
  209. 'input': '[a,b]]]',
  210. 'output': ['a]]', 'b]]']
  211. }, {
  212. 'input': '\\[a,b\\]',
  213. 'output': ['[a,b]']
  214. }]
  215.  
  216. for (var i = 0; i < tests.length; i++) {
  217. var passed = true;
  218. for (var r = 0; r < 1000; r++) {
  219. if (tests[i].output.indexOf(parse(tests[i].input)) === -1) {
  220. passed = false;
  221. }
  222. }
  223.  
  224. if (passed) {
  225. console.log('Test passed');
  226. } else {
  227. console.log('Test failed: ', tests[i].input);
  228. }
  229. }
  230. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement