anim-code.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. function hello(one, two, three) {
  2. console.log(`one: ${one}; two: ${two}; three: ${three}`)
  3. console.log(`one: ${one[two(three)]}; two: ${two(three[one])}; three: [({${three}})]`)
  4. one[two(three)]
  5. //two(three[one])
  6. ;[(three)]
  7. while (nested) {
  8. function nested() {
  9. if (nested) {
  10. console.log('I am nested');
  11. } else {
  12. console.log('I am not')
  13. }
  14. }
  15. }
  16. }
  17. const dogfoodRegex = /(\(|\)|\{|\}|\[|\]|\:|\;|\.|\s+|\`|\'|\"|[^\(\)\{\}\[\]\:\;\.\s\,\`\'\"]+)/gm
  18. console.clear()
  19. const sampleCode = `
  20. console.log(\`hello \${(world)}\`)
  21. console.log('hello \${(world)}')
  22. function hello(one, two, three) {
  23. console.log(\`one: \${one}; two: \${two}; three: \${three}\`)
  24. console.log(\`one: \${one[two(three)]}; two: \${two(three[one])}; three: [({\${three}\})]\`)
  25. one[two(three)]
  26. // two(three[one])
  27. ;[(three)]
  28. while (nested) {
  29. function nested() {
  30. if (nested) {
  31. console.log('I am nested');
  32. } else {
  33. console.log('I am not')
  34. }
  35. /*
  36. if (nested) {
  37. console.log('I am nested');
  38. } else {
  39. console.log('I am not')
  40. }
  41. */
  42. }
  43. }
  44. }
  45. const dogfoodRegex = /(\\(|\\)|\\{|\\}|\\[|\\]|\\:|\\;|\\.|\\s+|\\\`|\\'|\\"|[^\\(\\)\\{\\}\\[\\]\\:\\;\\.\\s\\,\\\`\\'\\"]+)/gm
  46. `
  47. const keywordRegex = /^(do|if|in|for|let|new|try|var|case|else|enum|eval|null|this|true|void|with|break|catch|class|const|false|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/
  48. const parse = code => {
  49. const regex = /(\/\/|\/\*|\*\/|\${|\,|\/|\\|\(|\)|\{|\}|\[|\]|\`|\'|\"|\n| +|\w+|[^\w\s]+)/gm
  50. let match
  51. const ret = []
  52. while (match = regex.exec(code)) {
  53. ret.push(match[1])
  54. }
  55. return ret
  56. }
  57. const defaultScope = ['root', 'paren', 'bracket', 'square', 'interpolation']
  58. const pairs = [
  59. { open: '(', close: ')', name: 'paren', scope: defaultScope },
  60. { open: '{', close: '}', name: 'bracket', scope: defaultScope },
  61. { open: '[', close: ']', name: 'square', scope: defaultScope },
  62. { open: "'", close: "'", name: 'string', scope: defaultScope },
  63. { open: '"', close: '"', name: 'string', scope: defaultScope },
  64. { open: '`', close: '`', name: 'template', scope: defaultScope },
  65. { open: '${', close: '}', name: 'interpolation', scope: ['template'] },
  66. { open: '//', close: '\n', name: 'comment', scope: defaultScope },
  67. { open: '/*', close: '*/', name: 'block-comment', scope: defaultScope }
  68. ]
  69. const organize = codes => {
  70. codes = codes.slice().reverse()
  71. const read = (closer, scope) => {
  72. const ret = []
  73. let escaped = false
  74. while (codes.length) {
  75. const c = codes.pop()
  76. if (c === '\\' && !escaped) {
  77. escaped = true
  78. ret.push({
  79. text: c,
  80. type: 'default'
  81. })
  82. continue
  83. } else if (c === closer && !escaped) {
  84. ret.push({
  85. text: c,
  86. type: scope
  87. })
  88. return ret
  89. } else {
  90. const pair = pairs.find(pair => pair.open === c && pair.scope.includes(scope))
  91. if (!escaped && pair) {
  92. const next = codes[codes.length - 1]
  93. if (/^\s/.test(next)) {
  94. // Block begins with whitespace. Concatenate.
  95. ret.push({
  96. text: c + codes.pop(),
  97. type: 'open-' + pair.name
  98. })
  99. } else {
  100. // Block begins with character. Append only block opener
  101. ret.push({
  102. text: c,
  103. type: 'open-' + pair.name
  104. })
  105. }
  106. const children = read(pair.close, pair.name)
  107. ret.push({
  108. children,
  109. type: pair.name
  110. })
  111. const blockEnd = []
  112. if (peek(children).text === pair.close) {
  113. const blockCloser = children.pop()
  114. blockEnd.push(blockCloser.text)
  115. }
  116. while (/^\s+$/.test(peek(children).text)) {
  117. blockEnd.unshift(children.pop().text)
  118. }
  119. if (blockEnd.length) {
  120. ret.push({
  121. text: blockEnd.join(''),
  122. type: 'close-' + pair.name
  123. })
  124. }
  125. } else {
  126. ret.push({
  127. text: c,
  128. type: 'default'
  129. })
  130. }
  131. }
  132. escaped = false
  133. } // while
  134. return ret
  135. }
  136. return read(null, 'root')
  137. }
  138. const toDom = (codes) => {
  139. const toDom = (codes) => {
  140. return codes.map(code => {
  141. if (Array.isArray(code)) {
  142. const container = document.createElement('span')
  143. toDom(code).forEach(child => container.appendChild(child))
  144. return container
  145. } else {
  146. const textNode = document.createTextNode(code)
  147. const container = document.createElement('span')
  148. container.appendChild(textNode)
  149. return container
  150. }
  151. })
  152. }
  153. return toDom([codes])[0]
  154. }
  155. const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
  156. const anim = ms => Promise.all([ sleep(ms), new Promise(requestAnimationFrame) ])
  157. const peek = (a => a && a.length && a[a.length - 1]) || {}
  158. const isBlock = x => x && x.children
  159. const toDomAsync = async (codes, target, delay) => {
  160. const toDom = async (codes, target) => {
  161. codes = codes.slice().reverse()
  162. const wrap = async (code, target, instant) => {
  163. const container = document.createElement('span')
  164. target.appendChild(container)
  165. container.className = 'code-' + code.type
  166. const checkKeyword = () => {
  167. if (keywordRegex.test(container.innerText)) {
  168. container.classList.add('code-keyword')
  169. } else {
  170. container.classList.remove('code-keyword')
  171. }
  172. }
  173. if (instant) {
  174. container.setAttribute('text', code.text)
  175. container.appendChild(document.createTextNode(code.text))
  176. checkKeyword()
  177. } else {
  178. for (let i = 0; i <= code.text.length; i++) {
  179. container.innerText = ''
  180. container.setAttribute('text', code.text.substr(0, i))
  181. container.appendChild(document.createTextNode(code.text.substr(0, i)))
  182. checkKeyword()
  183. await anim(delay)
  184. }
  185. }
  186. return container
  187. }
  188. while(codes.length) {
  189. const code = codes.pop()
  190. if (Array.isArray(code.children)) {
  191. await toDom(code.children, target)
  192. } else if (isBlock(peek(codes))) {
  193. await wrap(code, target, true)
  194. const container = document.createElement('span')
  195. const children = codes.pop()
  196. container.className = 'code-' + children.type
  197. const next = codes.pop()
  198. target.appendChild(container)
  199. if (next) {
  200. await wrap(next, target, true)
  201. }
  202. await anim(delay)
  203. await toDom(children.children, container)
  204. } else {
  205. await wrap(code, target)
  206. }
  207. await anim(delay)
  208. }
  209. }
  210. target.classList.add('code-root')
  211. await toDom(codes, target)
  212. }
  213. const animateCode =async ({code, target}) => {
  214. const codes = parse(code)
  215. const organized = organize(codes)
  216. await toDomAsync(organized, target, 0)
  217. }
  218. const main = async () => {
  219. const target = document.querySelector('pre')
  220. target.innerHTML = ''
  221. await animateCode({code: sampleCode, target})
  222. await sleep(15000)
  223. await main()
  224. }
  225. main()