Add support for reference boxes [#21]
This commit is contained in:
parent
26bc3acd3e
commit
587a6d7f26
|
@ -81,6 +81,14 @@ define(() => {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function flatMap(list, fn) {
|
||||||
|
const result = [];
|
||||||
|
list.forEach((item) => {
|
||||||
|
result.push(...fn(item));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
indexOf,
|
indexOf,
|
||||||
mergeSets,
|
mergeSets,
|
||||||
|
@ -89,5 +97,6 @@ define(() => {
|
||||||
remove,
|
remove,
|
||||||
last,
|
last,
|
||||||
combine,
|
combine,
|
||||||
|
flatMap,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -186,4 +186,18 @@ defineDescribe('ArrayUtilities', ['./ArrayUtilities'], (array) => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('.flatMap', () => {
|
||||||
|
it('applies the given function to all elements of the input', () => {
|
||||||
|
const fn = (x) => ([x + 1]);
|
||||||
|
const p1 = [2, 7];
|
||||||
|
expect(array.flatMap(p1, fn)).toEqual([3, 8]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('flattens the result', () => {
|
||||||
|
const fn = (x) => ([x + 1, x + 2]);
|
||||||
|
const p1 = [2, 7];
|
||||||
|
expect(array.flatMap(p1, fn)).toEqual([3, 4, 8, 9]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -136,6 +136,36 @@
|
||||||
'end'
|
'end'
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'References',
|
||||||
|
code: (
|
||||||
|
'begin reference: {Label} as {Name}\n' +
|
||||||
|
'{Agent1} -> {Name}\n' +
|
||||||
|
'end {Name}'
|
||||||
|
),
|
||||||
|
preview: (
|
||||||
|
'begin A\n' +
|
||||||
|
'begin reference: "See 1.3" as myRef\n' +
|
||||||
|
'A -> myRef\n' +
|
||||||
|
'myRef -> A\n' +
|
||||||
|
'end myRef'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'References over agents',
|
||||||
|
code: (
|
||||||
|
'begin reference over {Covered}: {Label} as {Name}\n' +
|
||||||
|
'{Agent1} -> {Name}\n' +
|
||||||
|
'end {Name}'
|
||||||
|
),
|
||||||
|
preview: (
|
||||||
|
'begin A, B, C\n' +
|
||||||
|
'begin reference over B, C: "See 1.3" as myRef\n' +
|
||||||
|
'A -> myRef\n' +
|
||||||
|
'myRef -> A\n' +
|
||||||
|
'end myRef'
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Note over agent',
|
title: 'Note over agent',
|
||||||
code: 'note over {Agent1}: {Message}',
|
code: 'note over {Agent1}: {Message}',
|
||||||
|
|
|
@ -2,28 +2,39 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
class AgentState {
|
class AgentState {
|
||||||
constructor(visible, locked = false) {
|
constructor({
|
||||||
|
visible = false,
|
||||||
|
locked = false,
|
||||||
|
blocked = false,
|
||||||
|
highlighted = false,
|
||||||
|
group = null,
|
||||||
|
covered = false,
|
||||||
|
} = {}) {
|
||||||
this.visible = visible;
|
this.visible = visible;
|
||||||
this.highlighted = false;
|
|
||||||
this.locked = locked;
|
this.locked = locked;
|
||||||
|
this.blocked = blocked;
|
||||||
|
this.highlighted = highlighted;
|
||||||
|
this.group = group;
|
||||||
|
this.covered = covered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AgentState.LOCKED = new AgentState({locked: true});
|
||||||
|
AgentState.DEFAULT = new AgentState();
|
||||||
|
|
||||||
function agentEqCheck(a, b) {
|
const Agent = {
|
||||||
|
equals: (a, b) => {
|
||||||
return a.name === b.name;
|
return a.name === b.name;
|
||||||
}
|
},
|
||||||
|
make: (name, {anchorRight = false} = {}) => {
|
||||||
function makeAgent(name, {anchorRight = false} = {}) {
|
|
||||||
return {name, anchorRight};
|
return {name, anchorRight};
|
||||||
}
|
},
|
||||||
|
getName: (agent) => {
|
||||||
function getAgentName(agent) {
|
|
||||||
return agent.name;
|
return agent.name;
|
||||||
}
|
},
|
||||||
|
hasFlag: (flag, has = true) => {
|
||||||
function agentHasFlag(flag, has = true) {
|
|
||||||
return (agent) => (agent.flags.includes(flag) === has);
|
return (agent) => (agent.flags.includes(flag) === has);
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const MERGABLE = {
|
const MERGABLE = {
|
||||||
'agent begin': {
|
'agent begin': {
|
||||||
|
@ -173,14 +184,14 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBounds(target, agentL, agentR, involvedAgents = null) {
|
function addBounds(target, agentL, agentR, involvedAgents = null) {
|
||||||
array.remove(target, agentL, agentEqCheck);
|
array.remove(target, agentL, Agent.equals);
|
||||||
array.remove(target, agentR, agentEqCheck);
|
array.remove(target, agentR, Agent.equals);
|
||||||
|
|
||||||
let indexL = 0;
|
let indexL = 0;
|
||||||
let indexR = target.length;
|
let indexR = target.length;
|
||||||
if(involvedAgents) {
|
if(involvedAgents) {
|
||||||
const found = (involvedAgents
|
const found = (involvedAgents
|
||||||
.map((agent) => array.indexOf(target, agent, agentEqCheck))
|
.map((agent) => array.indexOf(target, agent, Agent.equals))
|
||||||
.filter((p) => (p !== -1))
|
.filter((p) => (p !== -1))
|
||||||
);
|
);
|
||||||
indexL = found.reduce((a, b) => Math.min(a, b), target.length);
|
indexL = found.reduce((a, b) => Math.min(a, b), target.length);
|
||||||
|
@ -189,10 +200,9 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
target.splice(indexL, 0, agentL);
|
target.splice(indexL, 0, agentL);
|
||||||
target.splice(indexR + 1, 0, agentR);
|
target.splice(indexR + 1, 0, agentR);
|
||||||
}
|
|
||||||
|
|
||||||
const LOCKED_AGENT = new AgentState(false, true);
|
return {indexL, indexR: indexR + 1};
|
||||||
const DEFAULT_AGENT = new AgentState(false);
|
}
|
||||||
|
|
||||||
const NOTE_DEFAULT_AGENTS = {
|
const NOTE_DEFAULT_AGENTS = {
|
||||||
'note over': [{name: '[', flags: []}, {name: ']', flags: []}],
|
'note over': [{name: '[', flags: []}, {name: ']', flags: []}],
|
||||||
|
@ -204,6 +214,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.agentStates = new Map();
|
this.agentStates = new Map();
|
||||||
this.agentAliases = new Map();
|
this.agentAliases = new Map();
|
||||||
|
this.activeGroups = new Map();
|
||||||
this.agents = [];
|
this.agents = [];
|
||||||
this.labelPattern = null;
|
this.labelPattern = null;
|
||||||
this.blockCount = 0;
|
this.blockCount = 0;
|
||||||
|
@ -229,8 +240,10 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
'note right': this.handleNote.bind(this),
|
'note right': this.handleNote.bind(this),
|
||||||
'note between': this.handleNote.bind(this),
|
'note between': this.handleNote.bind(this),
|
||||||
};
|
};
|
||||||
|
this.expandGroupedAgent = this.expandGroupedAgent.bind(this);
|
||||||
this.handleStage = this.handleStage.bind(this);
|
this.handleStage = this.handleStage.bind(this);
|
||||||
this.convertAgent = this.convertAgent.bind(this);
|
this.convertAgent = this.convertAgent.bind(this);
|
||||||
|
this.endGroup = this.endGroup.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertAgent({alias, name}) {
|
convertAgent({alias, name}) {
|
||||||
|
@ -252,7 +265,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
this.agentAliases.set(alias, name);
|
this.agentAliases.set(alias, name);
|
||||||
}
|
}
|
||||||
return makeAgent(this.agentAliases.get(name) || name);
|
return Agent.make(this.agentAliases.get(name) || name);
|
||||||
}
|
}
|
||||||
|
|
||||||
addStage(stage, isVisible = true) {
|
addStage(stage, isVisible = true) {
|
||||||
|
@ -288,8 +301,44 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
defineAgents(colAgents) {
|
defineAgents(colAgents) {
|
||||||
array.mergeSets(this.currentNest.agents, colAgents, agentEqCheck);
|
array.mergeSets(this.currentNest.agents, colAgents, Agent.equals);
|
||||||
array.mergeSets(this.agents, colAgents, agentEqCheck);
|
array.mergeSets(this.agents, colAgents, Agent.equals);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAgentState(agent) {
|
||||||
|
return this.agentStates.get(agent.name) || AgentState.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAgentState(agent, change) {
|
||||||
|
const state = this.agentStates.get(agent.name);
|
||||||
|
if(state) {
|
||||||
|
Object.assign(state, change);
|
||||||
|
} else {
|
||||||
|
this.agentStates.set(agent.name, new AgentState(change));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateAgents(agents, {
|
||||||
|
allowGrouped = false,
|
||||||
|
rejectGrouped = false,
|
||||||
|
} = {}) {
|
||||||
|
agents.forEach((agent) => {
|
||||||
|
const state = this.getAgentState(agent);
|
||||||
|
if(state.covered) {
|
||||||
|
throw new Error(
|
||||||
|
'Agent ' + agent.name + ' is hidden behind group'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if(rejectGrouped && state.group !== null) {
|
||||||
|
throw new Error('Agent ' + agent.name + ' is in a group');
|
||||||
|
}
|
||||||
|
if(state.blocked && (!allowGrouped || state.group === null)) {
|
||||||
|
throw new Error('Duplicate agent name: ' + agent.name);
|
||||||
|
}
|
||||||
|
if(agent.name.startsWith('__')) {
|
||||||
|
throw new Error(agent.name + ' is a reserved name');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setAgentVis(colAgents, visible, mode, checked = false) {
|
setAgentVis(colAgents, visible, mode, checked = false) {
|
||||||
|
@ -299,8 +348,8 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
seen.add(agent.name);
|
seen.add(agent.name);
|
||||||
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
const state = this.getAgentState(agent);
|
||||||
if(state.locked) {
|
if(state.locked || state.blocked) {
|
||||||
if(checked) {
|
if(checked) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Cannot begin/end agent: ' + agent.name
|
'Cannot begin/end agent: ' + agent.name
|
||||||
|
@ -315,26 +364,21 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
filteredAgents.forEach((agent) => {
|
filteredAgents.forEach((agent) => {
|
||||||
const state = this.agentStates.get(agent.name);
|
this.updateAgentState(agent, {visible});
|
||||||
if(state) {
|
|
||||||
state.visible = visible;
|
|
||||||
} else {
|
|
||||||
this.agentStates.set(agent.name, new AgentState(visible));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
this.defineAgents(filteredAgents);
|
this.defineAgents(filteredAgents);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: (visible ? 'agent begin' : 'agent end'),
|
type: (visible ? 'agent begin' : 'agent end'),
|
||||||
agentNames: filteredAgents.map(getAgentName),
|
agentNames: filteredAgents.map(Agent.getName),
|
||||||
mode,
|
mode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setAgentHighlight(colAgents, highlighted, checked = false) {
|
setAgentHighlight(colAgents, highlighted, checked = false) {
|
||||||
const filteredAgents = colAgents.filter((agent) => {
|
const filteredAgents = colAgents.filter((agent) => {
|
||||||
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
const state = this.getAgentState(agent);
|
||||||
if(state.locked) {
|
if(state.locked || state.blocked) {
|
||||||
if(checked) {
|
if(checked) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Cannot highlight agent: ' + agent.name
|
'Cannot highlight agent: ' + agent.name
|
||||||
|
@ -349,20 +393,19 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
filteredAgents.forEach((agent) => {
|
filteredAgents.forEach((agent) => {
|
||||||
const state = this.agentStates.get(agent.name);
|
this.updateAgentState(agent, {highlighted});
|
||||||
state.highlighted = highlighted;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'agent highlight',
|
type: 'agent highlight',
|
||||||
agentNames: filteredAgents.map(getAgentName),
|
agentNames: filteredAgents.map(Agent.getName),
|
||||||
highlighted,
|
highlighted,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
beginNested(mode, label, name, ln) {
|
beginNested(mode, label, name, ln) {
|
||||||
const leftAgent = makeAgent(name + '[', {anchorRight: true});
|
const leftAgent = Agent.make(name + '[', {anchorRight: true});
|
||||||
const rightAgent = makeAgent(name + ']');
|
const rightAgent = Agent.make(name + ']');
|
||||||
const agents = [leftAgent, rightAgent];
|
const agents = [leftAgent, rightAgent];
|
||||||
const stages = [];
|
const stages = [];
|
||||||
this.currentSection = {
|
this.currentSection = {
|
||||||
|
@ -384,17 +427,21 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
hasContent: false,
|
hasContent: false,
|
||||||
sections: [this.currentSection],
|
sections: [this.currentSection],
|
||||||
};
|
};
|
||||||
this.agentStates.set(leftAgent.name, LOCKED_AGENT);
|
this.agentStates.set(leftAgent.name, AgentState.LOCKED);
|
||||||
this.agentStates.set(rightAgent.name, LOCKED_AGENT);
|
this.agentStates.set(rightAgent.name, AgentState.LOCKED);
|
||||||
this.nesting.push(this.currentNest);
|
this.nesting.push(this.currentNest);
|
||||||
|
|
||||||
return {agents, stages};
|
return {agents, stages};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlockBegin({ln, mode, label}) {
|
nextBlockName() {
|
||||||
const name = '__BLOCK' + this.blockCount;
|
const name = '__BLOCK' + this.blockCount;
|
||||||
this.beginNested(mode, label, name, ln);
|
|
||||||
++ this.blockCount;
|
++ this.blockCount;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlockBegin({ln, mode, label}) {
|
||||||
|
this.beginNested(mode, label, this.nextBlockName(), ln);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlockSplit({ln, mode, label}) {
|
handleBlockSplit({ln, mode, label}) {
|
||||||
|
@ -450,8 +497,85 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGroupBegin() {
|
makeGroupDetails(agents, alias) {
|
||||||
throw new Error('Groups are not supported yet');
|
const colAgents = agents.map(this.convertAgent);
|
||||||
|
this.validateAgents(colAgents, {rejectGrouped: true});
|
||||||
|
if(this.agentStates.has(alias)) {
|
||||||
|
throw new Error('Duplicate agent name: ' + alias);
|
||||||
|
}
|
||||||
|
const name = this.nextBlockName();
|
||||||
|
const leftAgent = Agent.make(name + '[', {anchorRight: true});
|
||||||
|
const rightAgent = Agent.make(name + ']');
|
||||||
|
this.agentStates.set(leftAgent.name, AgentState.LOCKED);
|
||||||
|
this.agentStates.set(rightAgent.name, AgentState.LOCKED);
|
||||||
|
this.updateAgentState(
|
||||||
|
{name: alias},
|
||||||
|
{blocked: true, group: alias}
|
||||||
|
);
|
||||||
|
this.defineAgents(colAgents);
|
||||||
|
const {indexL, indexR} = addBounds(
|
||||||
|
this.agents,
|
||||||
|
leftAgent,
|
||||||
|
rightAgent,
|
||||||
|
colAgents
|
||||||
|
);
|
||||||
|
|
||||||
|
const agentsCovered = [];
|
||||||
|
const agentsContained = colAgents.slice();
|
||||||
|
for(let i = indexL + 1; i < indexR; ++ i) {
|
||||||
|
agentsCovered.push(this.agents[i]);
|
||||||
|
}
|
||||||
|
array.removeAll(agentsCovered, agentsContained, Agent.equals);
|
||||||
|
|
||||||
|
return {
|
||||||
|
colAgents,
|
||||||
|
leftAgent,
|
||||||
|
rightAgent,
|
||||||
|
agentsContained,
|
||||||
|
agentsCovered,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGroupBegin({agents, mode, label, alias}) {
|
||||||
|
const details = this.makeGroupDetails(agents, alias);
|
||||||
|
|
||||||
|
details.agentsContained.forEach((agent) => {
|
||||||
|
this.updateAgentState(agent, {group: alias});
|
||||||
|
});
|
||||||
|
details.agentsCovered.forEach((agent) => {
|
||||||
|
this.updateAgentState(agent, {covered: true});
|
||||||
|
});
|
||||||
|
this.activeGroups.set(alias, details);
|
||||||
|
this.addStage(this.setAgentVis(details.colAgents, true, 'box'));
|
||||||
|
this.addStage({
|
||||||
|
type: 'block begin',
|
||||||
|
mode,
|
||||||
|
label,
|
||||||
|
left: details.leftAgent.name,
|
||||||
|
right: details.rightAgent.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
endGroup({name}) {
|
||||||
|
const details = this.activeGroups.get(name);
|
||||||
|
if(!details) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.activeGroups.delete(name);
|
||||||
|
|
||||||
|
details.agentsContained.forEach((agent) => {
|
||||||
|
this.updateAgentState(agent, {group: null});
|
||||||
|
});
|
||||||
|
details.agentsCovered.forEach((agent) => {
|
||||||
|
this.updateAgentState(agent, {covered: false});
|
||||||
|
});
|
||||||
|
this.updateAgentState({name}, {group: null});
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'block end',
|
||||||
|
left: details.leftAgent.name,
|
||||||
|
right: details.rightAgent.name,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMark({name}) {
|
handleMark({name}) {
|
||||||
|
@ -496,38 +620,82 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConnect({agents, label, options}) {
|
expandGroupedAgent(agent) {
|
||||||
|
const group = this.getAgentState(agent).group;
|
||||||
|
if(!group) {
|
||||||
|
return [agent];
|
||||||
|
}
|
||||||
|
const details = this.activeGroups.get(group);
|
||||||
|
return [details.leftAgent, details.rightAgent];
|
||||||
|
}
|
||||||
|
|
||||||
|
expandGroupedAgentConnection(agents) {
|
||||||
|
const agents1 = this.expandGroupedAgent(agents[0]);
|
||||||
|
const agents2 = this.expandGroupedAgent(agents[1]);
|
||||||
|
let ind1 = array.indexOf(this.agents, agents1[0], Agent.equals);
|
||||||
|
let ind2 = array.indexOf(this.agents, agents2[0], Agent.equals);
|
||||||
|
if(ind1 === -1) {
|
||||||
|
ind1 = this.agents.length;
|
||||||
|
}
|
||||||
|
if(ind2 === -1) {
|
||||||
|
ind2 = this.agents.length;
|
||||||
|
}
|
||||||
|
if(ind1 === ind2) {
|
||||||
|
// Self-connection
|
||||||
|
return [array.last(agents1), array.last(agents2)];
|
||||||
|
} else if(ind1 < ind2) {
|
||||||
|
return [array.last(agents1), agents2[0]];
|
||||||
|
} else {
|
||||||
|
return [agents1[0], array.last(agents2)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterConnectFlags(agents) {
|
||||||
const beginAgents = (agents
|
const beginAgents = (agents
|
||||||
.filter(agentHasFlag('begin'))
|
.filter(Agent.hasFlag('begin'))
|
||||||
.map(this.convertAgent)
|
.map(this.convertAgent)
|
||||||
);
|
);
|
||||||
const endAgents = (agents
|
const endAgents = (agents
|
||||||
.filter(agentHasFlag('end'))
|
.filter(Agent.hasFlag('end'))
|
||||||
.map(this.convertAgent)
|
.map(this.convertAgent)
|
||||||
);
|
);
|
||||||
if(array.hasIntersection(beginAgents, endAgents, agentEqCheck)) {
|
if(array.hasIntersection(beginAgents, endAgents, Agent.equals)) {
|
||||||
throw new Error('Cannot set agent visibility multiple times');
|
throw new Error('Cannot set agent visibility multiple times');
|
||||||
}
|
}
|
||||||
|
|
||||||
const startAgents = (agents
|
const startAgents = (agents
|
||||||
.filter(agentHasFlag('start'))
|
.filter(Agent.hasFlag('start'))
|
||||||
.map(this.convertAgent)
|
.map(this.convertAgent)
|
||||||
);
|
);
|
||||||
const stopAgents = (agents
|
const stopAgents = (agents
|
||||||
.filter(agentHasFlag('stop'))
|
.filter(Agent.hasFlag('stop'))
|
||||||
.map(this.convertAgent)
|
.map(this.convertAgent)
|
||||||
);
|
);
|
||||||
array.mergeSets(stopAgents, endAgents);
|
array.mergeSets(stopAgents, endAgents);
|
||||||
if(array.hasIntersection(startAgents, stopAgents, agentEqCheck)) {
|
if(array.hasIntersection(startAgents, stopAgents, Agent.equals)) {
|
||||||
throw new Error('Cannot set agent highlighting multiple times');
|
throw new Error('Cannot set agent highlighting multiple times');
|
||||||
}
|
}
|
||||||
|
|
||||||
const colAgents = agents.map(this.convertAgent);
|
this.validateAgents(beginAgents);
|
||||||
const agentNames = colAgents.map(getAgentName);
|
this.validateAgents(endAgents);
|
||||||
|
this.validateAgents(startAgents);
|
||||||
|
this.validateAgents(stopAgents);
|
||||||
|
|
||||||
|
return {beginAgents, endAgents, startAgents, stopAgents};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConnect({agents, label, options}) {
|
||||||
|
const flags = this.filterConnectFlags(agents);
|
||||||
|
|
||||||
|
let colAgents = agents.map(this.convertAgent);
|
||||||
|
this.validateAgents(colAgents, {allowGrouped: true});
|
||||||
|
colAgents = this.expandGroupedAgentConnection(colAgents);
|
||||||
|
|
||||||
|
const agentNames = colAgents.map(Agent.getName);
|
||||||
this.defineAgents(colAgents);
|
this.defineAgents(colAgents);
|
||||||
|
|
||||||
const implicitBegin = (agents
|
const implicitBegin = (agents
|
||||||
.filter(agentHasFlag('begin', false))
|
.filter(Agent.hasFlag('begin', false))
|
||||||
.map(this.convertAgent)
|
.map(this.convertAgent)
|
||||||
);
|
);
|
||||||
this.addStage(this.setAgentVis(implicitBegin, true, 'box'));
|
this.addStage(this.setAgentVis(implicitBegin, true, 'box'));
|
||||||
|
@ -540,11 +708,11 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addParallelStages([
|
this.addParallelStages([
|
||||||
this.setAgentVis(beginAgents, true, 'box', true),
|
this.setAgentVis(flags.beginAgents, true, 'box', true),
|
||||||
this.setAgentHighlight(startAgents, true, true),
|
this.setAgentHighlight(flags.startAgents, true, true),
|
||||||
connectStage,
|
connectStage,
|
||||||
this.setAgentHighlight(stopAgents, false, true),
|
this.setAgentHighlight(flags.stopAgents, false, true),
|
||||||
this.setAgentVis(endAgents, false, 'cross', true),
|
this.setAgentVis(flags.endAgents, false, 'cross', true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,7 +723,10 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
} else {
|
} else {
|
||||||
colAgents = agents.map(this.convertAgent);
|
colAgents = agents.map(this.convertAgent);
|
||||||
}
|
}
|
||||||
const agentNames = colAgents.map(getAgentName);
|
|
||||||
|
this.validateAgents(colAgents, {allowGrouped: true});
|
||||||
|
colAgents = array.flatMap(colAgents, this.expandGroupedAgent);
|
||||||
|
const agentNames = colAgents.map(Agent.getName);
|
||||||
const uniqueAgents = new Set(agentNames).size;
|
const uniqueAgents = new Set(agentNames).size;
|
||||||
if(type === 'note between' && uniqueAgents < 2) {
|
if(type === 'note between' && uniqueAgents < 2) {
|
||||||
throw new Error('note between requires at least 2 agents');
|
throw new Error('note between requires at least 2 agents');
|
||||||
|
@ -573,19 +744,30 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAgentDefine({agents}) {
|
handleAgentDefine({agents}) {
|
||||||
this.defineAgents(agents.map(this.convertAgent));
|
const colAgents = agents.map(this.convertAgent);
|
||||||
|
this.validateAgents(colAgents);
|
||||||
|
this.defineAgents(colAgents);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAgentBegin({agents, mode}) {
|
handleAgentBegin({agents, mode}) {
|
||||||
const colAgents = agents.map(this.convertAgent);
|
const colAgents = agents.map(this.convertAgent);
|
||||||
|
this.validateAgents(colAgents);
|
||||||
this.addStage(this.setAgentVis(colAgents, true, mode, true));
|
this.addStage(this.setAgentVis(colAgents, true, mode, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAgentEnd({agents, mode}) {
|
handleAgentEnd({agents, mode}) {
|
||||||
const colAgents = agents.map(this.convertAgent);
|
const groupAgents = (agents
|
||||||
|
.filter((agent) => this.activeGroups.has(agent.name))
|
||||||
|
);
|
||||||
|
const colAgents = (agents
|
||||||
|
.filter((agent) => !this.activeGroups.has(agent.name))
|
||||||
|
.map(this.convertAgent)
|
||||||
|
);
|
||||||
|
this.validateAgents(colAgents);
|
||||||
this.addParallelStages([
|
this.addParallelStages([
|
||||||
this.setAgentHighlight(colAgents, false),
|
this.setAgentHighlight(colAgents, false),
|
||||||
this.setAgentVis(colAgents, false, mode, true),
|
this.setAgentVis(colAgents, false, mode, true),
|
||||||
|
...groupAgents.map(this.endGroup),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,6 +790,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
this.agentStates.clear();
|
this.agentStates.clear();
|
||||||
this.markers.clear();
|
this.markers.clear();
|
||||||
this.agentAliases.clear();
|
this.agentAliases.clear();
|
||||||
|
this.activeGroups.clear();
|
||||||
this.agents.length = 0;
|
this.agents.length = 0;
|
||||||
this.blockCount = 0;
|
this.blockCount = 0;
|
||||||
this.nesting.length = 0;
|
this.nesting.length = 0;
|
||||||
|
@ -622,6 +805,9 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
(this.currentSection.header.ln + 1)
|
(this.currentSection.header.ln + 1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if(this.activeGroups.size > 0) {
|
||||||
|
throw new Error('Unterminated group');
|
||||||
|
}
|
||||||
|
|
||||||
const terminators = meta.terminators || 'none';
|
const terminators = meta.terminators || 'none';
|
||||||
|
|
||||||
|
|
|
@ -266,9 +266,9 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
|
|
||||||
it('rejects attempts to jump to markers not yet defined', () => {
|
it('rejects attempts to jump to markers not yet defined', () => {
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
{type: 'async', target: 'foo'},
|
{type: 'async', target: 'foo', ln: 10},
|
||||||
{type: 'mark', name: 'foo'},
|
{type: 'mark', name: 'foo'},
|
||||||
]})).toThrow();
|
]})).toThrow(new Error('Unknown marker: foo at line 11'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns aggregated agents', () => {
|
it('returns aggregated agents', () => {
|
||||||
|
@ -329,14 +329,18 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.defineAgents([{name: 'Foo', alias: 'B', flags: []}]),
|
PARSED.defineAgents([{name: 'Foo', alias: 'B', flags: []}]),
|
||||||
PARSED.defineAgents([{name: 'Bar', alias: 'B', flags: []}]),
|
PARSED.defineAgents([{name: 'Bar', alias: 'B', flags: []}]),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Cannot use B as an alias; it is already in use at line 1'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects using agent names as aliases', () => {
|
it('rejects using agent names as aliases', () => {
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.defineAgents([{name: 'Foo', alias: 'B', flags: []}]),
|
PARSED.defineAgents([{name: 'Foo', alias: 'B', flags: []}]),
|
||||||
PARSED.defineAgents([{name: 'Bar', alias: 'Foo', flags: []}]),
|
PARSED.defineAgents([{name: 'Bar', alias: 'Foo', flags: []}]),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Cannot use Foo as an alias; it is already in use at line 1'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates implicit begin stages for agents when used', () => {
|
it('creates implicit begin stages for agents when used', () => {
|
||||||
|
@ -675,28 +679,36 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
'A',
|
'A',
|
||||||
{name: 'B', alias: '', flags: ['start', 'stop']},
|
{name: 'B', alias: '', flags: ['start', 'stop']},
|
||||||
]),
|
]),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Cannot set agent highlighting multiple times at line 1'
|
||||||
|
));
|
||||||
|
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.connect([
|
PARSED.connect([
|
||||||
{name: 'A', alias: '', flags: ['start']},
|
{name: 'A', alias: '', flags: ['start']},
|
||||||
{name: 'A', alias: '', flags: ['stop']},
|
{name: 'A', alias: '', flags: ['stop']},
|
||||||
]),
|
]),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Cannot set agent highlighting multiple times at line 1'
|
||||||
|
));
|
||||||
|
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.connect([
|
PARSED.connect([
|
||||||
'A',
|
'A',
|
||||||
{name: 'B', alias: '', flags: ['begin', 'end']},
|
{name: 'B', alias: '', flags: ['begin', 'end']},
|
||||||
]),
|
]),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Cannot set agent visibility multiple times at line 1'
|
||||||
|
));
|
||||||
|
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.connect([
|
PARSED.connect([
|
||||||
{name: 'A', alias: '', flags: ['begin']},
|
{name: 'A', alias: '', flags: ['begin']},
|
||||||
{name: 'A', alias: '', flags: ['end']},
|
{name: 'A', alias: '', flags: ['end']},
|
||||||
]),
|
]),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Cannot set agent visibility multiple times at line 1'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds implicit highlight end with implicit terminator', () => {
|
it('adds implicit highlight end with implicit terminator', () => {
|
||||||
|
@ -948,7 +960,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
PARSED.blockBegin('if', 'abc'),
|
PARSED.blockBegin('if', 'abc'),
|
||||||
PARSED.blockSplit('else', 'xyz'),
|
PARSED.blockSplit('else', 'xyz'),
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd(),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error('Empty block at line 1'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects blocks containing only define statements / markers', () => {
|
it('rejects blocks containing only define statements / markers', () => {
|
||||||
|
@ -957,18 +969,306 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
PARSED.defineAgents(['A']),
|
PARSED.defineAgents(['A']),
|
||||||
{type: 'mark', name: 'foo'},
|
{type: 'mark', name: 'foo'},
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd(),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error('Empty block at line 1'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects entirely empty nested blocks', () => {
|
it('rejects entirely empty nested blocks', () => {
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.blockBegin('if', 'abc'),
|
PARSED.blockBegin('if', 'abc', {ln: 10}),
|
||||||
PARSED.connect(['A', 'B']),
|
PARSED.connect(['A', 'B']),
|
||||||
PARSED.blockSplit('else', 'xyz'),
|
PARSED.blockSplit('else', 'xyz', {ln: 20}),
|
||||||
PARSED.blockBegin('if', 'abc'),
|
PARSED.blockBegin('if', 'abc', {ln: 30}),
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd({ln: 40}),
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd({ln: 50}),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error('Empty block at line 41'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts groups into block commands', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B']),
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
const bounds = {
|
||||||
|
left: '__BLOCK0[',
|
||||||
|
right: '__BLOCK0]',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(sequence.agents).toEqual([
|
||||||
|
{name: '[', anchorRight: true},
|
||||||
|
{name: '__BLOCK0[', anchorRight: true},
|
||||||
|
{name: 'A', anchorRight: false},
|
||||||
|
{name: 'B', anchorRight: false},
|
||||||
|
{name: '__BLOCK0]', anchorRight: false},
|
||||||
|
{name: ']', anchorRight: false},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.blockBegin('ref', 'Foo', bounds),
|
||||||
|
GENERATED.blockEnd(bounds),
|
||||||
|
GENERATED.endAgents(['A', 'B']),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds implicit begin statements when creating groups', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
GENERATED.beginAgents(['A', 'B'], {mode: 'box'}),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('augments explicit begin statements when creating groups', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A']),
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
GENERATED.beginAgents(['A', 'B'], {mode: 'box'}),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects unterminated groups', () => {
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B']),
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
]})).toThrow(new Error('Unterminated group'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses group agent list when positioning bounds', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B', 'C', 'D']),
|
||||||
|
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.agents).toEqual([
|
||||||
|
{name: '[', anchorRight: true},
|
||||||
|
{name: 'A', anchorRight: false},
|
||||||
|
{name: '__BLOCK0[', anchorRight: true},
|
||||||
|
{name: 'B', anchorRight: false},
|
||||||
|
{name: 'C', anchorRight: false},
|
||||||
|
{name: '__BLOCK0]', anchorRight: false},
|
||||||
|
{name: 'D', anchorRight: false},
|
||||||
|
{name: ']', anchorRight: false},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('implicitly adds contained agents to groups', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B', 'C', 'D', 'E']),
|
||||||
|
PARSED.groupBegin('Bar', ['B', 'D'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.agents).toEqual([
|
||||||
|
{name: '[', anchorRight: true},
|
||||||
|
{name: 'A', anchorRight: false},
|
||||||
|
{name: '__BLOCK0[', anchorRight: true},
|
||||||
|
{name: 'B', anchorRight: false},
|
||||||
|
{name: 'C', anchorRight: false},
|
||||||
|
{name: 'D', anchorRight: false},
|
||||||
|
{name: '__BLOCK0]', anchorRight: false},
|
||||||
|
{name: 'E', anchorRight: false},
|
||||||
|
{name: ']', anchorRight: false},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('repoints explicit group connectors at bounds', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B', 'C', 'D']),
|
||||||
|
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
|
||||||
|
PARSED.connect(['A', 'Bar']),
|
||||||
|
PARSED.connect(['D', 'Bar']),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.connect(['A', '__BLOCK0[']),
|
||||||
|
GENERATED.connect(['D', '__BLOCK0]']),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly positions new agents when repointing at bounds', () => {
|
||||||
|
const sequence1 = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['B', 'C']),
|
||||||
|
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
|
||||||
|
PARSED.connect(['D', 'Bar']),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence1.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.connect(['D', '__BLOCK0]']),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const sequence2 = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['B', 'C']),
|
||||||
|
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
|
||||||
|
PARSED.connect(['Bar', 'D']),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence2.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.connect(['__BLOCK0]', 'D']),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('repoints explicit group notes at bounds', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B', 'C', 'D']),
|
||||||
|
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
|
||||||
|
PARSED.note('note over', ['Bar']),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.note('note over', ['__BLOCK0[', '__BLOCK0]']),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('repoints group self-connections to right bound', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B', 'C', 'D']),
|
||||||
|
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
|
||||||
|
PARSED.connect(['B', 'B']),
|
||||||
|
PARSED.connect(['Bar', 'Bar']),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.connect(['__BLOCK0]', '__BLOCK0]']),
|
||||||
|
GENERATED.connect(['__BLOCK0]', '__BLOCK0]']),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects using an agent in multiple groups simultaneously', () => {
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.groupBegin('Baz', ['B', 'C'], {label: 'Foob'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
PARSED.endAgents(['Baz']),
|
||||||
|
]})).toThrow(new Error('Agent B is in a group at line 1'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects explicit group connectors after ending', () => {
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.groupBegin('Bar', ['A'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
PARSED.connect(['B', 'Bar']),
|
||||||
|
]})).toThrow(new Error('Duplicate agent name: Bar at line 1'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects notes over groups after ending', () => {
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.groupBegin('Bar', ['A'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
PARSED.note('note over', ['Bar']),
|
||||||
|
]})).toThrow(new Error('Duplicate agent name: Bar at line 1'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('repoints implicit group connectors at bounds', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B', 'C', 'D']),
|
||||||
|
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
|
||||||
|
PARSED.connect(['A', 'C']),
|
||||||
|
PARSED.connect(['D', 'C']),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.connect(['A', '__BLOCK0[']),
|
||||||
|
GENERATED.connect(['D', '__BLOCK0]']),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not repoint implicit group connectors after ending', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B', 'C', 'D']),
|
||||||
|
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
PARSED.connect(['A', 'C']),
|
||||||
|
PARSED.connect(['D', 'C']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.connect(['A', 'C']),
|
||||||
|
GENERATED.connect(['D', 'C']),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can connect multiple reference blocks', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B', 'C', 'D']),
|
||||||
|
PARSED.groupBegin('AB', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.groupBegin('CD', ['C', 'D'], {label: 'Foo'}),
|
||||||
|
PARSED.connect(['AB', 'CD']),
|
||||||
|
PARSED.connect(['CD', 'AB']),
|
||||||
|
PARSED.endAgents(['AB']),
|
||||||
|
PARSED.endAgents(['CD']),
|
||||||
|
]});
|
||||||
|
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.connect(['__BLOCK0]', '__BLOCK1[']),
|
||||||
|
GENERATED.connect(['__BLOCK1[', '__BLOCK0]']),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects interactions with agents hidden beneath references', () => {
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['A', 'B', 'C', 'D']),
|
||||||
|
PARSED.groupBegin('AC', ['A', 'C'], {label: 'Foo'}),
|
||||||
|
PARSED.connect(['B', 'D']),
|
||||||
|
PARSED.endAgents(['AC']),
|
||||||
|
]})).toThrow(new Error('Agent B is hidden behind group at line 1'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects unterminated blocks', () => {
|
it('rejects unterminated blocks', () => {
|
||||||
|
@ -988,27 +1288,35 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
it('rejects extra block terminations', () => {
|
it('rejects extra block terminations', () => {
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd(),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Invalid block nesting (too many "end"s) at line 1'
|
||||||
|
));
|
||||||
|
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.blockBegin('if', 'abc'),
|
PARSED.blockBegin('if', 'abc'),
|
||||||
PARSED.connect(['A', 'B']),
|
PARSED.connect(['A', 'B']),
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd({ln: 10}),
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd({ln: 20}),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Invalid block nesting (too many "end"s) at line 21'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects block splitting without a block', () => {
|
it('rejects block splitting without a block', () => {
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.blockSplit('else', 'xyz'),
|
PARSED.blockSplit('else', 'xyz'),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Invalid block nesting ("else" inside global) at line 1'
|
||||||
|
));
|
||||||
|
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.blockBegin('if', 'abc'),
|
PARSED.blockBegin('if', 'abc'),
|
||||||
PARSED.connect(['A', 'B']),
|
PARSED.connect(['A', 'B']),
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd(),
|
||||||
PARSED.blockSplit('else', 'xyz'),
|
PARSED.blockSplit('else', 'xyz'),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Invalid block nesting ("else" inside global) at line 1'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects block splitting in non-splittable blocks', () => {
|
it('rejects block splitting in non-splittable blocks', () => {
|
||||||
|
@ -1017,7 +1325,9 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
PARSED.blockSplit('else', 'xyz'),
|
PARSED.blockSplit('else', 'xyz'),
|
||||||
PARSED.connect(['A', 'B']),
|
PARSED.connect(['A', 'B']),
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd(),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'Invalid block nesting ("else" inside repeat) at line 1'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes notes through', () => {
|
it('passes notes through', () => {
|
||||||
|
@ -1043,7 +1353,9 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
mode: 'foo',
|
mode: 'foo',
|
||||||
label: 'bar',
|
label: 'bar',
|
||||||
}),
|
}),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error(
|
||||||
|
'note between requires at least 2 agents at line 1'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('defaults to showing notes around the entire diagram', () => {
|
it('defaults to showing notes around the entire diagram', () => {
|
||||||
|
@ -1059,22 +1371,80 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects attempts to change implicit agents', () => {
|
it('rejects creating agents with the same name as a group', () => {
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
PARSED.beginAgents(['Bar']),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]})).toThrow(new Error('Duplicate agent name: Bar at line 1'));
|
||||||
|
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.beginAgents(['Bar']),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]})).toThrow(new Error('Duplicate agent name: Bar at line 1'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects explicit interactions with virtual group agents', () => {
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.connect(['C', '__BLOCK0[']),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]})).toThrow(new Error('__BLOCK0[ is a reserved name at line 1'));
|
||||||
|
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
PARSED.connect(['C', '__BLOCK0[']),
|
||||||
|
]})).toThrow(new Error('__BLOCK0[ is a reserved name at line 1'));
|
||||||
|
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.connect(['C', '__BLOCK0[']),
|
||||||
|
PARSED.groupBegin('Bar', ['A', 'B'], {label: 'Foo'}),
|
||||||
|
PARSED.endAgents(['Bar']),
|
||||||
|
]})).toThrow(new Error('__BLOCK0[ is a reserved name at line 1'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects explicit interactions with virtual block agents', () => {
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.blockBegin('if', 'abc'),
|
||||||
|
PARSED.connect(['C', '__BLOCK0[']),
|
||||||
|
PARSED.blockEnd(),
|
||||||
|
]})).toThrow(new Error('__BLOCK0[ is a reserved name at line 1'));
|
||||||
|
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.blockBegin('if', 'abc'),
|
||||||
|
PARSED.connect(['A', 'B']),
|
||||||
|
PARSED.blockEnd(),
|
||||||
|
PARSED.connect(['C', '__BLOCK0[']),
|
||||||
|
]})).toThrow(new Error('__BLOCK0[ is a reserved name at line 1'));
|
||||||
|
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.connect(['C', '__BLOCK0[']),
|
||||||
|
PARSED.blockBegin('if', 'abc'),
|
||||||
|
PARSED.connect(['A', 'B']),
|
||||||
|
PARSED.blockEnd(),
|
||||||
|
]})).toThrow(new Error('__BLOCK0[ is a reserved name at line 1'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects attempts to change virtual agents', () => {
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.beginAgents(['[']),
|
PARSED.beginAgents(['[']),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error('Cannot begin/end agent: [ at line 1'));
|
||||||
|
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.beginAgents([']']),
|
PARSED.beginAgents([']']),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error('Cannot begin/end agent: ] at line 1'));
|
||||||
|
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.endAgents(['[']),
|
PARSED.endAgents(['[']),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error('Cannot begin/end agent: [ at line 1'));
|
||||||
|
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.endAgents([']']),
|
PARSED.endAgents([']']),
|
||||||
]})).toThrow();
|
]})).toThrow(new Error('Cannot begin/end agent: ] at line 1'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -99,7 +99,10 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
render(stage, env) {
|
render(stage, env) {
|
||||||
env.state.blocks.set(stage.left, env.primaryY);
|
env.state.blocks.set(stage.left, {
|
||||||
|
mode: stage.mode,
|
||||||
|
startY: env.primaryY,
|
||||||
|
});
|
||||||
return super.render(stage, env, true);
|
return super.render(stage, env, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,16 +122,17 @@ define([
|
||||||
render({left, right}, env) {
|
render({left, right}, env) {
|
||||||
const config = env.theme.block;
|
const config = env.theme.block;
|
||||||
|
|
||||||
const startY = env.state.blocks.get(left);
|
const {startY, mode} = env.state.blocks.get(left);
|
||||||
|
|
||||||
const agentInfoL = env.agentInfos.get(left);
|
const agentInfoL = env.agentInfos.get(left);
|
||||||
const agentInfoR = env.agentInfos.get(right);
|
const agentInfoR = env.agentInfos.get(right);
|
||||||
|
const configMode = config.modes[mode] || config.modes[''];
|
||||||
env.blockLayer.appendChild(svg.make('rect', Object.assign({
|
env.blockLayer.appendChild(svg.make('rect', Object.assign({
|
||||||
'x': agentInfoL.x,
|
'x': agentInfoL.x,
|
||||||
'y': startY,
|
'y': startY,
|
||||||
'width': agentInfoR.x - agentInfoL.x,
|
'width': agentInfoR.x - agentInfoL.x,
|
||||||
'height': env.primaryY - startY,
|
'height': env.primaryY - startY,
|
||||||
}, config.boxAttrs)));
|
}, configMode.boxAttrs)));
|
||||||
|
|
||||||
return env.primaryY + config.margin.bottom + env.theme.actionMargin;
|
return env.primaryY + config.margin.bottom + env.theme.actionMargin;
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,17 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
},
|
},
|
||||||
|
modes: {
|
||||||
|
'ref': {
|
||||||
|
boxAttrs: {
|
||||||
|
'fill': '#FFFFFF',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1.5,
|
||||||
|
'rx': 2,
|
||||||
|
'ry': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'': {
|
||||||
boxAttrs: {
|
boxAttrs: {
|
||||||
'fill': 'none',
|
'fill': 'none',
|
||||||
'stroke': '#000000',
|
'stroke': '#000000',
|
||||||
|
@ -138,6 +149,8 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
||||||
'rx': 2,
|
'rx': 2,
|
||||||
'ry': 2,
|
'ry': 2,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
section: {
|
section: {
|
||||||
padding: {
|
padding: {
|
||||||
top: 3,
|
top: 3,
|
||||||
|
|
|
@ -139,6 +139,17 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
},
|
},
|
||||||
|
modes: {
|
||||||
|
'ref': {
|
||||||
|
boxAttrs: {
|
||||||
|
'fill': '#FFFFFF',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 4,
|
||||||
|
'rx': 5,
|
||||||
|
'ry': 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'': {
|
||||||
boxAttrs: {
|
boxAttrs: {
|
||||||
'fill': 'none',
|
'fill': 'none',
|
||||||
'stroke': '#000000',
|
'stroke': '#000000',
|
||||||
|
@ -146,6 +157,8 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
||||||
'rx': 5,
|
'rx': 5,
|
||||||
'ry': 5,
|
'ry': 5,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
section: {
|
section: {
|
||||||
padding: {
|
padding: {
|
||||||
top: 3,
|
top: 3,
|
||||||
|
|
Loading…
Reference in New Issue