Skip to content
This repository has been archived by the owner on Oct 9, 2020. It is now read-only.

Commit

Permalink
Merge pull request #535 from arackaf/parentheses-support-in-bundle-ar…
Browse files Browse the repository at this point in the history
…ithmetic

Bundle arithmetic now supports parens.
  • Loading branch information
guybedford committed Mar 29, 2016
2 parents eba62fe + 48bc668 commit b5cf432
Show file tree
Hide file tree
Showing 12 changed files with 400 additions and 56 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ test/output
test/output.js
test/output.js.map
dist
.idea/
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,14 @@ builder.bundle('app/**/* - [app/**/*]', 'dependencies.js', { minify: true, sourc

The above means _take the tree of app and all its dependencies, and subtract just the modules in app_, thus leaving us with just the tree of dependencies of the app package.

#### Example - Multiple Common Bundles

Parentheses are supported, so the following would bundle everything in common with `page1` and `page2`, and also everything in common between `page3` and `page4`:

```javascript
builder.bundle('(app/page1.js & app/page2.js) + (app/page3.js & app/page4.js)', 'common.js');
```

#### Example - Direct Trace API

Instead of using the arithmetic syntax, we can construct the trace ourselves.
Expand Down
198 changes: 144 additions & 54 deletions lib/arithmetic.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,108 @@ var verifyTree = require('./utils').verifyTree;

function parseExpression(expressionString) {
expressionString = ' + ' + expressionString;
var args = expressionString.split(/ [\+\-\&] /);

var index = 0;
var operations = [];
var operatorRegex = /[\+\-\&]/;
var errorMessagesFromIndex = 3;

var curLen = 0;
for (var i = 0; i < args.length - 1; i++) {
var operator = expressionString[curLen + 1];
var moduleName = args[i + 1];
function getNextIdentifier() {
eatWhitespace();
var firstChar = expressionString.charAt(index);

curLen += moduleName.length + 3;
if (operatorRegex.test(firstChar)){
throw 'Syntax Error: Identifier or sub expression expected after <' + expressionString.slice(errorMessagesFromIndex).substr(0, index - errorMessagesFromIndex) + '> but found <' + firstChar + '> instead';
}

if (operator !== '+' && operator !== '-' && operator !== '&')
throw new TypeError('Expected operator before ' + operator);
if (!moduleName)
throw new TypeError('A module name is needed after ' + operator);
if (firstChar === '(') {
var closingParenIndex = index,
numOpenBeforeSelf = 0;

while (++closingParenIndex < expressionString.length){
if (expressionString.charAt(closingParenIndex) === '('){
numOpenBeforeSelf++;
} else if (expressionString.charAt(closingParenIndex) === ')') {
if (numOpenBeforeSelf){
numOpenBeforeSelf--;
} else {
break;
}
}
}
if (expressionString.charAt(closingParenIndex) !== ')'){
throw 'Syntax Error: Expression <' + expressionString.substr(index) + '> is never terminated. Did you forget to add a closing ")"?';
}

// detect [moduleName] syntax for individual modules not trees
var singleModule = moduleName.substr(0, 1) == '[' && moduleName.substr(moduleName.length - 1, 1) == ']';
if (singleModule)
moduleName = moduleName.substr(1, moduleName.length - 2);
var wholeExpression = expressionString.substring(index + 1, closingParenIndex);
index = closingParenIndex + 1;
return { bulkOperation: wholeExpression };
}

var canonicalized = moduleName.substr(0, 1) == '`' && moduleName.substr(moduleName.length - 1, 1) == '`';
if (canonicalized)
moduleName = moduleName.substr(1, moduleName.length - 2);
var result = "";
//scan the identifier
for (; index < expressionString.length; index++) {
var currentChar = expressionString.charAt(index);
//can have spaces in file names - so we need whitespace, operator, whitespace.
if (/^\s+[\+\-\&]\s+/.test(expressionString.substr(index))) {
return result;
} else {
result += currentChar;
}
}
return result.replace(/\s+$/, ''); //it appears as though trailing whitespace is trimmed downstream, but I'm snipping here to be safe
}

operations.push({
operator: operator,
moduleName: moduleName,
singleModule: singleModule,
canonicalized: canonicalized
});
function getNextOperator() {
eatWhitespace();
if (index === expressionString.length) return null;

var candidateResult = expressionString.charAt(index++); //all operators are single characters at the moment

if (!operatorRegex.test(candidateResult)){
throw 'Syntax Error: An operator was expected after <' + expressionString.slice(errorMessagesFromIndex).substr(0, index - 1 - errorMessagesFromIndex) + '> but found <' + expressionString.substring(index - 1) + '> instead';
}

return candidateResult;
}

function eatWhitespace() {
//wind past whitespace
for (; index < expressionString.length; index++) {
if (/\S/.test(expressionString.charAt(index))) {
break;
}
}
}

var operator;
while (index < expressionString.length && (operator = getNextOperator())) {
var moduleNameOrSubExpression = getNextIdentifier();

if (typeof moduleNameOrSubExpression === 'object'){
operations.push({
operator: operator,
bulkOperation: moduleNameOrSubExpression.bulkOperation
});
} else {
// detect [moduleName] syntax for individual modules not trees
var singleModule = moduleNameOrSubExpression.substr(0, 1) == '[' && moduleNameOrSubExpression.substr(moduleNameOrSubExpression.length - 1, 1) == ']';
if (singleModule) {
moduleNameOrSubExpression = moduleNameOrSubExpression.substr(1, moduleNameOrSubExpression.length - 2);
}

var canonicalized = moduleNameOrSubExpression.substr(0, 1) == '`' && moduleNameOrSubExpression.substr(moduleNameOrSubExpression.length - 1, 1) == '`';
if (canonicalized) {
moduleNameOrSubExpression = moduleNameOrSubExpression.substr(1, moduleNameOrSubExpression.length - 2);
}

operations.push({
operator: operator,
moduleName: moduleNameOrSubExpression,
singleModule: singleModule,
canonicalized: canonicalized
});
}
}

return operations;
Expand Down Expand Up @@ -149,42 +220,61 @@ function expandGlobAndCanonicalize(builder, operation) {
});
}

exports.traceExpression = function(builder, expression, traceOpts) {
if (!expression)
throw new Error('A module expression must be provided to trace.');
exports.traceExpression = function(builder, expression, traceOpts) {
if (!expression)
throw new Error('A module expression must be provided to trace.');

return Promise
.resolve(expandAndCanonicalizeExpression(builder, expression))
.then(function processExpandedOperations(expandedOperations) {
// chain the operations, applying them with the trace of the next module
return expandedOperations.reduce(function(p, op) {
return p.then(function(curTree) {
// tree . module
if (op.singleModule)
return getTreeModuleOperation(builder, op.operator)(curTree, op.moduleName);

if (op.operationsTree){
return processExpandedOperations(op.operationsTree).then(function(expandedTree){
return getTreeOperation(op.operator)(curTree, expandedTree);
});
}
// tree . tree
return builder.tracer.traceCanonical(op.moduleName, traceOpts)
.then(function(nextTrace) {
return getTreeOperation(op.operator)(curTree, nextTrace.tree);
});
});
}, Promise.resolve({}));
});
};

function expandAndCanonicalizeExpression(builder, expression){
var operations = parseExpression(expression);
var expandPromise = Promise.resolve(1);
var expandedOperations = [];

// expand any globbing operations in the expression
var expandPromise = Promise.resolve();
operations.forEach(function(operation) {
expandPromise = expandPromise.then(function() {
return Promise.resolve(expandGlobAndCanonicalize(builder, operation))
.then(function(expanded) {
expandedOperations = expandedOperations.concat(expanded);
operations.forEach(function(operation){
if (operation.bulkOperation) {
var expandedTreePromise = expandAndCanonicalizeExpression(builder, operation.bulkOperation);
expandPromise = expandPromise.then(function() {
return Promise.resolve(expandedTreePromise)
.then(function(expressionsOperations){
expandedOperations = expandedOperations.concat({ operator: operation.operator, operationsTree: expressionsOperations });
});
});
});
});

return expandPromise.then(function() {
// chain the operations, applying them with the trace of the next module
return expandedOperations.reduce(function(p, op) {
return p.then(function(curTree) {
// tree . module
if (op.singleModule)
return getTreeModuleOperation(builder, op.operator)(curTree, op.moduleName);

// tree . tree
return builder.tracer.traceCanonical(op.moduleName, traceOpts)
.then(function(nextTrace) {
return getTreeOperation(op.operator)(curTree, nextTrace.tree);
});
});
}, Promise.resolve({}));
} else {
expandPromise = expandPromise.then(function() {
return Promise.resolve(expandGlobAndCanonicalize(builder, operation))
.then(function (expanded) {
expandedOperations = expandedOperations.concat(expanded);
});
})
}
});
};

return Promise.resolve(expandPromise).then(function(){ return expandedOperations; });
}

// returns a new tree containing tree1 n tree2
exports.intersectTrees = intersectTrees;
Expand All @@ -210,7 +300,7 @@ function intersectTrees(tree1, tree2) {
}

return intersectTree;
};
}

// returns a new tree containing tree1 + tree2
exports.addTrees = addTrees;
Expand Down Expand Up @@ -249,7 +339,7 @@ function subtractTrees(tree1, tree2) {
}

return subtractTree;
};
}

// pre-order tree traversal with a visitor and stop condition
exports.traverseTree = traverseTree;
Expand Down

0 comments on commit b5cf432

Please sign in to comment.