-
-
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 | |
} |
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