diff --git a/compiler/Builder.js b/compiler/Builder.js index dde55b7b6..0e2c455eb 100644 --- a/compiler/Builder.js +++ b/compiler/Builder.js @@ -19,20 +19,40 @@ var HtmlElement = require('./ast/HtmlElement'); var Html = require('./ast/Html'); var Text = require('./ast/Text'); var ForEach = require('./ast/ForEach'); +var ForRange = require('./ast/ForRange'); var Slot = require('./ast/Slot'); var HtmlComment = require('./ast/HtmlComment'); var SelfInvokingFunction = require('./ast/SelfInvokingFunction'); var ForStatement = require('./ast/ForStatement'); var BinaryExpression = require('./ast/BinaryExpression'); var UpdateExpression = require('./ast/UpdateExpression'); -var Expression = require('./ast/Expression'); +var UnaryExpression = require('./ast/UnaryExpression'); +var MemberExpression = require('./ast/MemberExpression'); +var parseExpression = require('./util/parseExpression'); + +function makeNode(arg) { + if (typeof arg === 'string') { + return parseExpression(arg); + } else if (arg instanceof Node) { + return arg; + } else { + return undefined; + } +} class Builder { - assignment(left, right) { - return new Assignment({left, right}); + assignment(left, right, operator) { + if (operator == null) { + operator = '='; + } + left = makeNode(left); + right = makeNode(right); + return new Assignment({left, right, operator}); } binaryExpression(left, operator, right) { + left = makeNode(left); + right = makeNode(right); return new BinaryExpression({left, operator, right}); } @@ -41,38 +61,66 @@ class Builder { } elseIfStatement(test, body, elseStatement) { + test = makeNode(test); + return new ElseIf({ if: new If({test, body, else: elseStatement}) }); } expression(value) { - return new Expression({value}); + return makeNode(value); } - forEach(varName, target, body) { - if (typeof varName === 'object') { - var options = varName; - return new ForEach(options); + forEach(varName, inExpression, body) { + if (arguments.length === 1) { + var def = arguments[0]; + return new ForEach(def); } else { - return new ForEach({varName, target, body}); + varName = makeNode(varName); + inExpression = makeNode(inExpression); + return new ForEach({varName, in: inExpression, body}); + } + } + + forRange(varName, from, to, step, body) { + if (arguments.length === 1) { + var def = arguments[0]; + return new ForRange(def); + } else { + varName = makeNode(varName); + from = makeNode(from); + to = makeNode(to); + step = makeNode(step); + body = makeNode(body); + + return new ForRange({varName, from, to, step, body}); } } forStatement(init, test, update, body) { - if (typeof init === 'object' && !init.type) { + if (arguments.length === 1) { var def = arguments[0]; return new ForStatement(def); } else { + init = makeNode(init); + test = makeNode(test); + update = makeNode(update); return new ForStatement({init, test, update, body}); } } functionCall(callee, args) { + callee = makeNode(callee); + if (args) { if (!isArray(args)) { args = [args]; } + + for (var i=0; i { + let id = new Identifier({name: key}); + let init = makeNode(declarations[key]); + return { id, init }; + }); + } + } + + return new Vars({declarations, kind}); } } diff --git a/compiler/ast/Assignment.js b/compiler/ast/Assignment.js index 3b9bfd0c4..07d523b00 100644 --- a/compiler/ast/Assignment.js +++ b/compiler/ast/Assignment.js @@ -7,15 +7,32 @@ class Assignment extends Node { super('Assignment'); this.left = def.left; this.right = def.right; + this.operator = def.operator; } generateCode(generator) { var left = this.left; var right = this.right; + var operator = this.operator; generator.generateCode(left); - generator.write(' = '); + generator.write(' ' + (operator || '=') + ' '); + + var wrap = right instanceof Assignment; + + if (wrap) { + generator.write('('); + } + generator.generateCode(right); + + if (wrap) { + generator.write(')'); + } + } + + isCompoundExpression() { + return true; } } diff --git a/compiler/ast/BinaryExpression.js b/compiler/ast/BinaryExpression.js index 7a2384bc3..000f3ea7f 100644 --- a/compiler/ast/BinaryExpression.js +++ b/compiler/ast/BinaryExpression.js @@ -1,6 +1,21 @@ 'use strict'; var Node = require('./Node'); +var isCompoundExpression = require('../util/isCompoundExpression'); + +function generateCodeForOperand(node, generator) { + var wrap = isCompoundExpression(node); + + if (wrap) { + generator.write('('); + } + + generator.generateCode(node); + + if (wrap) { + generator.write(')'); + } +} class BinaryExpression extends Node { constructor(def) { @@ -8,35 +23,26 @@ class BinaryExpression extends Node { this.left = def.left; this.operator = def.operator; this.right = def.right; - this.parens = def.parens === true; } generateCode(generator) { var left = this.left; + var operator = this.operator; var right = this.right; - var parens = this.parens || this.data.isSubExpression; - if (left instanceof Node) { - left.data.isSubExpression = true; + if (!left || !right) { + throw new Error('Invalid BinaryExpression: ' + this); } - if (right instanceof Node) { - right.data.isSubExpression = true; - } - - if (parens) { - generator.write('('); - } - - generator.generateCode(this.left); + generateCodeForOperand(left, generator); generator.write(' '); - generator.generateCode(this.operator); + generator.generateCode(operator); generator.write(' '); - generator.generateCode(this.right); + generateCodeForOperand(right, generator); + } - if (parens) { - generator.write(')'); - } + isCompoundExpression() { + return true; } toJSON() { diff --git a/compiler/ast/Expression.js b/compiler/ast/Expression.js index 20db685b6..c4137d0a1 100644 --- a/compiler/ast/Expression.js +++ b/compiler/ast/Expression.js @@ -1,16 +1,22 @@ 'use strict'; var Node = require('./Node'); +var ok = require('assert').ok; class Expression extends Node { constructor(def) { - super('Html'); + super('Expression'); this.value = def.value; + ok(this.value != null, 'Invalid expression'); } generateCode(generator) { generator.generateCode(this.value); } + + isCompoundExpression() { + return true; + } } module.exports = Expression; \ No newline at end of file diff --git a/compiler/ast/ForEach.js b/compiler/ast/ForEach.js index 2c2e22809..080cbcde0 100644 --- a/compiler/ast/ForEach.js +++ b/compiler/ast/ForEach.js @@ -6,66 +6,23 @@ class ForEach extends Node { constructor(def) { super('ForEach'); this.varName = def.varName; - this.target = def.target; + this.in = def.in; this.body = this.makeContainer(def.body); this.separator = def.separator; this.statusVarName = def.statusVarName; - this.from = def.from; - this.to = def.to; - this.step = def.step; ok(this.varName, '"varName" is required'); - ok(this.target != null || this.from != null, '"target" or "from" is required'); + ok(this.in != null, '"in" is required'); } generateCode(generator) { var varName = this.varName; - var target = this.target; + var inExpression = this.in; var separator = this.separator; var statusVarName = this.statusVarName; var builder = generator.builder; - if (this.from != null) { - // This is a range loop - var from = this.from; - var to = this.to; - var step = this.step; - var comparison = '<='; - - if (typeof step === 'number') { - if (step < 0) { - comparison = '>='; - } - - if (step === 1) { - step = varName + '++'; - } else if (step === -1) { - step = varName + '--'; - } else if (step > 0) { - step = varName + '+=' + step; - } else if (step === 0) { - throw new Error('Invalid step of 0'); - } else if (step < 0) { - step = 0-step; // Make the step positive and switch to -= - step = varName + '-=' + step; - } - } else { - step = varName + '+=' + step; - } - - return builder.selfInvokingFunction([ - builder.forStatement({ - init: [ - builder.vars([ { id: varName, init: from }]) - ], - test: varName + comparison + to, - update: step, - body: this.body - }) - ]); - } - if (separator && !statusVarName) { statusVarName = '__loop'; } @@ -75,22 +32,28 @@ class ForEach extends Node { let body = this.body; if (separator) { + let isNotLastTest = builder.functionCall( + builder.memberExpression(statusVarName, builder.identifier('isLast')), + []); + + isNotLastTest = builder.negate(isNotLastTest); + body = body.items.concat([ - builder.ifStatement('!' + statusVarName + '.isLast()', [ + builder.ifStatement(isNotLastTest, [ builder.text(separator) ]) ]); } return builder.functionCall(forEachVarName, [ - target, + inExpression, builder.functionDeclaration(null, [varName, statusVarName], body) ]); } else { let forEachVarName = generator.addStaticVar('forEach', '__helpers.f'); return builder.functionCall(forEachVarName, [ - target, + inExpression, builder.functionDeclaration(null, [varName], this.body) ]); } diff --git a/compiler/ast/ForRange.js b/compiler/ast/ForRange.js new file mode 100644 index 000000000..4682f7728 --- /dev/null +++ b/compiler/ast/ForRange.js @@ -0,0 +1,97 @@ +'use strict'; +var ok = require('assert').ok; +var Node = require('./Node'); +var Literal = require('./Literal'); +var Identifier = require('./Identifier'); + +class ForRange extends Node { + constructor(def) { + super('ForRange'); + this.varName = def.varName; + this.body = this.makeContainer(def.body); + this.from = def.from; + this.to = def.to; + this.step = def.step; + + ok(this.varName, '"varName" is required'); + ok(this.from != null, '"from" is required'); + } + + generateCode(generator) { + var varName = this.varName; + var from = this.from; + var to = this.to; + var step = this.step; + + var builder = generator.builder; + + var comparison = '<='; + + if (varName instanceof Identifier) { + varName = varName.name; + } + + var updateExpression; + + if (step == null) { + let fromLiteral = (from instanceof Literal) && from.value; + let toLiteral = (to instanceof Literal) && to.value; + + if (typeof fromLiteral === 'number' && typeof toLiteral === 'number') { + if (fromLiteral > toLiteral) { + updateExpression = varName + '--'; + comparison = '>='; + } else { + updateExpression = varName + '++'; + } + } + } else { + let stepLiteral; + + if (step instanceof Literal) { + stepLiteral = step.value; + } else if (typeof step === 'number') { + stepLiteral = step; + } + + if (typeof stepLiteral === 'number') { + if (stepLiteral < 0) { + comparison = '>='; + } + + if (stepLiteral === 1) { + updateExpression = varName + '++'; + } else if (stepLiteral === -1) { + updateExpression = varName + '--'; + } else if (stepLiteral > 0) { + updateExpression = varName + ' += ' + stepLiteral; + } else if (stepLiteral === 0) { + throw new Error('Invalid step of 0'); + } else if (stepLiteral < 0) { + stepLiteral = 0-stepLiteral; // Make the step positive and switch to -= + updateExpression = varName + ' -= ' + stepLiteral; + } + } else { + updateExpression = builder.assignment(varName, step, '+='); + } + } + + if (updateExpression == null) { + updateExpression = varName + '++'; + } + + return builder.selfInvokingFunction([ + builder.forStatement({ + init: [ + builder.vars([ { id: varName, init: from }]) + ], + test: builder.binaryExpression(varName, comparison, to), + update: updateExpression, + body: this.body + }) + ]); + + } +} + +module.exports = ForRange; \ No newline at end of file diff --git a/compiler/ast/FunctionDeclaration.js b/compiler/ast/FunctionDeclaration.js index eea60aadd..7c09f0b77 100644 --- a/compiler/ast/FunctionDeclaration.js +++ b/compiler/ast/FunctionDeclaration.js @@ -51,6 +51,10 @@ class FunctionDeclaration extends Node { generator.write('\n'); } } + + isCompoundExpression() { + return true; + } } module.exports = FunctionDeclaration; \ No newline at end of file diff --git a/compiler/ast/Html.js b/compiler/ast/Html.js index a1fbac8be..d3cdbff7b 100644 --- a/compiler/ast/Html.js +++ b/compiler/ast/Html.js @@ -14,8 +14,6 @@ class Html extends Node { generateHtmlCode(generator) { let argument = this.argument; - - console.log(module.id, 'Html', argument); generator.addWrite(argument); } } diff --git a/compiler/ast/MemberExpression.js b/compiler/ast/MemberExpression.js new file mode 100644 index 000000000..bb16561c6 --- /dev/null +++ b/compiler/ast/MemberExpression.js @@ -0,0 +1,40 @@ +'use strict'; + +var Node = require('./Node'); + +class MemberExpression extends Node { + constructor(def) { + super('MemberExpression'); + this.object = def.object; + this.property = def.property; + this.computed = def.computed; + } + + generateCode(generator) { + var object = this.object; + var property = this.property; + var computed = this.computed; + + generator.generateCode(object); + + if (computed) { + generator.write('['); + generator.generateCode(property); + generator.write(']'); + } else { + generator.write('.'); + generator.generateCode(property); + } + } + + toJSON() { + return { + type: 'MemberExpression', + object: this.object, + property: this.property, + computed: this.computed + }; + } +} + +module.exports = MemberExpression; \ No newline at end of file diff --git a/compiler/ast/Node.js b/compiler/ast/Node.js index e4ab64769..5aab0ec13 100644 --- a/compiler/ast/Node.js +++ b/compiler/ast/Node.js @@ -84,6 +84,14 @@ class Node { this.container.removeChild(this); } + /** + * Returns true if the current node represents a compound expression (e.g. ) + * @return {Boolean} [description] + */ + isCompoundExpression() { + return false; + } + isDetached() { return this.container == null; } diff --git a/compiler/ast/UnaryExpression.js b/compiler/ast/UnaryExpression.js new file mode 100644 index 000000000..00d5f75e8 --- /dev/null +++ b/compiler/ast/UnaryExpression.js @@ -0,0 +1,58 @@ +'use strict'; + +var Node = require('./Node'); +var isCompoundExpression = require('../util/isCompoundExpression'); + +class UnaryExpression extends Node { + constructor(def) { + super('UnaryExpression'); + this.argument = def.argument; + this.operator = def.operator; + this.prefix = def.prefix === true; + } + + generateCode(generator) { + var argument = this.argument; + var operator = this.operator; + var prefix = this.prefix; + + if (prefix) { + generator.write(operator); + + if (operator === 'typeof') { + generator.write(' '); + } + } + + var wrap = isCompoundExpression(argument); + + if (wrap) { + generator.write('('); + } + + generator.generateCode(argument); + + if (wrap) { + generator.write(')'); + } + + if (!prefix) { + generator.write(operator); + } + } + + isCompoundExpression() { + return true; + } + + toJSON() { + return { + type: 'UnaryExpression', + argument: this.argument, + operator: this.operator, + prefix: this.prefix + }; + } +} + +module.exports = UnaryExpression; \ No newline at end of file diff --git a/compiler/ast/UpdateExpression.js b/compiler/ast/UpdateExpression.js index 1a44dfddc..528257c15 100644 --- a/compiler/ast/UpdateExpression.js +++ b/compiler/ast/UpdateExpression.js @@ -1,6 +1,7 @@ 'use strict'; var Node = require('./Node'); +var isCompoundExpression = require('../util/isCompoundExpression'); class UpdateExpression extends Node { constructor(def) { @@ -19,13 +20,27 @@ class UpdateExpression extends Node { generator.generateCode(operator); } + var wrap = isCompoundExpression(argument); + + if (wrap) { + generator.write('('); + } + generator.generateCode(argument); + if (wrap) { + generator.write(')'); + } + if (!prefix) { generator.generateCode(operator); } } + isCompoundExpression() { + return true; + } + toJSON() { return { type: 'UpdateExpression', diff --git a/compiler/ast/Vars.js b/compiler/ast/Vars.js index e0d5e0710..d6ec1e2bf 100644 --- a/compiler/ast/Vars.js +++ b/compiler/ast/Vars.js @@ -24,14 +24,6 @@ class Vars extends Node { return generator.builder.selfInvokingFunction([ this ]); } - if (declarations && !Array.isArray(declarations) && typeof declarations === 'object') { - // Convert the object into an array of variables - declarations = Object.keys(declarations).map((id) => { - let init = declarations[id]; - return { id, init }; - }); - } - if (!declarations || !declarations.length) { return; } @@ -39,12 +31,6 @@ class Vars extends Node { for (let i=0; i +``` + +For example: + +```javascript +builder.negate(builder.identifier('foo')) + +// Output: +!foo +``` + ### node([type, ]generatCode) Returns a generic `Node` instance with the given node type (optional) and a `generateCode(node, generator)` function that should be used to generate the code for the node. If a `generateCode(node, generator)` function is not provided the node bust be monkey-patched to add a `generateCode(generator)` method. @@ -624,6 +646,8 @@ a === b ### text(argument, escape) +### unaryExpression(argument, operator, prefix) + ### updateExpression(argument, operator, prefix) ### vars(declarations, kind) diff --git a/taglibs/core/core-transformer.js b/taglibs/core/core-transformer.js index 71cf4147c..8e308f45f 100644 --- a/taglibs/core/core-transformer.js +++ b/taglibs/core/core-transformer.js @@ -1,9 +1,6 @@ 'use strict'; -var extend = require('raptor-util/extend'); -var parseComplexAttribute = require('./util/parseComplexAttribute'); -var parseForEach = require('./util/parseForEach'); -var parseJavaScriptIdentifier = require('./util/parseJavaScriptIdentifier'); +var createLoopNode = require('./util/createLoopNode'); var coreAttrHandlers = [ [ @@ -13,49 +10,12 @@ var coreAttrHandlers = [ return; } - var forEachProps = parseComplexAttribute(forArgument, { - 'each': true, - 'separator': true, - 'iterator': true, - 'status-var': true, - 'for-loop': true - }, - { - removeDashes: true, - defaultName: 'each', - errorHandler: function (message) { - this.addError('Invalid for attribute of "' + attr + '". Error: ' + message); - } - }); + var loopNode = createLoopNode(forArgument, null, this.builder); - if (!forEachProps.each) { - this.addError('Invalid "for" attribute.'); - } - - var statusVarName = forEachProps.statusVar; - - if (statusVarName) { - // statusVar is expected to be a String literal expression - // For example: statusVar: "'foo'" - // We need to parse it into an actual string such as "foo" - statusVarName = parseJavaScriptIdentifier(statusVarName); - if (!statusVarName) { - this.addError('Invalid "status-var": ' + forEachProps.statusVar); - } - delete forEachProps.statusVar; - forEachProps.statusVarName = statusVarName; - } - - var parsedForEach = parseForEach(forEachProps.each); - delete forEachProps.each; - extend(forEachProps, parsedForEach); - - forEachProps.pos = node.pos; - - //Copy the position property - var forEachNode = this.builder.forEach(forEachProps); - //Surround the existing node with a "forEach" node - node.wrap(forEachNode); + //Surround the existing node with the newly created loop node + // NOTE: The loop node will be one of the following: + // ForEach, ForRange, ForEachProp or ForStatement + node.wrap(loopNode); } ], [ diff --git a/taglibs/core/for-tag.js b/taglibs/core/for-tag.js index d0b28862e..23ee100d1 100644 --- a/taglibs/core/for-tag.js +++ b/taglibs/core/for-tag.js @@ -1,4 +1,4 @@ -var parseForEach = require('./util/parseForEach'); +var createLoopNode = require('./util/createLoopNode'); module.exports = function codeGenerator(elNode, generator) { var argument = elNode.argument; @@ -7,16 +7,14 @@ module.exports = function codeGenerator(elNode, generator) { return elNode; } - var forEachProps = parseForEach(argument); - forEachProps.body = elNode.body; + var builder = generator.builder; - if (elNode.hasAttribute('separator')) { - forEachProps.separator = elNode.getAttributeValue('separator'); + var loopNode = createLoopNode(argument, elNode.body, builder); + + if (loopNode.error) { + generator.addError(loopNode.error); + return elNode; } - if (elNode.hasAttribute('status-var')) { - forEachProps.statusVarName = elNode.getAttributeValue('status-var'); - } - - return generator.builder.forEach(forEachProps); + return loopNode; }; \ No newline at end of file diff --git a/taglibs/core/util/createLoopNode.js b/taglibs/core/util/createLoopNode.js new file mode 100644 index 000000000..750c6bb29 --- /dev/null +++ b/taglibs/core/util/createLoopNode.js @@ -0,0 +1,23 @@ +var parseFor = require('./parseFor'); + +function createLoopNode(str, body, builder) { + var forDef = parseFor(str); + + if (forDef.error) { + return forDef; + } + + forDef.body = body; + + if (forDef.loopType === 'ForEach') { + return builder.forEach(forDef); + } else if (forDef.loopType === 'ForRange') { + return builder.forRange(forDef); + } else if (forDef.loopType === 'For') { + return builder.forStatement(forDef); + } else { + throw new Error('Unsupported loop type: ' + forDef.loopType); + } +} + +module.exports = createLoopNode; diff --git a/taglibs/core/util/parseFor.js b/taglibs/core/util/parseFor.js new file mode 100644 index 000000000..18997ca6c --- /dev/null +++ b/taglibs/core/util/parseFor.js @@ -0,0 +1,312 @@ +'use strict'; +var Expression = require('../../../compiler/ast/Expression'); +var Literal = require('../../../compiler/ast/Literal'); +var Identifier = require('../../../compiler/ast/Identifier'); +var removeComments = require('./removeComments'); + +var integerRegExp = /^-?\d+$/; +var numberRegExp = /^-?(?:\d+|\d+\.\d*|\d*\.\d+|\d+\.\d+)$/; + +var tokenizer = require('./tokenizer').create([ + { + name: 'stringDouble', + pattern: /"(?:[^"]|\\")*"/, + }, + { + name: 'stringSingle', + pattern: /'(?:[^']|\\')*'/ + }, + { + name: 'in', + pattern: /\s+in\s+/, + }, + { + name: 'from', + pattern: /\s+from\s+/ + }, + { + name: 'to', + pattern: /\s+to\s+/, + }, + { + name: 'step', + pattern: /\s+step\s+/, + }, + { + name: 'semicolon', + pattern: /[;]/, + }, + { + name: 'separator', + pattern: /separator=/ + }, + { + name: 'status-var', + pattern: /status\-var=/ + }, + { + name: 'iterator', + pattern: /iterator=/ + }, + { + name: 'pipe', + pattern: /\s+\|\s+/ + }, + { + name: 'groupOpen', + pattern: /[\{\(\[]/ + }, + { + name: 'groupClose', + pattern: /[\}\)\]]/ + } +]); + + +function createNumberExpression(str) { + if (str == null) { + return null; + } + + if (integerRegExp.test(str)) { + return new Literal({value: parseInt(str, 10)}); + } else if (numberRegExp.test(str)) { + return new Literal({value: parseFloat(str)}); + } else { + return new Expression({value: str}); + } +} + +/** + * Parses a for loop string in the following forms: + * + * in + * in | status-var= separator= + * from to + * from to step + * ; ; + */ +module.exports = function(str) { + str = removeComments(str); + + let depth = 0; + var prevToken; + var loopType; + var pipeFound = false; + + var varName; + var nameVarName; + var valueVarName; + var inExpression; + var statusVarName; + var separatorExpression; + var fromExpression; + var toExpression; + var stepExpression; + var iteratorExpression; + + var forInit; + var forTest; + var forUpdate; + + function finishVarName(end) { + varName = str.substring(0, end).trim(); + } + + function finishPrevPart(end) { + if (!prevToken) { + return; + } + + var start = prevToken.end; + var part = str.substring(start, end).trim(); + + switch(prevToken.name) { + case 'from': + fromExpression = part; + break; + case 'to': + toExpression = part; + break; + case 'in': + inExpression = part; + break; + case 'step': + stepExpression = part; + break; + case 'status-var': + statusVarName = part; + break; + case 'separator': + separatorExpression = part; + break; + case 'iterator': + iteratorExpression = part; + break; + } + } + + tokenizer.forEachToken(str, (token) => { + switch(token.name) { + case 'groupOpen': + depth++; + break; + case 'groupClose': + depth--; + break; + case 'in': + if (depth === 0 && !loopType) { + loopType = 'ForEach'; + finishVarName(token.start); + prevToken = token; + } + break; + case 'from': + if (depth === 0 && !loopType) { + loopType = 'ForRange'; + finishVarName(token.start); + prevToken = token; + } + break; + case 'to': + if (depth === 0 && prevToken && prevToken.name === 'from') { + finishPrevPart(token.start); + prevToken = token; + } + break; + case 'step': + if (depth === 0 && prevToken && prevToken.name === 'to') { + finishPrevPart(token.start); + prevToken = token; + } + break; + case 'semicolon': + if (depth === 0) { + loopType = 'For'; + + if (forInit == null) { + forInit = str.substring(0, token.start); + } else if (forTest == null) { + forTest = str.substring(prevToken.end, token.start); + forUpdate = str.substring(token.end); + } else { + return { + error: 'Invalid native for loop. Expected format: ; ; ' + }; + } + + prevToken = token; + } + break; + case 'pipe': + if (depth === 0) { + pipeFound = true; + finishPrevPart(token.start); + prevToken = token; + } + break; + case 'status-var': + if (depth === 0 && pipeFound && str.charAt(token.start-1) === ' ') { + finishPrevPart(token.start); + prevToken = token; + } + break; + case 'separator': + if (depth === 0 && pipeFound && str.charAt(token.start-1) === ' ') { + finishPrevPart(token.start); + prevToken = token; + } + break; + case 'iterator': + if (depth === 0 && pipeFound && str.charAt(token.start-1) === ' ') { + finishPrevPart(token.start); + prevToken = token; + } + break; + } + }); + + finishPrevPart(str.length); + + if (loopType === 'ForEach') { + var nameValue = varName.split(','); + if (nameValue.length === 2) { + nameVarName = new Identifier({name: nameValue[0]}); + valueVarName = new Identifier({name: nameValue[1]}); + loopType = 'ForEachProp'; + } + } + + if (inExpression) { + inExpression = new Expression({value: inExpression}); + } + + if (separatorExpression) { + separatorExpression = new Expression({value: separatorExpression}); + } + + if (iteratorExpression) { + iteratorExpression = new Expression({value: iteratorExpression}); + } + + if (fromExpression) { + fromExpression = createNumberExpression(fromExpression); + } + + if (toExpression) { + toExpression = createNumberExpression(toExpression); + } + + if (stepExpression) { + stepExpression = createNumberExpression(stepExpression); + } + + varName = new Identifier({name: varName}); + + if (statusVarName) { + statusVarName = new Identifier({name: statusVarName}); + } + + // No more tokens... now we need to sort out what happened + if (loopType === 'ForEach') { + return { + 'loopType': loopType, + 'varName': varName, + 'in': inExpression, + 'separator': separatorExpression, + 'statusVarName': statusVarName, + 'iterator': iteratorExpression + }; + } else if (loopType === 'ForEachProp') { + return { + 'loopType': loopType, + 'nameVarName': nameVarName, + 'valueVarName': valueVarName, + 'in': inExpression + }; + } else if (loopType === 'ForRange') { + return { + 'loopType': loopType, + 'varName': varName, + 'from': fromExpression, + 'to': toExpression, + 'step': stepExpression + }; + } else if (loopType === 'For') { + if (forTest == null) { + return { + error: 'Invalid native for loop. Expected format: ; ; ' + }; + } + return { + 'loopType': loopType, + 'init': forInit, + 'test': forTest, + 'update': forUpdate + }; + } else { + return { + 'error': 'Invalid for loop' + }; + } +}; \ No newline at end of file diff --git a/taglibs/core/util/parseForEach.js b/taglibs/core/util/parseForEach.js deleted file mode 100644 index 24a7e3864..000000000 --- a/taglibs/core/util/parseForEach.js +++ /dev/null @@ -1,104 +0,0 @@ -var forEachRegEx = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+in\s+(.+)$/; -var forEachPropRegEx = /^\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)\s+in\s+(.+)$/; -var forRangeRegEx = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+from\s+(.+)$/; // i from 0 to 10 or i from 0 to 10 step 5 -var forRangeKeywordsRegExp = /"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\s+(to|step)\s+/g; -var integerRegExp = /^-?\d+$/; -var numberRegExp = /^-?(?:\d+|\d+\.\d*|\d*\.\d+|\d+\.\d+)$/; - -function convertNumber(str) { - if (!str) { - return str; - } - - if (integerRegExp.test(str)) { - return parseInt(str, 10); - } else if (numberRegExp.test(str)) { - return parseFloat(str); - } else { - return str; - } -} - -module.exports = function(value) { - var match = value.match(forEachRegEx); - if (match) { - return { - 'varName': match[1], - 'target': match[2] - }; - } else if ((match = value.match(forEachPropRegEx))) { - - - return { - 'nameVar': match[1], - 'valueVar': match[2], - 'target': match[3] - }; - } else if ((match = value.match(forRangeRegEx))) { - var nameVar = match[1]; - - - var remainder = match[2]; - var rangeMatches; - - var fromStart = 0; - var fromEnd = -1; - - var toStart = -1; - var toEnd = remainder.length; - - var stepStart = -1; - var stepEnd = -1; - - while ((rangeMatches = forRangeKeywordsRegExp.exec(remainder))) { - if (rangeMatches[1] === 'to') { - fromEnd = rangeMatches.index; - toStart = forRangeKeywordsRegExp.lastIndex; - } else if (rangeMatches[1] === 'step') { - if (toStart === -1) { - continue; - } - toEnd = rangeMatches.index; - stepStart = forRangeKeywordsRegExp.lastIndex; - stepEnd = remainder.length; - } - } - - if (toStart === -1 || fromEnd === -1) { - throw new Error('Invalid each attribute of "' + value + '"'); - } - - var from = remainder.substring(fromStart, fromEnd).trim(); - var to = remainder.substring(toStart, toEnd).trim(); - var step; - - from = convertNumber(from); - to = convertNumber(to); - - if (stepStart !== -1) { - step = remainder.substring(stepStart, stepEnd).trim(); - step = convertNumber(step); - } else { - if (typeof from === 'number' && typeof to === 'number') { - if (from < to) { - step = 1; - } else { - step = -1; - } - } else { - step = 1; - } - - } - - return { - 'varName': nameVar, - 'from': from, - 'to': to, - 'step': step - }; - } else { - throw new Error('Invalid each attribute of "' + value + '"'); - } - -}; \ No newline at end of file diff --git a/taglibs/core/util/removeComments.js b/taglibs/core/util/removeComments.js new file mode 100644 index 000000000..7a711fc7f --- /dev/null +++ b/taglibs/core/util/removeComments.js @@ -0,0 +1,53 @@ +'use strict'; +var tokenizer = require('./tokenizer').create([ + { + name: 'stringDouble', + pattern: /"(?:[^"]|\\")*"/, + }, + { + name: 'stringSingle', + pattern: /'(?:[^']|\\')*'/ + }, + { + name: 'singleLineComment', + pattern: /\/\/.*/ + }, + { + name: 'multiLineComment', + pattern: /\/\*(?:[\s\S]*?)\*\// + } +]); + +/** + * Parses a for loop string in the following forms: + * + * in + * in | status-var= separator= + * from to + * from to step + * ; ; + */ +module.exports = function removeComments(str) { + + var comments = []; + + tokenizer.forEachToken(str, (token) => { + switch(token.name) { + case 'singleLineComment': + case 'multiLineComment': + comments.push(token); + break; + } + }); + + var len = comments.length; + + if (len) { + for (var i=len-1; i>=0; i--) { + var comment = comments[i]; + str = str.substring(0, comment.start) + str.substring(comment.end); + } + } + + return str; +}; \ No newline at end of file diff --git a/taglibs/core/util/tokenizer.js b/taglibs/core/util/tokenizer.js new file mode 100644 index 000000000..4b72cd208 --- /dev/null +++ b/taglibs/core/util/tokenizer.js @@ -0,0 +1,37 @@ +'use strict'; + +function create(tokens) { + function getToken(matches) { + for (var i=0; i { + return '(' + token.pattern.source + ')'; + }) + .join('|'), 'g'); + + return { + forEachToken: function(value, callback, thisObj) { + tokensRegExp.lastIndex = 0; // Start searching from the beginning again + var matches; + while ((matches = tokensRegExp.exec(value))) { + let token = getToken(matches); + callback.call(this, token); + } + } + }; +} + +exports.create = create; \ No newline at end of file diff --git a/taglibs/core/var-tag.js b/taglibs/core/var-tag.js index 9051d6d5c..13307d49a 100644 --- a/taglibs/core/var-tag.js +++ b/taglibs/core/var-tag.js @@ -1,4 +1,12 @@ module.exports = function nodeFactory(el, context) { - var attributes = el.attributes; - return context.builder.vars(attributes); + var builder = context.builder; + + var declarations = el.attributes.map((attr) => { + return { + id: builder.identifier(attr.name), + init: builder.expression(attr.value) + }; + }); + + return context.builder.vars(declarations); }; \ No newline at end of file diff --git a/test/autotest.js b/test/autotest.js index 0de8cb864..c5b323e86 100644 --- a/test/autotest.js +++ b/test/autotest.js @@ -10,6 +10,11 @@ function autoTest(name, dir, run, options) { var actualPath = path.join(dir, 'actual' + compareExtension); var expectedPath = path.join(dir, 'expected' + compareExtension); + try { + fs.unlinkSync(actualPath); + } catch(e) {} + + var actual = run(dir); if (actual === '$PASS$') { return; diff --git a/test/fixtures/codegen/autotest-pending/forEachProps/index.js b/test/fixtures/codegen/autotest-pending/forEachProps/index.js index ee1e3dd32..6fdd22242 100644 --- a/test/fixtures/codegen/autotest-pending/forEachProps/index.js +++ b/test/fixtures/codegen/autotest-pending/forEachProps/index.js @@ -2,13 +2,12 @@ module.exports = function(builder) { var templateRoot = builder.templateRoot; - var forEach = builder.forEach; return templateRoot([ - forEach({ + builder.forEachProp({ nameVarName: 'k', valueVarName: 'v', - target: 'myArray', + in: 'myArray', body: [ builder.functionCall('console.log', [ builder.literal('k:'), diff --git a/test/fixtures/codegen/autotest/forEachRange/expected.js b/test/fixtures/codegen/autotest/forRange/expected.js similarity index 85% rename from test/fixtures/codegen/autotest/forEachRange/expected.js rename to test/fixtures/codegen/autotest/forRange/expected.js index 0848bfc13..0736273bc 100644 --- a/test/fixtures/codegen/autotest/forEachRange/expected.js +++ b/test/fixtures/codegen/autotest/forRange/expected.js @@ -6,7 +6,7 @@ function create(__helpers) { return function render(data, out) { (function() { - for (var i = 0; i<=myArray.length; i+=2) { + for (var i = 0; i <= myArray.length; i += 2) { console.log(i); } }()); diff --git a/test/fixtures/codegen/autotest/forEachRange/index.js b/test/fixtures/codegen/autotest/forRange/index.js similarity index 88% rename from test/fixtures/codegen/autotest/forEachRange/index.js rename to test/fixtures/codegen/autotest/forRange/index.js index 557f416dc..fbd18b066 100644 --- a/test/fixtures/codegen/autotest/forEachRange/index.js +++ b/test/fixtures/codegen/autotest/forRange/index.js @@ -2,10 +2,9 @@ module.exports = function(builder) { var templateRoot = builder.templateRoot; - var forEach = builder.forEach; return templateRoot([ - forEach({ + builder.forRange({ varName: 'i', from: 0, to: 'myArray.length', diff --git a/test/fixtures/codegen/autotest/negate/expected.js b/test/fixtures/codegen/autotest/negate/expected.js new file mode 100644 index 000000000..c1556419c --- /dev/null +++ b/test/fixtures/codegen/autotest/negate/expected.js @@ -0,0 +1 @@ +!foo \ No newline at end of file diff --git a/test/fixtures/codegen/autotest/negate/index.js b/test/fixtures/codegen/autotest/negate/index.js new file mode 100644 index 000000000..897979970 --- /dev/null +++ b/test/fixtures/codegen/autotest/negate/index.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function(builder) { + return builder.negate(builder.identifier('foo')); +}; \ No newline at end of file diff --git a/test/fixtures/codegen/autotest/unaryExpression/expected.js b/test/fixtures/codegen/autotest/unaryExpression/expected.js new file mode 100644 index 000000000..c1556419c --- /dev/null +++ b/test/fixtures/codegen/autotest/unaryExpression/expected.js @@ -0,0 +1 @@ +!foo \ No newline at end of file diff --git a/test/fixtures/codegen/autotest/unaryExpression/index.js b/test/fixtures/codegen/autotest/unaryExpression/index.js new file mode 100644 index 000000000..897979970 --- /dev/null +++ b/test/fixtures/codegen/autotest/unaryExpression/index.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function(builder) { + return builder.negate(builder.identifier('foo')); +}; \ No newline at end of file diff --git a/test/fixtures/parseExpression/autotest/binaryExpression/expected.json b/test/fixtures/parseExpression/autotest/binaryExpression/expected.json new file mode 100644 index 000000000..fc5d28576 --- /dev/null +++ b/test/fixtures/parseExpression/autotest/binaryExpression/expected.json @@ -0,0 +1,12 @@ +{ + "type": "BinaryExpression", + "left": { + "type": "Identifier", + "name": "foo" + }, + "operator": "+", + "right": { + "type": "Literal", + "value": "12" + } +} \ No newline at end of file diff --git a/test/fixtures/parseExpression/autotest/binaryExpression/input.txt b/test/fixtures/parseExpression/autotest/binaryExpression/input.txt new file mode 100644 index 000000000..1b3071bbe --- /dev/null +++ b/test/fixtures/parseExpression/autotest/binaryExpression/input.txt @@ -0,0 +1 @@ +foo+'12' \ No newline at end of file diff --git a/test/fixtures/parseExpression/autotest/complex/expected.json b/test/fixtures/parseExpression/autotest/complex/expected.json new file mode 100644 index 000000000..73f6ba95d --- /dev/null +++ b/test/fixtures/parseExpression/autotest/complex/expected.json @@ -0,0 +1,44 @@ +{ + "type": "Assignment", + "left": { + "type": "Identifier", + "name": "a" + }, + "right": { + "type": "Assignment", + "left": { + "type": "Identifier", + "name": "b" + }, + "right": { + "type": "BinaryExpression", + "left": { + "type": "BinaryExpression", + "left": { + "type": "Identifier", + "name": "a" + }, + "operator": "+", + "right": { + "type": "Identifier", + "name": "b" + } + }, + "operator": ">", + "right": { + "type": "Assignment", + "left": { + "type": "Identifier", + "name": "c" + }, + "right": { + "type": "Literal", + "value": 2 + }, + "operator": "+=" + } + }, + "operator": "=" + }, + "operator": "=" +} \ No newline at end of file diff --git a/test/fixtures/parseExpression/autotest/complex/input.txt b/test/fixtures/parseExpression/autotest/complex/input.txt new file mode 100644 index 000000000..8bf86c5ed --- /dev/null +++ b/test/fixtures/parseExpression/autotest/complex/input.txt @@ -0,0 +1 @@ +a = b = (a+b)>(c+=2) \ No newline at end of file diff --git a/test/fixtures/parseExpression/autotest/id/expected.json b/test/fixtures/parseExpression/autotest/id/expected.json new file mode 100644 index 000000000..d32cc17b7 --- /dev/null +++ b/test/fixtures/parseExpression/autotest/id/expected.json @@ -0,0 +1,4 @@ +{ + "type": "Identifier", + "name": "foo" +} \ No newline at end of file diff --git a/test/fixtures/parseExpression/autotest/id/input.txt b/test/fixtures/parseExpression/autotest/id/input.txt new file mode 100644 index 000000000..191028156 --- /dev/null +++ b/test/fixtures/parseExpression/autotest/id/input.txt @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/test/fixtures/parseExpression/autotest/negate/expected.json b/test/fixtures/parseExpression/autotest/negate/expected.json new file mode 100644 index 000000000..593780f26 --- /dev/null +++ b/test/fixtures/parseExpression/autotest/negate/expected.json @@ -0,0 +1,9 @@ +{ + "type": "UnaryExpression", + "argument": { + "type": "Identifier", + "name": "foo" + }, + "operator": "!", + "prefix": true +} \ No newline at end of file diff --git a/test/fixtures/parseExpression/autotest/negate/input.txt b/test/fixtures/parseExpression/autotest/negate/input.txt new file mode 100644 index 000000000..c1556419c --- /dev/null +++ b/test/fixtures/parseExpression/autotest/negate/input.txt @@ -0,0 +1 @@ +!foo \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-comment/expected.json b/test/fixtures/parseFor/autotest/forEach-comment/expected.json new file mode 100644 index 000000000..b7605074f --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-comment/expected.json @@ -0,0 +1,11 @@ +{ + "loopType": "ForEach", + "varName": { + "type": "Identifier", + "name": "color" + }, + "in": { + "type": "Expression", + "value": "colors" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-comment/input.txt b/test/fixtures/parseFor/autotest/forEach-comment/input.txt new file mode 100644 index 000000000..5174ccbc7 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-comment/input.txt @@ -0,0 +1 @@ +color in colors /* | separator="," */ \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-crazy/expected.json b/test/fixtures/parseFor/autotest/forEach-crazy/expected.json new file mode 100644 index 000000000..f753c8e0d --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-crazy/expected.json @@ -0,0 +1,23 @@ +{ + "loopType": "ForEach", + "varName": { + "type": "Identifier", + "name": "color" + }, + "in": { + "type": "Expression", + "value": "(a in test | 0)" + }, + "separator": { + "type": "Expression", + "value": "(iterator==1 ? 'foo' : 'bar')" + }, + "statusVarName": { + "type": "Identifier", + "name": "loop" + }, + "iterator": { + "type": "Expression", + "value": "my Iterator" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-crazy/input.txt b/test/fixtures/parseFor/autotest/forEach-crazy/input.txt new file mode 100644 index 000000000..148a36eea --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-crazy/input.txt @@ -0,0 +1 @@ +color in (a in test | 0) | status-var=loop separator=(iterator==1 ? 'foo' : 'bar') iterator=my Iterator \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-iterator/expected.json b/test/fixtures/parseFor/autotest/forEach-iterator/expected.json new file mode 100644 index 000000000..1a1dbab06 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-iterator/expected.json @@ -0,0 +1,15 @@ +{ + "loopType": "ForEach", + "varName": { + "type": "Identifier", + "name": "color" + }, + "in": { + "type": "Expression", + "value": "colors" + }, + "iterator": { + "type": "Expression", + "value": "myIterator" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-iterator/input.txt b/test/fixtures/parseFor/autotest/forEach-iterator/input.txt new file mode 100644 index 000000000..58a5d9f8c --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-iterator/input.txt @@ -0,0 +1 @@ +color in colors | iterator=myIterator \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-nesting/expected.json b/test/fixtures/parseFor/autotest/forEach-nesting/expected.json new file mode 100644 index 000000000..3344773b2 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-nesting/expected.json @@ -0,0 +1,11 @@ +{ + "loopType": "ForEach", + "varName": { + "type": "Identifier", + "name": "color" + }, + "in": { + "type": "Expression", + "value": "[(({a} in test | 0))]" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-nesting/input.txt b/test/fixtures/parseFor/autotest/forEach-nesting/input.txt new file mode 100644 index 000000000..de77c8415 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-nesting/input.txt @@ -0,0 +1 @@ +color in [(({a} in test | 0))] \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-simple/expected.json b/test/fixtures/parseFor/autotest/forEach-simple/expected.json new file mode 100644 index 000000000..b7605074f --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-simple/expected.json @@ -0,0 +1,11 @@ +{ + "loopType": "ForEach", + "varName": { + "type": "Identifier", + "name": "color" + }, + "in": { + "type": "Expression", + "value": "colors" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-simple/input.txt b/test/fixtures/parseFor/autotest/forEach-simple/input.txt new file mode 100644 index 000000000..67e65cba6 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-simple/input.txt @@ -0,0 +1 @@ +color in colors \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-status-var-separator-iterator/expected.json b/test/fixtures/parseFor/autotest/forEach-status-var-separator-iterator/expected.json new file mode 100644 index 000000000..3870899a8 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-status-var-separator-iterator/expected.json @@ -0,0 +1,23 @@ +{ + "loopType": "ForEach", + "varName": { + "type": "Identifier", + "name": "color" + }, + "in": { + "type": "Expression", + "value": "colors" + }, + "separator": { + "type": "Expression", + "value": "','" + }, + "statusVarName": { + "type": "Identifier", + "name": "loop" + }, + "iterator": { + "type": "Expression", + "value": "myIterator" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-status-var-separator-iterator/input.txt b/test/fixtures/parseFor/autotest/forEach-status-var-separator-iterator/input.txt new file mode 100644 index 000000000..f44bf3fd1 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-status-var-separator-iterator/input.txt @@ -0,0 +1 @@ +color in colors | status-var=loop separator=',' iterator=myIterator \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-status-var-separator/expected.json b/test/fixtures/parseFor/autotest/forEach-status-var-separator/expected.json new file mode 100644 index 000000000..fa48d5701 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-status-var-separator/expected.json @@ -0,0 +1,19 @@ +{ + "loopType": "ForEach", + "varName": { + "type": "Identifier", + "name": "color" + }, + "in": { + "type": "Expression", + "value": "colors" + }, + "separator": { + "type": "Expression", + "value": "','" + }, + "statusVarName": { + "type": "Identifier", + "name": "loop" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-status-var-separator/input.txt b/test/fixtures/parseFor/autotest/forEach-status-var-separator/input.txt new file mode 100644 index 000000000..a10b53587 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-status-var-separator/input.txt @@ -0,0 +1 @@ +color in colors | status-var=loop separator=',' \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-status-var/expected.json b/test/fixtures/parseFor/autotest/forEach-status-var/expected.json new file mode 100644 index 000000000..7c7bd684d --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-status-var/expected.json @@ -0,0 +1,15 @@ +{ + "loopType": "ForEach", + "varName": { + "type": "Identifier", + "name": "color" + }, + "in": { + "type": "Expression", + "value": "colors" + }, + "statusVarName": { + "type": "Identifier", + "name": "loop" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-status-var/input.txt b/test/fixtures/parseFor/autotest/forEach-status-var/input.txt new file mode 100644 index 000000000..e7c4e98d4 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-status-var/input.txt @@ -0,0 +1 @@ +color in colors | status-var=loop \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-strings/expected.json b/test/fixtures/parseFor/autotest/forEach-strings/expected.json new file mode 100644 index 000000000..41b23673c --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-strings/expected.json @@ -0,0 +1,11 @@ +{ + "loopType": "ForEach", + "varName": { + "type": "Identifier", + "name": "color" + }, + "in": { + "type": "Expression", + "value": "' | separator=\",\";'" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forEach-strings/input.txt b/test/fixtures/parseFor/autotest/forEach-strings/input.txt new file mode 100644 index 000000000..2ee3168aa --- /dev/null +++ b/test/fixtures/parseFor/autotest/forEach-strings/input.txt @@ -0,0 +1 @@ +color in ' | separator=",";' \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forRange-to-expr/expected.json b/test/fixtures/parseFor/autotest/forRange-to-expr/expected.json new file mode 100644 index 000000000..623e5dc7c --- /dev/null +++ b/test/fixtures/parseFor/autotest/forRange-to-expr/expected.json @@ -0,0 +1,15 @@ +{ + "loopType": "ForRange", + "varName": { + "type": "Identifier", + "name": "i" + }, + "from": { + "type": "Literal", + "value": 0 + }, + "to": { + "type": "Expression", + "value": "'abc'.length" + } +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/forRange-to-expr/input.txt b/test/fixtures/parseFor/autotest/forRange-to-expr/input.txt new file mode 100644 index 000000000..76452da64 --- /dev/null +++ b/test/fixtures/parseFor/autotest/forRange-to-expr/input.txt @@ -0,0 +1 @@ +i from 0 to 'abc'.length \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/nativeFor-empty-init/expected.json b/test/fixtures/parseFor/autotest/nativeFor-empty-init/expected.json new file mode 100644 index 000000000..18e85ce7b --- /dev/null +++ b/test/fixtures/parseFor/autotest/nativeFor-empty-init/expected.json @@ -0,0 +1,6 @@ +{ + "loopType": "For", + "init": "", + "test": " i; ; " +} \ No newline at end of file diff --git a/test/fixtures/parseFor/autotest/nativeFor-invalid/input.txt b/test/fixtures/parseFor/autotest/nativeFor-invalid/input.txt new file mode 100644 index 000000000..07182a0e9 --- /dev/null +++ b/test/fixtures/parseFor/autotest/nativeFor-invalid/input.txt @@ -0,0 +1 @@ +var i=0; i +
${item} - ${loop.isFirst()} - ${loop.isLast()} - ${loop.getIndex()} - ${loop.getLength()}
\ No newline at end of file diff --git a/test/fixtures/render/autotest/for-attr-separator/template.marko b/test/fixtures/render/autotest/for-attr-separator/template.marko index 2dff26497..c81f7c116 100644 --- a/test/fixtures/render/autotest/for-attr-separator/template.marko +++ b/test/fixtures/render/autotest/for-attr-separator/template.marko @@ -1,3 +1,3 @@ - + ${item} \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-descending-step/expected.html b/test/fixtures/render/autotest/for-range-descending-step/expected.html new file mode 100644 index 000000000..919c15914 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-descending-step/expected.html @@ -0,0 +1 @@ +9876543210 \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-descending-step/template.marko b/test/fixtures/render/autotest/for-range-descending-step/template.marko new file mode 100644 index 000000000..39ee2ea2f --- /dev/null +++ b/test/fixtures/render/autotest/for-range-descending-step/template.marko @@ -0,0 +1,3 @@ + + ${i} + \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-descending-step/test.js b/test/fixtures/render/autotest/for-range-descending-step/test.js new file mode 100644 index 000000000..c4013b344 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-descending-step/test.js @@ -0,0 +1 @@ +exports.templateData = {}; diff --git a/test/fixtures/render/autotest/for-range-descending/expected.html b/test/fixtures/render/autotest/for-range-descending/expected.html new file mode 100644 index 000000000..919c15914 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-descending/expected.html @@ -0,0 +1 @@ +9876543210 \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-descending/template.marko b/test/fixtures/render/autotest/for-range-descending/template.marko new file mode 100644 index 000000000..9e1cbcd54 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-descending/template.marko @@ -0,0 +1,3 @@ + + ${i} + \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-descending/test.js b/test/fixtures/render/autotest/for-range-descending/test.js new file mode 100644 index 000000000..c4013b344 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-descending/test.js @@ -0,0 +1 @@ +exports.templateData = {}; diff --git a/test/fixtures/render/autotest/for-range-from-to-expr/expected.html b/test/fixtures/render/autotest/for-range-from-to-expr/expected.html new file mode 100644 index 000000000..6da806d7a --- /dev/null +++ b/test/fixtures/render/autotest/for-range-from-to-expr/expected.html @@ -0,0 +1 @@ +0123 \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-from-to-expr/template.marko b/test/fixtures/render/autotest/for-range-from-to-expr/template.marko new file mode 100644 index 000000000..4cec18573 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-from-to-expr/template.marko @@ -0,0 +1,3 @@ + + ${i} + \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-from-to-expr/test.js b/test/fixtures/render/autotest/for-range-from-to-expr/test.js new file mode 100644 index 000000000..c4013b344 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-from-to-expr/test.js @@ -0,0 +1 @@ +exports.templateData = {}; diff --git a/test/fixtures/render/autotest/for-range-step-2/expected.html b/test/fixtures/render/autotest/for-range-step-2/expected.html new file mode 100644 index 000000000..6c0743e68 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-step-2/expected.html @@ -0,0 +1 @@ +02468 \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-step-2/template.marko b/test/fixtures/render/autotest/for-range-step-2/template.marko new file mode 100644 index 000000000..b3f59daad --- /dev/null +++ b/test/fixtures/render/autotest/for-range-step-2/template.marko @@ -0,0 +1,3 @@ + + ${i} + \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-step-2/test.js b/test/fixtures/render/autotest/for-range-step-2/test.js new file mode 100644 index 000000000..c4013b344 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-step-2/test.js @@ -0,0 +1 @@ +exports.templateData = {}; diff --git a/test/fixtures/render/autotest/for-range-step-neg2/expected.html b/test/fixtures/render/autotest/for-range-step-neg2/expected.html new file mode 100644 index 000000000..98cf7e8d9 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-step-neg2/expected.html @@ -0,0 +1 @@ +97531 \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-step-neg2/template.marko b/test/fixtures/render/autotest/for-range-step-neg2/template.marko new file mode 100644 index 000000000..3a3898b90 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-step-neg2/template.marko @@ -0,0 +1,3 @@ + + ${i} + \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-step-neg2/test.js b/test/fixtures/render/autotest/for-range-step-neg2/test.js new file mode 100644 index 000000000..c4013b344 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-step-neg2/test.js @@ -0,0 +1 @@ +exports.templateData = {}; diff --git a/test/fixtures/render/autotest/for-range-to-expr/expected.html b/test/fixtures/render/autotest/for-range-to-expr/expected.html new file mode 100644 index 000000000..6da806d7a --- /dev/null +++ b/test/fixtures/render/autotest/for-range-to-expr/expected.html @@ -0,0 +1 @@ +0123 \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-to-expr/template.marko b/test/fixtures/render/autotest/for-range-to-expr/template.marko new file mode 100644 index 000000000..82edd7a83 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-to-expr/template.marko @@ -0,0 +1,3 @@ + + ${i} + \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range-to-expr/test.js b/test/fixtures/render/autotest/for-range-to-expr/test.js new file mode 100644 index 000000000..c4013b344 --- /dev/null +++ b/test/fixtures/render/autotest/for-range-to-expr/test.js @@ -0,0 +1 @@ +exports.templateData = {}; diff --git a/test/fixtures/render/autotest/for-range/expected.html b/test/fixtures/render/autotest/for-range/expected.html index e135dea12..ad471007b 100644 --- a/test/fixtures/render/autotest/for-range/expected.html +++ b/test/fixtures/render/autotest/for-range/expected.html @@ -1 +1 @@ -0123456789 - 9876543210 - 9876543210 - 02468 - 97531 - 0123 - 0123 \ No newline at end of file +0123456789 \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-range/template.marko b/test/fixtures/render/autotest/for-range/template.marko index de3c4969d..2b2e896c5 100644 --- a/test/fixtures/render/autotest/for-range/template.marko +++ b/test/fixtures/render/autotest/for-range/template.marko @@ -1,27 +1,3 @@ ${i} - -- - - ${i} - -- - - ${i} - -- - - ${i} - -- - - ${i} - -- - - ${i} - -- - - ${i} \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-tag-native/expected.html b/test/fixtures/render/autotest/for-tag-native/expected.html new file mode 100644 index 000000000..1885a3e3c --- /dev/null +++ b/test/fixtures/render/autotest/for-tag-native/expected.html @@ -0,0 +1 @@ +
0
1
2
\ No newline at end of file diff --git a/test/fixtures/render/autotest/for-tag-native/template.marko b/test/fixtures/render/autotest/for-tag-native/template.marko new file mode 100644 index 000000000..dc0006365 --- /dev/null +++ b/test/fixtures/render/autotest/for-tag-native/template.marko @@ -0,0 +1,5 @@ + +
+ ${i} +
+ \ No newline at end of file diff --git a/test/fixtures/render/autotest/for-tag-native/test.js b/test/fixtures/render/autotest/for-tag-native/test.js new file mode 100644 index 000000000..c4013b344 --- /dev/null +++ b/test/fixtures/render/autotest/for-tag-native/test.js @@ -0,0 +1 @@ +exports.templateData = {}; diff --git a/test/fixtures/render/autotest/for-tag-separator-status-var/template.marko b/test/fixtures/render/autotest/for-tag-separator-status-var/template.marko index e2aea2d47..63427316d 100644 --- a/test/fixtures/render/autotest/for-tag-separator-status-var/template.marko +++ b/test/fixtures/render/autotest/for-tag-separator-status-var/template.marko @@ -1,4 +1,4 @@ - +
${item} - ${loop.isFirst()} - ${loop.isLast()} - ${loop.getIndex()} - ${loop.getLength()}
diff --git a/test/fixtures/render/autotest/for-tag-separator/template.marko b/test/fixtures/render/autotest/for-tag-separator/template.marko index 1a929d752..6f1732a4d 100644 --- a/test/fixtures/render/autotest/for-tag-separator/template.marko +++ b/test/fixtures/render/autotest/for-tag-separator/template.marko @@ -1,4 +1,4 @@ - + ${item} diff --git a/test/fixtures/render/autotest/var/expected.html b/test/fixtures/render/autotest/var/expected.html index 7ef5e2f97..2b018071a 100644 --- a/test/fixtures/render/autotest/var/expected.html +++ b/test/fixtures/render/autotest/var/expected.html @@ -1 +1 @@ -FRANK 3650 \ No newline at end of file +FRANK 3650 [] \ No newline at end of file diff --git a/test/fixtures/render/autotest/var/template.marko b/test/fixtures/render/autotest/var/template.marko index 8029abb9c..8a222b06c 100644 --- a/test/fixtures/render/autotest/var/template.marko +++ b/test/fixtures/render/autotest/var/template.marko @@ -1,3 +1,3 @@ - + -${name.toUpperCase()} ${age*365} \ No newline at end of file +${name.toUpperCase()} ${age*365} [${foo}] \ No newline at end of file diff --git a/test/parseExpression-test.js b/test/parseExpression-test.js new file mode 100644 index 000000000..707507a2a --- /dev/null +++ b/test/parseExpression-test.js @@ -0,0 +1,25 @@ +'use strict'; +var chai = require('chai'); +chai.config.includeStack = true; +var parseExpression = require('../compiler/util/parseExpression'); +var autotest = require('./autotest'); +var fs = require('fs'); +var path = require('path'); + +describe('parseExpression' , function() { + + var autoTestDir = path.join(__dirname, 'fixtures/parseExpression/autotest'); + + autotest.scanDir( + autoTestDir, + function run(dir) { + var inputPath = path.join(dir, 'input.txt'); + var input = fs.readFileSync(inputPath, {encoding: 'utf8'}); + var parsed = parseExpression(input); + return parsed; + }, + { + deepEqual: true, + compareExtension: '.json' + }); +}); diff --git a/test/parseFor-test.js b/test/parseFor-test.js new file mode 100644 index 000000000..f90202b4e --- /dev/null +++ b/test/parseFor-test.js @@ -0,0 +1,25 @@ +'use strict'; +var chai = require('chai'); +chai.config.includeStack = true; +var parseFor = require('../taglibs/core/util/parseFor.js'); +var autotest = require('./autotest'); +var fs = require('fs'); +var path = require('path'); + +describe('parseFor' , function() { + + var autoTestDir = path.join(__dirname, 'fixtures/parseFor/autotest'); + + autotest.scanDir( + autoTestDir, + function run(dir) { + var inputPath = path.join(dir, 'input.txt'); + var input = fs.readFileSync(inputPath, {encoding: 'utf8'}); + var parsed = parseFor(input); + return parsed; + }, + { + deepEqual: true, + compareExtension: '.json' + }); +});