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

Bundle arithmetic now supports parens. #535

Merged
merged 6 commits into from
Mar 29, 2016
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
142 changes: 112 additions & 30 deletions lib/arithmetic.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,106 @@ 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++) {
if (/\s/.test(expressionString.charAt(index))) {
return result;
} else {
result += expressionString.charAt(index);
}
}
return result;
}

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,7 +218,7 @@ function expandGlobAndCanonicalize(builder, operation) {
});
}

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

Expand All @@ -159,12 +228,22 @@ exports.traceExpression = function(builder, expression, traceOpts) {
// 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);
if (operation.bulkOperation) {
var expandedTreePromise = traceExpression(builder, operation.bulkOperation, traceOpts)
expandPromise = expandPromise.then(function() {
return Promise.resolve(expandedTreePromise)
.then(function(fullTree){
expandedOperations = expandedOperations.concat({ preExpandedTree: fullTree, operator: operation.operator })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this still be called bulkOperation instead of preExpandedTree or am I missing something here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right I see what you are doing here, running the actual arithmetic during this phase recursively.

To avoid conflating steps I think I'd rather take one of two approaches here:

  1. Instead of a single traceExpression separate it out into traceExpression and traceBulkExpression, where traceBulkExpression handles this sort of logic instead.
  2. During this expansion step, recursively expand globs only, leaving the arithmetic syntax in-tact.

I'd be happy with either method here, let me know if that makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify the reason being here that each "step" of the algorithm should have a clear purpose. To do globbing and paren expansion together is an arbitrary conflation just to take advantage of the same loop, something which can rather be split up into two loops (corresponding to either (1) or (2)).

});
});
});
} else if (operation.operator) {
expandPromise = expandPromise.then(function() {
return Promise.resolve(expandGlobAndCanonicalize(builder, operation))
.then(function (expanded) {
expandedOperations = expandedOperations.concat(expanded);
});
});
}
});

return expandPromise.then(function() {
Expand All @@ -174,7 +253,10 @@ exports.traceExpression = function(builder, expression, traceOpts) {
// tree . module
if (op.singleModule)
return getTreeModuleOperation(builder, op.operator)(curTree, op.moduleName);


if (op.preExpandedTree){
return getTreeOperation(op.operator)(curTree, op.preExpandedTree);
}
// tree . tree
return builder.tracer.traceCanonical(op.moduleName, traceOpts)
.then(function(nextTrace) {
Expand Down