From 80e9b259cca878b0512defb8c3137f0305ae3ce3 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Gangumalla Date: Tue, 20 Aug 2024 16:34:33 +0530 Subject: [PATCH 1/3] Added initial functionality for nested-forloop Added initial functionality to handle nested for-loop Signed-off-by: Suresh Kumar Gangumalla --- src/lib/codegenerator/generator.js | 71 +++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/src/lib/codegenerator/generator.js b/src/lib/codegenerator/generator.js index ab97fea3..209fdb6a 100644 --- a/src/lib/codegenerator/generator.js +++ b/src/lib/codegenerator/generator.js @@ -16,6 +16,9 @@ */ let counter +let nestedForDepth +let scopeCounter +let forEndCounterMap export default function (templateObject = { children: [] }) { const ctx = { @@ -25,6 +28,9 @@ export default function (templateObject = { children: [] }) { } counter = -1 + scopeCounter = -1 + nestedForDepth = -1 + forEndCounterMap = new Map() generateCode.call(ctx, templateObject) ctx.renderCode.push('return elms') @@ -264,11 +270,23 @@ const generateComponentCode = function ( } const generateForLoopCode = function (templateObject, parent) { + // Set actual endCounter of previous for-loop + if (forEndCounterMap.has(nestedForDepth) === true) { + forEndCounterMap.set(nestedForDepth, counter - 1) + } + + nestedForDepth++ + + // Set initial endCounter of for-loop to -1 + forEndCounterMap.set(nestedForDepth, -1) + const forLoop = templateObject[':for'] delete templateObject[':for'] const key = templateObject['key'] - const forKey = interpolate(key, 'scope.') + + scopeCounter++ + const forKey = interpolate(key, `scope${scopeCounter}.`) const shallow = !!!( templateObject['$shallow'] && templateObject['$shallow'].toLowerCase() === 'false' @@ -287,7 +305,7 @@ const generateForLoopCode = function (templateObject, parent) { .replace(')', '') .split(/\s*,\s*/) - const scopeRegex = new RegExp(`(scope\\.(?!${item}\\.|${index}|key)(\\w+))`, 'gi') + const scopeRegex = new RegExp(`(scope${scopeCounter}\\.(?!${item}\\.|${index}|key)(\\w+))`, 'gi') // local context const ctx = { @@ -323,22 +341,23 @@ const generateForLoopCode = function (templateObject, parent) { created.length = 0 const length = rawCollection.length for(let __index = 0; __index < length; __index++) { - const scope = Object.create(component) + const scope${scopeCounter} = Object.create(component) parent = ${parent} - scope['${index}'] = __index - scope['${item}'] = rawCollection[__index] - scope['key'] = '' + ${forKey || '__index'} + scope${scopeCounter}['${index}'] = __index + scope${scopeCounter}['${item}'] = rawCollection[__index] + scope${scopeCounter}['key'] = '' + ${forKey || '__index'} `) + if ('ref' in templateObject && templateObject.ref.indexOf('$') === -1) { // automatically map the ref for each item in the loop based on the given ref key ctx.renderCode.push(` - scope['__ref'] = '${templateObject.ref}' + __index + scope${scopeCounter}['__ref'] = '${templateObject.ref}' + __index `) templateObject.ref = '$__ref' } ctx.renderCode.push(` - created.push(scope.key) + created.push(scope${scopeCounter}.key) `) if ( @@ -347,15 +366,15 @@ const generateForLoopCode = function (templateObject, parent) { templateObject[Symbol.for('componentType')] === 'Text' ) { generateElementCode.call(ctx, templateObject, parent, { - key: 'scope.key', - component: 'scope.', + key: `scope${scopeCounter}.key`, + component: `scope${scopeCounter}.`, forceEffect: false, forloop: true, }) } else { generateComponentCode.call(ctx, templateObject, false, { - key: 'scope.key', - component: 'scope.', + key: `scope${scopeCounter}.key`, + component: `scope${scopeCounter}.`, forceEffect: false, forloop: true, }) @@ -373,8 +392,8 @@ const generateForLoopCode = function (templateObject, parent) { if (shallow === false) { ctx.renderCode.push(` - scope['${item}'] = null - scope['${item}'] = collection[__index] + scope${scopeCounter}['${item}'] = null + scope${scopeCounter}['${item}'] = collection[__index] `) } @@ -410,7 +429,11 @@ const generateForLoopCode = function (templateObject, parent) { if (keys.has(created[i]) === false) { const key = created[i] `) - const forEndCounter = counter + // Get endCounter from Map that was stored, for non nested for-loop + // endCounter would be -1 then get's real endCounter from counter + + const endCounter = forEndCounterMap.get(nestedForDepth) + const forEndCounter = endCounter === -1 ? counter : endCounter for (let i = forStartCounter; i <= forEndCounter; i++) { destroyCode.push(` @@ -427,7 +450,11 @@ const generateForLoopCode = function (templateObject, parent) { ctx.renderCode.splice(indexToInjectDestroyCode, 0, ...destroyCode) ctx.renderCode.push(` effect(() => { - forloop${forStartCounter}(${cast(result[2], ':for')}, elms, created${forStartCounter}) + forloop${forStartCounter}(${ + nestedForDepth == 0 + ? cast(result[2], ':for') + : cast(result[2], ':for', `scope${scopeCounter - 1}.`) + }, elms, created${forStartCounter}) }, '${interpolate(result[2], '')}') `) @@ -448,10 +475,10 @@ const generateForLoopCode = function (templateObject, parent) { effect(() => { void ${refs.join(', ')} for(let __index = 0; __index < ${interpolate(result[2])}.length; __index++) { - const scope = {} - scope['${index}'] = __index - scope['${item}'] = ${interpolate(result[2])}[__index] - scope['key'] = ${forKey || '__index'} + const scope${scopeCounter} = {} + scope${scopeCounter}['${index}'] = __index + scope${scopeCounter}['${item}'] = ${interpolate(result[2])}[__index] + scope${scopeCounter}['key'] = ${forKey || '__index'} `) ctx.renderCode.push(` @@ -460,8 +487,10 @@ const generateForLoopCode = function (templateObject, parent) { }) `) }) - this.renderCode.push(ctx.renderCode.join('\n')) + + // Decrement counter + nestedForDepth-- } const generateCode = function (templateObject, parent = false, options = {}) { From 78430d84130998612e273ef91c52b2c1564a0216 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Gangumalla Date: Wed, 21 Aug 2024 12:28:56 +0530 Subject: [PATCH 2/3] Updated to handle multiple levels of nested Signed-off-by: Suresh Kumar Gangumalla --- src/lib/codegenerator/generator.js | 105 ++++++++++++++++++----------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/src/lib/codegenerator/generator.js b/src/lib/codegenerator/generator.js index 209fdb6a..1088752f 100644 --- a/src/lib/codegenerator/generator.js +++ b/src/lib/codegenerator/generator.js @@ -17,8 +17,8 @@ let counter let nestedForDepth -let scopeCounter -let forEndCounterMap +let forCounter +let nestedForMetadataMap export default function (templateObject = { children: [] }) { const ctx = { @@ -28,9 +28,9 @@ export default function (templateObject = { children: [] }) { } counter = -1 - scopeCounter = -1 + forCounter = -1 nestedForDepth = -1 - forEndCounterMap = new Map() + nestedForMetadataMap = new Map() generateCode.call(ctx, templateObject) ctx.renderCode.push('return elms') @@ -270,23 +270,20 @@ const generateComponentCode = function ( } const generateForLoopCode = function (templateObject, parent) { - // Set actual endCounter of previous for-loop - if (forEndCounterMap.has(nestedForDepth) === true) { - forEndCounterMap.set(nestedForDepth, counter - 1) + if (nestedForMetadataMap.has(nestedForDepth) === true) { + nestedForMetadataMap.get(nestedForDepth).set('endCounter', counter - 1) } + // By default consider their will be a nested forloop nestedForDepth++ - // Set initial endCounter of for-loop to -1 - forEndCounterMap.set(nestedForDepth, -1) - const forLoop = templateObject[':for'] delete templateObject[':for'] const key = templateObject['key'] - scopeCounter++ - const forKey = interpolate(key, `scope${scopeCounter}.`) + forCounter++ + const forKey = interpolate(key, `scope${forCounter}.`) const shallow = !!!( templateObject['$shallow'] && templateObject['$shallow'].toLowerCase() === 'false' @@ -299,13 +296,29 @@ const generateForLoopCode = function (templateObject, parent) { const result = regex.exec(forLoop) + const pathPrefix = nestedForMetadataMap.has(nestedForDepth - 1) + ? nestedForMetadataMap.get(nestedForDepth - 1).get('path') + : 'component.' + const currentForInfo = new Map() + + // This should be modified with regular expressions + currentForInfo.set( + 'path', + `${pathPrefix}${ + result[2].split('.').reverse()[0].split('$').reverse()[0] + }[__index${forCounter}].` + ) + currentForInfo.set('endCounter', -1) + + nestedForMetadataMap.set(nestedForDepth, currentForInfo) + // can be improved with a smarter regex const [item, index = 'index'] = result[1] .replace('(', '') .replace(')', '') .split(/\s*,\s*/) - const scopeRegex = new RegExp(`(scope${scopeCounter}\\.(?!${item}\\.|${index}|key)(\\w+))`, 'gi') + const scopeRegex = new RegExp(`(scope${forCounter}\\.(?!${item}\\.|${index}|key)(\\w+))`, 'gi') // local context const ctx = { @@ -340,24 +353,24 @@ const generateForLoopCode = function (templateObject, parent) { ctx.renderCode.push(` created.length = 0 const length = rawCollection.length - for(let __index = 0; __index < length; __index++) { - const scope${scopeCounter} = Object.create(component) + for(let __index${forCounter} = 0; __index${forCounter} < length; __index${forCounter}++) { + const scope${forCounter} = Object.create(component) parent = ${parent} - scope${scopeCounter}['${index}'] = __index - scope${scopeCounter}['${item}'] = rawCollection[__index] - scope${scopeCounter}['key'] = '' + ${forKey || '__index'} + scope${forCounter}['${index}'] = __index${forCounter} + scope${forCounter}['${item}'] = rawCollection[__index${forCounter}] + scope${forCounter}['key'] = '' + ${forKey || '__index' + forCounter} `) if ('ref' in templateObject && templateObject.ref.indexOf('$') === -1) { // automatically map the ref for each item in the loop based on the given ref key ctx.renderCode.push(` - scope${scopeCounter}['__ref'] = '${templateObject.ref}' + __index + scope${forCounter}['__ref'] = '${templateObject.ref}' + __index${forCounter} `) templateObject.ref = '$__ref' } ctx.renderCode.push(` - created.push(scope${scopeCounter}.key) + created.push(scope${forCounter}.key) `) if ( @@ -366,15 +379,15 @@ const generateForLoopCode = function (templateObject, parent) { templateObject[Symbol.for('componentType')] === 'Text' ) { generateElementCode.call(ctx, templateObject, parent, { - key: `scope${scopeCounter}.key`, - component: `scope${scopeCounter}.`, + key: `scope${forCounter}.key`, + component: `scope${forCounter}.`, forceEffect: false, forloop: true, }) } else { generateComponentCode.call(ctx, templateObject, false, { - key: `scope${scopeCounter}.key`, - component: `scope${scopeCounter}.`, + key: `scope${forCounter}.key`, + component: `scope${forCounter}.`, forceEffect: false, forloop: true, }) @@ -392,8 +405,8 @@ const generateForLoopCode = function (templateObject, parent) { if (shallow === false) { ctx.renderCode.push(` - scope${scopeCounter}['${item}'] = null - scope${scopeCounter}['${item}'] = collection[__index] + scope${forCounter}['${item}'] = null + scope${forCounter}['${item}'] = collection[__index${forCounter}] `) } @@ -429,11 +442,13 @@ const generateForLoopCode = function (templateObject, parent) { if (keys.has(created[i]) === false) { const key = created[i] `) - // Get endCounter from Map that was stored, for non nested for-loop - // endCounter would be -1 then get's real endCounter from counter + // const forEndCounter = counter - const endCounter = forEndCounterMap.get(nestedForDepth) - const forEndCounter = endCounter === -1 ? counter : endCounter + const currentForStoredInfo = nestedForMetadataMap.get(nestedForDepth) + const forEndCounter = + currentForStoredInfo && currentForStoredInfo.get('endCounter') === -1 + ? counter + : currentForStoredInfo.get('endCounter') for (let i = forStartCounter; i <= forEndCounter; i++) { destroyCode.push(` @@ -448,14 +463,20 @@ const generateForLoopCode = function (templateObject, parent) { // inject the destroy code in the correct spot ctx.renderCode.splice(indexToInjectDestroyCode, 0, ...destroyCode) + + const loopProp = + nestedForDepth == 0 + ? interpolate(result[2], '') + : result[2].split('.').reverse()[0].split('$').reverse()[0] + ctx.renderCode.push(` effect(() => { forloop${forStartCounter}(${ nestedForDepth == 0 ? cast(result[2], ':for') - : cast(result[2], ':for', `scope${scopeCounter - 1}.`) + : cast(`$${loopProp}`, ':for', `${nestedForMetadataMap.get(nestedForDepth - 1).get('path')}`) }, elms, created${forStartCounter}) - }, '${interpolate(result[2], '')}') + }, '${nestedForDepth == 0 ? interpolate(result[2], '') : loopProp}') `) outerScopeEffects.forEach((effect) => { @@ -471,14 +492,23 @@ const generateForLoopCode = function (templateObject, parent) { effect.substring(0, match.index) + ref + effect.substring(match.index + match[1].length) } + // interpolate statement should be moved out to top level ctx.renderCode.push(` effect(() => { void ${refs.join(', ')} - for(let __index = 0; __index < ${interpolate(result[2])}.length; __index++) { - const scope${scopeCounter} = {} - scope${scopeCounter}['${index}'] = __index - scope${scopeCounter}['${item}'] = ${interpolate(result[2])}[__index] - scope${scopeCounter}['key'] = ${forKey || '__index'} + for(let __index${forCounter} = 0; __index${forCounter} < ${ + nestedForDepth === 0 + ? interpolate(result[2]) + : interpolate('$' + loopProp, nestedForMetadataMap.get(nestedForDepth - 1).get('path')) + }.length; __index${forCounter}++) { + const scope${forCounter} = {} + scope${forCounter}['${index}'] = __index${forCounter} + scope${forCounter}['${item}'] = ${ + nestedForDepth === 0 + ? interpolate(result[2]) + : interpolate('$' + loopProp, nestedForMetadataMap.get(nestedForDepth - 1).get('path')) + }[__index${forCounter}] + scope${forCounter}['key'] = ${forKey || '__index' + forCounter} `) ctx.renderCode.push(` @@ -489,7 +519,6 @@ const generateForLoopCode = function (templateObject, parent) { }) this.renderCode.push(ctx.renderCode.join('\n')) - // Decrement counter nestedForDepth-- } From 96ea1366d458bb6a172680dc9ca259392535c14d Mon Sep 17 00:00:00 2001 From: Suresh Kumar Gangumalla Date: Wed, 21 Aug 2024 15:11:30 +0530 Subject: [PATCH 3/3] Extracted formation of dataArray to top Signed-off-by: Suresh Kumar Gangumalla --- src/lib/codegenerator/generator.js | 71 ++++++++++++------------------ 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/src/lib/codegenerator/generator.js b/src/lib/codegenerator/generator.js index 1088752f..3fb55b64 100644 --- a/src/lib/codegenerator/generator.js +++ b/src/lib/codegenerator/generator.js @@ -16,9 +16,9 @@ */ let counter -let nestedForDepth +let forRelativeDepth let forCounter -let nestedForMetadataMap +let forMetadataMap export default function (templateObject = { children: [] }) { const ctx = { @@ -29,8 +29,8 @@ export default function (templateObject = { children: [] }) { counter = -1 forCounter = -1 - nestedForDepth = -1 - nestedForMetadataMap = new Map() + forRelativeDepth = -1 + forMetadataMap = new Map() generateCode.call(ctx, templateObject) ctx.renderCode.push('return elms') @@ -270,18 +270,20 @@ const generateComponentCode = function ( } const generateForLoopCode = function (templateObject, parent) { - if (nestedForMetadataMap.has(nestedForDepth) === true) { - nestedForMetadataMap.get(nestedForDepth).set('endCounter', counter - 1) + // This is to handle number of elements created by previous forloop in nested case + if (forMetadataMap.has(forRelativeDepth) === true) { + forMetadataMap.get(forRelativeDepth).set('endCounter', counter - 1) } - // By default consider their will be a nested forloop - nestedForDepth++ + // Increment forloop relative depth by 1 + forRelativeDepth++ const forLoop = templateObject[':for'] delete templateObject[':for'] const key = templateObject['key'] + // Increment forCounter to make sure scope, __index variables always get unique names forCounter++ const forKey = interpolate(key, `scope${forCounter}.`) @@ -296,21 +298,19 @@ const generateForLoopCode = function (templateObject, parent) { const result = regex.exec(forLoop) - const pathPrefix = nestedForMetadataMap.has(nestedForDepth - 1) - ? nestedForMetadataMap.get(nestedForDepth - 1).get('path') + // Get previous for-loop data member path, like component.data + const pathPrefix = forMetadataMap.has(forRelativeDepth - 1) + ? forMetadataMap.get(forRelativeDepth - 1).get('path') : 'component.' - const currentForInfo = new Map() - // This should be modified with regular expressions - currentForInfo.set( - 'path', - `${pathPrefix}${ - result[2].split('.').reverse()[0].split('$').reverse()[0] - }[__index${forCounter}].` - ) + const loopingOverProp = result[2].replace('$', '').split('.').pop() + const path = `${pathPrefix}${loopingOverProp}[__index${forCounter}].` + + const currentForInfo = new Map() + currentForInfo.set('path', path) currentForInfo.set('endCounter', -1) - nestedForMetadataMap.set(nestedForDepth, currentForInfo) + forMetadataMap.set(forRelativeDepth, currentForInfo) // can be improved with a smarter regex const [item, index = 'index'] = result[1] @@ -444,7 +444,7 @@ const generateForLoopCode = function (templateObject, parent) { `) // const forEndCounter = counter - const currentForStoredInfo = nestedForMetadataMap.get(nestedForDepth) + const currentForStoredInfo = forMetadataMap.get(forRelativeDepth) const forEndCounter = currentForStoredInfo && currentForStoredInfo.get('endCounter') === -1 ? counter @@ -464,19 +464,15 @@ const generateForLoopCode = function (templateObject, parent) { // inject the destroy code in the correct spot ctx.renderCode.splice(indexToInjectDestroyCode, 0, ...destroyCode) - const loopProp = - nestedForDepth == 0 - ? interpolate(result[2], '') - : result[2].split('.').reverse()[0].split('$').reverse()[0] + const dataArr = + forRelativeDepth == 0 + ? cast(result[2], ':for') + : cast(`$${loopingOverProp}`, ':for', forMetadataMap.get(forRelativeDepth - 1).get('path')) ctx.renderCode.push(` effect(() => { - forloop${forStartCounter}(${ - nestedForDepth == 0 - ? cast(result[2], ':for') - : cast(`$${loopProp}`, ':for', `${nestedForMetadataMap.get(nestedForDepth - 1).get('path')}`) - }, elms, created${forStartCounter}) - }, '${nestedForDepth == 0 ? interpolate(result[2], '') : loopProp}') + forloop${forStartCounter}(${dataArr}, elms, created${forStartCounter}) + }, '${forRelativeDepth == 0 ? interpolate(result[2], '') : loopingOverProp}') `) outerScopeEffects.forEach((effect) => { @@ -492,22 +488,13 @@ const generateForLoopCode = function (templateObject, parent) { effect.substring(0, match.index) + ref + effect.substring(match.index + match[1].length) } - // interpolate statement should be moved out to top level ctx.renderCode.push(` effect(() => { void ${refs.join(', ')} - for(let __index${forCounter} = 0; __index${forCounter} < ${ - nestedForDepth === 0 - ? interpolate(result[2]) - : interpolate('$' + loopProp, nestedForMetadataMap.get(nestedForDepth - 1).get('path')) - }.length; __index${forCounter}++) { + for(let __index${forCounter} = 0; __index${forCounter} < ${dataArr}.length; __index${forCounter}++) { const scope${forCounter} = {} scope${forCounter}['${index}'] = __index${forCounter} - scope${forCounter}['${item}'] = ${ - nestedForDepth === 0 - ? interpolate(result[2]) - : interpolate('$' + loopProp, nestedForMetadataMap.get(nestedForDepth - 1).get('path')) - }[__index${forCounter}] + scope${forCounter}['${item}'] = ${dataArr}[__index${forCounter}] scope${forCounter}['key'] = ${forKey || '__index' + forCounter} `) @@ -519,7 +506,7 @@ const generateForLoopCode = function (templateObject, parent) { }) this.renderCode.push(ctx.renderCode.join('\n')) - nestedForDepth-- + forRelativeDepth-- } const generateCode = function (templateObject, parent = false, options = {}) {