From dc3d930544d7e1c98035293de81a1cd063794ab9 Mon Sep 17 00:00:00 2001 From: David Evans Date: Fri, 3 Nov 2017 22:56:48 +0000 Subject: [PATCH] Consistent agent flag handling in parser --- scripts/sequence/Generator.js | 6 +- scripts/sequence/Generator_spec.js | 336 ++++++++++++++--------------- scripts/sequence/Parser.js | 103 ++++----- scripts/sequence/Parser_spec.js | 85 +++++--- scripts/sequence/Renderer.js | 16 +- scripts/sequence/Renderer_spec.js | 42 ++-- 6 files changed, 298 insertions(+), 290 deletions(-) diff --git a/scripts/sequence/Generator.js b/scripts/sequence/Generator.js index 7b3861a..73a3cb2 100644 --- a/scripts/sequence/Generator.js +++ b/scripts/sequence/Generator.js @@ -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, diff --git a/scripts/sequence/Generator_spec.js b/scripts/sequence/Generator_spec.js index 9a6570a..c78831e 100644 --- a/scripts/sequence/Generator_spec.js +++ b/scripts/sequence/Generator_spec.js @@ -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(); }); }); diff --git a/scripts/sequence/Parser.js b/scripts/sequence/Parser.js index ce38216..6ee861d 100644 --- a/scripts/sequence/Parser.js +++ b/scripts/sequence/Parser.js @@ -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, }; }, diff --git a/scripts/sequence/Parser_spec.js b/scripts/sequence/Parser_spec.js index 92810f8..5b06a15 100644 --- a/scripts/sequence/Parser_spec.js +++ b/scripts/sequence/Parser_spec.js @@ -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'}, ]); }); diff --git a/scripts/sequence/Renderer.js b/scripts/sequence/Renderer.js index ec664a7..6160864 100644 --- a/scripts/sequence/Renderer.js +++ b/scripts/sequence/Renderer.js @@ -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); } diff --git a/scripts/sequence/Renderer_spec.js b/scripts/sequence/Renderer_spec.js index 97864f9..9e88687 100644 --- a/scripts/sequence/Renderer_spec.js +++ b/scripts/sequence/Renderer_spec.js @@ -25,18 +25,20 @@ defineDescribe('Sequence Renderer', [ }); }); - function connectionStage(agentNames, label = '') { - return { - type: 'connection', - agentNames, - label, - options: { - line: 'solid', - left: false, - right: true, - }, - }; - } + const GENERATED = { + connect: (agentNames, label = '') => { + return { + type: 'connect', + agentNames, + label, + options: { + line: 'solid', + left: false, + 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'}, ], });