|
@@ -0,0 +1,237 @@
|
|
|
|
|
+
|
|
|
|
|
+ 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()
|