function hello(one, two, three) { console.log(`one: ${one}; two: ${two}; three: ${three}`) console.log(`one: ${one[two(three)]}; two: ${two(three[one])}; three: [({${three}})]`) one[two(three)] //two(three[one]) ;[(three)] while (nested) { function nested() { if (nested) { console.log('I am nested'); } else { console.log('I am not') } } } } const dogfoodRegex = /(\(|\)|\{|\}|\[|\]|\:|\;|\.|\s+|\`|\'|\"|[^\(\)\{\}\[\]\:\;\.\s\,\`\'\"]+)/gm console.clear() const sampleCode = ` console.log(\`hello \${(world)}\`) console.log('hello \${(world)}') function hello(one, two, three) { console.log(\`one: \${one}; two: \${two}; three: \${three}\`) console.log(\`one: \${one[two(three)]}; two: \${two(three[one])}; three: [({\${three}\})]\`) one[two(three)] // two(three[one]) ;[(three)] while (nested) { function nested() { if (nested) { console.log('I am nested'); } else { console.log('I am not') } /* if (nested) { console.log('I am nested'); } else { console.log('I am not') } */ } } } const dogfoodRegex = /(\\(|\\)|\\{|\\}|\\[|\\]|\\:|\\;|\\.|\\s+|\\\`|\\'|\\"|[^\\(\\)\\{\\}\\[\\]\\:\\;\\.\\s\\,\\\`\\'\\"]+)/gm ` 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)$/ const parse = code => { const regex = /(\/\/|\/\*|\*\/|\${|\,|\/|\\|\(|\)|\{|\}|\[|\]|\`|\'|\"|\n| +|\w+|[^\w\s]+)/gm let match const ret = [] while (match = regex.exec(code)) { ret.push(match[1]) } return ret } const defaultScope = ['root', 'paren', 'bracket', 'square', 'interpolation'] const pairs = [ { open: '(', close: ')', name: 'paren', scope: defaultScope }, { open: '{', close: '}', name: 'bracket', scope: defaultScope }, { open: '[', close: ']', name: 'square', scope: defaultScope }, { open: "'", close: "'", name: 'string', scope: defaultScope }, { open: '"', close: '"', name: 'string', scope: defaultScope }, { open: '`', close: '`', name: 'template', scope: defaultScope }, { open: '${', close: '}', name: 'interpolation', scope: ['template'] }, { open: '//', close: '\n', name: 'comment', scope: defaultScope }, { open: '/*', close: '*/', name: 'block-comment', scope: defaultScope } ] const organize = codes => { codes = codes.slice().reverse() const read = (closer, scope) => { const ret = [] let escaped = false while (codes.length) { const c = codes.pop() if (c === '\\' && !escaped) { escaped = true ret.push({ text: c, type: 'default' }) continue } else if (c === closer && !escaped) { ret.push({ text: c, type: scope }) return ret } else { const pair = pairs.find(pair => pair.open === c && pair.scope.includes(scope)) if (!escaped && pair) { const next = codes[codes.length - 1] if (/^\s/.test(next)) { // Block begins with whitespace. Concatenate. ret.push({ text: c + codes.pop(), type: 'open-' + pair.name }) } else { // Block begins with character. Append only block opener ret.push({ text: c, type: 'open-' + pair.name }) } const children = read(pair.close, pair.name) ret.push({ children, type: pair.name }) const blockEnd = [] if (peek(children).text === pair.close) { const blockCloser = children.pop() blockEnd.push(blockCloser.text) } while (/^\s+$/.test(peek(children).text)) { blockEnd.unshift(children.pop().text) } if (blockEnd.length) { ret.push({ text: blockEnd.join(''), type: 'close-' + pair.name }) } } else { ret.push({ text: c, type: 'default' }) } } escaped = false } // while return ret } return read(null, 'root') } const toDom = (codes) => { const toDom = (codes) => { return codes.map(code => { if (Array.isArray(code)) { const container = document.createElement('span') toDom(code).forEach(child => container.appendChild(child)) return container } else { const textNode = document.createTextNode(code) const container = document.createElement('span') container.appendChild(textNode) return container } }) } return toDom([codes])[0] } const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) const anim = ms => Promise.all([ sleep(ms), new Promise(requestAnimationFrame) ]) const peek = (a => a && a.length && a[a.length - 1]) || {} const isBlock = x => x && x.children const toDomAsync = async (codes, target, delay) => { const toDom = async (codes, target) => { codes = codes.slice().reverse() const wrap = async (code, target, instant) => { const container = document.createElement('span') target.appendChild(container) container.className = 'code-' + code.type const checkKeyword = () => { if (keywordRegex.test(container.innerText)) { container.classList.add('code-keyword') } else { container.classList.remove('code-keyword') } } if (instant) { container.setAttribute('text', code.text) container.appendChild(document.createTextNode(code.text)) checkKeyword() } else { for (let i = 0; i <= code.text.length; i++) { container.innerText = '' container.setAttribute('text', code.text.substr(0, i)) container.appendChild(document.createTextNode(code.text.substr(0, i))) checkKeyword() await anim(delay) } } return container } while(codes.length) { const code = codes.pop() if (Array.isArray(code.children)) { await toDom(code.children, target) } else if (isBlock(peek(codes))) { await wrap(code, target, true) const container = document.createElement('span') const children = codes.pop() container.className = 'code-' + children.type const next = codes.pop() target.appendChild(container) if (next) { await wrap(next, target, true) } await anim(delay) await toDom(children.children, container) } else { await wrap(code, target) } await anim(delay) } } target.classList.add('code-root') await toDom(codes, target) } const animateCode =async ({code, target}) => { const codes = parse(code) const organized = organize(codes) await toDomAsync(organized, target, 0) } const main = async () => { const target = document.querySelector('pre') target.innerHTML = '' await animateCode({code: sampleCode, target}) await sleep(15000) await main() } main()