Add support for aliases, and fix various issues when using the same agent multiple times in a statement [#19]
This commit is contained in:
parent
4384afdd03
commit
ddb4430ed2
13
README.md
13
README.md
|
@ -150,6 +150,19 @@ terminators bar
|
|||
# (options are: box, bar, cross, none)
|
||||
```
|
||||
|
||||
### Agent Aliases
|
||||
|
||||
<img src="screenshots/AgentAliases.png" alt="Agent Aliases preview" width="200" align="right" />
|
||||
|
||||
```
|
||||
define My complicated agent name as A
|
||||
define "Another agent name,
|
||||
and this one's multi-line!" as B
|
||||
|
||||
A -> B: this is much easier
|
||||
A <- B: than writing the whole name
|
||||
```
|
||||
|
||||
### Alternative Agent Ordering
|
||||
|
||||
<img src="screenshots/AlternativeAgentOrdering.png" alt="Alternative Agent Ordering preview" width="150" align="right" />
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -1,52 +1,62 @@
|
|||
define(['core/ArrayUtilities'], (array) => {
|
||||
'use strict';
|
||||
|
||||
const CM_END = {type: '', suggest: '\n', then: {}};
|
||||
const CM_HIDDEN_END = {type: '', then: {}};
|
||||
const CM_ERROR = {type: 'error line-error', then: {'': 0}};
|
||||
|
||||
function makeCMCommaBlock(type, suggest, exits = {}) {
|
||||
return {type, suggest, then: Object.assign({
|
||||
'': 0,
|
||||
',': {type: 'operator', suggest: true, then: {
|
||||
'': 1,
|
||||
}},
|
||||
}, exits)};
|
||||
}
|
||||
const CM_COMMANDS = ((() => {
|
||||
const end = {type: '', suggest: '\n', then: {}};
|
||||
const hiddenEnd = {type: '', then: {}};
|
||||
|
||||
const CM_TEXT_TO_END = {type: 'string', then: {'': 0, '\n': CM_END}};
|
||||
const CM_AGENT_LIST_TO_END = makeCMCommaBlock('variable', 'Agent', {
|
||||
'\n': CM_END,
|
||||
});
|
||||
const CM_AGENT_LIST_TO_TEXT = makeCMCommaBlock('variable', 'Agent', {
|
||||
':': {type: 'operator', suggest: true, then: {'': CM_TEXT_TO_END}},
|
||||
});
|
||||
const CM_AGENT_TO_OPTTEXT = {type: 'variable', suggest: 'Agent', then: {
|
||||
const ARROWS = [
|
||||
'->', '-->',
|
||||
'<-', '<--',
|
||||
'<->', '<-->',
|
||||
];
|
||||
|
||||
const textToEnd = {type: 'string', then: {'': 0, '\n': end}};
|
||||
const aliasListToEnd = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
'as': {type: 'keyword', suggest: true, then: {
|
||||
'': {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
',': {type: 'operator', suggest: true, then: {'': 3}},
|
||||
'\n': end,
|
||||
}},
|
||||
}},
|
||||
',': {type: 'operator', suggest: true, then: {'': 1}},
|
||||
'\n': end,
|
||||
}};
|
||||
const agentListToEnd = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
',': {type: 'operator', suggest: true, then: {'': 1}},
|
||||
':': {type: 'operator', suggest: true, then: {'': textToEnd}},
|
||||
}};
|
||||
const agentToOptText = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
':': {type: 'operator', suggest: true, then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
'\n': CM_HIDDEN_END,
|
||||
'': textToEnd,
|
||||
'\n': hiddenEnd,
|
||||
}},
|
||||
'\n': CM_END,
|
||||
'\n': end,
|
||||
}};
|
||||
|
||||
function makeCMSideNote(side) {
|
||||
function makeSideNote(side) {
|
||||
return {
|
||||
type: 'keyword',
|
||||
suggest: [side + ' of ', side + ': '],
|
||||
then: {
|
||||
'of': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_AGENT_LIST_TO_TEXT,
|
||||
'': agentListToEnd,
|
||||
}},
|
||||
':': {type: 'operator', suggest: true, then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
'': textToEnd,
|
||||
}},
|
||||
'': CM_AGENT_LIST_TO_TEXT,
|
||||
'': agentListToEnd,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeCMOperatorBlock(exit) {
|
||||
function makeOpBlock(exit) {
|
||||
const op = {type: 'operator', suggest: true, then: {
|
||||
'+': CM_ERROR,
|
||||
'-': CM_ERROR,
|
||||
|
@ -91,24 +101,25 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
const connect = {
|
||||
type: 'keyword',
|
||||
suggest: true,
|
||||
then: makeCMOperatorBlock(CM_AGENT_TO_OPTTEXT),
|
||||
then: makeOpBlock(agentToOptText),
|
||||
};
|
||||
|
||||
return makeCMOperatorBlock({type: 'variable', suggest: 'Agent', then: {
|
||||
'->': connect,
|
||||
'-->': connect,
|
||||
'<-': connect,
|
||||
'<--': connect,
|
||||
'<->': connect,
|
||||
'<-->': connect,
|
||||
':': {type: 'operator', suggest: true, override: 'Label', then: {}},
|
||||
const then = {
|
||||
':': {
|
||||
type: 'operator',
|
||||
suggest: true,
|
||||
override: 'Label',
|
||||
then: {},
|
||||
},
|
||||
'': 0,
|
||||
}});
|
||||
};
|
||||
ARROWS.forEach((arrow) => (then[arrow] = connect));
|
||||
return makeOpBlock({type: 'variable', suggest: 'Agent', then});
|
||||
}
|
||||
|
||||
const CM_COMMANDS = {type: 'error line-error', then: Object.assign({
|
||||
return {type: 'error line-error', then: Object.assign({
|
||||
'title': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
'': textToEnd,
|
||||
}},
|
||||
'terminators': {type: 'keyword', suggest: true, then: {
|
||||
'none': {type: 'keyword', suggest: true, then: {}},
|
||||
|
@ -117,56 +128,56 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
'bar': {type: 'keyword', suggest: true, then: {}},
|
||||
}},
|
||||
'define': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_AGENT_LIST_TO_END,
|
||||
'': aliasListToEnd,
|
||||
}},
|
||||
'begin': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_AGENT_LIST_TO_END,
|
||||
'': aliasListToEnd,
|
||||
}},
|
||||
'end': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_AGENT_LIST_TO_END,
|
||||
'\n': CM_END,
|
||||
'': aliasListToEnd,
|
||||
'\n': end,
|
||||
}},
|
||||
'if': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
'': textToEnd,
|
||||
':': {type: 'operator', suggest: true, then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
'': textToEnd,
|
||||
}},
|
||||
'\n': CM_END,
|
||||
'\n': end,
|
||||
}},
|
||||
'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: {
|
||||
'if': {type: 'keyword', suggest: 'if: ', then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
'': textToEnd,
|
||||
':': {type: 'operator', suggest: true, then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
'': textToEnd,
|
||||
}},
|
||||
}},
|
||||
'\n': CM_END,
|
||||
'\n': end,
|
||||
}},
|
||||
'repeat': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
'': textToEnd,
|
||||
':': {type: 'operator', suggest: true, then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
'': textToEnd,
|
||||
}},
|
||||
'\n': CM_END,
|
||||
'\n': end,
|
||||
}},
|
||||
'note': {type: 'keyword', suggest: true, then: {
|
||||
'over': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_AGENT_LIST_TO_TEXT,
|
||||
'': agentListToEnd,
|
||||
}},
|
||||
'left': makeCMSideNote('left'),
|
||||
'right': makeCMSideNote('right'),
|
||||
'left': makeSideNote('left'),
|
||||
'right': makeSideNote('right'),
|
||||
'between': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_AGENT_LIST_TO_TEXT,
|
||||
'': agentListToEnd,
|
||||
}},
|
||||
}},
|
||||
'state': {type: 'keyword', suggest: 'state over ', then: {
|
||||
'over': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_AGENT_LIST_TO_TEXT,
|
||||
'': agentListToEnd,
|
||||
}},
|
||||
}},
|
||||
'text': {type: 'keyword', suggest: true, then: {
|
||||
'left': makeCMSideNote('left'),
|
||||
'right': makeCMSideNote('right'),
|
||||
'left': makeSideNote('left'),
|
||||
'right': makeSideNote('right'),
|
||||
}},
|
||||
'simultaneously': {type: 'keyword', suggest: true, then: {
|
||||
':': {type: 'operator', suggest: true, then: {}},
|
||||
|
@ -178,6 +189,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}},
|
||||
}},
|
||||
}, makeCMConnect())};
|
||||
})());
|
||||
|
||||
function cmCappedToken(token, current) {
|
||||
if(Object.keys(current.then).length > 0) {
|
||||
|
|
|
@ -17,10 +17,6 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
return {name, anchorRight};
|
||||
}
|
||||
|
||||
function convertAgent(agent) {
|
||||
return makeAgent(agent.name);
|
||||
}
|
||||
|
||||
function getAgentName(agent) {
|
||||
return agent.name;
|
||||
}
|
||||
|
@ -181,6 +177,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
return class Generator {
|
||||
constructor() {
|
||||
this.agentStates = new Map();
|
||||
this.agentAliases = new Map();
|
||||
this.agents = [];
|
||||
this.blockCount = 0;
|
||||
this.nesting = [];
|
||||
|
@ -204,6 +201,29 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
'block end': this.handleBlockEnd.bind(this),
|
||||
};
|
||||
this.handleStage = this.handleStage.bind(this);
|
||||
this.convertAgent = this.convertAgent.bind(this);
|
||||
}
|
||||
|
||||
convertAgent({alias, name}) {
|
||||
if(alias) {
|
||||
if(this.agentAliases.has(name)) {
|
||||
throw new Error(
|
||||
'Cannot alias ' + name + '; it is already an alias'
|
||||
);
|
||||
}
|
||||
const old = this.agentAliases.get(alias);
|
||||
if(
|
||||
(old && old !== alias) ||
|
||||
this.agents.some((agent) => (agent.name === alias))
|
||||
) {
|
||||
throw new Error(
|
||||
'Cannot use ' + alias +
|
||||
' as an alias; it is already in use'
|
||||
);
|
||||
}
|
||||
this.agentAliases.set(alias, name);
|
||||
}
|
||||
return makeAgent(this.agentAliases.get(name) || name);
|
||||
}
|
||||
|
||||
addStage(stage, isVisible = true) {
|
||||
|
@ -230,13 +250,18 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
});
|
||||
}
|
||||
|
||||
defineAgents(agents) {
|
||||
array.mergeSets(this.currentNest.agents, agents, agentEqCheck);
|
||||
array.mergeSets(this.agents, agents, agentEqCheck);
|
||||
defineAgents(colAgents) {
|
||||
array.mergeSets(this.currentNest.agents, colAgents, agentEqCheck);
|
||||
array.mergeSets(this.agents, colAgents, agentEqCheck);
|
||||
}
|
||||
|
||||
setAgentVisRaw(agents, visible, mode, checked = false) {
|
||||
const filteredAgents = agents.filter((agent) => {
|
||||
setAgentVis(colAgents, visible, mode, checked = false) {
|
||||
const seen = new Set();
|
||||
const filteredAgents = colAgents.filter((agent) => {
|
||||
if(seen.has(agent.name)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(agent.name);
|
||||
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
||||
if(state.locked) {
|
||||
if(checked) {
|
||||
|
@ -269,17 +294,8 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
};
|
||||
}
|
||||
|
||||
setAgentVis(agents, visible, mode, checked = false) {
|
||||
return this.setAgentVisRaw(
|
||||
agents.map(convertAgent),
|
||||
visible,
|
||||
mode,
|
||||
checked
|
||||
);
|
||||
}
|
||||
|
||||
setAgentHighlight(agents, highlighted, checked = false) {
|
||||
const filteredAgents = agents.filter((agent) => {
|
||||
setAgentHighlight(colAgents, highlighted, checked = false) {
|
||||
const filteredAgents = colAgents.filter((agent) => {
|
||||
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
||||
if(state.locked) {
|
||||
if(checked) {
|
||||
|
@ -349,27 +365,44 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}
|
||||
|
||||
handleConnect({agents, label, options}) {
|
||||
const beginAgents = agents.filter(agentHasFlag('begin'));
|
||||
const endAgents = agents.filter(agentHasFlag('end'));
|
||||
const beginAgents = (agents
|
||||
.filter(agentHasFlag('begin'))
|
||||
.map(this.convertAgent)
|
||||
);
|
||||
const endAgents = (agents
|
||||
.filter(agentHasFlag('end'))
|
||||
.map(this.convertAgent)
|
||||
);
|
||||
if(array.hasIntersection(beginAgents, endAgents, agentEqCheck)) {
|
||||
throw new Error('Cannot set agent visibility multiple times');
|
||||
}
|
||||
|
||||
const startAgents = agents.filter(agentHasFlag('start'));
|
||||
const stopAgents = agents.filter(agentHasFlag('stop'));
|
||||
const startAgents = (agents
|
||||
.filter(agentHasFlag('start'))
|
||||
.map(this.convertAgent)
|
||||
);
|
||||
const stopAgents = (agents
|
||||
.filter(agentHasFlag('stop'))
|
||||
.map(this.convertAgent)
|
||||
);
|
||||
array.mergeSets(stopAgents, endAgents);
|
||||
if(array.hasIntersection(startAgents, stopAgents, agentEqCheck)) {
|
||||
throw new Error('Cannot set agent highlighting multiple times');
|
||||
}
|
||||
|
||||
this.defineAgents(agents.map(convertAgent));
|
||||
const colAgents = agents.map(this.convertAgent);
|
||||
const agentNames = colAgents.map(getAgentName);
|
||||
this.defineAgents(colAgents);
|
||||
|
||||
const implicitBegin = agents.filter(agentHasFlag('begin', false));
|
||||
const implicitBegin = (agents
|
||||
.filter(agentHasFlag('begin', false))
|
||||
.map(this.convertAgent)
|
||||
);
|
||||
this.addStage(this.setAgentVis(implicitBegin, true, 'box'));
|
||||
|
||||
const connectStage = {
|
||||
type: 'connect',
|
||||
agentNames: agents.map(getAgentName),
|
||||
agentNames,
|
||||
label,
|
||||
options,
|
||||
};
|
||||
|
@ -388,32 +421,39 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
if(agents.length === 0) {
|
||||
colAgents = NOTE_DEFAULT_AGENTS[type] || [];
|
||||
} else {
|
||||
colAgents = agents.map(convertAgent);
|
||||
colAgents = agents.map(this.convertAgent);
|
||||
}
|
||||
const agentNames = colAgents.map(getAgentName);
|
||||
const uniqueAgents = new Set(agentNames).size;
|
||||
if(type === 'note between' && uniqueAgents < 2) {
|
||||
throw new Error('note between requires at least 2 agents');
|
||||
}
|
||||
|
||||
this.addStage(this.setAgentVisRaw(colAgents, true, 'box'));
|
||||
this.addStage(this.setAgentVis(colAgents, true, 'box'));
|
||||
this.defineAgents(colAgents);
|
||||
|
||||
this.addStage({
|
||||
type,
|
||||
agentNames: colAgents.map(getAgentName),
|
||||
agentNames,
|
||||
mode,
|
||||
label,
|
||||
});
|
||||
}
|
||||
|
||||
handleAgentDefine({agents}) {
|
||||
this.defineAgents(agents.map(convertAgent));
|
||||
this.defineAgents(agents.map(this.convertAgent));
|
||||
}
|
||||
|
||||
handleAgentBegin({agents, mode}) {
|
||||
this.addStage(this.setAgentVis(agents, true, mode, true));
|
||||
const colAgents = agents.map(this.convertAgent);
|
||||
this.addStage(this.setAgentVis(colAgents, true, mode, true));
|
||||
}
|
||||
|
||||
handleAgentEnd({agents, mode}) {
|
||||
const colAgents = agents.map(this.convertAgent);
|
||||
this.addParallelStages([
|
||||
this.setAgentHighlight(agents, false),
|
||||
this.setAgentVis(agents, false, mode, true),
|
||||
this.setAgentHighlight(colAgents, false),
|
||||
this.setAgentVis(colAgents, false, mode, true),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -467,6 +507,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
generate({stages, meta = {}}) {
|
||||
this.agentStates.clear();
|
||||
this.markers.clear();
|
||||
this.agentAliases.clear();
|
||||
this.agents.length = 0;
|
||||
this.blockCount = 0;
|
||||
this.nesting.length = 0;
|
||||
|
@ -485,7 +526,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
|
||||
this.addParallelStages([
|
||||
this.setAgentHighlight(this.agents, false),
|
||||
this.setAgentVisRaw(this.agents, false, terminators),
|
||||
this.setAgentVis(this.agents, false, terminators),
|
||||
]);
|
||||
|
||||
addBounds(
|
||||
|
|
|
@ -8,7 +8,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
if(typeof item === 'object') {
|
||||
return item;
|
||||
} else {
|
||||
return {name: item, flags: []};
|
||||
return {name: item, alias: '', flags: []};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -231,6 +231,33 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('converts aliases', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.defineAgents([{name: 'Baz', alias: 'B', flags: []}]),
|
||||
PARSED.connect(['A', 'B']),
|
||||
]});
|
||||
expect(sequence.agents).toEqual([
|
||||
{name: '[', anchorRight: true},
|
||||
{name: 'Baz', anchorRight: false},
|
||||
{name: 'A', anchorRight: false},
|
||||
{name: ']', anchorRight: false},
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects duplicate aliases', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.defineAgents([{name: 'Foo', alias: 'B', flags: []}]),
|
||||
PARSED.defineAgents([{name: 'Bar', alias: 'B', flags: []}]),
|
||||
]})).toThrow();
|
||||
});
|
||||
|
||||
it('rejects using agent names as aliases', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.defineAgents([{name: 'Foo', alias: 'B', flags: []}]),
|
||||
PARSED.defineAgents([{name: 'Bar', alias: 'Foo', flags: []}]),
|
||||
]})).toThrow();
|
||||
});
|
||||
|
||||
it('creates implicit begin stages for agents when used', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', 'B']),
|
||||
|
@ -347,6 +374,16 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('removes duplicate begin agents', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.beginAgents(['A', 'A']),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
GENERATED.beginAgents(['A']),
|
||||
jasmine.anything(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('collapses adjacent begin statements', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', 'B']),
|
||||
|
@ -378,6 +415,17 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('removes duplicate end agents', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.beginAgents(['A']),
|
||||
PARSED.endAgents(['A', 'A']),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
jasmine.anything(),
|
||||
GENERATED.endAgents(['A']),
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes superfluous end statements', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.defineAgents(['E']),
|
||||
|
@ -409,8 +457,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
|
||||
it('adds parallel highlighting stages', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['start']}]),
|
||||
PARSED.connect(['A', {name: 'B', flags: ['stop']}]),
|
||||
PARSED.connect(['A', {name: 'B', alias: '', flags: ['start']}]),
|
||||
PARSED.connect(['A', {name: 'B', alias: '', flags: ['stop']}]),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
jasmine.anything(),
|
||||
|
@ -428,7 +476,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
|
||||
it('adds parallel begin stages', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['begin']}]),
|
||||
PARSED.connect(['A', {name: 'B', alias: '', flags: ['begin']}]),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
GENERATED.beginAgents(['A']),
|
||||
|
@ -442,7 +490,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
|
||||
it('adds parallel end stages', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['end']}]),
|
||||
PARSED.connect(['A', {name: 'B', alias: '', flags: ['end']}]),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
GENERATED.beginAgents(['A', 'B']),
|
||||
|
@ -456,8 +504,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
|
||||
it('implicitly ends highlighting when ending a stage', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['start']}]),
|
||||
PARSED.connect(['A', {name: 'B', flags: ['end']}]),
|
||||
PARSED.connect(['A', {name: 'B', alias: '', flags: ['start']}]),
|
||||
PARSED.connect(['A', {name: 'B', alias: '', flags: ['end']}]),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
jasmine.anything(),
|
||||
|
@ -472,32 +520,41 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
});
|
||||
|
||||
it('rejects conflicting flags', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['start', 'stop']}]),
|
||||
]})).toThrow();
|
||||
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.connect([
|
||||
{name: 'A', flags: ['start']},
|
||||
{name: 'A', flags: ['stop']},
|
||||
'A',
|
||||
{name: 'B', alias: '', flags: ['start', 'stop']},
|
||||
]),
|
||||
]})).toThrow();
|
||||
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['begin', 'end']}]),
|
||||
PARSED.connect([
|
||||
{name: 'A', alias: '', flags: ['start']},
|
||||
{name: 'A', alias: '', flags: ['stop']},
|
||||
]),
|
||||
]})).toThrow();
|
||||
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.connect([
|
||||
{name: 'A', flags: ['begin']},
|
||||
{name: 'A', flags: ['end']},
|
||||
'A',
|
||||
{name: 'B', alias: '', flags: ['begin', 'end']},
|
||||
]),
|
||||
]})).toThrow();
|
||||
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.connect([
|
||||
{name: 'A', alias: '', flags: ['begin']},
|
||||
{name: 'A', alias: '', flags: ['end']},
|
||||
]),
|
||||
]})).toThrow();
|
||||
});
|
||||
|
||||
it('adds implicit highlight end with implicit terminator', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['start']}]),
|
||||
PARSED.connect([
|
||||
'A',
|
||||
{name: 'B', alias: '', flags: ['start']},
|
||||
]),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
jasmine.anything(),
|
||||
|
@ -511,7 +568,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
|
||||
it('adds implicit highlight end with explicit terminator', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['start']}]),
|
||||
PARSED.connect(['A', {name: 'B', alias: '', flags: ['start']}]),
|
||||
PARSED.endAgents(['A', 'B']),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
|
@ -527,8 +584,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
it('collapses adjacent end statements containing highlighting', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect([
|
||||
{name: 'A', flags: ['start']},
|
||||
{name: 'B', flags: ['start']},
|
||||
{name: 'A', alias: '', flags: ['start']},
|
||||
{name: 'B', alias: '', flags: ['start']},
|
||||
]),
|
||||
PARSED.endAgents(['A']),
|
||||
PARSED.endAgents(['B']),
|
||||
|
@ -849,6 +906,15 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('rejects note between with a repeated agent', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.note('note between', ['A', 'A'], {
|
||||
mode: 'foo',
|
||||
label: 'bar',
|
||||
}),
|
||||
]})).toThrow();
|
||||
});
|
||||
|
||||
it('defaults to showing notes around the entire diagram', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.note('note right', []),
|
||||
|
|
|
@ -114,7 +114,27 @@ define([
|
|||
return -1;
|
||||
}
|
||||
|
||||
function readAgent(line, start, end, flagTypes = {}) {
|
||||
function readAgentAlias(line, start, end, enableAlias) {
|
||||
let aliasSep = -1;
|
||||
if(enableAlias) {
|
||||
aliasSep = findToken(line, 'as', start);
|
||||
}
|
||||
if(aliasSep === -1 || aliasSep >= end) {
|
||||
aliasSep = end;
|
||||
}
|
||||
if(start >= aliasSep) {
|
||||
throw new Error('Missing agent name');
|
||||
}
|
||||
return {
|
||||
name: joinLabel(line, start, aliasSep),
|
||||
alias: joinLabel(line, aliasSep + 1, end),
|
||||
};
|
||||
}
|
||||
|
||||
function readAgent(line, start, end, {
|
||||
flagTypes = {},
|
||||
aliases = false,
|
||||
} = {}) {
|
||||
const flags = [];
|
||||
let p = start;
|
||||
for(; p < end; ++ p) {
|
||||
|
@ -129,23 +149,22 @@ define([
|
|||
break;
|
||||
}
|
||||
}
|
||||
if(p >= end) {
|
||||
throw new Error('Missing agent name');
|
||||
}
|
||||
const {name, alias} = readAgentAlias(line, p, end, aliases);
|
||||
return {
|
||||
name: joinLabel(line, p, end),
|
||||
name,
|
||||
alias,
|
||||
flags,
|
||||
};
|
||||
}
|
||||
|
||||
function readAgentList(line, start, end, flagTypes) {
|
||||
function readAgentList(line, start, end, readAgentOpts) {
|
||||
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));
|
||||
list.push(readAgent(line, currentStart, i, readAgentOpts));
|
||||
currentStart = -1;
|
||||
}
|
||||
} else if(currentStart === -1) {
|
||||
|
@ -153,7 +172,7 @@ define([
|
|||
}
|
||||
}
|
||||
if(currentStart !== -1) {
|
||||
list.push(readAgent(line, currentStart, end, flagTypes));
|
||||
list.push(readAgent(line, currentStart, end, readAgentOpts));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
@ -208,7 +227,7 @@ define([
|
|||
return null;
|
||||
}
|
||||
return Object.assign({
|
||||
agents: readAgentList(line, 1, line.length),
|
||||
agents: readAgentList(line, 1, line.length, {aliases: true}),
|
||||
}, type);
|
||||
},
|
||||
|
||||
|
@ -280,11 +299,14 @@ define([
|
|||
if(typePos <= 0 || typePos >= labelSep - 1) {
|
||||
return null;
|
||||
}
|
||||
const readAgentOpts = {
|
||||
flagTypes: CONNECT_AGENT_FLAGS,
|
||||
};
|
||||
return {
|
||||
type: 'connect',
|
||||
agents: [
|
||||
readAgent(line, 0, typePos, CONNECT_AGENT_FLAGS),
|
||||
readAgent(line, typePos + 1, labelSep, CONNECT_AGENT_FLAGS),
|
||||
readAgent(line, 0, typePos, readAgentOpts),
|
||||
readAgent(line, typePos + 1, labelSep, readAgentOpts),
|
||||
],
|
||||
label: joinLabel(line, labelSep + 1),
|
||||
options,
|
||||
|
|
|
@ -12,7 +12,11 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
} = {}) => {
|
||||
return {
|
||||
type: 'connect',
|
||||
agents: agentNames.map((name) => ({name, flags: []})),
|
||||
agents: agentNames.map((name) => ({
|
||||
name,
|
||||
alias: '',
|
||||
flags: [],
|
||||
})),
|
||||
label,
|
||||
options: {
|
||||
line,
|
||||
|
@ -64,6 +68,15 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('propagates aliases', () => {
|
||||
const parsed = parser.parse('define Foo Bar as A B');
|
||||
expect(parsed.stages).toEqual([
|
||||
{type: 'agent define', agents: [
|
||||
{name: 'Foo Bar', alias: 'A B', flags: []},
|
||||
]},
|
||||
]);
|
||||
});
|
||||
|
||||
it('respects spacing within agent names', () => {
|
||||
const parsed = parser.parse('A+B -> C D');
|
||||
expect(parsed.stages).toEqual([
|
||||
|
@ -84,8 +97,12 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
{
|
||||
type: 'connect',
|
||||
agents: [
|
||||
{name: 'A', flags: ['start']},
|
||||
{name: 'B', flags: ['stop', 'begin', 'end']},
|
||||
{name: 'A', alias: '', flags: ['start']},
|
||||
{name: 'B', alias: '', flags: [
|
||||
'stop',
|
||||
'begin',
|
||||
'end',
|
||||
]},
|
||||
],
|
||||
label: jasmine.anything(),
|
||||
options: jasmine.anything(),
|
||||
|
@ -192,7 +209,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', flags: []}],
|
||||
agents: [{name: 'A', alias: '', flags: []}],
|
||||
mode: 'note',
|
||||
label: 'hello there',
|
||||
}]);
|
||||
|
@ -209,31 +226,34 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
expect(parsed.stages).toEqual([
|
||||
{
|
||||
type: 'note left',
|
||||
agents: [{name: 'A', flags: []}],
|
||||
agents: [{name: 'A', alias: '', flags: []}],
|
||||
mode: 'note',
|
||||
label: 'hello there',
|
||||
},
|
||||
{
|
||||
type: 'note left',
|
||||
agents: [{name: 'A', flags: []}],
|
||||
agents: [{name: 'A', alias: '', flags: []}],
|
||||
mode: 'note',
|
||||
label: 'hello there',
|
||||
},
|
||||
{
|
||||
type: 'note right',
|
||||
agents: [{name: 'A', flags: []}],
|
||||
agents: [{name: 'A', alias: '', flags: []}],
|
||||
mode: 'note',
|
||||
label: 'hello there',
|
||||
},
|
||||
{
|
||||
type: 'note right',
|
||||
agents: [{name: 'A', flags: []}],
|
||||
agents: [{name: 'A', alias: '', flags: []}],
|
||||
mode: 'note',
|
||||
label: 'hello there',
|
||||
},
|
||||
{
|
||||
type: 'note between',
|
||||
agents: [{name: 'A', flags: []}, {name: 'B', flags: []}],
|
||||
agents: [
|
||||
{name: 'A', alias: '', flags: []},
|
||||
{name: 'B', alias: '', flags: []},
|
||||
],
|
||||
mode: 'note',
|
||||
label: 'hi',
|
||||
},
|
||||
|
@ -244,7 +264,10 @@ 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', flags: []}, {name: 'C D', flags: []}],
|
||||
agents: [
|
||||
{name: 'A B', alias: '', flags: []},
|
||||
{name: 'C D', alias: '', flags: []},
|
||||
],
|
||||
mode: 'note',
|
||||
label: 'hi',
|
||||
}]);
|
||||
|
@ -258,7 +281,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', flags: []}],
|
||||
agents: [{name: 'A', alias: '', flags: []}],
|
||||
mode: 'state',
|
||||
label: 'doing stuff',
|
||||
}]);
|
||||
|
@ -272,7 +295,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', flags: []}],
|
||||
agents: [{name: 'A', alias: '', flags: []}],
|
||||
mode: 'text',
|
||||
label: 'doing stuff',
|
||||
}]);
|
||||
|
@ -287,16 +310,25 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
expect(parsed.stages).toEqual([
|
||||
{
|
||||
type: 'agent define',
|
||||
agents: [{name: 'A', flags: []}, {name: 'B', flags: []}],
|
||||
agents: [
|
||||
{name: 'A', alias: '', flags: []},
|
||||
{name: 'B', alias: '', flags: []},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'agent begin',
|
||||
agents: [{name: 'A', flags: []}, {name: 'B', flags: []}],
|
||||
agents: [
|
||||
{name: 'A', alias: '', flags: []},
|
||||
{name: 'B', alias: '', flags: []},
|
||||
],
|
||||
mode: 'box',
|
||||
},
|
||||
{
|
||||
type: 'agent end',
|
||||
agents: [{name: 'A', flags: []}, {name: 'B', flags: []}],
|
||||
agents: [
|
||||
{name: 'A', alias: '', flags: []},
|
||||
{name: 'B', alias: '', flags: []},
|
||||
],
|
||||
mode: 'cross',
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -103,11 +103,10 @@ define(['./BaseComponent'], (BaseComponent) => {
|
|||
config.padding.right
|
||||
);
|
||||
|
||||
if(agentNames.length > 1) {
|
||||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
||||
const infoL = env.agentInfos.get(left);
|
||||
const infoR = env.agentInfos.get(right);
|
||||
|
||||
if(infoL !== infoR) {
|
||||
const hangL = infoL.currentMaxRad + config.overlap.left;
|
||||
const hangR = infoR.currentMaxRad + config.overlap.right;
|
||||
|
||||
|
@ -116,7 +115,7 @@ define(['./BaseComponent'], (BaseComponent) => {
|
|||
env.addSpacing(left, {left: hangL, right: 0});
|
||||
env.addSpacing(right, {left: 0, right: hangR});
|
||||
} else {
|
||||
env.addSpacing(agentNames[0], {
|
||||
env.addSpacing(left, {
|
||||
left: width / 2,
|
||||
right: width / 2,
|
||||
});
|
||||
|
@ -126,10 +125,10 @@ define(['./BaseComponent'], (BaseComponent) => {
|
|||
render({agentNames, mode, label}, env) {
|
||||
const config = env.theme.note[mode];
|
||||
|
||||
if(agentNames.length > 1) {
|
||||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
||||
const infoL = env.agentInfos.get(left);
|
||||
const infoR = env.agentInfos.get(right);
|
||||
if(infoL !== infoR) {
|
||||
return this.renderNote({
|
||||
x0: infoL.x - infoL.currentMaxRad - config.overlap.left,
|
||||
x1: infoR.x + infoR.currentMaxRad + config.overlap.right,
|
||||
|
@ -138,7 +137,7 @@ define(['./BaseComponent'], (BaseComponent) => {
|
|||
label,
|
||||
}, env);
|
||||
} else {
|
||||
const xMid = env.agentInfos.get(agentNames[0]).x;
|
||||
const xMid = infoL.x;
|
||||
return this.renderNote({
|
||||
xMid,
|
||||
anchor: 'middle',
|
||||
|
|
|
@ -34,7 +34,7 @@ define(['jshintConfig', 'specs'], (jshintConfig) => {
|
|||
|
||||
const OPTS_TEST = Object.assign({}, jshintConfig, {
|
||||
predef: PREDEF_TEST,
|
||||
maxstatements: 50, // allow lots of tests
|
||||
maxstatements: 100, // allow lots of tests
|
||||
});
|
||||
|
||||
function formatError(error) {
|
||||
|
|
|
@ -88,6 +88,7 @@ html, body {
|
|||
background: #FFFFFF;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.options.links {
|
||||
|
|
Loading…
Reference in New Issue