Improve rules around when parallel stages are allowed [#11]

This commit is contained in:
David Evans 2018-05-04 20:47:18 +01:00
parent 918c62f049
commit e402eb2a0e
5 changed files with 203 additions and 34 deletions

View File

@ -1677,6 +1677,19 @@
}
function checkReferenceConflicts(allStages) {
const count = allStages
.filter((stage) => (
stage.type === 'block begin' ||
stage.type === 'block end'
))
.length;
if(!count) {
return null;
} else if(count !== allStages.length) {
return 'Cannot use parallel here';
}
const leftIDs = allStages
.filter((stage) => (stage.type === 'block begin'))
.map((stage) => stage.left);
@ -1708,6 +1721,21 @@
return null;
}
const PARALLEL_STAGES = [
'agent begin',
'agent end',
'agent highlight',
'block begin',
'block end',
'connect',
'connect-delay-begin',
'connect-delay-end',
'note over',
'note right',
'note left',
'note between',
];
function errorForParallel(existing, latest) {
if(!existing) {
return 'Nothing to run statement in parallel with';
@ -1717,6 +1745,9 @@
extractParallel(allStages, [existing]);
extractParallel(allStages, [latest]);
if(allStages.some((stage) => !PARALLEL_STAGES.includes(stage.type))) {
return 'Cannot use parallel here';
}
return (
checkAgentConflicts(allStages) ||
checkReferenceConflicts(allStages) ||
@ -1875,7 +1906,7 @@
const {stages} = this.currentSection;
if(parallel) {
const target = stages[stages.length - 2];
if(!target) {
if(stages.length === 0) {
throw new Error('Nothing to run statement in parallel with');
}
if(errorForParallel(target, stage)) {
@ -2085,7 +2116,10 @@
return name;
}
handleBlockBegin({ln, blockType, tag, label}) {
handleBlockBegin({ln, blockType, tag, label, parallel}) {
if(parallel) {
throw new Error('Cannot use parallel here');
}
this.beginNested(blockType, {
label,
ln,
@ -2094,7 +2128,10 @@
});
}
handleBlockSplit({ln, blockType, tag, label}) {
handleBlockSplit({ln, blockType, tag, label, parallel}) {
if(parallel) {
throw new Error('Cannot use parallel here');
}
if(this.currentNest.blockType !== 'if') {
throw new Error(
'Invalid block nesting ("else" inside ' +
@ -2114,7 +2151,7 @@
this.currentNest.sections.push(this.currentSection);
}
handleBlockEnd() {
handleBlockEnd({parallel}) {
if(this.nesting.length <= 1) {
throw new Error('Invalid block nesting (too many "end"s)');
}
@ -2142,7 +2179,7 @@
left: nested.leftGAgent.id,
right: nested.rightGAgent.id,
type: 'block end',
});
}, {parallel});
}
makeGroupDetails(pAgents, alias) {
@ -2231,25 +2268,25 @@
};
}
handleMark({name}) {
handleMark({name, parallel}) {
this.markers.add(name);
this.addStage({name, type: 'mark'}, {isVisible: false});
this.addStage({name, type: 'mark'}, {isVisible: false, parallel});
}
handleDivider({mode, height, label}) {
handleDivider({mode, height, label, parallel}) {
this.addStage({
formattedLabel: this.textFormatter(label),
height,
mode,
type: 'divider',
}, {isVisible: false});
}, {isVisible: false, parallel});
}
handleAsync({target}) {
handleAsync({target, parallel}) {
if(target !== '' && !this.markers.has(target)) {
throw new Error('Unknown marker: ' + target);
}
this.addStage({target, type: 'async'}, {isVisible: false});
this.addStage({target, type: 'async'}, {isVisible: false, parallel});
}
handleLabelPattern({pattern}) {

File diff suppressed because one or more lines are too long

View File

@ -1677,6 +1677,19 @@
}
function checkReferenceConflicts(allStages) {
const count = allStages
.filter((stage) => (
stage.type === 'block begin' ||
stage.type === 'block end'
))
.length;
if(!count) {
return null;
} else if(count !== allStages.length) {
return 'Cannot use parallel here';
}
const leftIDs = allStages
.filter((stage) => (stage.type === 'block begin'))
.map((stage) => stage.left);
@ -1708,6 +1721,21 @@
return null;
}
const PARALLEL_STAGES = [
'agent begin',
'agent end',
'agent highlight',
'block begin',
'block end',
'connect',
'connect-delay-begin',
'connect-delay-end',
'note over',
'note right',
'note left',
'note between',
];
function errorForParallel(existing, latest) {
if(!existing) {
return 'Nothing to run statement in parallel with';
@ -1717,6 +1745,9 @@
extractParallel(allStages, [existing]);
extractParallel(allStages, [latest]);
if(allStages.some((stage) => !PARALLEL_STAGES.includes(stage.type))) {
return 'Cannot use parallel here';
}
return (
checkAgentConflicts(allStages) ||
checkReferenceConflicts(allStages) ||
@ -1875,7 +1906,7 @@
const {stages} = this.currentSection;
if(parallel) {
const target = stages[stages.length - 2];
if(!target) {
if(stages.length === 0) {
throw new Error('Nothing to run statement in parallel with');
}
if(errorForParallel(target, stage)) {
@ -2085,7 +2116,10 @@
return name;
}
handleBlockBegin({ln, blockType, tag, label}) {
handleBlockBegin({ln, blockType, tag, label, parallel}) {
if(parallel) {
throw new Error('Cannot use parallel here');
}
this.beginNested(blockType, {
label,
ln,
@ -2094,7 +2128,10 @@
});
}
handleBlockSplit({ln, blockType, tag, label}) {
handleBlockSplit({ln, blockType, tag, label, parallel}) {
if(parallel) {
throw new Error('Cannot use parallel here');
}
if(this.currentNest.blockType !== 'if') {
throw new Error(
'Invalid block nesting ("else" inside ' +
@ -2114,7 +2151,7 @@
this.currentNest.sections.push(this.currentSection);
}
handleBlockEnd() {
handleBlockEnd({parallel}) {
if(this.nesting.length <= 1) {
throw new Error('Invalid block nesting (too many "end"s)');
}
@ -2142,7 +2179,7 @@
left: nested.leftGAgent.id,
right: nested.rightGAgent.id,
type: 'block end',
});
}, {parallel});
}
makeGroupDetails(pAgents, alias) {
@ -2231,25 +2268,25 @@
};
}
handleMark({name}) {
handleMark({name, parallel}) {
this.markers.add(name);
this.addStage({name, type: 'mark'}, {isVisible: false});
this.addStage({name, type: 'mark'}, {isVisible: false, parallel});
}
handleDivider({mode, height, label}) {
handleDivider({mode, height, label, parallel}) {
this.addStage({
formattedLabel: this.textFormatter(label),
height,
mode,
type: 'divider',
}, {isVisible: false});
}, {isVisible: false, parallel});
}
handleAsync({target}) {
handleAsync({target, parallel}) {
if(target !== '' && !this.markers.has(target)) {
throw new Error('Unknown marker: ' + target);
}
this.addStage({target, type: 'async'}, {isVisible: false});
this.addStage({target, type: 'async'}, {isVisible: false, parallel});
}
handleLabelPattern({pattern}) {

View File

@ -227,6 +227,19 @@ function checkAgentConflicts(allStages) {
}
function checkReferenceConflicts(allStages) {
const count = allStages
.filter((stage) => (
stage.type === 'block begin' ||
stage.type === 'block end'
))
.length;
if(!count) {
return null;
} else if(count !== allStages.length) {
return 'Cannot use parallel here';
}
const leftIDs = allStages
.filter((stage) => (stage.type === 'block begin'))
.map((stage) => stage.left);
@ -258,6 +271,21 @@ function checkDelayedConflicts(allStages) {
return null;
}
const PARALLEL_STAGES = [
'agent begin',
'agent end',
'agent highlight',
'block begin',
'block end',
'connect',
'connect-delay-begin',
'connect-delay-end',
'note over',
'note right',
'note left',
'note between',
];
function errorForParallel(existing, latest) {
if(!existing) {
return 'Nothing to run statement in parallel with';
@ -267,6 +295,9 @@ function errorForParallel(existing, latest) {
extractParallel(allStages, [existing]);
extractParallel(allStages, [latest]);
if(allStages.some((stage) => !PARALLEL_STAGES.includes(stage.type))) {
return 'Cannot use parallel here';
}
return (
checkAgentConflicts(allStages) ||
checkReferenceConflicts(allStages) ||
@ -425,7 +456,7 @@ export default class Generator {
const {stages} = this.currentSection;
if(parallel) {
const target = stages[stages.length - 2];
if(!target) {
if(stages.length === 0) {
throw new Error('Nothing to run statement in parallel with');
}
if(errorForParallel(target, stage)) {
@ -635,7 +666,10 @@ export default class Generator {
return name;
}
handleBlockBegin({ln, blockType, tag, label}) {
handleBlockBegin({ln, blockType, tag, label, parallel}) {
if(parallel) {
throw new Error('Cannot use parallel here');
}
this.beginNested(blockType, {
label,
ln,
@ -644,7 +678,10 @@ export default class Generator {
});
}
handleBlockSplit({ln, blockType, tag, label}) {
handleBlockSplit({ln, blockType, tag, label, parallel}) {
if(parallel) {
throw new Error('Cannot use parallel here');
}
if(this.currentNest.blockType !== 'if') {
throw new Error(
'Invalid block nesting ("else" inside ' +
@ -664,7 +701,7 @@ export default class Generator {
this.currentNest.sections.push(this.currentSection);
}
handleBlockEnd() {
handleBlockEnd({parallel}) {
if(this.nesting.length <= 1) {
throw new Error('Invalid block nesting (too many "end"s)');
}
@ -692,7 +729,7 @@ export default class Generator {
left: nested.leftGAgent.id,
right: nested.rightGAgent.id,
type: 'block end',
});
}, {parallel});
}
makeGroupDetails(pAgents, alias) {
@ -781,25 +818,25 @@ export default class Generator {
};
}
handleMark({name}) {
handleMark({name, parallel}) {
this.markers.add(name);
this.addStage({name, type: 'mark'}, {isVisible: false});
this.addStage({name, type: 'mark'}, {isVisible: false, parallel});
}
handleDivider({mode, height, label}) {
handleDivider({mode, height, label, parallel}) {
this.addStage({
formattedLabel: this.textFormatter(label),
height,
mode,
type: 'divider',
}, {isVisible: false});
}, {isVisible: false, parallel});
}
handleAsync({target}) {
handleAsync({target, parallel}) {
if(target !== '' && !this.markers.has(target)) {
throw new Error('Unknown marker: ' + target);
}
this.addStage({target, type: 'async'}, {isVisible: false});
this.addStage({target, type: 'async'}, {isVisible: false, parallel});
}
handleLabelPattern({pattern}) {

View File

@ -2125,6 +2125,28 @@ describe('Sequence Generator', () => {
]);
});
it('adds implicit stages for parallel actions', () => {
const sequence = invoke([
PARSED.beginAgents(['A']),
PARSED.blockBegin('tag', ''),
PARSED.note('note over', ['A']),
PARSED.connect(['A', 'B'], {parallel: true}),
PARSED.blockEnd(),
]);
expect(sequence.stages).toEqual([
any(),
any(),
GENERATED.beginAgents(['B']),
GENERATED.parallel([
GENERATED.note('note over', ['A']),
GENERATED.connect(['A', 'B']),
]),
any(),
any(),
]);
});
it('combines parallel connects and implicit begins', () => {
const sequence = invoke([
PARSED.connect(['A', 'B']),
@ -2231,6 +2253,42 @@ describe('Sequence Generator', () => {
));
});
it('rejects using parallel with mixed actions', () => {
expect(() => invoke([
PARSED.connect(['A', 'B']),
{type: 'mark', name: 'foo', ln: 0, parallel: true},
])).toThrow(new Error(
'Cannot use parallel here at line 1'
));
expect(() => invoke([
{type: 'mark', name: 'foo', ln: 0},
PARSED.connect(['A', 'B'], {parallel: true}),
])).toThrow(new Error(
'Cannot use parallel here at line 1'
));
});
it('rejects using parallel with restricted actions', () => {
expect(() => invoke([
PARSED.connect(['A', 'B']),
PARSED.blockBegin('tag', '', {parallel: true}),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
])).toThrow(new Error(
'Cannot use parallel here at line 1'
));
expect(() => invoke([
PARSED.blockBegin('tag', ''),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
PARSED.connect(['A', 'B'], {parallel: true}),
])).toThrow(new Error(
'Cannot use parallel here at line 1'
));
});
it('rejects note between with a repeated agent', () => {
expect(() => invoke([
PARSED.note('note between', ['A', 'A'], {