-
-
Save bgub/7f1d0d0c80b90c86ed629cc8a10e6cb5 to your computer and use it in GitHub Desktop.
| // Version 1.0.21 | |
| var parseTag = require('./TagParse') | |
| module.exports = function Parse (str, tagOpen, tagClose) { | |
| var lastIndex = 0 // Because lastIndex can be complicated, and this way the minifier can minify more | |
| var regEx = new RegExp(tagOpen + '(-)?([^]*?)(-)?' + tagClose, 'g') | |
| var stringLength = str.length | |
| function parseContext (parentObj, firstParse) { | |
| var lastBlock = false | |
| var buffer = [] | |
| function pushString (indx) { | |
| if (lastIndex !== indx) { | |
| buffer.push( | |
| str | |
| .slice(lastIndex, indx) | |
| .replace(/\\/g, '\\\\') | |
| .replace(/'/g, "\\'") | |
| ) | |
| } | |
| } | |
| // Random TODO: parentObj.b doesn't need to have t: # | |
| var m | |
| while ((m = regEx.exec(str)) !== null) { | |
| pushString(m.index) | |
| lastIndex = regEx.lastIndex // TODO: Check performance gains | |
| var currentObj = parseTag(m) | |
| // ===== NOW ADD THE OBJECT TO OUR BUFFER ===== | |
| var currentType = currentObj.t | |
| if (currentType === '~') { | |
| currentObj = parseContext(currentObj) // No need to pass in false, undefined is falsy | |
| buffer.push(currentObj) | |
| } else if (currentType === '/') { | |
| if (parentObj.n === currentObj.n) { | |
| if (lastBlock) { | |
| lastBlock.d = buffer | |
| parentObj.b.push(lastBlock) | |
| } else { | |
| parentObj.d = buffer | |
| } | |
| // console.log('parentObj: ' + JSON.stringify(parentObj)) | |
| return parentObj | |
| } else { | |
| throw Error("Helper start and end don't match") | |
| } | |
| } else if (currentType === '#') { | |
| if (lastBlock) { | |
| lastBlock.d = buffer | |
| parentObj.b.push(lastBlock) | |
| } else { | |
| parentObj.d = buffer | |
| parentObj.b = [] // Create a new array to store the parent's blocks | |
| } | |
| lastBlock = currentObj // Set the 'lastBlock' object to the value of the current block | |
| buffer = [] | |
| } else { | |
| buffer.push(currentObj) | |
| } | |
| // ===== DONE ADDING OBJECT TO BUFFER ===== | |
| } | |
| if (firstParse) { | |
| // TODO: more intuitive | |
| pushString(stringLength) | |
| parentObj.d = buffer | |
| return parentObj | |
| } | |
| return parentObj | |
| } | |
| var parseResult = parseContext({}, true) | |
| // console.log(JSON.stringify(parseResult)) | |
| return parseResult.d // Parse the very outside context | |
| } |
| // Version 1.0.21 | |
| module.exports = function parseTag (match) { | |
| // console.log(JSON.stringify(match)) | |
| var currentObj = { s: match[1], e: match[3] } | |
| var innerTag = match[2].trim() | |
| if (/^\/\*[^]*\*\/$/.test(innerTag) || !innerTag) { | |
| // It's a comment, or innerTag is blank: like {{}}. TODO: Run tests | |
| return currentObj | |
| } | |
| var escaped = false | |
| var currentAttribute = '' // Valid values: 'c'=content, 'f'=filter, 'fp'=filter params, 'p'=param, 'n'=name | |
| var quoteType // Valid values: '"', "'", "`", false | |
| var insideQuotes = false // Valid values: true, false | |
| var numParens = 0 | |
| var filterNumber = 0 | |
| var currentType = '' | |
| var startInd = 0 | |
| function addAttrValue (indx, strng) { | |
| var val = (innerTag.slice(startInd, indx) + (strng || '')).trim() | |
| if (currentAttribute === 'f') { | |
| currentObj.f[filterNumber - 1][0] += val // filterNumber - 1 because first filter: 0->1, but zero-indexed arrays | |
| } else if (currentAttribute === 'fp') { | |
| currentObj.f[filterNumber - 1][1] += val | |
| } else if (currentAttribute !== '') { | |
| if (currentObj[currentAttribute]) { | |
| currentObj[currentAttribute] += val | |
| } else { | |
| currentObj[currentAttribute] = val | |
| } | |
| } | |
| startInd = indx + 1 | |
| } | |
| var i = 0 | |
| for (; i < innerTag.length; i++) { | |
| var char = innerTag[i] | |
| if (currentType === '') { | |
| startInd = i + 1 // Default | |
| currentAttribute = 'c' // Default | |
| currentType = char // Default | |
| if (/[a-zA-Z$_]/.test(char)) { | |
| currentType = 'r' // Reference | |
| startInd -= 1 // Include the first character | |
| } else if (char === '~' || char === '#' || char === '/') { | |
| currentAttribute = 'n' | |
| } else if (char === '=' /* || char === '>' */ || char === '!') { | |
| // Do nothing | |
| } else if (char === '@') { | |
| currentObj.l = getHrefScope(i, innerTag) | |
| i += 3 * currentObj.l | |
| startInd += 3 * currentObj.l // TODO: Eventually, put this in getHrefScope | |
| } else { | |
| currentType = 'c' // Custom | |
| startInd -= 1 // Include the first character | |
| } | |
| } else { | |
| if (char === '\\') { | |
| escaped = !escaped // Toggle the escape | |
| } else if (!escaped && (char === '"' || char === "'" || char === '`')) { | |
| // Test if it's a valid quote | |
| if (insideQuotes && quoteType === char) { | |
| // If inside quotes, and the quote type is the current char | |
| // then this is a closing quote | |
| insideQuotes = false | |
| quoteType = '' // We should be able to remove this... | |
| } else if (!insideQuotes) { | |
| insideQuotes = true | |
| quoteType = char | |
| } | |
| } else if (!insideQuotes) { | |
| if (char === '@') { | |
| var j = getHrefScope(i, innerTag) | |
| // str.slice includes that index | |
| addAttrValue(i, 'h.r(' + j + ').') | |
| /* In the generated function: 'function hr(a,b){return a[a.length-1-b]}' */ | |
| i += 3 * j | |
| startInd = i + 1 | |
| } else if ( | |
| currentAttribute === 'f' && | |
| currentType === '~' && | |
| char === '/' | |
| ) { | |
| // Assume it's a self-closing helper | |
| addAttrValue(i - 1) | |
| startInd += 1 | |
| // I removed error checking, all error checking for bad syntax will be done in the Compiler | |
| } else if (char === '(' && !escaped) { | |
| if (numParens === 0) { | |
| if (currentAttribute === 'n') { | |
| addAttrValue(i) | |
| currentAttribute = 'p' | |
| } else if (currentAttribute === 'f') { | |
| addAttrValue(i) | |
| currentAttribute = 'fp' | |
| } | |
| } | |
| numParens++ | |
| } else if (char === ')' && !escaped) { | |
| numParens-- | |
| if (numParens === 0 && currentAttribute !== 'c') { | |
| // Then it's closing a filter, block, or helper | |
| addAttrValue(i) | |
| currentAttribute = '' // Reset the current attribute | |
| } | |
| } else if ( | |
| numParens === 0 && | |
| !escaped && | |
| char === '|' && | |
| innerTag[i - 1] !== '|' && // Checking to make sure it's not an OR || | |
| innerTag[i + 1] !== '|' && | |
| (currentType === '@' || currentType === 'r' || currentType === '~') // TODO: Add >? | |
| ) { | |
| addAttrValue(i) | |
| currentAttribute = 'f' | |
| if (filterNumber === 0) { | |
| currentObj.f = [] // Initial assign | |
| } | |
| filterNumber++ | |
| currentObj.f[filterNumber - 1] = ['', ''] | |
| } else { | |
| // It's a regular character | |
| escaped = false | |
| } | |
| } else { | |
| // Inside quotes and not one of '"` | |
| escaped = false | |
| } | |
| } | |
| } | |
| // ===== NOW LAST STEPS, RETURN CURRENTOBJ ===== | |
| addAttrValue(i) | |
| if ( | |
| innerTag.slice(-1) === '/' && // Self-closing helper. innerTag.slice(-1) returns last character of innerTag | |
| currentType === '~' // Make sure it is a helper | |
| ) { | |
| currentType = 's' // For self-closing | |
| } | |
| currentObj.t = currentType | |
| return currentObj | |
| } | |
| function getHrefScope (indx, str) { | |
| var j = 0 // Number of '../' | |
| while ( | |
| str[indx + 3 * j + 1] === '.' && | |
| str[indx + 3 * j + 2] === '.' && | |
| str[indx + 3 * j + 3] === '/' | |
| ) { | |
| j++ // TODO: Change to += 3 (ACTUALLY PROBABLY DON'T) | |
| } | |
| return j | |
| } |
The 8th revision makes it significantly faster
With the 9th revision, PARSING IS FASTER THAN SQRL.COMPILE!
Instead of adding each individual character to currentObj[currentAttribute], I push a sliced string (using addAttrValue()) containing the last characters. I do this whenever the attribute changes or the tag closes
Revision 10 fixes an issue where {{val}}{{someval}} wasn't being parsed correctly.
Instead of i += cTag.length, I have i += cTag.length -1 and lastInd = i + 1
More intelligent tags with revision 11
Revision 12, which I like to call speedyTags2 or speedyTagsCached, creates a variable to hold tagOpen.length (it also renamed oTag to tagOpen ) and tagClose.length
Benchmarks (Compile is the old version of Squirrelly, turned into a string)
Compile x 59,106 ops/sec ±1.06% (92 runs sampled)
Parse#WithBrackets x 71,463 ops/sec ±1.23% (91 runs sampled)
Parse#SpeedyTags x 88,479 ops/sec ±1.16% (96 runs sampled)
Parse#SpeedyTagsCached x 91,750 ops/sec ±0.59% (93 runs sampled)
Fastest is Parse#SpeedyTagsCached
Revision 13 uses indexOf so it's faster and more concise.
It also slightly modifies the pushString function
Revision 15 DOES NOT WORK but it's to save a WIP
Revision 16 should work again
With the 18th revision, I refactored the parser into 2 parts: Parse.js, which handles high-level TT (Template Tree - I may come up with a better name) generation, and TagParse.js, which handles parsing inside of tags. Additionally, I refactored so the Parser uses RegExp to loop through tags
Revision 19: IT WORKS!
With version 21 I updated {l and r} to {s, e} since l was already taken
Still slower than
Sqrl.Compile. I'm going to have to get really tricky