prism-naniscript.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. (function (Prism) {
  2. var expressionDef = /\{[^\r\n\[\]{}]*\}/;
  3. var params = {
  4. 'quoted-string': {
  5. pattern: /"(?:[^"\\]|\\.)*"/,
  6. alias: 'operator'
  7. },
  8. 'command-param-id': {
  9. pattern: /(\s)\w+:/,
  10. lookbehind: true,
  11. alias: 'property'
  12. },
  13. 'command-param-value': [
  14. {
  15. pattern: expressionDef,
  16. alias: 'selector',
  17. },
  18. {
  19. pattern: /([\t ])\S+/,
  20. lookbehind: true,
  21. greedy: true,
  22. alias: 'operator',
  23. },
  24. {
  25. pattern: /\S(?:.*\S)?/,
  26. alias: 'operator',
  27. }
  28. ]
  29. };
  30. Prism.languages.naniscript = {
  31. // ; ...
  32. 'comment': {
  33. pattern: /^([\t ]*);.*/m,
  34. lookbehind: true,
  35. },
  36. // > ...
  37. // Define is a control line starting with '>' followed by a word, a space and a text.
  38. 'define': {
  39. pattern: /^>.+/m,
  40. alias: 'tag',
  41. inside: {
  42. 'value': {
  43. pattern: /(^>\w+[\t ]+)(?!\s)[^{}\r\n]+/,
  44. lookbehind: true,
  45. alias: 'operator'
  46. },
  47. 'key': {
  48. pattern: /(^>)\w+/,
  49. lookbehind: true,
  50. }
  51. }
  52. },
  53. // # ...
  54. 'label': {
  55. pattern: /^([\t ]*)#[\t ]*\w+[\t ]*$/m,
  56. lookbehind: true,
  57. alias: 'regex'
  58. },
  59. 'command': {
  60. pattern: /^([\t ]*)@\w+(?=[\t ]|$).*/m,
  61. lookbehind: true,
  62. alias: 'function',
  63. inside: {
  64. 'command-name': /^@\w+/,
  65. 'expression': {
  66. pattern: expressionDef,
  67. greedy: true,
  68. alias: 'selector'
  69. },
  70. 'command-params': {
  71. pattern: /\s*\S[\s\S]*/,
  72. inside: params
  73. },
  74. }
  75. },
  76. // Generic is any line that doesn't start with operators: ;>#@
  77. 'generic-text': {
  78. pattern: /(^[ \t]*)[^#@>;\s].*/m,
  79. lookbehind: true,
  80. alias: 'punctuation',
  81. inside: {
  82. // \{ ... \} ... \[ ... \] ... \"
  83. 'escaped-char': /\\[{}\[\]"]/,
  84. 'expression': {
  85. pattern: expressionDef,
  86. greedy: true,
  87. alias: 'selector'
  88. },
  89. 'inline-command': {
  90. pattern: /\[[\t ]*\w[^\r\n\[\]]*\]/,
  91. greedy: true,
  92. alias: 'function',
  93. inside: {
  94. 'command-params': {
  95. pattern: /(^\[[\t ]*\w+\b)[\s\S]+(?=\]$)/,
  96. lookbehind: true,
  97. inside: params
  98. },
  99. 'command-param-name': {
  100. pattern: /^(\[[\t ]*)\w+/,
  101. lookbehind: true,
  102. alias: 'name',
  103. },
  104. 'start-stop-char': /[\[\]]/,
  105. }
  106. },
  107. }
  108. }
  109. };
  110. Prism.languages.nani = Prism.languages['naniscript'];
  111. /** @typedef {InstanceType<import("./prism-core")["Token"]>} Token */
  112. /**
  113. * This hook is used to validate generic-text tokens for balanced brackets.
  114. * Mark token as bad-line when contains not balanced brackets: {},[]
  115. */
  116. Prism.hooks.add('after-tokenize', function (env) {
  117. /** @type {(Token | string)[]} */
  118. var tokens = env.tokens;
  119. tokens.forEach(function (token) {
  120. if (typeof token !== 'string' && token.type === 'generic-text') {
  121. var content = getTextContent(token);
  122. if (!isBracketsBalanced(content)) {
  123. token.type = 'bad-line';
  124. token.content = content;
  125. }
  126. }
  127. });
  128. });
  129. /**
  130. * @param {string} input
  131. * @returns {boolean}
  132. */
  133. function isBracketsBalanced(input) {
  134. var brackets = '[]{}';
  135. var stack = [];
  136. for (var i = 0; i < input.length; i++) {
  137. var bracket = input[i];
  138. var bracketsIndex = brackets.indexOf(bracket);
  139. if (bracketsIndex !== -1) {
  140. if (bracketsIndex % 2 === 0) {
  141. stack.push(bracketsIndex + 1);
  142. } else if (stack.pop() !== bracketsIndex) {
  143. return false;
  144. }
  145. }
  146. }
  147. return stack.length === 0;
  148. }
  149. /**
  150. * @param {string | Token | (string | Token)[]} token
  151. * @returns {string}
  152. */
  153. function getTextContent(token) {
  154. if (typeof token === 'string') {
  155. return token;
  156. } else if (Array.isArray(token)) {
  157. return token.map(getTextContent).join('');
  158. } else {
  159. return getTextContent(token.content);
  160. }
  161. }
  162. }(Prism));