Consistent agent flag handling in parser

This commit is contained in:
David Evans 2017-11-03 22:56:48 +00:00
parent 93c1e9cb8e
commit dc3d930544
6 changed files with 298 additions and 290 deletions

View File

@ -49,7 +49,7 @@ define(['core/ArrayUtilities'], (array) => {
'agent define': this.handleAgentDefine.bind(this),
'agent begin': this.handleAgentBegin.bind(this),
'agent end': this.handleAgentEnd.bind(this),
'connection': this.handleConnection.bind(this),
'connect': this.handleConnect.bind(this),
'note over': this.handleNote.bind(this),
'note left': this.handleNote.bind(this),
'note right': this.handleNote.bind(this),
@ -171,13 +171,13 @@ define(['core/ArrayUtilities'], (array) => {
this.addStage({type: 'async', target}, false);
}
handleConnection({agents, label, options}) {
handleConnect({agents, label, options}) {
const colAgents = agents.map(convertAgent);
this.setAgentVis(colAgents, true, 'box');
this.defineAgents(colAgents);
this.addStage({
type: 'connection',
type: 'connect',
agentNames: agents.map(getAgentName),
label,
options,

View File

@ -3,7 +3,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
const generator = new Generator();
const parsed = {
const PARSED = {
blockBegin: (mode, label) => {
return {type: 'block begin', mode, label};
},
@ -19,14 +19,14 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
defineAgents: (agentNames) => {
return {
type: 'agent define',
agents: agentNames.map((name) => ({name})),
agents: agentNames.map((name) => ({name, flags: []})),
};
},
beginAgents: (agentNames, {mode = 'box'} = {}) => {
return {
type: 'agent begin',
agents: agentNames.map((name) => ({name})),
agents: agentNames.map((name) => ({name, flags: []})),
mode,
};
},
@ -34,7 +34,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
endAgents: (agentNames, {mode = 'cross'} = {}) => {
return {
type: 'agent end',
agents: agentNames.map((name) => ({name})),
agents: agentNames.map((name) => ({name, flags: []})),
mode,
};
},
@ -46,8 +46,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
right = false,
} = {}) => {
return {
type: 'connection',
agents: agentNames.map((name) => ({name})),
type: 'connect',
agents: agentNames.map((name) => ({name, flags: []})),
label,
options: {
line,
@ -64,14 +64,14 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
} = {}) => {
return {
type,
agents: agentNames.map((name) => ({name})),
agents: agentNames.map((name) => ({name, flags: []})),
mode,
label,
};
},
};
const generated = {
const GENERATED = {
beginAgents: (agentNames, {
mode = jasmine.anything(),
} = {}) => {
@ -99,7 +99,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
right = jasmine.anything(),
} = {}) => {
return {
type: 'connection',
type: 'connect',
agentNames,
label,
options: {
@ -169,9 +169,9 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('returns aggregated agents', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
parsed.connect(['C', 'D']),
parsed.beginAgents(['E']),
PARSED.connect(['A', 'B']),
PARSED.connect(['C', 'D']),
PARSED.beginAgents(['E']),
]});
expect(sequence.agents).toEqual([
{name: '[', anchorRight: true},
@ -186,7 +186,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('always puts the implicit right agent on the right', () => {
const sequence = generator.generate({stages: [
parsed.connect([']', 'B']),
PARSED.connect([']', 'B']),
]});
expect(sequence.agents).toEqual([
{name: '[', anchorRight: true},
@ -197,8 +197,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('accounts for define calls when ordering agents', () => {
const sequence = generator.generate({stages: [
parsed.defineAgents(['B']),
parsed.connect(['A', 'B']),
PARSED.defineAgents(['B']),
PARSED.connect(['A', 'B']),
]});
expect(sequence.agents).toEqual([
{name: '[', anchorRight: true},
@ -210,32 +210,32 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('creates implicit begin stages for agents when used', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
parsed.connect(['B', 'C']),
PARSED.connect(['A', 'B']),
PARSED.connect(['B', 'C']),
]});
expect(sequence.stages).toEqual([
generated.beginAgents(['A', 'B']),
GENERATED.beginAgents(['A', 'B']),
jasmine.anything(),
generated.beginAgents(['C']),
GENERATED.beginAgents(['C']),
jasmine.anything(),
jasmine.anything(),
]);
});
it('passes connections through', () => {
it('passes connects through', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
PARSED.connect(['A', 'B']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
generated.connect(['A', 'B']),
GENERATED.connect(['A', 'B']),
jasmine.anything(),
]);
});
it('propagates connection information', () => {
it('propagates connect information', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B'], {
PARSED.connect(['A', 'B'], {
label: 'foo',
line: 'bar',
left: true,
@ -244,7 +244,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
generated.connect(['A', 'B'], {
GENERATED.connect(['A', 'B'], {
label: 'foo',
line: 'bar',
left: true,
@ -260,65 +260,65 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
terminators: 'foo',
},
stages: [
parsed.connect(['A', 'B']),
PARSED.connect(['A', 'B']),
],
});
expect(sequence.stages).toEqual([
jasmine.anything(),
jasmine.anything(),
generated.endAgents(['A', 'B'], {mode: 'foo'}),
GENERATED.endAgents(['A', 'B'], {mode: 'foo'}),
]);
});
it('defaults to mode "none" for implicit end stages', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
PARSED.connect(['A', 'B']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
jasmine.anything(),
generated.endAgents(['A', 'B'], {mode: 'none'}),
GENERATED.endAgents(['A', 'B'], {mode: 'none'}),
]);
});
it('defaults to mode "cross" for explicit end stages', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
parsed.endAgents(['A', 'B']),
PARSED.connect(['A', 'B']),
PARSED.endAgents(['A', 'B']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
jasmine.anything(),
generated.endAgents(['A', 'B'], {mode: 'cross'}),
GENERATED.endAgents(['A', 'B'], {mode: 'cross'}),
]);
});
it('does not create duplicate begin stages', () => {
const sequence = generator.generate({stages: [
parsed.beginAgents(['A', 'B', 'C']),
parsed.connect(['A', 'B']),
parsed.connect(['B', 'C']),
PARSED.beginAgents(['A', 'B', 'C']),
PARSED.connect(['A', 'B']),
PARSED.connect(['B', 'C']),
]});
expect(sequence.stages).toEqual([
generated.beginAgents(['A', 'B', 'C']),
generated.connect(jasmine.anything()),
generated.connect(jasmine.anything()),
generated.endAgents(['A', 'B', 'C']),
GENERATED.beginAgents(['A', 'B', 'C']),
GENERATED.connect(jasmine.anything()),
GENERATED.connect(jasmine.anything()),
GENERATED.endAgents(['A', 'B', 'C']),
]);
});
it('redisplays agents if they have been hidden', () => {
const sequence = generator.generate({stages: [
parsed.beginAgents(['A', 'B']),
parsed.connect(['A', 'B']),
parsed.endAgents(['B']),
parsed.connect(['A', 'B']),
PARSED.beginAgents(['A', 'B']),
PARSED.connect(['A', 'B']),
PARSED.endAgents(['B']),
PARSED.connect(['A', 'B']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
jasmine.anything(),
generated.endAgents(['B']),
generated.beginAgents(['B']),
GENERATED.endAgents(['B']),
GENERATED.beginAgents(['B']),
jasmine.anything(),
jasmine.anything(),
]);
@ -326,69 +326,69 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('collapses adjacent begin statements', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
parsed.beginAgents(['D']),
parsed.connect(['B', 'C']),
parsed.connect(['C', 'D']),
PARSED.connect(['A', 'B']),
PARSED.beginAgents(['D']),
PARSED.connect(['B', 'C']),
PARSED.connect(['C', 'D']),
]});
expect(sequence.stages).toEqual([
generated.beginAgents(['A', 'B']),
generated.connect(jasmine.anything()),
generated.beginAgents(['D', 'C']),
generated.connect(jasmine.anything()),
generated.connect(jasmine.anything()),
GENERATED.beginAgents(['A', 'B']),
GENERATED.connect(jasmine.anything()),
GENERATED.beginAgents(['D', 'C']),
GENERATED.connect(jasmine.anything()),
GENERATED.connect(jasmine.anything()),
jasmine.anything(),
]);
});
it('removes superfluous begin statements', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
parsed.beginAgents(['A', 'C', 'D']),
parsed.beginAgents(['C', 'E']),
PARSED.connect(['A', 'B']),
PARSED.beginAgents(['A', 'C', 'D']),
PARSED.beginAgents(['C', 'E']),
]});
expect(sequence.stages).toEqual([
generated.beginAgents(['A', 'B']),
generated.connect(jasmine.anything()),
generated.beginAgents(['C', 'D', 'E']),
GENERATED.beginAgents(['A', 'B']),
GENERATED.connect(jasmine.anything()),
GENERATED.beginAgents(['C', 'D', 'E']),
jasmine.anything(),
]);
});
it('removes superfluous end statements', () => {
const sequence = generator.generate({stages: [
parsed.defineAgents(['E']),
parsed.beginAgents(['C', 'D']),
parsed.connect(['A', 'B']),
parsed.endAgents(['A', 'B', 'C']),
parsed.endAgents(['A', 'D', 'E']),
PARSED.defineAgents(['E']),
PARSED.beginAgents(['C', 'D']),
PARSED.connect(['A', 'B']),
PARSED.endAgents(['A', 'B', 'C']),
PARSED.endAgents(['A', 'D', 'E']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
generated.connect(jasmine.anything()),
generated.endAgents(['A', 'B', 'C', 'D']),
GENERATED.connect(jasmine.anything()),
GENERATED.endAgents(['A', 'B', 'C', 'D']),
]);
});
it('does not merge different modes of end', () => {
const sequence = generator.generate({stages: [
parsed.beginAgents(['C', 'D']),
parsed.connect(['A', 'B']),
parsed.endAgents(['A', 'B', 'C']),
PARSED.beginAgents(['C', 'D']),
PARSED.connect(['A', 'B']),
PARSED.endAgents(['A', 'B', 'C']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
generated.connect(jasmine.anything()),
generated.endAgents(['A', 'B', 'C'], {mode: 'cross'}),
generated.endAgents(['D'], {mode: 'none'}),
GENERATED.connect(jasmine.anything()),
GENERATED.endAgents(['A', 'B', 'C'], {mode: 'cross'}),
GENERATED.endAgents(['D'], {mode: 'none'}),
]);
});
it('creates virtual agents for block statements', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
]});
expect(sequence.agents).toEqual([
@ -403,14 +403,14 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('positions virtual block agents near involved agents', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
parsed.blockBegin('if', 'abc'),
parsed.connect(['C', 'D']),
parsed.blockBegin('if', 'abc'),
parsed.connect(['E', 'F']),
parsed.blockEnd(),
parsed.blockEnd(),
parsed.connect(['G', 'H']),
PARSED.connect(['A', 'B']),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['C', 'D']),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['E', 'F']),
PARSED.blockEnd(),
PARSED.blockEnd(),
PARSED.connect(['G', 'H']),
]});
expect(sequence.agents).toEqual([
@ -433,9 +433,9 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('records virtual block agent names in blocks', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
]});
const block0 = sequence.stages[0];
@ -446,35 +446,35 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('records all sections within blocks', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockSplit('else', 'xyz'),
parsed.connect(['A', 'C']),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
PARSED.blockSplit('else', 'xyz'),
PARSED.connect(['A', 'C']),
PARSED.blockEnd(),
]});
const block0 = sequence.stages[0];
expect(block0.sections).toEqual([
{mode: 'if', label: 'abc', stages: [
generated.beginAgents(['A', 'B']),
generated.connect(['A', 'B']),
GENERATED.beginAgents(['A', 'B']),
GENERATED.connect(['A', 'B']),
]},
{mode: 'else', label: 'xyz', stages: [
generated.beginAgents(['C']),
generated.connect(['A', 'C']),
GENERATED.beginAgents(['C']),
GENERATED.connect(['A', 'C']),
]},
]);
});
it('records virtual block agents in nested blocks', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockSplit('else', 'xyz'),
parsed.blockBegin('if', 'def'),
parsed.connect(['A', 'C']),
parsed.blockEnd(),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
PARSED.blockSplit('else', 'xyz'),
PARSED.blockBegin('if', 'def'),
PARSED.connect(['A', 'C']),
PARSED.blockEnd(),
PARSED.blockEnd(),
]});
expect(sequence.agents).toEqual([
@ -501,12 +501,12 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('preserves block boundaries when agents exist outside', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
parsed.blockBegin('if', 'abc'),
parsed.blockBegin('if', 'def'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
parsed.blockEnd(),
PARSED.connect(['A', 'B']),
PARSED.blockBegin('if', 'abc'),
PARSED.blockBegin('if', 'def'),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
PARSED.blockEnd(),
]});
expect(sequence.agents).toEqual([
@ -532,10 +532,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('allows empty block parts after split', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockSplit('else', 'xyz'),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
PARSED.blockSplit('else', 'xyz'),
PARSED.blockEnd(),
]});
const block0 = sequence.stages[0];
@ -550,10 +550,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('allows empty block parts before split', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.blockSplit('else', 'xyz'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.blockSplit('else', 'xyz'),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
]});
const block0 = sequence.stages[0];
@ -568,11 +568,11 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('removes entirely empty blocks', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.blockSplit('else', 'xyz'),
parsed.blockBegin('if', 'abc'),
parsed.blockEnd(),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.blockSplit('else', 'xyz'),
PARSED.blockBegin('if', 'abc'),
PARSED.blockEnd(),
PARSED.blockEnd(),
]});
expect(sequence.stages).toEqual([]);
@ -580,10 +580,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('removes blocks containing only define statements / markers', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.defineAgents(['A']),
PARSED.blockBegin('if', 'abc'),
PARSED.defineAgents(['A']),
{type: 'mark', name: 'foo'},
parsed.blockEnd(),
PARSED.blockEnd(),
]});
expect(sequence.stages).toEqual([]);
@ -591,11 +591,11 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('does not create virtual agents for empty blocks', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.blockSplit('else', 'xyz'),
parsed.blockBegin('if', 'abc'),
parsed.blockEnd(),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.blockSplit('else', 'xyz'),
PARSED.blockBegin('if', 'abc'),
PARSED.blockEnd(),
PARSED.blockEnd(),
]});
expect(sequence.agents).toEqual([
@ -606,12 +606,12 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('removes entirely empty nested blocks', () => {
const sequence = generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockSplit('else', 'xyz'),
parsed.blockBegin('if', 'abc'),
parsed.blockEnd(),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
PARSED.blockSplit('else', 'xyz'),
PARSED.blockBegin('if', 'abc'),
PARSED.blockEnd(),
PARSED.blockEnd(),
]});
const block0 = sequence.stages[0];
@ -626,56 +626,56 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('rejects unterminated blocks', () => {
expect(() => generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
]})).toThrow();
expect(() => generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.blockBegin('if', 'def'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.blockBegin('if', 'def'),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
]})).toThrow();
});
it('rejects extra block terminations', () => {
expect(() => generator.generate({stages: [
parsed.blockEnd(),
PARSED.blockEnd(),
]})).toThrow();
expect(() => generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
parsed.blockEnd(),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
PARSED.blockEnd(),
]})).toThrow();
});
it('rejects block splitting without a block', () => {
expect(() => generator.generate({stages: [
parsed.blockSplit('else', 'xyz'),
PARSED.blockSplit('else', 'xyz'),
]})).toThrow();
expect(() => generator.generate({stages: [
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
parsed.blockSplit('else', 'xyz'),
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
PARSED.blockSplit('else', 'xyz'),
]})).toThrow();
});
it('rejects block splitting in non-splittable blocks', () => {
expect(() => generator.generate({stages: [
parsed.blockBegin('repeat', 'abc'),
parsed.blockSplit('else', 'xyz'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
PARSED.blockBegin('repeat', 'abc'),
PARSED.blockSplit('else', 'xyz'),
PARSED.connect(['A', 'B']),
PARSED.blockEnd(),
]})).toThrow();
});
it('passes notes through', () => {
const sequence = generator.generate({stages: [
parsed.note(['A', 'B'], {
PARSED.note(['A', 'B'], {
type: 'note right',
mode: 'foo',
label: 'bar',
@ -683,7 +683,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
generated.note(['A', 'B'], {
GENERATED.note(['A', 'B'], {
type: 'note right',
mode: 'foo',
label: 'bar',
@ -694,32 +694,32 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('defaults to showing notes around the entire diagram', () => {
const sequence = generator.generate({stages: [
parsed.note([], {type: 'note right'}),
parsed.note([], {type: 'note left'}),
parsed.note([], {type: 'note over'}),
PARSED.note([], {type: 'note right'}),
PARSED.note([], {type: 'note left'}),
PARSED.note([], {type: 'note over'}),
]});
expect(sequence.stages).toEqual([
generated.note([']'], {type: 'note right'}),
generated.note(['['], {type: 'note left'}),
generated.note(['[', ']'], {type: 'note over'}),
GENERATED.note([']'], {type: 'note right'}),
GENERATED.note(['['], {type: 'note left'}),
GENERATED.note(['[', ']'], {type: 'note over'}),
]);
});
it('rejects attempts to change implicit agents', () => {
expect(() => generator.generate({stages: [
parsed.beginAgents(['[']),
PARSED.beginAgents(['[']),
]})).toThrow();
expect(() => generator.generate({stages: [
parsed.beginAgents([']']),
PARSED.beginAgents([']']),
]})).toThrow();
expect(() => generator.generate({stages: [
parsed.endAgents(['[']),
PARSED.endAgents(['[']),
]})).toThrow();
expect(() => generator.generate({stages: [
parsed.endAgents([']']),
PARSED.endAgents([']']),
]})).toThrow();
});
});

View File

@ -16,7 +16,7 @@ define([
'repeat': {type: 'block begin', mode: 'repeat', skip: []},
};
const CONNECTION_TYPES = {
const CONNECT_TYPES = {
'->': {line: 'solid', left: false, right: true},
'<-': {line: 'solid', left: true, right: false},
'<->': {line: 'solid', left: true, right: true},
@ -25,7 +25,7 @@ define([
'<-->': {line: 'dash', left: true, right: true},
};
const AGENT_OPTIONS = {
const CONNECT_AGENT_FLAGS = {
'+': 'start',
'-': 'stop',
};
@ -112,50 +112,43 @@ define([
return -1;
}
function parseAgentList(line, start, end) {
const list = [];
let current = '';
let first = true;
for(let i = start; i < end; ++ i) {
const token = line[i];
if(tokenKeyword(token) === ',') {
if(current) {
list.push({name: current});
current = '';
first = true;
}
} else {
if(!first) {
current += token.s;
} else {
first = false;
}
current += token.v;
}
}
if(current) {
list.push({name: current});
}
return list;
}
function readAgentDetails(line, begin, end) {
const options = [];
let p = begin;
function readAgent(line, start, end, flagTypes = {}) {
const flags = [];
let p = start;
for(; p < end; ++ p) {
const option = AGENT_OPTIONS[tokenKeyword(line[p])];
if(option) {
options.push(option);
const flag = flagTypes[tokenKeyword(line[p])];
if(flag) {
flags.push(flag);
} else {
break;
}
}
return {
agent: {name: joinLabel(line, p, end)},
options,
name: joinLabel(line, p, end),
flags,
};
}
function readAgentList(line, start, end, flagTypes) {
const list = [];
let currentStart = -1;
for(let i = start; i < end; ++ i) {
const token = line[i];
if(tokenKeyword(token) === ',') {
if(currentStart !== -1) {
list.push(readAgent(line, currentStart, i, flagTypes));
currentStart = -1;
}
} else if(currentStart === -1) {
currentStart = i;
}
}
if(currentStart !== -1) {
list.push(readAgent(line, currentStart, end, flagTypes));
}
return list;
}
const PARSERS = [
(line, meta) => { // title
if(tokenKeyword(line[0]) !== 'title') {
@ -206,7 +199,7 @@ define([
return null;
}
return Object.assign({
agents: parseAgentList(line, 1, line.length),
agents: readAgentList(line, 1, line.length),
}, type);
},
@ -232,8 +225,8 @@ define([
(line) => { // note
const mode = NOTE_TYPES[tokenKeyword(line[0])];
const labelSplit = findToken(line, ':');
if(!mode || labelSplit === -1) {
const labelSep = findToken(line, ':');
if(!mode || labelSep === -1) {
return null;
}
const type = mode.types[tokenKeyword(line[1])];
@ -242,7 +235,7 @@ define([
}
let skip = 2;
skip = skipOver(line, skip, type.skip);
const agents = parseAgentList(line, skip, labelSplit);
const agents = readAgentList(line, skip, labelSep);
if(
agents.length < type.min ||
(type.max !== null && agents.length > type.max)
@ -256,37 +249,35 @@ define([
type: type.type,
agents,
mode: mode.mode,
label: joinLabel(line, labelSplit + 1),
label: joinLabel(line, labelSep + 1),
};
},
(line) => { // connection
let labelSplit = findToken(line, ':');
if(labelSplit === -1) {
labelSplit = line.length;
(line) => { // connect
let labelSep = findToken(line, ':');
if(labelSep === -1) {
labelSep = line.length;
}
let typeSplit = -1;
let typePos = -1;
let options = null;
for(let j = 0; j < line.length; ++ j) {
const opts = CONNECTION_TYPES[tokenKeyword(line[j])];
const opts = CONNECT_TYPES[tokenKeyword(line[j])];
if(opts) {
typeSplit = j;
typePos = j;
options = opts;
break;
}
}
if(typeSplit <= 0 || typeSplit >= labelSplit - 1) {
if(typePos <= 0 || typePos >= labelSep - 1) {
return null;
}
const from = readAgentDetails(line, 0, typeSplit);
const to = readAgentDetails(line, typeSplit + 1, labelSplit);
return {
type: 'connection',
type: 'connect',
agents: [
from.agent,
to.agent,
readAgent(line, 0, typePos, CONNECT_AGENT_FLAGS),
readAgent(line, typePos + 1, labelSep, CONNECT_AGENT_FLAGS),
],
label: joinLabel(line, labelSplit + 1),
label: joinLabel(line, labelSep + 1),
options,
};
},

View File

@ -4,15 +4,15 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
const parser = new Parser();
const PARSED = {
connection: (agentNames, {
connect: (agentNames, {
line = jasmine.anything(),
left = jasmine.anything(),
right = jasmine.anything(),
label = jasmine.anything(),
} = {}) => {
return {
type: 'connection',
agents: agentNames.map((name) => ({name})),
type: 'connect',
agents: agentNames.map((name) => ({name, flags: []})),
label,
options: {
line,
@ -53,44 +53,59 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
it('converts entries into abstract form', () => {
const parsed = parser.parse('A -> B');
expect(parsed.stages).toEqual([
PARSED.connection(['A', 'B']),
PARSED.connect(['A', 'B']),
]);
});
it('combines multiple tokens into single entries', () => {
const parsed = parser.parse('A B -> C D');
expect(parsed.stages).toEqual([
PARSED.connection(['A B', 'C D']),
PARSED.connect(['A B', 'C D']),
]);
});
it('respects spacing within agent names', () => {
const parsed = parser.parse('A+B -> C D');
expect(parsed.stages).toEqual([
PARSED.connection(['A+B', 'C D']),
PARSED.connect(['A+B', 'C D']),
]);
});
it('parses optional labels', () => {
const parsed = parser.parse('A B -> C D: foo bar');
expect(parsed.stages).toEqual([
PARSED.connection(['A B', 'C D'], {label: 'foo bar'}),
PARSED.connect(['A B', 'C D'], {label: 'foo bar'}),
]);
});
it('parses optional flags', () => {
const parsed = parser.parse('+A -> -B');
expect(parsed.stages).toEqual([
{
type: 'connect',
agents: [
{name: 'A', flags: ['start']},
{name: 'B', flags: ['stop']},
],
label: jasmine.anything(),
options: jasmine.anything(),
},
]);
});
it('converts multiple entries', () => {
const parsed = parser.parse('A -> B\nB -> A');
expect(parsed.stages).toEqual([
PARSED.connection(['A', 'B']),
PARSED.connection(['B', 'A']),
PARSED.connect(['A', 'B']),
PARSED.connect(['B', 'A']),
]);
});
it('ignores blank lines', () => {
const parsed = parser.parse('A -> B\n\nB -> A\n');
expect(parsed.stages).toEqual([
PARSED.connection(['A', 'B']),
PARSED.connection(['B', 'A']),
PARSED.connect(['A', 'B']),
PARSED.connect(['B', 'A']),
]);
});
@ -104,37 +119,37 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
'A <--> B\n'
);
expect(parsed.stages).toEqual([
PARSED.connection(['A', 'B'], {
PARSED.connect(['A', 'B'], {
line: 'solid',
left: false,
right: true,
label: '',
}),
PARSED.connection(['A', 'B'], {
PARSED.connect(['A', 'B'], {
line: 'solid',
left: true,
right: false,
label: '',
}),
PARSED.connection(['A', 'B'], {
PARSED.connect(['A', 'B'], {
line: 'solid',
left: true,
right: true,
label: '',
}),
PARSED.connection(['A', 'B'], {
PARSED.connect(['A', 'B'], {
line: 'dash',
left: false,
right: true,
label: '',
}),
PARSED.connection(['A', 'B'], {
PARSED.connect(['A', 'B'], {
line: 'dash',
left: true,
right: false,
label: '',
}),
PARSED.connection(['A', 'B'], {
PARSED.connect(['A', 'B'], {
line: 'dash',
left: true,
right: true,
@ -149,13 +164,13 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
'A -> B: B <- A\n'
);
expect(parsed.stages).toEqual([
PARSED.connection(['A', 'B'], {
PARSED.connect(['A', 'B'], {
line: 'solid',
left: true,
right: false,
label: 'B -> A',
}),
PARSED.connection(['A', 'B'], {
PARSED.connect(['A', 'B'], {
line: 'solid',
left: false,
right: true,
@ -168,7 +183,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
const parsed = parser.parse('note over A: hello there');
expect(parsed.stages).toEqual([{
type: 'note over',
agents: [{name: 'A'}],
agents: [{name: 'A', flags: []}],
mode: 'note',
label: 'hello there',
}]);
@ -185,31 +200,31 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
expect(parsed.stages).toEqual([
{
type: 'note left',
agents: [{name: 'A'}],
agents: [{name: 'A', flags: []}],
mode: 'note',
label: 'hello there',
},
{
type: 'note left',
agents: [{name: 'A'}],
agents: [{name: 'A', flags: []}],
mode: 'note',
label: 'hello there',
},
{
type: 'note right',
agents: [{name: 'A'}],
agents: [{name: 'A', flags: []}],
mode: 'note',
label: 'hello there',
},
{
type: 'note right',
agents: [{name: 'A'}],
agents: [{name: 'A', flags: []}],
mode: 'note',
label: 'hello there',
},
{
type: 'note between',
agents: [{name: 'A'}, {name: 'B'}],
agents: [{name: 'A', flags: []}, {name: 'B', flags: []}],
mode: 'note',
label: 'hi',
},
@ -220,7 +235,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
const parsed = parser.parse('note over A B, C D: hi');
expect(parsed.stages).toEqual([{
type: 'note over',
agents: [{name: 'A B'}, {name: 'C D'}],
agents: [{name: 'A B', flags: []}, {name: 'C D', flags: []}],
mode: 'note',
label: 'hi',
}]);
@ -234,7 +249,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
const parsed = parser.parse('state over A: doing stuff');
expect(parsed.stages).toEqual([{
type: 'note over',
agents: [{name: 'A'}],
agents: [{name: 'A', flags: []}],
mode: 'state',
label: 'doing stuff',
}]);
@ -248,7 +263,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
const parsed = parser.parse('text right of A: doing stuff');
expect(parsed.stages).toEqual([{
type: 'note right',
agents: [{name: 'A'}],
agents: [{name: 'A', flags: []}],
mode: 'text',
label: 'doing stuff',
}]);
@ -263,16 +278,16 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
expect(parsed.stages).toEqual([
{
type: 'agent define',
agents: [{name: 'A'}, {name: 'B'}],
agents: [{name: 'A', flags: []}, {name: 'B', flags: []}],
},
{
type: 'agent begin',
agents: [{name: 'A'}, {name: 'B'}],
agents: [{name: 'A', flags: []}, {name: 'B', flags: []}],
mode: 'box',
},
{
type: 'agent end',
agents: [{name: 'A'}, {name: 'B'}],
agents: [{name: 'A', flags: []}, {name: 'B', flags: []}],
mode: 'cross',
},
]);
@ -315,12 +330,12 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
);
expect(parsed.stages).toEqual([
{type: 'block begin', mode: 'if', label: 'something happens'},
PARSED.connection(['A', 'B']),
PARSED.connect(['A', 'B']),
{type: 'block split', mode: 'else', label: 'something else'},
PARSED.connection(['A', 'C']),
PARSED.connection(['C', 'B']),
PARSED.connect(['A', 'C']),
PARSED.connect(['C', 'B']),
{type: 'block split', mode: 'else', label: ''},
PARSED.connection(['A', 'D']),
PARSED.connect(['A', 'D']),
{type: 'block end'},
]);
});

View File

@ -67,7 +67,7 @@ define([
'async': this.separationAsync.bind(this),
'agent begin': this.separationAgent.bind(this),
'agent end': this.separationAgent.bind(this),
'connection': this.separationConnection.bind(this),
'connect': this.separationConnect.bind(this),
'note over': this.separationNoteOver.bind(this),
'note left': this.separationNoteSide.bind(this, false),
'note right': this.separationNoteSide.bind(this, true),
@ -86,7 +86,7 @@ define([
'async': this.renderAsync.bind(this),
'agent begin': this.renderAgentBegin.bind(this),
'agent end': this.renderAgentEnd.bind(this),
'connection': this.renderConnection.bind(this),
'connect': this.renderConnect.bind(this),
'note over': this.renderNoteOver.bind(this),
'note left': this.renderNoteLeft.bind(this),
'note right': this.renderNoteRight.bind(this),
@ -263,7 +263,7 @@ define([
}
}
separationConnection({agentNames, label}) {
separationConnect({agentNames, label}) {
const config = this.theme.connect;
const labelWidth = (
@ -528,7 +528,7 @@ define([
this.markAgentRange(agentNames);
}
renderSelfConnection({label, agentNames, options}) {
renderSelfConnect({label, agentNames, options}) {
const config = this.theme.connect;
const from = this.agentInfos.get(agentNames[0]);
@ -601,7 +601,7 @@ define([
this.currentY = y1 + dy + this.theme.actionMargin;
}
renderSimpleConnection({label, agentNames, options}) {
renderSimpleConnect({label, agentNames, options}) {
const config = this.theme.connect;
const from = this.agentInfos.get(agentNames[0]);
const to = this.agentInfos.get(agentNames[1]);
@ -659,12 +659,12 @@ define([
this.currentY = y + dy + this.theme.actionMargin;
}
renderConnection(stage) {
renderConnect(stage) {
this.checkAgentRange(stage.agentNames);
if(stage.agentNames[0] === stage.agentNames[1]) {
this.renderSelfConnection(stage);
this.renderSelfConnect(stage);
} else {
this.renderSimpleConnection(stage);
this.renderSimpleConnect(stage);
}
this.markAgentRange(stage.agentNames);
}

View File

@ -25,9 +25,10 @@ defineDescribe('Sequence Renderer', [
});
});
function connectionStage(agentNames, label = '') {
const GENERATED = {
connect: (agentNames, label = '') => {
return {
type: 'connection',
type: 'connect',
agentNames,
label,
options: {
@ -36,7 +37,8 @@ defineDescribe('Sequence Renderer', [
right: true,
},
};
}
},
};
describe('.render', () => {
it('populates the SVG with content', () => {
@ -70,7 +72,7 @@ defineDescribe('Sequence Renderer', [
],
stages: [
{type: 'agent begin', agentNames: ['A', 'B'], mode: 'box'},
connectionStage(['A', 'B']),
GENERATED.connect(['A', 'B']),
{type: 'agent end', agentNames: ['A', 'B'], mode: 'none'},
],
});
@ -105,10 +107,10 @@ defineDescribe('Sequence Renderer', [
agentNames: ['A', 'B', 'C'],
mode: 'box',
},
connectionStage(['[', 'A']),
connectionStage(['A', 'B']),
connectionStage(['B', 'C']),
connectionStage(['C', ']']),
GENERATED.connect(['[', 'A']),
GENERATED.connect(['A', 'B']),
GENERATED.connect(['B', 'C']),
GENERATED.connect(['C', ']']),
{
type: 'agent end',
agentNames: ['A', 'B', 'C'],
@ -151,13 +153,13 @@ defineDescribe('Sequence Renderer', [
],
stages: [
{type: 'agent begin', agentNames: ['A', 'B'], mode: 'box'},
connectionStage(['A', 'B'], 'short'),
GENERATED.connect(['A', 'B'], 'short'),
{type: 'agent end', agentNames: ['B'], mode: 'cross'},
{type: 'agent begin', agentNames: ['C'], mode: 'box'},
connectionStage(['A', 'C'], 'long description here'),
GENERATED.connect(['A', 'C'], 'long description here'),
{type: 'agent end', agentNames: ['C'], mode: 'cross'},
{type: 'agent begin', agentNames: ['D'], mode: 'box'},
connectionStage(['A', 'D'], 'short again'),
GENERATED.connect(['A', 'D'], 'short again'),
{type: 'agent end', agentNames: ['A', 'D'], mode: 'cross'},
],
});