-
-
Save nebrelbug/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 | |
} |
With the 5th revision I added support for self-closing helpers, throw an error when an invalid character is in a filter name
In revision six I created a currentType
variable and moved buffer.push(str.slice(...
into a separate function
Still slower than Sqrl.Compile
. I'm going to have to get really tricky
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
With the 4th revision I fixed an issue where characters outside of tags, but inside comments, would be added to the buffer