SequenceDiagram/scripts/sequence/Generator.js

195 lines
4.0 KiB
JavaScript

define(() => {
'use strict';
function mergeSets(target, b) {
if(!b) {
return;
}
for(let i = 0; i < b.length; ++ i) {
if(target.indexOf(b[i]) === -1) {
target.push(b[i]);
}
}
}
function lastElement(list) {
return list[list.length - 1];
}
return class Generator {
findAgents(stages) {
const agents = ['['];
stages.forEach((stage) => {
if(stage.agents) {
mergeSets(agents, stage.agents);
}
});
if(agents.indexOf(']') !== -1) {
agents.splice(agents.indexOf(']'), 1);
}
agents.push(']');
return agents;
}
generate({meta = {}, stages}) {
const agents = this.findAgents(stages);
const agentStates = new Map();
agents.forEach((agent) => {
agentStates.set(agent, {visible: false, locked: false});
});
agentStates.get('[').locked = true;
agentStates.get(']').locked = true;
const rootStages = [];
let currentSection = {
mode: 'global',
label: '',
stages: rootStages,
};
let currentNest = {
type: 'block',
agents: [],
root: true,
sections: [currentSection],
};
const nesting = [currentNest];
function beginNested(stage) {
currentSection = {
mode: stage.mode,
label: stage.label,
stages: [],
};
currentNest = {
type: 'block',
agents: [],
sections: [currentSection],
};
nesting.push(currentNest);
}
function splitNested(stage) {
if(currentNest.sections[0].mode !== 'if') {
throw new Error('Invalid block nesting');
}
currentSection = {
mode: stage.mode,
label: stage.label,
stages: [],
};
currentNest.sections.push(currentSection);
}
function addStage(stage) {
currentSection.stages.push(stage);
mergeSets(currentNest.agents, stage.agents);
}
function endNested() {
if(currentNest.root) {
throw new Error('Invalid block nesting');
}
const subNest = nesting.pop();
currentNest = lastElement(nesting);
currentSection = lastElement(currentNest.sections);
if(subNest.agents.length > 0) {
addStage(subNest);
}
}
function addAgentMod(stageAgents, markVisible, mode) {
if(stageAgents.length === 0) {
return;
}
stageAgents.forEach((agent) => {
agentStates.get(agent).visible = markVisible;
});
const type = (markVisible ? 'agent begin' : 'agent end');
const existing = lastElement(currentSection.stages) || {};
if(existing.type === type && existing.mode === mode) {
mergeSets(existing.agents, stageAgents);
mergeSets(currentNest.agents, stageAgents);
} else {
addStage({
type,
agents: stageAgents,
mode,
});
}
}
function filterVis(stageAgents, visible, implicit = false) {
return stageAgents.filter((agent) => {
const state = agentStates.get(agent);
if(!state.locked) {
return state.visible === visible;
} else if(!implicit) {
throw new Error('Cannot modify agent ' + agent);
} else {
return false;
}
});
}
stages.forEach((stage) => {
/* jshint -W074 */ // It's only a switch statement
switch(stage.type) {
case 'agent define':
break;
case 'agent begin':
addAgentMod(
filterVis(stage.agents, false),
true,
stage.mode
);
break;
case 'agent end':
addAgentMod(
filterVis(stage.agents, true),
false,
stage.mode
);
break;
case 'block begin':
beginNested(stage);
break;
case 'block split':
splitNested(stage);
break;
case 'block end':
endNested(stage);
break;
default:
addAgentMod(
filterVis(stage.agents, false, true),
true,
'box'
);
addStage(stage);
break;
}
});
if(nesting.length !== 1) {
throw new Error('Invalid block nesting');
}
addAgentMod(
filterVis(agents, true, true),
false,
meta.terminators || 'none'
);
return {
meta,
agents,
stages: rootStages,
};
}
};
});